Skip to content

fix(mobile): corta o sangramento da aureola na tela, e devolve o pacote ao centro - #26

Merged
mcsscalabrin merged 2 commits into
mainfrom
fix/rolagem-horizontal-mobile
Aug 14, 2026
Merged

fix(mobile): corta o sangramento da aureola na tela, e devolve o pacote ao centro#26
mcsscalabrin merged 2 commits into
mainfrom
fix/rolagem-horizontal-mobile

Conversation

@mcsscalabrin

Copy link
Copy Markdown
Owner

What changed, and why

Fecha #16: a página da carta rolava de lado no telefone.

A origem não é o pacote. É a auréola do elemento atrás da carta — .card-center::before, um quadrado de 620px que sangra da carta de propósito. Num viewport de 390 ela sangra para fora da tela também: scrollWidth 505 contra clientWidth 390. Como é pseudo-elemento, não aparece em varredura de DOM; só no scrollWidth do .card-center (485 contra 350 de clientWidth).

O pacote entra como vítima, e é por isso que parecia o culpado. Documento mais largo que a tela faz o navegador móvel esticar o viewport de layout até caber o vazamento, e é dele que sai a caixa de position: fixed; inset: 0. Medido: .pack ficava com 505px numa tela de 390 — saía do centro e levava o botão "Pular abertura" para fora da borda (a 320px o Playwright nem conseguia clicar nele, o overlay interceptava o ponteiro).

Por que duas linhas, e não uma

overflow-x: clip na raiz e no body. Medi cada variante em 390×844, em /torvalds:

variante scrollWidth largura do .pack
sem correção 505 505
html 505 505
body 505 505
html + body 390 390

Só na raiz não resolve: ela propaga o valor para o viewport em vez de aplicá-lo em si mesma, o documento continua largo e o viewport de layout esticado. Só no body também não: com a raiz em visible, quem propaga é o body, e cai no mesmo caso. A linha da raiz existe para tirar o body do papel de propagador — e aí o corte acontece no body, na largura da tela.

Alternativas descartadas

  • Cortar em .card-center: a auréola vira um retângulo duro. O sangramento é intencional.
  • Encolher a auréola (width: min(620px, 100%)): o gradiente é circle sem tamanho, então escala com a caixa — a 350px o brilho fica menor que a carta. Perde no telefone, que é justamente onde o bug aparece.
  • Cortar em .shell: quebra o desktop, onde .card-panel escapa do shell de propósito (width: min(1360px, 100vw - 40px)).
  • hidden em vez de clip: hidden deixa o eixo rolável por script — um scrollIntoView de algo que vazou ainda arrastaria a página de lado, agora sem barra para desfazer.

O corte fica no nível da tela, então a auréola mantém os 620px inteiros e some só o que já estava fora do viewport — que ali é cauda de gradiente já transparente.

O segundo commit

test(e2e): espera a hidratacao antes de mandar Escape no pacote — corrige uma flakiness pré-existente que apareceu enquanto eu rodava a suíte. O overlay vem pronto no HTML, mas quem ouve o Escape é um listener que só existe depois da hidratação; Escape mandado antes dela não fica na fila, some, e o teste falhava acusando o pacote. Reproduzido de forma determinística com a CPU estrangulada em 20× via CDP (Escape imediato deixa o pacote na tela; Escape depois do foco dispensa). A espera é o mesmo sinal que o teste de confinamento de foco já usava antes de mandar Tab.

What you saw

Rodei npm run dev e olhei, em 390×844 e em 1440×900:

  • Telefone, pacote lacrado: a embalagem volta ao centro da tela e o "Pular abertura" cabe inteiro na largura. Antes ficava descentrado, com o botão pela metade fora da borda.
  • Telefone, carta revelada: a auréola continua igual — como o corte é na borda da tela e o que passava dela já era gradiente transparente, não há diferença visível.
  • Desktop: sem mudança. As três colunas seguem lá e o painel mantém o full bleed (mede 40..1400 numa viewport de 1440).
  • Durante o rasgo do pacote: a tira sai para a direita e o documento fica em 390 — antes essa animação também vazava.

Medições em /, /torvalds, /torvalds/linux e /battle/…, a 320px e 390px: todas com scrollWidth == clientWidth depois da correção. Home e batalha já estavam limpas.

O teste e2e novo (largura no telefone) mede o documento, e não a auréola: o sintoma a evitar é a página mais larga que a tela, venha de onde vier. Confirmei que ele falha sem a correção (505 ≠ 390) revertendo o CSS.

Checks

  • npm run lint
  • npm run typecheck
  • npm test (161 testes, 13 arquivos)
  • Touched the card's shape (new field, removed enum value)? Bumped CARD_VERSION in lib/cards/index.ts — não toquei
  • Touched a scoring formula? — não toquei
  • Added an i18n key? — nenhuma

E2E local contra npm run dev: os testes de layout e de pacote passam. Ficaram falhando as rotas de imagem e o pôster de batalha, por page.goto estourando 30s com o servidor de dev frio nesta máquina ("Slow filesystem detected") e pelo pôster depender de REDIS_URL — nenhuma delas toca CSS. A esteira contra o preview é quem dá a palavra final aí.

A pagina da carta rolava de lado no telefone (#16). A origem nao e o pacote: e
a aureola do elemento atras da carta, um quadrado de 620px que sangra de
proposito e, num viewport de 390, sangra para fora da tela tambem — 115px de
gradiente ja transparente, e roláveis.

O pacote entra como vitima, e e por isso que ele parecia o culpado. Documento
mais largo que a tela faz o navegador movel esticar o viewport de layout ate
caber o vazamento, e e dele que sai a caixa do overlay (`position: fixed;
inset: 0`): medido, `.pack` ficava com 505px numa tela de 390 — saia do centro
e levava o botao de pular para fora da borda.

`overflow-x: clip` na raiz **e** no body. So na raiz nao resolve: ela propaga o
valor para o viewport em vez de aplica-lo em si mesma, o documento continua
largo e o viewport de layout esticado (medido: `.pack` seguia com 505px). A
linha da raiz existe para tirar o body do papel de propagador — e ai o corte
acontece no body, na largura da tela.

O corte fica nesse nivel porque o sangramento e intencional: a aureola mantem
os 620px inteiros, some so o que ja estava fora da tela, e o painel continua
escapando do shell no desktop.

O teste mede o documento, e nao a aureola: o sintoma a evitar e a pagina mais
larga que a tela, venha de onde vier.

Closes #16
O teste do Escape falhava sob carga acusando o pacote, que estava certo: o
overlay vem pronto no HTML, mas quem ouve a tecla e um listener que so passa a
existir depois da hidratacao. Escape mandado antes dela nao fica na fila — some.

Reproduzido de forma deterministica com a CPU estrangulada em 20x, que alarga a
janela entre o `load` e a hidratacao: Escape imediato deixa o pacote na tela,
Escape depois do foco dispensa. O sinal de que o componente montou e o foco que
ele proprio move para o botao de rasgar — o mesmo que o teste de confinamento ja
espera antes de mandar Tab.
@github-actions

Copy link
Copy Markdown

@mcsscalabrin mcsscalabrin added the bug Something isn't working label Aug 14, 2026
@mcsscalabrin
mcsscalabrin merged commit c42ebae into main Aug 14, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant