{
    "componentChunkName": "component---src-templates-tag-page-jsx",
    "path": "/pt-br/blog/tags/website",
    "result": {"pageContext":{"pageType":"tag","title":"website","posts":[{"node":{"html":"<p>Uma das side quests mais chatas de publicar um app na Google Play Store é que o Google às vezes decide que você também virou dono de site.</p>\n<p>Lá em 2024, quando eu <a href=\"/pt-br/blog/coding/musclog-aproveitando-minha-experiencia-com-reactjs-para-criar-um-app-em-react-native/\">escrevi pela primeira vez sobre o Musclog</a>, o site existia basicamente porque o Google queria que ele existisse. Eu precisava da política de privacidade, das páginas públicas, do kit básico do desenvolvedor de app respeitável, então subi rapidinho um site pequeno em Next.js, consegui aprovar o app, e segui com a minha vida.</p>\n<p>Funcionou perfeitamente até o dia em que eu precisei mexer no site de novo.</p>\n<p>Screenshots novas? Outro repo. Texto de feature nova? Outro repo. Atualização de página legal? Outro repo. Ajustezinho de tradução? Outro repo.</p>\n<p>Nada disso era difícil de verdade, o que quase piorava a situação. Era só chato o suficiente pra continuar me lembrando que eu tinha dividido um produto em duas bases de código por um motivo que eu já não respeitava mais.</p>\n<p>Então agora eu não faço mais isso.</p>\n<p>O site do Musclog mora dentro do repo do app. Mesmo projeto com Expo Router. Mesmo deploy. <a href=\"https://musclog.app/\" target=\"_blank\" rel=\"noreferrer\">musclog.app</a> é o site público, <a href=\"https://musclog.app/app\" target=\"_blank\" rel=\"noreferrer\">musclog.app/app</a> é o app web de verdade, e Android + iOS continuam saindo da mesma base de código.</p>\n<p>Boa prática? Questionável. Conveniente? Demais.</p>\n<p><figure class=\"gatsby-resp-image-figure\" style=\"\">\n    <span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; \"\n    >\n      <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 61.71875%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB0ElEQVQoz2WRTW7bMBCF1YVjcSiL4j8pkbRkxQri2G4c20mzyC5IWyDdNAVa9Bg9Q7e9StGz9EQFZcVNUeDbzOA9vuFMkjIS4YQEgwVDrECHTg8SNDMCK4E3Dl9XEy0mTqeKprxIGUlAUJAUBB3l2QnNxyxHfTmgOJQyDZKvVvrtx1fvb9C6wV6B5SBZctSdPV6fPWxn3dw4N1GibzJQDDs9qsn89uv+x+/k13d43OFKQiWfzbzAkq0/v1t8ul89PdC7dWo48H4ixcAp8Ao+vB7//DK+36XLJmstlBLUMZnTfNmQN4ti09GbBa4UiGIwVxJ7nd2d4m/7UefR3GWnJVgBkg5mJGjpvbHW1VMm46ugOdYcDMelhKCKrhbL9eiihc5ltQbD/5ohblWClVAZmFY4GOxlJMgsqCyoSaPRbX2y9RBU5iVoBuKFOWIkrh1uA24qPDNZayMzndUKnCCdy9sSLOtj/zdLBkaAldhKsBz1wAHDkKLximo4bTQjyY5AzwkjZjY9314udpuL3dX51aVqwpgX8EJ5IIkzPIM0Q7oP15w4w0LJQplXuk/msT8w6JO4dBtrMve8DcRbpNnEKeotMSrXEktG9jWZlv2P/uEPhuJAcZJWdDcAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Site público do Musclog renderizado pela mesma base de código com Expo Router do app\"\n        title=\"Site público do Musclog renderizado pela mesma base de código com Expo Router do app\"\n        src=\"/static/f71a459f3034e47544de2f3ce309dd61/42a19/musclog-shared-router-website-home-1.png\"\n        srcset=\"/static/f71a459f3034e47544de2f3ce309dd61/e3135/musclog-shared-router-website-home-1.png 256w,\n/static/f71a459f3034e47544de2f3ce309dd61/06341/musclog-shared-router-website-home-1.png 512w,\n/static/f71a459f3034e47544de2f3ce309dd61/42a19/musclog-shared-router-website-home-1.png 1024w,\n/static/f71a459f3034e47544de2f3ce309dd61/e8464/musclog-shared-router-website-home-1.png 1536w,\n/static/f71a459f3034e47544de2f3ce309dd61/2eb59/musclog-shared-router-website-home-1.png 2048w,\n/static/f71a459f3034e47544de2f3ce309dd61/d7fb2/musclog-shared-router-website-home-1.png 2184w\"\n        sizes=\"(max-width: 1024px) 100vw, 1024px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n    </span>\n    <figcaption class=\"gatsby-resp-image-figcaption\">Site público do Musclog renderizado pela mesma base de código com Expo Router do app</figcaption>\n  </figure></p>\n<h2 id=\"ok-mas-por-que\" style=\"position:relative;\"><a href=\"#ok-mas-por-que\" aria-label=\"ok mas por que permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Ok mas por quê?</h2>\n<p>Basicamente porque o Expo Router arrancou a minha última desculpa.</p>\n<p>O Musclog já usava <a href=\"https://expo.github.io/router/docs\" target=\"_blank\" rel=\"noreferrer\">Expo Router</a>, então o app já estava organizado em rotas baseadas em arquivos dentro da pasta <code class=\"language-text\">app/</code>. Aí eu percebi que podia simplesmente criar um grupo de rotas chamado <code class=\"language-text\">(website)</code>, mover as páginas públicas pra lá, deixar o app de verdade em <code class=\"language-text\">app/app/*</code>, e parar de fingir que eu estava lidando com dois produtos diferentes.</p>\n<p>O repo separado tinha virado uma daquelas decisões de arquitetura que parecem limpas na teoria e depois ficam te cobrando imposto de manutenção pra sempre:</p>\n<ul>\n<li>dois PRs pra uma mudança num produto</li>\n<li>dois deploys</li>\n<li>dois lugares pras traduções</li>\n<li>dois lugares pras páginas legais</li>\n<li>dois lugares pra esquecer alguma coisa</li>\n</ul>\n<p>Sem condições.</p>\n<h2 id=\"a-divisao\" style=\"position:relative;\"><a href=\"#a-divisao\" aria-label=\"a divisao permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>A divisão</h2>\n<p>A parte engraçada é que o roteamento em si acabou sendo a parte menos amaldiçoada dessa história:</p>\n<ul>\n<li><code class=\"language-text\">app/app/*</code> é o Musclog de verdade. Treinos, nutrição, coach com IA, tudo.</li>\n<li><code class=\"language-text\">app/(website)/*</code> é o site público. Landing page, política de privacidade, termos, contato, calculadora.</li>\n</ul>\n<p>A rota raiz só checa a plataforma e te joga pra onde você pertence:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token comment\">// app/index.tsx</span>\r\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Redirect<span class=\"token punctuation\">,</span> useRouter <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'expo-router'</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> useEffect <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'react'</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Platform <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'react-native'</span><span class=\"token punctuation\">;</span>\r\n\r\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">Index</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">const</span> router <span class=\"token operator\">=</span> <span class=\"token function\">useRouter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n\r\n  <span class=\"token function\">useEffect</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>Platform<span class=\"token punctuation\">.</span><span class=\"token constant\">OS</span> <span class=\"token operator\">===</span> <span class=\"token string\">'web'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n      router<span class=\"token punctuation\">.</span><span class=\"token function\">replace</span><span class=\"token punctuation\">(</span><span class=\"token string\">'/home'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    <span class=\"token punctuation\">}</span>\r\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span>router<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n\r\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>Platform<span class=\"token punctuation\">.</span><span class=\"token constant\">OS</span> <span class=\"token operator\">===</span> <span class=\"token string\">'web'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token punctuation\">}</span>\r\n\r\n  <span class=\"token keyword\">return</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">Redirect</span></span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/app<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>É isso. No web, <code class=\"language-text\">/</code> vira o site. No native, <code class=\"language-text\">/</code> vira o app.</p>\n<p>O site também ganha o próprio layout mais leve:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token comment\">// app/(website)/_layout.web.tsx</span>\r\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Slot <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'expo-router'</span><span class=\"token punctuation\">;</span>\r\n\r\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> WebsiteChrome <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@/components/website/WebsiteChrome'</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> WebsiteProviders <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@/components/website/WebsiteProviders'</span><span class=\"token punctuation\">;</span>\r\n\r\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">WebsiteLayout</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\r\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">WebsiteProviders</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">WebsiteChrome</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n        </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">Slot</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\r\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">WebsiteChrome</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">WebsiteProviders</span></span><span class=\"token punctuation\">></span></span>\r\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Isso importa porque o layout do app de verdade já tinha crescido e virado um layout de app mesmo. Boot de banco, migrações, React Query, safe area, gestos, modais, snackbars, câmera, contexto do coach, todas aquelas mentiras clássicas de “é só um projetinho paralelo”. O site não precisa de nada disso só pra explicar o que o app faz.</p>\n<p>E se algum usuário nativo cair numa rota exclusiva do site, a correção é maravilhosamente direta:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token comment\">// app/(website)/home.tsx</span>\r\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Redirect <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'expo-router'</span><span class=\"token punctuation\">;</span>\r\n\r\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">Home</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">return</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">Redirect</span></span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/app<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Pronto. Volta pros macros.</p>\n<p><figure class=\"gatsby-resp-image-figure\" style=\"\">\n    <span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; \"\n    >\n      <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 61.71875%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAABt0lEQVQoz02SWW/jMAyE/RaLdqyLkiXfpxznstOi6S6w//93LZS4aYB5IuYDhkMGO554iWRfpSBZyJNQUoIslDREClrEBkHweLbxRxkpGWuxITwJQIuniGSgOaQcjOyW03Rfp++bu6/ua2mWmShOOCWaE8Uh3ZAAUvlUlKvIyCjDqFBxY5gr+VyzU8Omcl+bqNRRoSKDkUUwGxKARbBIrMwvU309qKHSc9v9XextEktb/LvUf67q2KlD236es0MvuwIyfFIbDBaxr/RY41iLsTTXIb05eevxo09vTp97PhQ4VGqoRJu/kMBneAinWg0VqywfC7M6PHf83OivSS8DHwoxFubUyyanpXkhW2FEczM0xTyqrsSuqNdjvjicG3t15XpIp1a2RXV02dSJOnt1HBBkT4XICE9AcaJFiAwUi6wkRhAtIBVEiVBSEA/DDxIQyV6CPvcpWAKlhrnyQ0EhV9udUkmm8t3/CwNyc3GsMDu6T+pMXR2RLBQU+4pm6Y4lsUU8D0S8wz8ZfHKeEP9e3GOc+mdI/Qp+ovyQsOTd//th8NjtKWJkUlt3X6bvlTaW+G7fDD+F/QccPj1gi9mJrAAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Site público do Musclog renderizado pela mesma base de código com Expo Router do app\"\n        title=\"Site público do Musclog renderizado pela mesma base de código com Expo Router do app\"\n        src=\"/static/c50b08ecc21b0e02ed7774578063a5f6/42a19/musclog-shared-router-website-home-2.png\"\n        srcset=\"/static/c50b08ecc21b0e02ed7774578063a5f6/e3135/musclog-shared-router-website-home-2.png 256w,\n/static/c50b08ecc21b0e02ed7774578063a5f6/06341/musclog-shared-router-website-home-2.png 512w,\n/static/c50b08ecc21b0e02ed7774578063a5f6/42a19/musclog-shared-router-website-home-2.png 1024w,\n/static/c50b08ecc21b0e02ed7774578063a5f6/e8464/musclog-shared-router-website-home-2.png 1536w,\n/static/c50b08ecc21b0e02ed7774578063a5f6/2eb59/musclog-shared-router-website-home-2.png 2048w,\n/static/c50b08ecc21b0e02ed7774578063a5f6/d7fb2/musclog-shared-router-website-home-2.png 2184w\"\n        sizes=\"(max-width: 1024px) 100vw, 1024px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n    </span>\n    <figcaption class=\"gatsby-resp-image-figcaption\">Site público do Musclog renderizado pela mesma base de código com Expo Router do app</figcaption>\n  </figure></p>\n<h2 id=\"a-palhacada-do-celular-no-desktop\" style=\"position:relative;\"><a href=\"#a-palhacada-do-celular-no-desktop\" aria-label=\"a palhacada do celular no desktop permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>A palhaçada do celular no desktop</h2>\n<p>Foi aqui que eu parei de me comportar como uma pessoa normal.</p>\n<p>Eu queria que <a href=\"https://musclog.app/app/\" target=\"_blank\" rel=\"noreferrer\">musclog.app/app</a>, no desktop, mostrasse o app funcionando dentro de uma moldura de celular. Um pouco porque fica bonito. Um pouco porque funciona como preview do produto. Mas principalmente porque, depois que a ideia entrou na minha cabeça, qualquer opção menos ridícula começou a parecer errada.</p>\n<p>O detalhe chato era que <code class=\"language-text\">/app</code> precisava parecer um celular, mas <code class=\"language-text\">/home</code> definitivamente não. Então só o roteamento não bastava. Eu precisava que o shell bruto de HTML soubesse, antes da hidratação, se o navegador deveria renderizar a gambiarra do celular fake ou não.</p>\n<p>Essa lógica mora em <code class=\"language-text\">app/+html.tsx</code>, dentro de um script no <code class=\"language-text\">&lt;head></code> do documento:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token keyword\">function</span> <span class=\"token function\">landingPanelGate</span><span class=\"token punctuation\">(</span>base<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token keyword\">function</span> <span class=\"token function\">update</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n      <span class=\"token keyword\">const</span> raw <span class=\"token operator\">=</span> window<span class=\"token punctuation\">.</span>location<span class=\"token punctuation\">.</span>pathname<span class=\"token punctuation\">;</span>\r\n      <span class=\"token keyword\">const</span> path <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>base <span class=\"token operator\">&amp;&amp;</span> raw<span class=\"token punctuation\">.</span><span class=\"token function\">startsWith</span><span class=\"token punctuation\">(</span>base<span class=\"token punctuation\">)</span> <span class=\"token operator\">?</span> raw<span class=\"token punctuation\">.</span><span class=\"token function\">slice</span><span class=\"token punctuation\">(</span>base<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">)</span> <span class=\"token operator\">:</span> raw<span class=\"token punctuation\">)</span> <span class=\"token operator\">||</span> <span class=\"token string\">'/'</span><span class=\"token punctuation\">;</span>\r\n\r\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>path<span class=\"token punctuation\">.</span><span class=\"token function\">startsWith</span><span class=\"token punctuation\">(</span><span class=\"token string\">'/app'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n        document<span class=\"token punctuation\">.</span>documentElement<span class=\"token punctuation\">.</span>classList<span class=\"token punctuation\">.</span><span class=\"token function\">add</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hide-desktop-wrapper'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\r\n        document<span class=\"token punctuation\">.</span>documentElement<span class=\"token punctuation\">.</span>classList<span class=\"token punctuation\">.</span><span class=\"token function\">remove</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hide-desktop-wrapper'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n      <span class=\"token punctuation\">}</span>\r\n    <span class=\"token punctuation\">}</span>\r\n\r\n    <span class=\"token function\">update</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    window<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">'popstate'</span><span class=\"token punctuation\">,</span> update<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n\r\n    <span class=\"token keyword\">const</span> origPush <span class=\"token operator\">=</span> history<span class=\"token punctuation\">.</span><span class=\"token function\">pushState</span><span class=\"token punctuation\">.</span><span class=\"token function\">bind</span><span class=\"token punctuation\">(</span>history<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    history<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">pushState</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>args<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n      <span class=\"token function\">origPush</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>args<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n      <span class=\"token function\">update</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\r\n\r\n    <span class=\"token keyword\">const</span> origReplace <span class=\"token operator\">=</span> history<span class=\"token punctuation\">.</span><span class=\"token function\">replaceState</span><span class=\"token punctuation\">.</span><span class=\"token function\">bind</span><span class=\"token punctuation\">(</span>history<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    history<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">replaceState</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>args<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n      <span class=\"token function\">origReplace</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>args<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n      <span class=\"token function\">update</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>_<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>A parte importante é a gambiarra em <code class=\"language-text\">pushState</code> / <code class=\"language-text\">replaceState</code>. A primeira renderização foi tranquila. A navegação client-side é que era a parte chata. Sem isso, dava pra sair de <code class=\"language-text\">/app</code> e continuar com o shell errado ali, como se o Chrome tivesse esquecido em que página estava.</p>\n<p>O shell HTML em si é basicamente o painel da landing, o app roteado, e a moldura do celular:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>body</span> <span class=\"token attr-name\">className</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>expo-web-body<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n  </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">className</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>expo-web-landing<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">...</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n  </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span> <span class=\"token attr-name\">dangerouslySetInnerHTML</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> __html<span class=\"token operator\">:</span> <span class=\"token constant\">LANDING_I18N_SCRIPT</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\r\n  </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">className</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>expo-web-root<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">className</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>expo-web-app-shell<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">{</span>children<span class=\"token punctuation\">}</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>img</span>\r\n      <span class=\"token attr-name\">className</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>expo-web-phone-frame<span class=\"token punctuation\">\"</span></span>\r\n      <span class=\"token attr-name\">src</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token function\">withExpoBaseUrl</span><span class=\"token punctuation\">(</span><span class=\"token constant\">PHONE_FRAME_SRC</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span></span>\r\n      <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span><span class=\"token punctuation\">\"</span></span>\r\n      <span class=\"token attr-name\">aria-hidden</span>\r\n    <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\r\n  </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>body</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>Aí o CSS comete o crime:</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">min-width</span><span class=\"token punctuation\">:</span> 1024px<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token selector\">.expo-web-root</span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token property\">--frame-h</span><span class=\"token punctuation\">:</span> <span class=\"token function\">min</span><span class=\"token punctuation\">(</span>100dvh<span class=\"token punctuation\">,</span> <span class=\"token function\">max</span><span class=\"token punctuation\">(</span><span class=\"token function\">min</span><span class=\"token punctuation\">(</span>360px<span class=\"token punctuation\">,</span> 100dvh<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> 85dvh<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">aspect-ratio</span><span class=\"token punctuation\">:</span> 1438 / 2976<span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">width</span><span class=\"token punctuation\">:</span> <span class=\"token function\">min</span><span class=\"token punctuation\">(</span>100vw<span class=\"token punctuation\">,</span> <span class=\"token function\">calc</span><span class=\"token punctuation\">(</span><span class=\"token function\">var</span><span class=\"token punctuation\">(</span>--frame-h<span class=\"token punctuation\">)</span> * 1438 / 2976<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">max-height</span><span class=\"token punctuation\">:</span> <span class=\"token function\">var</span><span class=\"token punctuation\">(</span>--frame-h<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">overflow</span><span class=\"token punctuation\">:</span> hidden<span class=\"token punctuation\">;</span>\r\n  <span class=\"token punctuation\">}</span>\r\n\r\n  <span class=\"token selector\">.expo-web-app-shell</span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token property\">position</span><span class=\"token punctuation\">:</span> absolute<span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">left</span><span class=\"token punctuation\">:</span> 7.4409%<span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">top</span><span class=\"token punctuation\">:</span> 2.9906%<span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">right</span><span class=\"token punctuation\">:</span> 6.3282%<span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">bottom</span><span class=\"token punctuation\">:</span> 3.125%<span class=\"token punctuation\">;</span>\r\n    <span class=\"token property\">zoom</span><span class=\"token punctuation\">:</span> 0.85<span class=\"token punctuation\">;</span>\r\n  <span class=\"token punctuation\">}</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Sim, eu medi a área transparente da tela dentro do PNG até o app encaixar direito. Não tem geometria elegante nenhuma por trás dessas porcentagens. Era só eu, uma moldura de celular, e muito empurra-pra-lá até o app parar de parecer torto.</p>\n<p>Nas rotas que não são <code class=\"language-text\">/app</code>, o espetáculo inteiro é desligado:</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token selector\">.hide-desktop-wrapper .expo-web-landing,\r\n.hide-desktop-wrapper .expo-web-phone-frame</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token property\">display</span><span class=\"token punctuation\">:</span> none <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span>\r\n\r\n<span class=\"token selector\">.hide-desktop-wrapper .expo-web-app-shell</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token property\">position</span><span class=\"token punctuation\">:</span> static <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token property\">width</span><span class=\"token punctuation\">:</span> 100% <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token property\">height</span><span class=\"token punctuation\">:</span> auto <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token property\">zoom</span><span class=\"token punctuation\">:</span> 1 <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token property\">overflow</span><span class=\"token punctuation\">:</span> visible <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Sim, usa <code class=\"language-text\">!important</code>. Isso é CSS de pré-hidratação cujo trabalho inteiro é manter a mentira de pé. Não estamos fazendo arquitetura refinada nessa camada.</p>\n<p><figure class=\"gatsby-resp-image-figure\" style=\"\">\n    <span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; \"\n    >\n      <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 61.71875%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAABS0lEQVQoz42SyUoDQRCGgxOTrq7pfbbuZBKTmTEwWSAgKKLghtvBQy6CHs3Nk94UwZPP4Wv4DD6VqBdhION3Kor6qL+gGrQCfvNbER/bApEDFehXJxvVFhACAEDandHYTbcxG9BJHpQZZwwAVskAoIzWJvTWGvunrwfPn+uPD633F/V0IynC6s0AECZRFMWEeMX4bHR8z04O/dtzc7EjfQYUamIro6WSCDQunN0aJoU1iQptKKWsiU0plVoro6Hhda+Pyo+3dHkpho4PEsF5vYyUEiC0Tewk71/tmVnGXShcyFfLiNhsNufz+XJ5Z7TWUWDTDguUH2kZB/Vyq9Uqy3KxWGilgtT1Z6XNM512w15XiLrYiEgI8TyP+X6Q2rgY2s1cpd14oyel+MfNP/iIUWp13yW7UzPoxH3Hap/kL4wxLjgXXAjBOa++5xcHAzI9yvyvqQAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"App web do Musclog no desktop renderizado dentro de uma moldura de celular ao lado da landing\"\n        title=\"App web do Musclog no desktop renderizado dentro de uma moldura de celular ao lado da landing\"\n        src=\"/static/49361b3283ae34ef30a950b13c7f5594/42a19/musclog-desktop-phone-frame-wrapper.png\"\n        srcset=\"/static/49361b3283ae34ef30a950b13c7f5594/e3135/musclog-desktop-phone-frame-wrapper.png 256w,\n/static/49361b3283ae34ef30a950b13c7f5594/06341/musclog-desktop-phone-frame-wrapper.png 512w,\n/static/49361b3283ae34ef30a950b13c7f5594/42a19/musclog-desktop-phone-frame-wrapper.png 1024w,\n/static/49361b3283ae34ef30a950b13c7f5594/e8464/musclog-desktop-phone-frame-wrapper.png 1536w,\n/static/49361b3283ae34ef30a950b13c7f5594/2eb59/musclog-desktop-phone-frame-wrapper.png 2048w,\n/static/49361b3283ae34ef30a950b13c7f5594/d7fb2/musclog-desktop-phone-frame-wrapper.png 2184w\"\n        sizes=\"(max-width: 1024px) 100vw, 1024px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n    </span>\n    <figcaption class=\"gatsby-resp-image-figcaption\">App web do Musclog no desktop renderizado dentro de uma moldura de celular ao lado da landing</figcaption>\n  </figure></p>\n<h2 id=\"tres-pequenos-crimes\" style=\"position:relative;\"><a href=\"#tres-pequenos-crimes\" aria-label=\"tres pequenos crimes permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Três pequenos crimes</h2>\n<p><img src=\"/377ac4354ce553fccf98c5d8d1017e50/straight-to-jail.gif\" alt=\"Direto pra cadeia. Na hora.\"></p>\n<p>Depois que o shell funcionou, os incômodos menores começaram a aparecer como se tivessem marcado horário.</p>\n<h3 id=\"1-o-painel-da-landing-precisava-de-i18n-antes-da-hidratacao\" style=\"position:relative;\"><a href=\"#1-o-painel-da-landing-precisava-de-i18n-antes-da-hidratacao\" aria-label=\"1 o painel da landing precisava de i18n antes da hidratacao permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>1. O painel da landing precisava de i18n antes da hidratação</h3>\n<p>O texto do lado do celular mora em HTML cru, o que significa que React e i18n ainda estão dormindo quando a página renderiza pela primeira vez. Então, se eu quisesse que a página em português não desse um flash de inglês antes, eu tinha que remendar isso manualmente a partir do <code class=\"language-text\">localStorage</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token keyword\">function</span> <span class=\"token function\">landingI18nPatcher</span><span class=\"token punctuation\">(</span>translations<span class=\"token punctuation\">,</span> storageKey<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token keyword\">let</span> lang <span class=\"token operator\">=</span> localStorage<span class=\"token punctuation\">.</span><span class=\"token function\">getItem</span><span class=\"token punctuation\">(</span>storageKey<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n    <span class=\"token keyword\">let</span> s <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>lang <span class=\"token operator\">&amp;&amp;</span> translations<span class=\"token punctuation\">[</span>lang<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">||</span> translations<span class=\"token punctuation\">[</span><span class=\"token string\">'en-US'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\r\n\r\n    document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelectorAll</span><span class=\"token punctuation\">(</span><span class=\"token string\">'[data-landing-i18n]'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span>el<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n      <span class=\"token keyword\">let</span> k <span class=\"token operator\">=</span> el<span class=\"token punctuation\">.</span><span class=\"token function\">getAttribute</span><span class=\"token punctuation\">(</span><span class=\"token string\">'data-landing-i18n'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>k <span class=\"token operator\">&amp;&amp;</span> s<span class=\"token punctuation\">[</span>k<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n        el<span class=\"token punctuation\">.</span>textContent <span class=\"token operator\">=</span> s<span class=\"token punctuation\">[</span>k<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\r\n      <span class=\"token punctuation\">}</span>\r\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>_<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Não é elegante. Também evita jogar um flashbang de texto em inglês numa página em português, então eu vou manter.</p>\n<h3 id=\"2-assets-em-html-cru-nao-recebem-a-ajuda-de-sempre-do-expo\" style=\"position:relative;\"><a href=\"#2-assets-em-html-cru-nao-recebem-a-ajuda-de-sempre-do-expo\" aria-label=\"2 assets em html cru nao recebem a ajuda de sempre do expo permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>2. Assets em HTML cru não recebem a ajuda de sempre do Expo</h3>\n<p>O PNG da moldura do celular e o QR code também são referenciados a partir de HTML cru, então aquela conveniência normal de assets do Expo não me ajudava muito ali. Resultado: eu precisei de um helper pra base URL:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token keyword\">function</span> <span class=\"token function\">withExpoBaseUrl</span><span class=\"token punctuation\">(</span>path<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token builtin\">string</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">const</span> base <span class=\"token operator\">=</span> process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span><span class=\"token constant\">EXPO_BASE_URL</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>base <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span> <span class=\"token operator\">||</span> base <span class=\"token operator\">===</span> <span class=\"token string\">''</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token keyword\">return</span> path<span class=\"token punctuation\">;</span>\r\n  <span class=\"token punctuation\">}</span>\r\n\r\n  <span class=\"token keyword\">const</span> basePath <span class=\"token operator\">=</span> <span class=\"token function\">String</span><span class=\"token punctuation\">(</span>base<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">replace</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">^\\/+|\\/+$</span><span class=\"token regex-delimiter\">/</span><span class=\"token regex-flags\">g</span></span><span class=\"token punctuation\">,</span> <span class=\"token string\">''</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token keyword\">const</span> normalized <span class=\"token operator\">=</span> path<span class=\"token punctuation\">.</span><span class=\"token function\">startsWith</span><span class=\"token punctuation\">(</span><span class=\"token string\">'/'</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">?</span> path <span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">/</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>path<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token keyword\">return</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">/</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>basePath<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>normalized<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>E como esses arquivos vivem fora do pipeline normal de assets do React Native, eu copio tudo pra <code class=\"language-text\">public/</code> antes de rodar dev e export. Esquece esse passo uma vez e o site imediatamente te lembra quem manda.</p>\n<h3 id=\"3-o-expo-export-as-vezes-precisava-de-apoio-emocional\" style=\"position:relative;\"><a href=\"#3-o-expo-export-as-vezes-precisava-de-apoio-emocional\" aria-label=\"3 o expo export as vezes precisava de apoio emocional permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>3. O <code class=\"language-text\">expo export</code> às vezes precisava de apoio emocional</h3>\n<p>Em alguns ambientes, <code class=\"language-text\">expo export --platform web</code> terminava com sucesso e depois só… continuava vivo sem motivo nenhum. Pasta <code class=\"language-text\">dist</code> lá. Arquivos gerados. Processo espiritualmente concluído, tecnicamente ainda pendurado.</p>\n<p>Então agora existe um script wrapper:</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// scripts/export-web-wrapper.js</span>\r\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>output<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">'Exported: dist'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'[export-web-wrapper] Detected successful export. Forcing exit in 5s...'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> process<span class=\"token punctuation\">.</span><span class=\"token function\">exit</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token number\">5000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Não é glamouroso… mas resolve.</p>\n<h2 id=\"ai-os-modais-ficaram-esquisitos\" style=\"position:relative;\"><a href=\"#ai-os-modais-ficaram-esquisitos\" aria-label=\"ai os modais ficaram esquisitos permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Aí os modais ficaram esquisitos</h2>\n<p><figure class=\"gatsby-resp-image-figure\" style=\"\">\n    <span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; \"\n    >\n      <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 61.71875%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAABD0lEQVQoz53SS27CMBCAYUtUIp5x4ldMnAST2E3BcVA3kXqOcgbuf4eKhypoN8DoW4xG+ndDAAARAYDxAoucMnxUjiSj5wFYb4cqeN7Uom0eoXaBKK2lEBTApbRJ+01MF904ddO0HqMbx/UY2xj9lHyK/bi76D4TWdnKGAOIKjj1vhFdK/o7/N/lytVElaUQHBB5dMXQsGBv5aGWW/fnyILFfiXngUitzjEUoSl8XXh70tvLwn2tt4756k6osDNi/iCAmC2XyJj+SnlnWWPy1sBKgrmipfjdb1HJyTzPx+NRac1syWqNlXoQs5pM+/334SCVXIqcavEUkmXZ22IBCM+Wp/j6YYhUvRSfBuGF+AfXMjfjsQh0mgAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Modal do Musclog no desktop renderizado corretamente dentro da moldura do celular\"\n        title=\"Modal do Musclog no desktop renderizado corretamente dentro da moldura do celular\"\n        src=\"/static/a2af4c3fcf36eee559fddfb48a5143d1/42a19/musclog-desktop-modal-inside-phone-frame.png\"\n        srcset=\"/static/a2af4c3fcf36eee559fddfb48a5143d1/e3135/musclog-desktop-modal-inside-phone-frame.png 256w,\n/static/a2af4c3fcf36eee559fddfb48a5143d1/06341/musclog-desktop-modal-inside-phone-frame.png 512w,\n/static/a2af4c3fcf36eee559fddfb48a5143d1/42a19/musclog-desktop-modal-inside-phone-frame.png 1024w,\n/static/a2af4c3fcf36eee559fddfb48a5143d1/e8464/musclog-desktop-modal-inside-phone-frame.png 1536w,\n/static/a2af4c3fcf36eee559fddfb48a5143d1/2eb59/musclog-desktop-modal-inside-phone-frame.png 2048w,\n/static/a2af4c3fcf36eee559fddfb48a5143d1/d7fb2/musclog-desktop-modal-inside-phone-frame.png 2184w\"\n        sizes=\"(max-width: 1024px) 100vw, 1024px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n    </span>\n    <figcaption class=\"gatsby-resp-image-figcaption\">Modal do Musclog no desktop renderizado corretamente dentro da moldura do celular</figcaption>\n  </figure></p>\n<p>Claro que ficaram.</p>\n<p>Quando o app web mora dentro de um celular fake no desktop, o comportamento padrão de <code class=\"language-text\">Modal</code> no React Native Web começa a ficar ridículo bem rápido. Fazer portal direto pro <code class=\"language-text\">document.body</code> funciona bem quando o app é dono da página inteira. Funciona bem menos quando o app está visualmente recortado dentro de uma carcaça de celular e o modal decide do nada que agora pertence à janela inteira do navegador.</p>\n<p>Então eu adicionei um <code class=\"language-text\">WebModalShellProvider</code> lá em cima em <code class=\"language-text\">app/app/_layout.tsx</code>, com um host de overlay dentro do shell do celular:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token comment\">// context/WebModalShellContext.web.tsx</span>\r\n<span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\r\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">WebModalShellContext.Provider</span></span> <span class=\"token attr-name\">value</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> hostElement <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">View</span></span> <span class=\"token attr-name\">style</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> flex<span class=\"token operator\">:</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span> minHeight<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> height<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span><span class=\"token punctuation\">,</span> width<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span><span class=\"token punctuation\">,</span> position<span class=\"token operator\">:</span> <span class=\"token string\">'relative'</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">View</span></span> <span class=\"token attr-name\">style</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> flex<span class=\"token operator\">:</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span> minHeight<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> height<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span><span class=\"token punctuation\">,</span> width<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span> <span class=\"token attr-name\">collapsable</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token boolean\">false</span><span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n        </span><span class=\"token punctuation\">{</span>children<span class=\"token punctuation\">}</span><span class=\"token plain-text\">\r\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">View</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">View</span></span>\r\n        <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>expo-web-modal-shell-host<span class=\"token punctuation\">\"</span></span>\r\n        <span class=\"token attr-name\">ref</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>setHostRef<span class=\"token punctuation\">}</span></span>\r\n        <span class=\"token attr-name\">collapsable</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token boolean\">false</span><span class=\"token punctuation\">}</span></span>\r\n        <span class=\"token attr-name\">style</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span>\r\n          position<span class=\"token operator\">:</span> <span class=\"token string\">'absolute'</span><span class=\"token punctuation\">,</span>\r\n          left<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n          right<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n          top<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n          bottom<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n          zIndex<span class=\"token operator\">:</span> <span class=\"token number\">1_000_000</span><span class=\"token punctuation\">,</span>\r\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span>\r\n      <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\r\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">View</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n  </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">WebModalShellContext.Provider</span></span><span class=\"token punctuation\">></span></span>\r\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Aí o <code class=\"language-text\">Modal.web.tsx</code> faz aquela coisa óbvia que só parece óbvia depois, alternando entre um portal dentro desse host e o modal normal do RN:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token keyword\">const</span> useShellPortal <span class=\"token operator\">=</span> Platform<span class=\"token punctuation\">.</span><span class=\"token constant\">OS</span> <span class=\"token operator\">===</span> <span class=\"token string\">'web'</span> <span class=\"token operator\">&amp;&amp;</span> isDesktopFrame <span class=\"token operator\">&amp;&amp;</span> hostElement <span class=\"token operator\">!=</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\r\n\r\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>useShellPortal<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">return</span> <span class=\"token function\">createPortal</span><span class=\"token punctuation\">(</span>\r\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">View</span></span>\r\n      <span class=\"token attr-name\">style</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span>\r\n        position<span class=\"token operator\">:</span> <span class=\"token string\">'absolute'</span><span class=\"token punctuation\">,</span>\r\n        left<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n        right<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n        top<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n        bottom<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n        width<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span><span class=\"token punctuation\">,</span>\r\n        height<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span><span class=\"token punctuation\">,</span>\r\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span>\r\n    <span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\r\n      </span><span class=\"token punctuation\">{</span>children<span class=\"token punctuation\">}</span><span class=\"token plain-text\">\r\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">View</span></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">,</span>\r\n    hostElement\r\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span>\r\n\r\n<span class=\"token keyword\">return</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">RNModal</span></span> <span class=\"token attr-name\">visible</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>visible<span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">{</span>children<span class=\"token punctuation\">}</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span><span class=\"token class-name\">RNModal</span></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>E o hook compartilhado do overlay sabe se ele deve se comportar como um modal de viewport inteira ou como um modal de viewport de celular fake:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">useWebModalLayerStyle</span><span class=\"token punctuation\">(</span>options <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token keyword\">const</span> isDesktopFrame <span class=\"token operator\">=</span> <span class=\"token function\">useWebDesktopPhoneFrame</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\r\n\r\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>isDesktopFrame<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\r\n    <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\r\n      position<span class=\"token operator\">:</span> <span class=\"token string\">'absolute'</span><span class=\"token punctuation\">,</span>\r\n      top<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n      left<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n      right<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n      bottom<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n      width<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span><span class=\"token punctuation\">,</span>\r\n      height<span class=\"token operator\">:</span> <span class=\"token string\">'100%'</span><span class=\"token punctuation\">,</span>\r\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\r\n  <span class=\"token punctuation\">}</span>\r\n\r\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\r\n    position<span class=\"token operator\">:</span> <span class=\"token string\">'fixed'</span><span class=\"token punctuation\">,</span>\r\n    top<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n    left<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n    right<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n    bottom<span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\r\n    width<span class=\"token operator\">:</span> <span class=\"token string\">'100vw'</span><span class=\"token punctuation\">,</span>\r\n    height<span class=\"token operator\">:</span> <span class=\"token string\">'100dvh'</span><span class=\"token punctuation\">,</span>\r\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Isso resolveu a parte visual. Aí os pointer events decidiram que também queriam atenção.</p>\n<p>No native, <code class=\"language-text\">pointerEvents=\"box-none\"</code> é normal. No HTML, isso vira <code class=\"language-text\">pointer-events: box-none</code>, que não é CSS de verdade, então o navegador fica livre pra improvisar. Então agora existe uma regra defensiva pro host do overlay:</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token selector\">.expo-web-app-shell #expo-web-modal-shell-host</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token property\">pointer-events</span><span class=\"token punctuation\">:</span> none <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span>\r\n\r\n<span class=\"token selector\">.expo-web-app-shell #expo-web-modal-shell-host *</span> <span class=\"token punctuation\">{</span>\r\n  <span class=\"token property\">pointer-events</span><span class=\"token punctuation\">:</span> auto <span class=\"token important\">!important</span><span class=\"token punctuation\">;</span>\r\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Também existe um <code class=\"language-text\">useLayoutEffect</code> forçando a mesma coisa inline, porque a essa altura eu já não tinha mais interesse em descobrir quão elegante seria a solução correta.</p>\n<h2 id=\"eu-claramente-tenho-um-padrao\" style=\"position:relative;\"><a href=\"#eu-claramente-tenho-um-padrao\" aria-label=\"eu claramente tenho um padrao permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Eu claramente tenho um padrão</h2>\n<p>Se você já lê este blog há algum tempo, nada disso deveria te surpreender.</p>\n<p>Eu já <a href=\"/pt-br/blog/coding/eu-criei-um-jogo-para-acessar-o-conteudo-do-meu-blog-com-phaser-e-react/\">transformei este blog num RPG top-down</a> porque o Konami Code merecia uma recompensa maior do que só um efeitinho de fundo.</p>\n<p>Depois eu <a href=\"/pt-br/blog/coding/meu-blog-tem-stories-agora-mas-nao-me-pergunte-por-que/\">coloquei Stories no blog</a> porque o app da minha operadora tinha Stories, e isso me irritou profundamente num nível suficiente pra eu transformar o problema em problema de todo mundo.</p>\n<p>Então sim, é claro que agora o meu app de fitness serve o site a partir do mesmo repo com Expo Router, e é claro que o app web no desktop roda dentro de uma foto gigante de um celular. Isso não é uma escalada surpreendente. É só continuidade.</p>\n<h2 id=\"por-que-eu-vou-manter-isso\" style=\"position:relative;\"><a href=\"#por-que-eu-vou-manter-isso\" aria-label=\"por que eu vou manter isso permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Por que eu vou manter isso</h2>\n<p>Passando da piada, isso resolveu um imposto de manutenção bem real.</p>\n<p>As traduções são compartilhadas. Os design tokens são compartilhados. Os componentes são compartilhados. As páginas legais ficam do lado do produto que elas estão legalmente defendendo. Quando eu mudo texto, screenshot ou posicionamento de feature, eu não preciso lembrar qual repo guarda a versão pública e comportada do mesmo app.</p>\n<p>O deploy também ficou melhor. Um build. Um export. Um artefato. O site e o app web não têm como sair de sincronia a não ser que eu faça questão de separá-los, o que fica bem mais difícil quando eles literalmente são publicados juntos.</p>\n<p>E quando eu lancei o <a href=\"/pt-br/blog/coding/musclog-redesign-acompanhamento-nutricional-e-por-que-a-assinatura-do-seu-app-de-fitness-e-uma-enganacao/\">redesign do Musclog</a>, o site basicamente veio junto no embalo. Mesmo spacing, mesmas cores, screenshots atualizadas, mesmo PR. Exatamente o tipo de vitória chata de manutenção que faz uma decisão de arquitetura meio estranha parecer inteligente seis meses depois.</p>\n<h2 id=\"conclusao\" style=\"position:relative;\"><a href=\"#conclusao\" aria-label=\"conclusao permalink\" class=\"post-headers-link before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Conclusão</h2>\n<p>Eu poderia ter mantido o site num repo separado em Next.js como uma pessoa emocionalmente mais estável? Poderia.</p>\n<p>Eu ia continuar fazendo isso depois que o Expo Router deixou esse caminho aberto? Não.</p>\n<p>A parte engraçada é que o roteamento foi a parte sem graça. O Expo Router resolveu isso muito bem. O trabalho irritante estava em todas as mentirinhas de navegador ao redor: bloqueio de rota antes da hidratação, i18n em HTML cru, sincronização de assets, modais que sabiam em que shell estavam, esquisitice com pointer-events, e convencer o Chrome do desktop a acreditar temporariamente que era um celular.</p>\n<p>Ainda assim, valeu muito a pena.</p>\n<p>Se quiser fuçar o código, o <a href=\"https://github.com/blopa/musclog-app\" target=\"_blank\" rel=\"noreferrer\">Musclog é open-source no GitHub</a>. Se quiser usar, tá em <a href=\"https://musclog.app\" target=\"_blank\" rel=\"noreferrer\">musclog.app</a>. O site mora dentro do repo do app agora.</p>\n<p>Agora mora todo mundo aqui.</p>","fields":{"postHashId":"Y29kaW5ndHJ1ZW51bGwyMDI2LTA0LTI1VDAwOjAwOjAwLjAwMFo=","slug":"/2026/2026-04-25-site-ou-app-sim-o-guia-gambiarrístico-para-sites-feitos-em-expo-router.pt-br/","path":"/blog/coding/site-ou-app-sim-o-guia-gambiarrístico-para-sites-feitos-em-expo-router/","locale":"pt-br"},"frontmatter":{"tags":["expo","expo router","react native","musclog","website","monorepo","javascript","typescript"],"categories":["coding"],"allowComments":true,"publishOnMedium":false,"cover":null,"date":"2026-04-25T00:00:00.000Z","id":null,"path":"site-ou-app-sim-o-guia-gambiarrístico-para-sites-feitos-em-expo-router","show":true,"title":"Site ou App? Sim. O guia gambiarrístico para sites feitos em Expo Router","hideExcerpt":false,"subtitle":"O roteamento foi fácil. O celular fake no navegador foi a parte amaldiçoada."}}}],"ogImage":"/static/f71a459f3034e47544de2f3ce309dd61/42a19/musclog-shared-router-website-home-1.png","language":"pt-br","intl":{"language":"pt-br","languages":["en","pt-br"],"messages":{"site_title":"pablo.gg","title":"Título","author":"@thepiratepablo","search_placeholder":"Buscar...","about":"Sobre","photos":"Fotos","archive":"Arquivo","contact":"Contato","close":"Fechar","contact_page":"Página de contato","see_more":"Veja mais posts","built_with":"Feito com ","buy_me_a_soda":"Me pague uma bebida","blog":"Blog","blog_posts":"Posts do blog","go_to_post":"Ir para o post","search":"Busca","loading":"Carregando...","search_results":"Resultados de busca","search_results_for":"{quantity} resultados de busca para: \"{query}\"","search_for_query":"Buscar por \"{query}\"","no_results":"Sem resultados","home":"Home","description":"Apenas mais um blog pessoal","go_back":"Voltar para home","thats_me":"Esse sou eu!","got_it":"Entendi!","check_it_out":"Confira!","we_are":"Faltam","e3":"E3","away_from_next_sgf":"para a Summer Game Fest 2026","away_from_next_gamescom":"para a Gamescom 2026","sgf_countdown":"Contador para Summer Game Fest","gamescom_countdown":"Contador para Gamescom","e3_paragraph_1":"Esta página já contou com uma contagem regressiva para o próximo evento E3, um momento que inúmeros jogadores e profissionais da indústria aguardavam ansiosamente a cada ano. E3 não era apenas um evento; era uma celebração da nossa paixão compartilhada por videogames, um lugar onde sonhos se realizavam e memórias eram feitas.","e3_paragraph_2":"Desde os anúncios eletrizantes até as demos práticas, a E3 era o coração do mundo dos jogos. Ela reunia pessoas de todos os cantos do globo, unidas pelo amor aos jogos. Para muitos, era uma chance de conhecer seus heróis, descobrir novos títulos e experimentar a emoção das últimas inovações em tecnologia de jogos.","e3_paragraph_3":"No entanto, à medida que o cenário dos jogos evoluiu, também evoluiu a maneira como nos conectamos e celebramos nossa paixão. Embora a E3 tenha chegado ao fim, o espírito de excitação e comunidade que ela promoveu continua vivo. Agora esperamos por novas maneiras de nos reunir, compartilhar nosso amor pelos jogos e criar novas memórias.","e3_paragraph_4":"Embora a contagem regressiva tenha acabado, o legado da E3 permanecerá para sempre em nossos corações, nos lembrando das jornadas incríveis que fizemos e dos laços que formamos ao longo do caminho.","sec":"Seg","secs":"Segs","min":"Min","mins":"Mins","hour":"Hora","hours":"Horas","day":"Dia","days":"Dias","month":"Mês","months":"Meses","year":"Ano","years":"Anos","recent_posts":"Posts recentes","email":"E-mail","twitter":"Twitter","name":"Nome","page":"Página","fill_this_want_reply":"Preencha isso se quiser que eu entre em contato com você","sorry_this_post_unavailable_language":"Desculpe, este post não está disponível no idioma que você escolheu","language":"Idioma","comment":"Comentário","comments":"Comentários","no_comments":"Nenhum comentário.","post_comment":"Publicar comentário","send_message":"Enviar mensagem","message":"Mensagem","post_a_comment":"Publicar um comentário","your_comment_submitted":"Seu comentário foi enviado com sucesso.","your_message_submitted":"Sua mensagem foi enviada com sucesso.","on":"em","ok":"Ok","copy":"Copiar","copied":"Copiado","photo_num":"Foto {num}","the_matrix_has_you":"The Matrix has you...","about_paragraph_1":"Entusiasta de tecnologia desde pequeno, sempre me interessei por computadores e videogames.","about_paragraph_2":"Me formei em Tecnologia da Informação na Faculdade Estácio de Sá e sempre procuro me informar sobre novas tecnologias e me envolver em novos projetos de desenvolvimento, alguns deles o código fonte pode ser encontrado no GitHub.","cookie_banner_consent":"Ao usar este site, você concorda com o uso de cookies para oferecer uma melhor experiência.","written_in":"Escrito em ","no_post_this_tag":"Nenhum post em Português contém essa tag.","tags":"Tags","tag_colon":"Tag: ","tags_colon":"Tags: ","posts_tagged":"Posts com a tag ","categories":"Categorias","category":"Categoria","category_colon":"Categoria: ","posts_on_category":"Posts na categoria ","related_posts":"Posts relacionados","read_time":"🕒 {time} min. de leitura","create_post":"Criar Post","show":"Mostrar","date":"Data","download":"Download","add_tag":"Adicionar Tag","hide_excerpt":"Esconder Excerpt","publish_on_medium":"Publicar no Medium","allow_comments":"Permitir Comentários","subtitle":"Subtítulo","you_must_be_truly_desperate":"Você deve estar muito desesperado para me pedir ajuda","game.game_title":"pablo.gg - O Jogo","game.next":"Próxima","game.ok":"Ok","game.loading_asset_colon":"Carregando asset:","game.loading":"Carregando...","game.characters.npc_01":"Frost","game.characters.npc_02":"Gavin","game.characters.npc_03":"Giles","game.characters.npc_04":"Godfrey","game.characters.npc_05":"Hugh","game.characters.npc_06":"Ivar","game.characters.npc_07":"Leopold","game.characters.npc_08":"Lucian","game.characters.npc_09":"Gumercindo","game.characters.npc_10":"Mr. Flower","game.characters.npc_11":"Maxim","game.characters.npc_12":"Milo","game.characters.npc_13":"Otto","game.characters.npc_14":"Palmer","game.characters.npc_15":"Quentin","game.characters.npc_16":"Sebastian","game.characters.npc_17":"Neville","game.characters.npc_18":"Cassian","game.characters.npc_19":"Balthasar","game.characters.npc_20":"Jasper","game.characters.sign_01":"Placa","game.characters.book_01":"Livro","game.characters.home_page_city_sign":"Placa","game.characters.coding_category_city_sign_01":"Placa","game.characters.coding_category_city_sign_02":"Placa","game.characters.events_category_city_sign":"Placa","game.characters.funny_category_city_sign":"Placa","game.characters.gadgets_category_city_sign_01":"Placa","game.characters.gadgets_category_city_sign_02":"Placa","game.characters.games_category_city_sign":"Placa","game.characters.general_category_city_sign":"Placa","game.characters.tips_category_city_sign":"Placa","game.characters.collectibles_category_city_sign":"Placa","game.characters.sword":"Info","game.characters.push":"Info","game.gamepad.a_button":"Botão A","game.gamepad.b_button":"Botão B","game.gamepad.d_pad_left":"D-Pad Esquerda","game.gamepad.d_pad_up":"D-Pad Cima","game.gamepad.d_pad_right":"D-Pad Direita","game.gamepad.d_pad_down":"D-Pad Baixo","game.gamepad.start_button":"Botão Start","game.menu.start":"Começar","game.menu.exit":"Sair","game.menu.settings":"Configurações","game.start_menu.save_game":"Salvar Jogo","game.start_menu.exit":"Sair","game.game_over.game_over":"Game Over","game.game_over.retry":"Tentar Novamente","game.game_over.exit":"Sair","game.browse_posts.choose_a_post":"Escolha um post para ler","game.dialogs.npc_01.01":"Ei, você finalmente acordou!","game.dialogs.npc_01.02":"O quê, você não sabe onde está?","game.dialogs.npc_01.03":"Não seja bobo, você está na Cidade Página Inicial, lembra?","game.dialogs.npc_01.04":"Esta cidade foi fundada por Pablo Montenegro para ser o início de sua jornada","game.dialogs.npc_01.05":"Explore o mundo e encontre outras cidades onde você possa ler o conhecimento acumulado de nossa civilização...","game.dialogs.npc_01.06":"... algumas pessoas chamam de \"Posts do Blog\", não sei por quê...","game.dialogs.npc_02.01":"Tenha cuidado com os Slimes que vivem no mundo aberto.","game.dialogs.npc_02.02":"Pressione ESPAÇO para usar sua espada","game.dialogs.npc_02.03":"O que é ESPAÇO? Eu não faço ideia.","game.dialogs.npc_03.01":"Olá, bem vindo a nossa biblioteca","game.dialogs.npc_03.02":"Temos apenas um livro, que contém todas as publicações da categoria dessa cidade.","game.dialogs.npc_03.03":"Vá dar uma olhada!","game.dialogs.npc_04.01":"Eu gosto de caracóis","game.dialogs.npc_05.01":"Frases incompletas podem causar","game.dialogs.npc_06.01":"O vermelho é mais verde do que o roxo, com certeza.","game.dialogs.npc_07.01":"Ter barba é o novo não ter barba","game.dialogs.npc_08.01":"E aí","game.dialogs.npc_09.01":"\" - Cooper, o que está fazendo?\"\n\" - Atracando.\"","game.dialogs.npc_10.01":"Eu deveria comprar um barco","game.dialogs.npc_11.01":"Conhece a piada do não nem eu? Não? Nem eu!","game.dialogs.npc_12.01":"Eu limpo o banheiro e resgato a princesas, vida boa, certo?","game.dialogs.npc_13.01":"Queremos as ondas de rádio de volta","game.dialogs.npc_14.01":"Salve a líder de torcida, salve o mundo","game.dialogs.npc_15.01":"Olá, como vai?","game.dialogs.npc_15.02":"OK, tchau!","game.dialogs.npc_16.01":"Um canguru é realmente apenas um coelho com esteróides","game.dialogs.npc_17.01":"Pela 216ª vez, ele disse que pararia de beber refrigerante após esta última Coca","game.dialogs.npc_18.01":"Nada e tudo é possímpossivel","game.dialogs.npc_19.01":"Para uma cidade chamada \"Eventos\", não há muito acontecendo...","game.dialogs.npc_20.01":"Eu ouvi dizer que existe um jeito de empurrar alguns objetos neste jogo, mas não sei como.","game.dialogs.sign_01.01":"Parabéns, você pode ler isso!","game.dialogs.book_01.01":"Hey, obrigado por testar esse novo jeito bem esquisito de acessar o meu site","game.dialogs.book_01.02":"Este projeto não seria possível sem o incrível trabalho de muitas pessoas, como:","game.dialogs.book_01.03":"ArMM1998 - Pelos sprites dos personagens e os tilesets","game.dialogs.book_01.04":"PixElthen - Pelos sprites do slime","game.dialogs.book_01.05":"pixelartm - Pelos sprites do chapéu de pirata","game.dialogs.book_01.06":"jkjkke - Pela imagem da tela de Game Over","game.dialogs.book_01.07":"KnoblePersona - Pela imagem da tela do menu inicial","game.dialogs.book_01.08":"Min - Pelo sprite do livro aberto","game.dialogs.book_01.09":"E claro, ao Richard Davey por ter criado o Phaser.io!","game.dialogs.home_page_city_sign":"Cidade Página Inicial","game.dialogs.coding_category_city_sign.01":"Cidade da Categoria Programação","game.dialogs.coding_category_city_sign.02":"Cidade da Categoria Programação","game.dialogs.events_category_city_sign":"Cidade da Categoria Eventos","game.dialogs.funny_category_city_sign":"Cidade da Categoria Engraçado","game.dialogs.gadgets_category_city_sign.01":"Cidade da Categoria Eletrónicos","game.dialogs.gadgets_category_city_sign.02":"Cidade da Categoria Eletrónicos","game.dialogs.games_category_city_sign":"Cidade da Categoria Jogos","game.dialogs.general_category_city_sign":"Cidade da Categoria Geral","game.dialogs.tips_category_city_sign":"Cidade da Categoria Dicas","game.dialogs.collectibles_category_city_sign":"Cidade da Categoria Brinquedos","game.dialogs.sword_item_description":"Agora você pode atacar, pressione ESPAÇO para usar sua espada.","game.dialogs.push_item_description":"Agora você pode empurrar alguns objetos, pressione ESPAÇO na frente de um objeto para empurrá-lo.","zelda_timeline.title":"Timeline de Zelda","zelda_timeline.timeline_split":"Divisão da Timeline","zelda_timeline.timeline_unification":"Timeline Unificada","zelda_timeline.icons_from":"Os ícones usados nesta página são do zeldauniverse.net e game-icons.net","zelda_timeline.creation":"Criação","zelda_timeline.creation_of_land_sky":"A Criação da Terra e do Céu","zelda_timeline.goddess_hylia_and_sky_era":"Deusa Hylia e a Era do Céu","zelda_timeline.skyward_sword":"Skyward Sword","zelda_timeline.the_ancient_battle":"A Antiga Batalha e a reencarnação da Deusa Hylia","zelda_timeline.return_to_surface":"O retorno à superfície","zelda_timeline.era_of_chaos":"Era do caos","zelda_timeline.sacred_realm_sealed":"O Sacred Realm é selado","zelda_timeline.era_of_prosperity":"Era da Prosperidade","zelda_timeline.establishment_of_hyrule":"O Reino de Hyrule é estabelecido","zelda_timeline.force_era":"Era da Força","zelda_timeline.the_minish_cap":"The Minish Cap","zelda_timeline.rise_of_evil_vaati":"A Ascensão do Maligno Vaati","zelda_timeline.four_swords":"Four Swords","zelda_timeline.resurrection_of_vaati":"A Ressurreição de Vaati","zelda_timeline.era_of_the_hero_of_time":"Era do Herói do Tempo","zelda_timeline.hyrulean_civil_war":"Guerra Civil Hyruleana","zelda_timeline.ocarina_of_time":"Ocarina of Time","zelda_timeline.sacred_realm_becomes_dark_world":"O Sacred Realm se torna o Dark World","zelda_timeline.ganondorf_becomes_ganon":"Ganondorf se torna Ganon","zelda_timeline.hero_is_defeated":"O herói é derrotado","zelda_timeline.decline_of_last_hero":"O declínio de Hyrule e o último herói","zelda_timeline.the_imprisoning_war":"A Guerra do Aprisionamento","zelda_timeline.era_of_dark_and_light":"Era de Luz e Escuridão","zelda_timeline.a_link_to_the_past":"A Link to the Past","zelda_timeline.resurrection_of_ganon":"A Ressurreição de Ganon","zelda_timeline.resurrection_of_ganon_is_prevented":"A ressurreição de Ganon é evitada","zelda_timeline.links_awakening":"Link's Awakening","zelda_timeline.oracle_of_ages_and_seasons":"Oracle of Ages e Oracle of Seasons","zelda_timeline.a_link_between_worlds":"A Link Between Worlds","zelda_timeline.tri_force_heroes":"Tri Force Heroes","zelda_timeline.the_gold_era":"The Gold Era","zelda_timeline.monarchs_of_hyrule_use_triforce":"Os Monarcas de Hyrule usam a Triforce","zelda_timeline.era_of_decline":"A Era do Declínio","zelda_timeline.tragedy_of_princess_zelda_1":"A Tragédia da Princesa Zelda I","zelda_timeline.the_legend_of_zelda":"The Legend of Zelda","zelda_timeline.adventure_of_link":"Adventure of Link","zelda_timeline.hero_defeated":"Hero Derrotado","zelda_timeline.child_era":"Era Infantil","zelda_timeline.adult_era":"Era Adulta","zelda_timeline.sacred_realm_protected":"Sacred Realm está protegido","zelda_timeline.twilight_realm_and_legacy_of_hero":"O Twilight Realm e o legado do Herói","zelda_timeline.majoras_mask":"Majora's Mask","zelda_timeline.prince_of_thieves_is_executed":"O Príncipe dos Ladrões Ganondorf é executado","zelda_timeline.twilight_era":"A Era do Twilight","zelda_timeline.twilight_princess":"Twilight Princess","zelda_timeline.shadow_invasion":"A invasão das sombras","zelda_timeline.shadow_era":"A Era das Sombras","zelda_timeline.four_swords_adventures":"Four Swords Adventures","zelda_timeline.reincarnation_of_ganondorf":"A Reencarnação de Ganondorf","zelda_timeline.ganondorf_is_sealed":"Ganondorf é selado","zelda_timeline.hero_of_wind_and_new_world":"O Herói do Vento e um Novo Mundo","zelda_timeline.era_without_a_hero":"A era sem um herói","zelda_timeline.ganondorf_is_resurrected":"Ganondorf é ressuscitado","zelda_timeline.hyrule_is_sealed_and_flooded":"Hyrule é selado e então inundado","zelda_timeline.era_of_the_great_sea":"A Era do Great Sea","zelda_timeline.the_wind_waker":"The Wind Waker","zelda_timeline.era_of_the_great_voyage":"A Era da Grande Viagem","zelda_timeline.phantom_hourglass":"Phantom Hourglass","zelda_timeline.era_of_hyrule_rebirth":"A Era do Renascimento de Hyrule","zelda_timeline.new_continent_discovered":"Novo continente descoberto","zelda_timeline.new_hyrule_is_founded":"Um novo reino de Hyrule é fundado","zelda_timeline.spirit_tracks":"Spirit Tracks","zelda_timeline.evil_king_malladus_is_resurrected":"O Rei Maligno Malladus é ressuscitado","zelda_timeline.age_of_calamity":"Age of Calamity","zelda_timeline.breath_of_the_wild":"Breath of the Wild","zelda_timeline.era_of_the_wilds":"A Era dos Selvagens","zelda_timeline.calamity_ganon_is_sealed":"Calamity Ganon é Selado. A tecnologia é proibida, levando alguns Sheikah a formar o Yiga Clan","zelda_timeline.divine_beasts_are_cleansed":"As Bestas Divinas são purificadas e Calamity Ganon é Selado","zelda_timeline.tears_of_the_kingdom":"Tears of the Kingdom","zelda_timeline.hyrule_kingdom_is_teared_apart":"Ganondorf é ressuscitado (Será?)","blog_categories.games":"Jogos","blog_categories.general":"Geral","blog_categories.tips":"Dicas","blog_categories.events":"Eventos","blog_categories.coding":"Programação","blog_categories.funny":"Engraçado","blog_categories.collectibles":"Colecionáveis","blog_categories.gadgets":"Eletrónicos","e3_2012_photos.title":"E3 2012","e3_2012_photos.description":"Em junho de 2012 participei da E3 como imprensa para uma cobertura completa para o Nintendo Blast.","e3_2013_photos.title":"E3 2013","e3_2013_photos.description":"Em junho de 2013 participei da E3 como imprensa para uma cobertura completa para o Game Blast.","e3_2014_photos.title":"E3 2014","e3_2014_photos.description":"Em junho de 2014 participei da E3 como imprensa para uma cobertura completa para o Game Blast.","e3_2015_photos.title":"E3 2015","e3_2015_photos.description":"Em junho de 2015 participei da E3 como imprensa para uma cobertura completa para o Game Blast e Game Over TV.","e3_2017_photos.title":"E3 2017","e3_2017_photos.description":"Em junho de 2017 participei da E3 como imprensa para uma cobertura completa para o PlayReplay e Game Over TV.","e3_2019_photos.title":"E3 2019","e3_2019_photos.description":"Em junho de 2019 participei da E3 como imprensa para uma cobertura completa para o PlayReplay.","gamescom_2019_photos.title":"Gamescom 2019","gamescom_2019_photos.description":"Em agosto de 2019 participei da Gamescom como imprensa para uma cobertura completa para o PlayReplay.","san_francisco_2019_photos.title":"San Francisco 2019","san_francisco_2019_photos.description":"Em setembro de 2019, viajei para San Francisco para o show do Metallica S&M2.","forty_two_page.title":"Quarenta e Dois","forty_two_page.description":"Até mais, e obrigado pelos peixes!","projects_page.title":"Projetos","projects_page.description":"Aqui está uma lista de alguns dos meus projetos pessoais favoritos.","projects_page.gatsbyMaterialUiBlogDescription":"Um simples Gatsby Blog Starter com Material UI.","projects_page.contractBuilderDescription":"O Contract Builder é um projeto de código aberto gratuito que permite a qualquer pessoa manter e construir facilmente qualquer tipo de contrato (documentos legais, processos judiciais, aluguel, acordos, construção e assim por diante) usando o Google Spreadsheets. Este foi desenvolvido como um projeto pessoal para ajudar uma amiga que estava com dificuldades de gastar até uma hora para fazer um contrato personalizado, agora ela consegue fazer em menos de 5 minutos. Hooray!","projects_page.resumeBuilderDescription":"Resume Builder é um projeto de código aberto gratuito que permite a qualquer pessoa manter e construir facilmente qualquer tipo de currículo usando o Google Spreadsheets. Este foi desenvolvido como um projeto pessoal para ajudar um amigo que estava com dificuldades de gastar até uma hora para fazer um currículo personalizado.","projects_page.magentoChatbotDescription":"Com este módulo, você pode integrar totalmente a sua loja Magento com os aplicativos de chat mais populares do mercado. Isso significa que simplesmente instalando este módulo e alguns cliques você pode ter uma nova forma de mostrar e vender seus produtos aos seus clientes. Muito fácil de usar! Experimente agora, é GRÁTIS.","projects_page.jamStackSortenerDescription":"Este é um POC de um encurtador de URL básico desenvolvido com Gatsby.","projects_page.gotinhaDescription":"Sempre foi meu sonho fazer meu próprio jogo, e depois de experimentar o Unity alguns anos atrás, decidi tentar novamente com algo com o qual estou mais familiarizado: Javascript. Como desenvolvedor front-end, Javascript já é a linguagem com a qual escrevo a maior parte do meu código no trabalho e também em meus projetos pessoais, e após uma rápida pesquisa consegui encontrar o incrível PhaserJS Framework para construção de jogos web 2D.","notfound.title":"404: Não encontrado","notfound.header":"404 NÃO ENCONTRADO","notfound.description":"Desculpe, esta página parece não existir. Talvez os arquivos estejam incompletos?","seo_keywords.developer":"desenvolvedor","seo_keywords.development":"desenvolvimento","seo_keywords.javascript":"javascript","seo_keywords.es6":"es6","seo_keywords.e3":"e3","seo_keywords.sgf":"sgf","seo_keywords.gamescom":"gamescom","seo_keywords.countdown":"contador","seo_keywords.archive":"arquivo","seo_keywords.about_me":"sobre mim","seo_keywords.personal_blog":"blog pessoal","seo_keywords.personal_projects":"projetos pessoais","seo_keywords.travels":"viagens","seo_keywords.tips":"dicas","seo_keywords.lifehacks":"truques de produtividade","seo_keywords.reviews":"analises","seo_keywords.games":"games","seo_keywords.timeline":"linha do tempo","seo_keywords.photos":"fotos","cookie_law.we_use_cookies":"Usamos cookies para garantir que você obtenha a melhor experiência em nosso site. Ao usar nosso site, você concorda com nossa ","cookie_law.title":"Política de cookies","cookie_law.what_are_cookies":"O que são cookies?","cookie_law.what_are_cookies_text":"Como é prática comum em quase todos os sites profissionais, este site usa cookies, que são pequenos arquivos baixados para o seu computador, para melhorar a sua experiência. Esta página descreve quais informações eles coletam, como as usamos e por que às vezes precisamos armazenar esses cookies. Também compartilharemos como você pode evitar que esses cookies sejam armazenados, no entanto, isso pode diminuir ou 'quebrar' certos elementos da funcionalidade do site. Para obter mais informações gerais sobre cookies, leia ","cookie_law.what_are_cookies_more_info_url":"https://pt.wikipedia.org/wiki/Cookie_(informática)","cookie_law.how_we_use_cookies":"Como usamos cookies","cookie_law.how_we_use_cookies_text":"Usamos cookies por vários motivos detalhados abaixo. Infelizmente, na maioria dos casos, não há opções padrão da indústria para desabilitar cookies sem desabilitar completamente a funcionalidade e os recursos que eles adicionam a este site. Recomenda-se que você deixe todos os cookies se não tiver certeza se precisa deles ou não, caso sejam usados para fornecer um serviço que você usa.","cookie_law.disabling_cookies":"Desativando cookies","cookie_law.disabling_cookies_text":"Você pode impedir a configuração de cookies ajustando as configurações do seu navegador (consulte a Ajuda do navegador para saber como fazer isso). Esteja ciente de que a desativação de cookies afetará a funcionalidade deste e de muitos outros sites que você visita. A desativação dos cookies normalmente resultará na desativação de certas funcionalidades e recursos deste site. Portanto, é recomendável que você não desative os cookies.","cookie_law.the_cookies_we_set":"Os cookies que definimos","cookie_law.site_preferences_cookie":"Cookies de preferências do site","cookie_law.site_preferences_cookie_text":"Para lhe proporcionar uma excelente experiência neste site, fornecemos a funcionalidade para definir as suas preferências de funcionamento deste site quando o utiliza. Para lembrar suas preferências, precisamos definir cookies para que essas informações possam ser chamadas sempre que você interagir com uma página afetada por suas preferências.","cookie_law.third_party_cookies":"Cookies de terceiros","cookie_law.third_party_cookies_text":"Em alguns casos especiais, também usamos cookies fornecidos por terceiros confiáveis. A seção a seguir detalha quais cookies de terceiros você pode encontrar neste site.","cookie_law.third_party_cookies_item_1":"Este site usa o Google Analytics, que é uma das soluções de análise mais difundidas e confiáveis na web para nos ajudar a entender como você usa o site e como podemos melhorar sua experiência. Esses cookies podem rastrear coisas como quanto tempo você passa no site e as páginas que você visita para que possamos continuar a produzir conteúdo envolvente. Para obter mais informações sobre os cookies do Google Analytics, consulte a página oficial do Google Analytics.","cookie_law.third_party_cookies_item_2":"De vez em quando, testamos novos recursos e fazemos mudanças sutis na maneira como o site é fornecido. Quando ainda estamos testando novos recursos, esses cookies podem ser usados para garantir que você receba uma experiência consistente enquanto estiver no site, garantindo que entendemos quais otimizações nossos usuários mais apreciam.","cookie_law.more_information":"Mais informações","cookie_law.more_information_text":"Esperamos que isso tenha esclarecido as coisas para você e, conforme mencionado anteriormente, se há algo que você não tem certeza se precisa ou não, geralmente é mais seguro deixar os cookies ativados, caso eles interajam com um dos recursos que você usa em nosso site. No entanto, se você ainda estiver procurando por mais informações, entre em contato conosco através de nossa "},"routed":true,"originalPath":"/blog/tags/website","redirect":true,"redirectDefaultLanguageToRoot":false,"defaultLanguage":"en","fallbackLanguage":"","ignoredPaths":[]},"locale":"pt-br"}},
    "staticQueryHashes": ["1156153307","1355482417","1591365477","1628619374","2127381735","2288279559","26159077","3566410298","3649515864","3847325417","3982724423","928834867"]}