/* NodeLogic web — estilos partilhados por todas as páginas.
   Antes viviam duplicados no <style> inline de cada página (9 variantes com drift).
   Ordem de cascade por página: nl-shared.css (tokens) → site.css → <style> inline
   (overrides específicos da página) → tailwind.css.
   Ao alterar este ficheiro: incrementar ?v=N do <link> nas 13 páginas (regra 7 do CLAUDE.md). */

* { font-style: normal !important; box-sizing: border-box; }
html, body { position: relative; width: 100%; }  /* overflow-x só no body — html com overflow próprio tornava o body um scroller aninhado (wheel presa) */
body { background-color: var(--bg-dark); color: #ffffff; font-family: 'Inter', sans-serif; overflow-x: hidden; margin: 0; }

.page-bg {
    position: fixed; inset: 0; z-index: -2; background-color: var(--bg-dark);
    background-image:
        radial-gradient(circle at 0% 0%, rgba(59, 130, 246, 0.12) 0%, transparent 50%),
        radial-gradient(circle at 100% 100%, rgba(59, 130, 246, 0.08) 0%, transparent 50%);
}

.liquid-glass {
    background: rgba(255, 255, 255, 0.03);
    backdrop-filter: blur(24px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 2.5rem;
}

.nav-link { position: relative; opacity: 0.5; transition: opacity 0.3s; }
.nav-link:hover { opacity: 1; }
.nav-link::after {
    content: ''; position: absolute; width: 0; height: 2px; bottom: -6px; left: 50%;
    background-color: var(--accent-blue); transition: all 0.3s; transform: translateX(-50%);
    border-radius: 2px;
}
.nav-link:hover::after { width: 12px; }

.btn-pill { border-radius: 9999px; transition: all 0.3s ease; display: inline-flex; align-items: center; justify-content: center; }

/* Unified 1100px Alignment */
.nav-wrapper { position: fixed; top: 2rem; left: 0; right: 0; z-index: 100; padding: 0 1.5rem; }
.nav-inner { max-width: 1100px; margin: 0 auto; width: 100%; }
.content-container { max-width: 1100px; margin: 0 auto; width: 100%; }

/* Mobile Menu */
#mobile-menu {
    position: fixed; inset: 0; background: rgba(5, 5, 5, 0.98); z-index: 200;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    opacity: 0; pointer-events: none; visibility: hidden;
    transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.4s;
}
#mobile-menu.active { opacity: 1; pointer-events: auto; visibility: visible; backdrop-filter: blur(20px); }
.mobile-link { font-size: 2rem; font-weight: 800; text-transform: uppercase; letter-spacing: -0.02em; margin: 1.5rem 0; transition: all 0.3s; }
.mobile-link:hover { color: var(--accent-blue); transform: scale(1.1); }

.section-tag { font-size: 10px; font-weight: 800; letter-spacing: 0.2em; text-transform: uppercase; color: var(--accent-blue); }
.category-tag { font-size: 10px; font-weight: 800; letter-spacing: 0.2em; text-transform: uppercase; padding: 6px 16px; border-radius: 99px; background: rgba(59, 130, 246, 0.1); color: var(--accent-blue); display: inline-block; }

/* Hero das páginas de pilar. As versões inline traziam "md:border-radius"/"md:margin-bottom"
   (sintaxe Tailwind em CSS puro = declaração ignorada); a intenção era responsivo. */
.project-hero-img { width: 100%; height: auto; min-height: 40vh; max-height: 60vh; object-fit: cover; border-radius: 2rem; margin-bottom: 2rem; }
@media (min-width: 768px) {
    .project-hero-img { border-radius: 3rem; margin-bottom: 4rem; }
}

/* Alternância real de tom entre secções (zebra): secção "alt" leva tint branco subtil
   + um acento azul radial cuja posição varia por secção (l/r/c) — o azul da marca
   "viaja" ao longo do scroll. Alternar l/r pela página; c para o footer. */
.section-alt {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
}
.section-alt.accent-l {
    background: radial-gradient(ellipse 60% 80% at 8% 0%, rgba(59, 130, 246, 0.10), transparent 55%),
                linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
}
.section-alt.accent-r {
    background: radial-gradient(ellipse 60% 80% at 92% 0%, rgba(59, 130, 246, 0.10), transparent 55%),
                linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
}
.section-alt.accent-c {
    background: radial-gradient(ellipse 70% 90% at 50% 100%, rgba(59, 130, 246, 0.08), transparent 60%),
                linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
}

::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--bg-dark); }
::-webkit-scrollbar-thumb { background: var(--bg-tooltip); border-radius: 20px; }

/* ── portado do portfólio: transição de página + traço da navbar ── */
.page { opacity: 0; transform: translateY(14px); transition: opacity 0.35s ease, transform 0.45s cubic-bezier(0.16, 1, 0.3, 1); }
.page.in { opacity: 1; transform: none; }
.page.out { opacity: 0; transform: translateY(-8px); transition: opacity 0.22s ease, transform 0.22s ease; }
@media (prefers-reduced-motion: reduce) { .page, .page.in, .page.out { transition: none; transform: none; } }

/* View Transitions (W4). Foram rejeitadas em julho — "nem senti diferença" — e
   estavam certas: com a página toda a recarregar, faziam fade entre dois headers
   iguais. Agora que o shell fica montado e só o miolo troca, há passagem para fazer.
   O router só as usa se o browser as tiver e não houver movimento reduzido; senão
   cai no fade do .page acima. */
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: var(--nl-vt-duration, 260ms);
    animation-timing-function: var(--nl-vt-ease, cubic-bezier(0.16, 1, 0.3, 1));
}

.nav-link.active { opacity: 1; }
.nav-link::after { display: none; }   /* desliga o sublinhado hover individual */
.nav-underline {
    position: absolute; bottom: -8px; left: 0; margin: 0 !important; width: 14px; height: 2px;
    background: var(--accent-blue); border-radius: 2px; opacity: 0;
    transform: translateX(0); pointer-events: none;
    transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s ease;
}


/* ── Demo frame (protótipos navegáveis: browser frame + screenshot + CTA) ──
   Usado nas páginas de pilar (cultura, synapse) para lançar demos em demos/<slug>/. */
.demo-frame { display: block; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 1.5rem; overflow: hidden; background: #0a0a0a; transition: border-color 0.4s cubic-bezier(0.16, 1, 0.3, 1); }
.demo-frame:hover { border-color: rgba(59, 130, 246, 0.4); }
.demo-frame-bar { display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: rgba(255, 255, 255, 0.03); border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
.demo-frame-bar .wdot { width: 10px; height: 10px; border-radius: 50%; background: rgba(255, 255, 255, 0.12); }
.demo-frame-url { flex: 1; max-width: 420px; margin: 0 auto; text-align: center; font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); background: rgba(255, 255, 255, 0.04); border-radius: 99px; padding: 6px 16px; }
.demo-shot { position: relative; }
.demo-shot img { display: block; width: 100%; height: auto; }
.demo-shot .demo-cta {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    background: rgba(5, 5, 5, 0.35); opacity: 0; transition: opacity 0.35s ease;
}
.demo-frame:hover .demo-cta { opacity: 1; }
@media (hover: none) { .demo-shot .demo-cta { opacity: 1; background: transparent; align-items: flex-end; padding-bottom: 2rem; } }

/* ── Mosaico do hero ────────────────────────────────────────────────────────
   Os trabalhos a passar por trás do wordmark. Colunas em loop contínuo, umas a
   subir outras a descer, o conjunto rodado 45 graus.

   CADA COLUNA É UMA IMAGEM, não doze. As tiras vêm já recortadas e já gradadas do
   build-hero-strip.py, portanto não há filtro nenhum a correr aqui. O mecanismo do
   portfolio, que monta isto com 120 <img> de 1080x1080 e filtros em tempo real,
   custava 4,68 MB e 233 megapixéis; estas seis tiras custam 238 KB. Ver
   HERO-WORKS-PLAN.md.

   Camadas: mosaico (0) → véu (1) → nós (2) → texto (10). */
/* O mosaico do hero mudou-se para shared/css/mosaic.css (componente
   NLShared.mosaic, partilhado com os portfólios) — ver shared/js/mosaic.js. */

/* O véu escurece o centro para o wordmark mandar, e deixa respirar nos cantos. */
.nl-hero::after {
    content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
    background: radial-gradient(ellipse 62% 58% at 50% 46%,
        rgba(5, 7, 13, 0.93) 0%, rgba(5, 7, 13, 0.8) 38%, rgba(5, 7, 13, 0.42) 72%, rgba(5, 7, 13, 0.2) 100%);
}

/* ── Plano a sangrar ────────────────────────────────────────────────────────
   Uma imagem de trabalho a toda a largura, a cortar o ritmo entre secções densas.
   É o dispositivo que o portfolio tem e o site não tinha, e a homepage do site não
   mostrava um único trabalho. Ver HERO-WORKS-PLAN.md, parte 2.

   NÃO ANIMA, de propósito. A fase H0 mediu que o que custa nesta página é animar
   área grande: o orçamento de movimento gasta-se no mosaico do hero, e um plano
   parado custa zero por frame.

   A imagem vem já recortada do build (1920x840). O escurecimento para o texto ler é
   um gradiente aqui, e não no ficheiro: assim a imagem fica fiel ao trabalho e o véu
   adapta-se ao tamanho do ecrã. */
.nl-shot { position: relative; overflow: hidden; min-height: clamp(360px, 58vh, 620px); display: flex; align-items: flex-end; }
.nl-shot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* Dois véus, não um. O de baixo assenta o bloco de texto; o da esquerda escurece a
   coluna onde o texto vive, para o título não depender do que a fotografia tem por
   trás. Sem o segundo, um título longo acabava por cima de céu claro e deixava de se
   ler — visto no plano do BMW, onde a metade direita é céu. */
.nl-shot::after {
    content: ''; position: absolute; inset: 0;
    background:
        linear-gradient(to right, rgba(5, 7, 13, 0.86) 0%, rgba(5, 7, 13, 0.62) 34%, rgba(5, 7, 13, 0.12) 62%, rgba(5, 7, 13, 0) 100%),
        linear-gradient(to top, rgba(5, 7, 13, 0.94) 0%, rgba(5, 7, 13, 0.62) 34%, rgba(5, 7, 13, 0.12) 72%, rgba(5, 7, 13, 0) 100%);
}
.nl-shot-in { position: relative; z-index: 1; width: 100%; padding: 3rem 0 3.5rem; }
/* O título é limitado em CARACTERES, não em px: um nome curto ocupa uma linha e um
   longo parte em duas, sem nunca invadir a metade da direita da fotografia. */
.nl-shot h3 { font-size: clamp(1.75rem, 4vw, 3.1rem); font-weight: 800; text-transform: uppercase; letter-spacing: -0.03em; line-height: 0.98; color: #fff; margin: 0.9rem 0 0; max-width: 17ch; }
.nl-shot p { color: #b9c0cf; font-weight: 300; line-height: 1.6; max-width: 42ch; margin: 1.1rem 0 0; }
.nl-shot .nl-shot-cta { display: inline-flex; align-items: center; gap: 0.6rem; margin-top: 1.75rem; }
@media (max-width: 640px) { .nl-shot { align-items: flex-end; } .nl-shot-in { padding: 2rem 0 2.5rem; } }

/* Palco 3D — usado no monumonta.html (o modelo glb) e no immersive.html (a visita
   splat). Estava escrito byte a byte igual no <style> das duas páginas, que é
   exatamente o caso da regra 8 do CLAUDE.md: regra em 2+ páginas vive aqui.
   Consolidado a 2026-08-26, W2 do SPA-ROUTER-PLAN.md. */
model-viewer {
    width: 100%; height: 560px; display: block;
    background: transparent; --poster-color: transparent;
}
@media (max-width: 767px) { model-viewer { height: 420px; } }
.viewer-shell { border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 1.5rem; overflow: hidden; background: rgba(255, 255, 255, 0.02); position: relative; }
.viewer-hint { position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); pointer-events: none; }

/* ── O banner de sub-marca (social-strings: Midnight; synapse: Cultura) ──
   A fotografia DELA a sangrar com o texto por cima. A metade esquerda da foto
   tem de ser escura (limite dos planos da homepage: 140/255; midnight-photo-2
   mede 24, cultura-bg mede 38) — o gradiente só reforça. */
.sub-banner {
    display: block; position: relative; overflow: hidden;
    background-size: cover; background-position: center;
}
.sub-banner::before {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, rgba(5, 7, 13, 0.86) 0%, rgba(5, 7, 13, 0.45) 55%, rgba(5, 7, 13, 0.08) 100%);
}
.sub-banner-inner { position: relative; z-index: 1; padding-top: 7rem; padding-bottom: 7rem; }
.sub-banner .seta { transition: transform 0.3s; }
.sub-banner:hover .seta { transform: translateX(4px); }

/* A nav usa justify-between: os links do meio só ficavam centrados enquanto os
   dois lados tivessem a mesma largura. O seletor de língua engordou o lado
   direito e empurrou-os para a esquerda — visível de imediato. Centrar no NAV
   (absoluto, translate) torna a posição independente do que cresce aos lados.
   O .nav-underline continua a medir dentro do grupo, portanto não muda nada. */
@media (min-width: 768px) {
    .nav-inner > nav { position: relative; }   /* âncora do centro */
    /* `nav >` para ganhar à classe `relative` do Tailwind, que vem depois no
       ficheiro e, com a mesma especificidade, deixava o elemento em relative com
       um left de 510px — empurrado 366px para a direita em vez de centrado. */
    .nav-inner > nav > .nav-links-group { position: absolute; left: 50%; transform: translateX(-50%); }
}

/* ── seletor de língua ────────────────────────────────────────────────────
   Correção, não caminho principal: a língua do sistema já decide, e isto serve
   quem aterrou na errada (o português com o portátil em inglês). Por isso vive
   na nav, onde o desencontro se nota — no rodapé obrigava a percorrer a página
   toda numa língua que não se quer — e em texto pequeno, ao peso dos links da
   nav, para não competir com eles. Nada de bandeiras: bandeira é país, não
   língua (e obrigava a escolher entre PT e BR, UK e US). */
.nl-lang { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 9px; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; }
.nl-lang a { color: rgba(255, 255, 255, 0.45); transition: color 0.3s; }
.nl-lang a:hover { color: #fff; }
.nl-lang .on { color: #fff; }
.nl-lang .sep { color: rgba(255, 255, 255, 0.2); }
@media (max-width: 639px) { .nl-lang { display: none; } }   /* no telemóvel vive no menu */
#mobile-menu .nl-lang { display: inline-flex; margin-top: 2.5rem; font-size: 11px; }

/* ── mockups da Vintedex ────────────────────────────────────────────
   Chegaram do João já com dispositivo, cenário e cor. O raio grande e a sombra
   assentam-nos na página escura sem lhes tocar; NÃO levam moldura por cima (a
   .site-print aqui daria moldura sobre moldura). */
.vx-mockups { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(1.5rem, 3vw, 2.5rem); }
.vx-mock { margin: 0; }
.vx-mock img {
    display: block; width: 100%; height: auto; border-radius: 1.75rem;
    box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.8);
}
.vx-mock figcaption { color: #9ca3af; font-weight: 300; font-size: 0.925rem; line-height: 1.6; margin-top: 1.1rem; }

/* O ritmo da secção "Por Dentro": mockup a toda a largura a abrir e a fechar,
   pares no meio, e os recortes técnicos AGRUPADOS num painel por superfície —
   um objeto com uma legenda, não meia dúzia de figuras soltas. */
.vx-full { margin: 0 0 clamp(1.5rem, 3vw, 2.5rem); }
.vx-mockups + .vx-painel, .vx-mock + .vx-mockups, .vx-painel + .vx-sub, .vx-painel + .vx-full { margin-top: clamp(1.5rem, 3vw, 2.5rem); }
.vx-sub { margin: clamp(2.5rem, 5vw, 4rem) 0 1.25rem; }
.vx-painel {
    margin: 0; padding: clamp(0.8rem, 1.4vw, 1.25rem);
    background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 1.75rem;
}
.vx-painel-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(0.6rem, 1.2vw, 1rem); }
.vx-painel-grid img { display: block; width: 100%; height: auto; border-radius: 0.9rem; border: 1px solid rgba(255, 255, 255, 0.06); }
.vx-painel figcaption { color: #9ca3af; font-weight: 300; font-size: 0.9rem; line-height: 1.6; margin-top: 1rem; padding: 0 0.35rem; }
@media (max-width: 767px) { .vx-painel-grid { grid-template-columns: 1fr; } }
@media (max-width: 767px) { .vx-mockups { grid-template-columns: 1fr; } }

/* ── páginas das apps de TV (NodeLogic Home, TV, Keyboard) ──────────
   Software próprio do Synapse. As capturas são a 1280x720, que é o painel da
   televisão: esticá-las não inventa detalhe, por isso a imagem nunca passa da
   sua largura natural e é o cartão que a acompanha. */
.nlh-shot {
    display: block; width: 100%; height: auto; border-radius: 1.75rem;
    box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.8);
}
/* O vidro, a borda e o raio vêm do .liquid-glass — o componente da casa, não
   uma réplica dele (regra 11 do CLAUDE.md: card standard = liquid-glass). Estas
   classes só acrescentam o que é destas páginas: espaçamento e tipografia. */
.nlh-num { padding: 2rem; display: flex; flex-direction: column; gap: 0.75rem; }
.nlh-num-v { font-size: clamp(1.75rem, 3vw, 2.5rem); font-weight: 800; letter-spacing: -0.03em; color: #fff; line-height: 1; }
.nlh-num-l { color: #9ca3af; font-weight: 300; font-size: 0.95rem; line-height: 1.55; }
.nlh-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; color: #c3c9d4; }

.nlh-card, .nlh-nota { padding: clamp(1.75rem, 2.5vw, 2.5rem); }
.nlh-card i { display: block; width: 22px; height: 22px; color: #3b82f6; margin-bottom: 1.1rem; }
/* o card também serve de link (a lista de apps na página do Synapse); o realce
   de clicável é o standard do site: bordo azul a 20%, nada mais forte */
a.nlh-card { transition: border-color 0.5s, background-color 0.5s; }
a.nlh-card:hover { border-color: rgba(59, 130, 246, 0.2); }
.nlh-card h3, .nlh-nota h3 {
    color: #fff; font-weight: 700; font-size: 1.05rem; letter-spacing: -0.01em; margin-bottom: 0.7rem;
}
.nlh-card p, .nlh-nota p { color: #9ca3af; font-weight: 300; font-size: 0.95rem; line-height: 1.65; }
/* As notas são o que o produto NÃO faz, mas NÃO levam borda de destaque de um só
   lado: é uma regra da casa (a tira colorida ao lado de um card é marca de
   template) e eu já a tinha quebrado aqui uma vez. O que as separa é o título e
   o sítio na página, não um adorno. */
