:root{
  /* dark surfaces (page moved from cream/paper to near-black teal) */
  --bg:#14110c;
  --bg-alt:#1c1811;
  --surface:#241f15;
  /* .93 used to be near-opaque, which smothered the fixed bg canvas
     (assets/site.js) everywhere except the hero (the one section explicitly
     set transparent) — the ambient blobs were invisible the moment you
     scrolled past it. Dropped to .6 so the drift reads through every
     section, not just the hero, while staying dark enough for text contrast. */
  --tint-warm:rgba(28,22,14,.6);
  --tint-sand:rgba(24,19,12,.6);
  --tint-mint:rgba(20,16,10,.6);
  --tint-sky:rgba(16,13,8,.6);

  /* text (was dark ink on a light page; now light ink on a dark page) */
  --black:#F2E9D8;
  --white:#F7F0E3;
  --ink:#F2E9D8;
  --ink-soft:rgba(242,233,216,.64);
  --line:rgba(242,233,216,.14);
  --line-strong:rgba(242,233,216,.32);

  /* legacy names kept for the few rules that read them directly */
  --paper:var(--bg);
  --panel:var(--bg-alt);

  --seafoam-tint:#E9DCC2;
  --aqua-tint:#EDE2CC;
  --accent:#EE9B00;
  --accent-deep:#C98300;
  /* Heritage redesign (2026-08-21): --dark used to be a pre-redesign
     mid-brown (#3A2410, brighter still as #4a2f14 on the homepage below) —
     never migrated when the site moved to the near-black stone palette
     above. Every full-bleed section that reads it (.cta-band, #work,
     .process, .compare-col.us) was rendering as a flat orange-brown block
     that broke the dark-stone/torchlight world everywhere else. Brought in
     line with --bg/--bg-alt/--surface: still the darkest, warmest stone in
     the family (so .compare-col.us / #work keep some separation from
     --surface), just no longer a different, brighter palette entirely. */
  --dark:#17130c;
  --dark-deep:#0c0905;
  /* recessed ground for the secondary CTA. Translucent rather than a flat
     fill so the hero/section video still reads faintly through the chip,
     but solid enough that the label never sits directly on moving footage. */
  --surface-recessed:rgba(10,8,5,.6);

  /* the glow accent introduced with the low-poly mark: used for primary
     CTAs and any "inverted/filled" chip, replacing the old black-chip
     pattern now that the page itself is dark */
  --glow:#EE9B00;
  --glow-deep:#C98300;
  /* deep-teal variant of the glow, used only for the hero's atmospheric
     bloom/mound-glow (the ambient-glow radial and the hero-mark's glow) —
     never on chrome or interactive elements, those stay on --glow */
  --glow-ambient:#8A5C05;
  /* rgb-triplet twins of the two tokens above, for the one spot (the
     ambient-glow radial-gradient) that needs alpha-blended stops — hex
     custom properties can't be dropped into rgba(), so this is the
     token-compliant way to get transparency instead of a literal hex */
  --glow-rgb:238,155,0;
  --glow-ambient-rgb:138,92,5;

  /* soft dark drop-shadow behind light text sitting over busy/animated
     backgrounds (hero, header nav, breadcrumbs) — --shadow-ink is the
     common case, --shadow-ink-strong only for the oversized hero h1 */
  --shadow-ink:rgba(0,0,0,.4);
  --shadow-ink-strong:rgba(0,0,0,.5);

  --pastel-yellow:#F6E3A1;
  --pastel-yellow-deep:#E3C158;

  /* project accent colors, used only in Work-section mockup panels — each
     matches that project's own real, already-built palette. Unchanged:
     these are other clients' brand colors, not Nerwx's own. */
  --proj-bakery:#C1502E;
  --proj-bakery-2:#E8B565;
  --proj-barber:#E8397A;
  --proj-barber-2:#2B0F3D;
  --proj-pelagos:#8C8574;
  --proj-pelagos-2:#B08D57;
  --proj-lumen:#3B2159;
  --proj-lumen-2:#D4AF37;
  --proj-mike:#7A1518;
  --proj-mike-2:#0A0A0C;
  --proj-rina:#D998AC;
  --proj-rina-2:#C57D93;

  --font-display:"Fraunces",Georgia,serif;
  --font-body:"Archivo","Helvetica Neue",Arial,sans-serif;
  --font-mono:"IBM Plex Mono","SFMono-Regular",Consolas,"Courier New",monospace;
}

/* Heritage redesign (2026-08-21): this used to push --panel/--dark/--dark-deep
   even brighter/browner on the homepage specifically, on top of the
   already-wrong root values above — the homepage's #work and .cta-band
   ended up the most visually broken instance of the bug on the whole site.
   Removed outright so the homepage matches every other page's dark-stone
   sections instead of having its own, lighter palette. */

*{ box-sizing:border-box; margin:0; padding:0; }
html{ scroll-behavior:smooth; }
@media (prefers-reduced-motion: reduce){ html{ scroll-behavior:auto; } *{ animation-duration:.001ms !important; transition-duration:.001ms !important; } }

/* safety net: the hero's decorative layers use overflow:visible SVGs and
   vw-based sizing that can bleed past their own box on narrow screens —
   this guarantees that bleed never turns into a page-wide horizontal
   scrollbar, regardless of how any single decorative element is sized */
html, body{ overflow-x:hidden; }

/* Real bug, not a taste call: the live site's body was ALWAYS a flat
   background:var(--bg) — every bit of depth on the page came from the JS
   canvas (assets/site.js) painting on top of it. Disabling that canvas
   (per explicit feedback) left nothing behind it at all — pure flat black,
   exactly the "no animation, nothing, only black" complaint. Fix: give the
   body its own real, static gradient — the same three warm positions the
   old animated embers used to occupy, just frozen in place. Depth without
   motion, and it costs nothing (no JS, no repaint loop, one paint ever). */
body{
  background:
    radial-gradient(ellipse 900px 700px at 20% 26%, rgba(138,92,5,.30), transparent 60%),
    radial-gradient(ellipse 1000px 800px at 82% 20%, rgba(238,155,0,.20), transparent 55%),
    radial-gradient(ellipse 1000px 900px at 50% 92%, rgba(90,60,30,.28), transparent 62%),
    linear-gradient(180deg, var(--bg-alt) 0%, var(--bg) 45%, #0e0b07 100%);
  color:var(--ink); font-family:var(--font-body); font-size:1.02rem; line-height:1.7; -webkit-font-smoothing:antialiased;
}
/* transform:translateZ(0) + will-change force this onto its own GPU
   compositor layer — without it, iOS Safari tends to redraw/jump fixed,
   negative-z-index elements during scroll instead of leaving them locked to
   the viewport, reading as glitchy. Was likely always doing this; only
   visible now that the section tints let the canvas actually show through. */
#bgCanvas{ position:fixed; inset:0; width:100vw; height:100vh; z-index:-1; transform:translateZ(0); will-change:transform; }
.wrap{ max-width:1140px; margin:0 auto; padding:0 2rem; }
a{ color:inherit; }
:focus-visible{ outline:2px solid var(--black); outline-offset:3px; }
.price-card.featured :focus-visible{ outline-color:var(--bg); }
::selection{ background:var(--glow); color:var(--bg); }

.eyebrow{ display:block; font-family:var(--font-mono); text-transform:uppercase; font-size:.72rem; letter-spacing:.2em; color:var(--ink-soft); margin-bottom:1rem; }
h1,h2,h3{ font-family:var(--font-display); font-weight:600; letter-spacing:-.01em; text-wrap:balance; color:var(--black); }

.reveal{ opacity:0; transform:translateY(18px); transition:opacity .8s ease, transform .8s ease; }
.reveal.in-view{ opacity:1; transform:none; }

/* z-index bumped 50->70 (2026-08-20): must clear both .nav-backdrop (55)
   and the mobile full-screen nav (60) so the header's own close (X) button
   and GR/EN language switch stay visible and clickable while the mobile
   menu is open — before this, both were silently covered by the backdrop
   the instant the menu opened, leaving no visible way to close it except
   tapping the dimmed area blind. */
/* padding is deliberately NOT transitioned: it is a layout property, so
   animating it re-lays-out the header on every frame, and that change
   fires while the user is scrolling. Size snaps, background still fades. */
header{ position:fixed; top:0; left:0; right:0; z-index:70; padding:1.6rem 0;
  transition:background .3s ease, box-shadow .3s ease; contain:layout; }
/* Perf fix (2026-08-16): backdrop-filter:blur() was forcing a full re-blur of
   everything behind the header on every scroll frame — one of the heaviest
   things a fixed header can do, especially on non-flagship phones. Swapped
   for a solid near-opaque fill that reads almost the same but costs nothing
   during scroll. */
header.scrolled{ background:rgba(9,17,18,.96); padding:1.1rem 0; box-shadow:0 1px 0 var(--line); }
header .wrap{ display:flex; align-items:center; justify-content:space-between; }
/* z-index:65 (2026-08-20): nav is a *descendant* of header (nav sits inside
   .header-right), and nav gets its own z-index:60 to work as a mobile
   full-screen overlay — meaning without this, nav would paint over its own
   siblings-in-spirit here (brand logo, lang switch, close button) the
   instant it opens, regardless of header's own z-index relative to
   .nav-backdrop outside it. Needs to explicitly outrank nav's 60. */
.brand{ position:relative; z-index:65; display:flex; align-items:center; gap:.6rem; text-decoration:none; }
/* Perf fix (2026-08-16): was animating the filter:drop-shadow() value itself
   (0px blur -> 6px blur -> 0px) directly on the icon, which forces the
   browser to recompute the blur from scratch every frame, forever, on every
   page — the single most expensive loop on the whole site since the header
   is always mounted. Icon itself is now static (always fully crisp/visible,
   never dims). The breathing glow is a separate blurred blob sitting behind
   it (::before) with a fixed, pre-computed blur — only its opacity animates,
   which the browser can rasterize once and then just cross-fade in
   compositing (cheap, GPU-friendly, no per-frame blur recompute). */
/* 2026-08-16: dropped filter:blur() here (and everywhere else it appeared
   for a soft-glow look) in favor of more radial-gradient color stops doing
   the same softening with zero filter/compositing cost. blur() needs GPU
   compositing to stay cheap; on a machine where hardware acceleration is
   off or blocklisted (common on some Windows laptops with older Intel
   graphics drivers — check chrome://gpu, "Graphics Feature Status" should
   say Hardware accelerated, not Software only), every blur() on the page
   falls back to a real per-frame software recompute regardless of what's
   actually animating, which would explain lag on effects that should be
   trivially cheap. A multi-stop gradient never needs GPU compositing to be
   fast, so this removes that whole risk regardless of the cause. */
.brand::before{
  content:''; position:absolute; left:0; top:50%; width:38px; height:38px;
  transform:translateY(-50%); border-radius:50%; pointer-events:none; z-index:0;
  background:radial-gradient(circle, rgba(var(--glow-rgb),.55) 0%, rgba(var(--glow-rgb),.32) 35%, rgba(var(--glow-rgb),.12) 60%, transparent 85%);
  /* Heritage redesign: was a smooth 2-point sine "breathing" pulse (a
     mechanical loading-spinner cadence). Real firelight doesn't breathe on
     a clean sine curve, it flickers unevenly — same torchFlicker keyframe
     the meander strip uses, at its own duration/delay so the two never
     visibly lock into sync across the page. */
  opacity:.75; animation:torchFlicker 4.1s ease-in-out .6s infinite;
}
.brand svg{ position:relative; z-index:1; width:38px; height:38px; display:block; }
/* Hover transitioned a drop-shadow filter, repainting the mark for the whole
   250ms. The ::before glow already sits behind it, so hover brightens that
   instead: opacity and transform only. */
.brand::before{ transition:opacity .25s ease, transform .25s ease; }
.brand:hover::before{ opacity:1; transform:translateY(-50%) scale(1.18); }
@media (prefers-reduced-motion: reduce){
  .brand::before{ animation:none; opacity:.35; }
}
.wordmark{ font-family:var(--font-display); font-weight:600; font-size:1.28rem; letter-spacing:-.01em; color:var(--black); }
.header-right{ display:flex; align-items:center; gap:1.4rem; }
nav{ display:flex; align-items:center; gap:1.7rem; }
nav a{ position:relative; font-family:var(--font-mono); font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; text-decoration:none; color:var(--black); border-bottom:1px solid transparent; text-shadow:0 1px 10px rgba(0,0,0,.4); }
/* Motion pass: was an instant border-color swap. Now an amber underline that
   draws in from the left, like a line catching light, instead of just
   appearing. Hover-gated per review-animations standard #8 so it doesn't
   stick on touch. transform-only, GPU-cheap. */
nav a:not(.nav-cta)::after{
  content:''; position:absolute; left:0; right:0; bottom:-2px; height:1px;
  background:var(--glow); transform:scaleX(0); transform-origin:left;
  transition:transform .25s cubic-bezier(.16,1,.3,1);
}
nav a[aria-current="page"]::after{ transform:scaleX(1); }
@media (hover:hover) and (pointer:fine){
  nav a:not(.nav-cta):hover::after{ transform:scaleX(1); }
}
.nav-cta{ background:var(--glow); color:var(--bg); padding:.65rem 1.2rem; border-radius:4px; text-shadow:none; transition:background .2s ease, box-shadow .2s ease, transform .18s ease; }
.nav-cta:hover{ background:var(--glow-deep); box-shadow:0 0 22px rgba(var(--glow-rgb),.45); transform:translateY(-1px); }
.nav-cta:active{ transform:translateY(0) scale(.97); transition-duration:.08s; }
@media (prefers-reduced-motion: reduce){ .nav-cta, .nav-cta:hover, .nav-cta:active{ transform:none; } }
/* Real bug, predates this redesign entirely (confirmed against the CSS
   fetched from the live site before any of today's changes): this base
   rule used to sit AFTER the @media(max-width:860px) block below, so its
   `display:none` always won the cascade regardless of viewport — the
   mobile hamburger button was never actually visible on any phone. Fixed
   by moving the base (hidden-by-default) rule ahead of the mobile
   override, the normal base-then-override source order. */
/* Hit area bumped 34px -> 44px (touch-target guideline) without growing the
   icon itself — spans keep a fixed 22px width via align-items:center on the
   now-larger flex box, so the hamburger glyph looks identical, it just has
   more forgiving tap padding around it on mobile. */
.menu-toggle{ position:relative; z-index:65; display:none; flex-direction:column; align-items:center; justify-content:center; gap:5px; width:44px; height:44px; background:none; border:none; cursor:pointer; padding:0; flex:none; }
.menu-toggle span{ display:block; width:22px; height:2px; background:var(--black); transition:transform .2s ease, opacity .2s ease; }
.menu-toggle[aria-expanded="true"] span:nth-child(1){ transform:translateY(7px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2){ opacity:0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3){ transform:translateY(-7px) rotate(-45deg); }
/* Mobile nav, round 2 (2026-08-20): the 78vw side-drawer read as weak —
   its background (--bg-alt) sits so close to the dimmed backdrop's own
   color that the "panel" barely registered as a distinct surface, and the
   small uppercase-mono links (a legibility hedge from round 1, see the
   dated comments this replaces) had no real typographic presence. Rebuilt
   as a genuine full-screen takeover instead of a sliver, following the
   real pattern most strong mobile navs use (a dedicated full-screen mode,
   not a floating panel) — big Fraunces display type per link (matching
   the site's own headline font, not a mono utility font), a numbered
   index (01/02/03...) in the amber accent, and a hairline rule between
   items instead of relying on font-weight tricks for legibility. */
@media (max-width:860px){
  nav a:not(.nav-cta){ display:none; }
  .menu-toggle{ display:flex; }
  nav{
    position:fixed; inset:0; width:100%; height:100vh; height:100svh;
    background:
      radial-gradient(ellipse 700px 500px at 88% -6%, rgba(var(--glow-ambient-rgb),.35), transparent 60%),
      linear-gradient(180deg, var(--bg-alt) 0%, var(--bg) 55%, #0e0b07 100%);
    flex-direction:column; align-items:stretch; justify-content:center;
    gap:0; padding:6.5rem 1.8rem 3rem;
    transform:translateX(100%); transition:transform .4s cubic-bezier(.16,1,.3,1);
    box-shadow:none; z-index:60; overflow-y:auto;
    counter-reset:navitem;
  }
  nav.nav-open{ transform:translateX(0); }
  nav.nav-open a:not(.nav-cta){
    display:flex; align-items:baseline; gap:1rem;
    font-family:var(--font-display); font-size:clamp(1.7rem,7.5vw,2.5rem); font-weight:600;
    padding:.85rem 0; border-bottom:1px solid var(--line);
    color:var(--black); text-shadow:none;
  }
  nav.nav-open a:not(.nav-cta)::before{
    content:counter(navitem,decimal-leading-zero); counter-increment:navitem;
    font-family:var(--font-mono); font-size:.8rem; font-weight:600; letter-spacing:.05em;
    color:var(--glow); flex:none;
  }
  nav.nav-open a.nav-cta{ margin-top:1.8rem; align-self:flex-start; }
  /* The close (X) affordance and language switch live inside the same
     fixed header as everything else, but the header's own z-index (see
     header{} above) used to sit BELOW both the backdrop and this drawer —
     meaning the X button and GR/EN toggle were invisible AND unclickable
     the whole time the menu was open, with no visible way to close other
     than tapping the dimmed backdrop blind. header{} now sits above both. */
}
@media (max-width:860px) and (prefers-reduced-motion: reduce){ nav{ transition:none; } }

.lang-switch{ position:relative; z-index:65; display:flex; gap:.2rem; border:1px solid var(--line-strong); border-radius:999px; padding:.2rem; flex:none; }
/* Sliding pill (2026-08-21): was a flat background swap on whichever button
   was active — the pill is an absolutely-positioned sibling behind both
   buttons that slides between the two positions instead of popping. Driven
   by [data-active] on .lang-switch itself (set alongside the existing
   aria-pressed toggle in site.js), not by JS measuring button rects, so it
   stays a one-line CSS transition with no layout reads. */
.lang-pill{ position:absolute; top:3px; bottom:3px; left:3px; width:calc(50% - 3px); background:var(--glow); border-radius:999px; transition:transform .3s cubic-bezier(.16,1,.3,1); z-index:0; pointer-events:none; }
.lang-switch[data-active="en"] .lang-pill{ transform:translateX(100%); }
.lang-btn{ position:relative; z-index:1; font-family:var(--font-mono); font-size:.68rem; letter-spacing:.05em; padding:.4rem .65rem; border-radius:999px; border:none; background:transparent; color:var(--black); cursor:pointer; transition:color .2s ease; text-shadow:0 1px 10px rgba(0,0,0,.4); }
.lang-btn[aria-pressed="true"]{ color:var(--bg); text-shadow:none; }
.lang-btn:hover:not([aria-pressed="true"]){ color:var(--glow); }
@media (prefers-reduced-motion: reduce){ .lang-pill{ transition:none; } }

.nav-backdrop{ display:none; position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:55; opacity:0; transition:opacity .25s ease; }
.nav-backdrop.show{ display:block; opacity:1; }
@media (prefers-reduced-motion: reduce){ .nav-backdrop{ transition:none; } }

/* hero */
.hero{ position:relative; min-height:96vh; min-height:96svh; display:flex; align-items:center; overflow:hidden; }
.hero .wrap{ position:relative; z-index:2; padding-top:5rem; max-width:900px; }
.hero .ambient-glow{ top:52%; right:8%; width:min(36vw,520px); aspect-ratio:1; transform:translateY(-50%); }
/* the wireframe-mountain backdrop: sized independently from the WebGL mark
   below it, so it can stay big and layered without re-bloating the mark.
   Masked so it fades out over the text column instead of cutting lines
   straight across the headline/lede/buttons — full strength from roughly
   where the N mark starts, nothing competing with the copy on the left. */
.hero .hero-bg-scene{
  top:50%; right:4%; width:min(78vw,1180px); aspect-ratio:600/640; transform:translateY(-50%);
  -webkit-mask-image:linear-gradient(100deg, transparent 0%, transparent 32%, black 58%);
  mask-image:linear-gradient(100deg, transparent 0%, transparent 32%, black 58%);
}
.hero .hero-mark{ top:52%; right:9%; width:min(30vw,420px); aspect-ratio:600/640; transform:translateY(-50%); }
/* Below ~980px there's no room for the mark beside the text, so instead of
   hiding it, it drops to a centered mark that sits above the headline —
   still a real presence, just sized for a phone screen. The wireframe
   backdrop used to just disappear here; now it sits behind that centered
   mark instead, scaled down and dimmed rather than cut entirely. */
@media (max-width:980px){
  .hero{ flex-direction:column; justify-content:center; padding:7rem 0 3rem; gap:1.6rem; }
  .hero .wrap{ order:2; padding-top:0; }
  .hero .ambient-glow{ display:none; }
  .hero .hero-bg-scene{
    top:6.5rem; right:auto; left:50%; width:min(58vw,300px);
    transform:translate(-50%,0); opacity:.5; mask-image:none; -webkit-mask-image:none;
  }
  .hero .hero-mark{
    position:relative; inset:auto; top:auto; right:auto; transform:none;
    order:1; width:min(26vw,120px); aspect-ratio:600/640; margin:0 auto;
  }
}
.tags{ display:flex; flex-wrap:wrap; gap:.6rem; margin-bottom:1.8rem; }
.tag{ font-family:var(--font-mono); font-size:.68rem; letter-spacing:.08em; text-transform:uppercase; padding:.4rem .8rem; border-radius:999px; border:1px solid var(--line-strong); color:var(--black); background:transparent; }
.hero h1{ font-size:clamp(2.8rem,7vw,5.4rem); line-height:.98; margin-bottom:1.6rem; font-weight:600; }
.hero h1 span{ color:var(--black); text-shadow:0 2px 24px var(--shadow-ink-strong); }
.hero h1 .accent{ font-style:italic; color:var(--glow); }
.hero .lede{ max-width:50ch; color:var(--ink); font-size:1.2rem; margin-bottom:2.4rem; text-shadow:0 1px 14px var(--shadow-ink); }

/* Phone hero typography.
   The headline is two clauses, and letting them wrap as one inline run put
   the break wherever the measure happened to fall — at 360px that orphaned
   the amber "no" onto the end of line 2 and split "no compromise." across
   two lines. Promoting .accent to a block gives the amber clause its own
   line at every width, so the phrase never breaks and the mobile headline
   keeps the same two-part structure as the desktop one. text-wrap:balance
   is dropped here for the same reason: with each clause now wrapping on its
   own, balance only reintroduces width-dependent break points. */
@media (max-width:640px){
  /* cap matches the desktop clamp's 2.8rem floor so crossing 640px doesn't
     step the headline size backwards */
  .hero h1{ font-size:clamp(2.1rem,8.6vw,2.8rem); line-height:1.04; margin-bottom:1.15rem; text-wrap:normal; }
  .hero h1 .accent{ display:block; }

  /* 46 words at 1.2rem ran to six full-width lines — a wall of text as the
     first thing on the page. Smaller with tighter leading and a real measure
     cap gives it back a shape without touching the copy (it's translated). */
  .hero .lede{ font-size:1rem; line-height:1.6; max-width:38ch; margin-bottom:2rem; }

  /* Stacked CTAs sized to their own labels read as two mismatched fragments.
     auto-fit keeps them equal at every width and decides for itself: two
     columns while 190px each still fits (large phones, ~472px up), stacked
     full-width below. Straight full-width stretched them to 418px at 640px,
     which is a banner, not a button. */
  .hero .actions{ display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
    gap:.75rem; width:100%; }
  .hero .actions .btn{ width:100%; justify-content:center; padding:1.05rem 1.4rem; }
}

.actions{ display:flex; flex-wrap:wrap; gap:1.1rem; }
.btn{ font-family:var(--font-mono); font-weight:600; font-size:.78rem; letter-spacing:.06em; text-transform:uppercase; padding:1rem 1.8rem; text-decoration:none; display:inline-flex; align-items:center; border-radius:4px; border:1px solid var(--line-strong); transition:background .2s ease, color .2s ease, border-color .2s ease, box-shadow .2s ease, transform .22s cubic-bezier(.16,1,.3,1); }
/* Motion pass: `transform` wasn't in the transition list above, so both the
   press-down and the release used to snap with zero interpolation. Now the
   press is fast (.08s, the :active override below) and the release runs on
   the base .22s curve — asymmetric on purpose (review-animations standard
   #9): the deliberate action (press) is quick, the response (release) eases
   out slower, and the hover glow's own transition supplies the "catch"
   flash as it lets go. */
.btn:active{ transform:scale(.97); transition-duration:.08s; }
@media (prefers-reduced-motion: reduce){ .btn:active{ transform:none; } }
.btn-primary{ background:var(--glow); border-color:var(--glow); color:var(--bg); }
.btn-primary:hover{ background:var(--glow-deep); border-color:var(--glow-deep); box-shadow:0 0 30px rgba(var(--glow-rgb),.4); }
/* Secondary CTA: recessed slab, no ornament.
   Two separate problems were solved here and only one of them was the
   ornament. The original treatment was a transparent chip on a
   --line-strong (32% ink) hairline, which measured 2.5:1 against the
   backdrop the hero CTAs actually sit on — under the 3:1 minimum for a UI
   boundary, and invisible on brighter frames of the video behind it. That
   fix (a recessed ground plus a border with real presence) stays. The
   amber Greek-key corner brackets that briefly sat on top of it are gone
   with the rest of the meander motif — decoration, not legibility.
   Border carries the weight the brackets used to: 7.7:1 at the worst point
   under the CTAs, label 16:1. */
.btn-outline{ background:var(--surface-recessed);
  border-color:rgba(242,233,216,.7); color:var(--black); }
.btn-outline:hover{ background:rgba(var(--glow-rgb),.12);
  border-color:var(--black); color:var(--black); }
.btn .arrow{ display:inline-block; max-width:0; opacity:0; overflow:hidden; white-space:nowrap; transition:max-width .25s ease, opacity .2s ease; }
.btn:hover .arrow{ max-width:20px; opacity:1; margin-left:.55rem; }
@media (prefers-reduced-motion: reduce){ .btn .arrow, .btn:hover .arrow{ transition:opacity .2s ease; } }
/* The arrow only ever existed on hover, so on a touch device it never
   appeared at all — the buttons lost their directional cue on exactly the
   devices where the label is smallest. Where there's no hover, render it
   permanently instead of animating it in. */
@media (hover: none){
  .btn .arrow{ max-width:20px; opacity:1; margin-left:.55rem; transition:none; }
}

/* page-hero: compact header used on every subpage instead of the full-height homepage hero.
   overflow:hidden keeps the wireframe backdrop below fully contained instead
   of bleeding into the section that follows. */
.page-hero{ position:relative; min-height:52vh; min-height:52svh; display:flex; align-items:center; overflow:hidden; }
.page-hero .wrap{ position:relative; z-index:2; padding-top:6.5rem; padding-bottom:1rem; max-width:800px; }
/* Same wireframe-mountain backdrop as the homepage hero, dropped in behind
   every subpage's compact header (2026-08-14) so the animated-glow language
   carries across the whole site, not just the homepage. Unlike the homepage
   version this is a single-column layout (no mark beside the text to dodge),
   so it's centered rather than masked to one side, and dimmed down since it
   sits directly behind the h1/lede instead of beside them. */
.page-hero .hero-bg-scene{
  top:50%; left:50%; width:min(70vw,900px); aspect-ratio:600/640;
  transform:translate(-50%,-50%); opacity:.85;
}
@media (max-width:640px){
  /* smaller box here (sized closer to the shallow page-hero's actual height)
     so more of the graphic stays inside the visible area instead of getting
     cropped off by .page-hero's overflow:hidden */
  .page-hero .hero-bg-scene{ width:min(85vw,380px); opacity:.7; }
}
.breadcrumb{ display:block; font-family:var(--font-mono); font-size:.7rem; letter-spacing:.08em; text-transform:uppercase; color:var(--black); margin-bottom:1.2rem; text-shadow:0 1px 10px rgba(0,0,0,.4); }
.breadcrumb a{ text-decoration:none; color:var(--black); opacity:.72; }
.breadcrumb a:hover{ opacity:1; }
.page-hero h1{ font-size:clamp(2.3rem,4.8vw,3.4rem); line-height:1.05; margin-bottom:1.2rem; font-weight:600; }
.page-hero .lede{ max-width:58ch; color:var(--ink); font-size:1.1rem; text-shadow:0 1px 12px rgba(0,0,0,.4); }

section{ padding:7rem 0; border-top:1px solid var(--line); background:var(--tint-warm); }
.hero, .page-hero{ background:transparent; }
.section-head{ max-width:58ch; margin-bottom:3.4rem; }
.section-head.center{ margin-left:auto; margin-right:auto; text-align:center; }
h2.section-title{ font-size:clamp(1.9rem,3.6vw,2.6rem); }

/* services: two-column editorial split instead of matching boxed cards */
.pillars{ display:grid; grid-template-columns:1fr 1fr; gap:0; border-top:1px solid var(--line-strong); }
.pillar{ padding:2.6rem 2.4rem 2.6rem 0; border-bottom:1px solid var(--line); }
.pillar:first-child{ border-right:1px solid var(--line); }
.pillar h3{ font-size:1.5rem; font-weight:600; margin-bottom:1.4rem; }
.pillar ul{ list-style:none; display:flex; flex-direction:column; gap:.75rem; }
.pillar li{ padding-left:1.3rem; position:relative; color:var(--ink-soft); font-size:.98rem; }
.pillar li::before{ content:"\2014"; position:absolute; left:0; color:var(--glow); }
@media (max-width:700px){ .pillars{ grid-template-columns:1fr; } .pillar{ padding:2.2rem 0; } .pillar:first-child{ border-right:none; } }

/* about */
.about-grid{ display:grid; grid-template-columns:1.1fr 1fr; gap:3.5rem; align-items:start; }
.about-grid p{ color:var(--ink-soft); font-size:1.04rem; max-width:52ch; margin-bottom:1.2rem; }
.badges{ display:flex; flex-direction:column; gap:0; border-top:1px solid var(--line); }
.badge{ border-bottom:1px solid var(--line); padding:1.1rem 0; font-family:var(--font-mono); font-size:.78rem; letter-spacing:.04em; text-transform:uppercase; color:var(--black); }
@media (max-width:760px){ .about-grid{ grid-template-columns:1fr; } }

/* quicklinks (home page site directory) */
#quicklinks{ background:var(--tint-sand); }
.quick-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(240px,1fr)); gap:0; border-top:1px solid var(--line-strong); border-left:1px solid var(--line); }
.quick-card{ display:block; text-decoration:none; padding:2rem 1.9rem; background:transparent; border-right:1px solid var(--line); border-bottom:1px solid var(--line); transition:background .25s ease; }
.quick-card:hover{ background:var(--surface); }
.quick-card h3{ font-size:1.15rem; font-weight:600; margin-bottom:.6rem; }
.quick-card p{ color:var(--ink-soft); font-size:.9rem; margin-bottom:1.1rem; }
.quick-card span.quick-link{ font-family:var(--font-mono); font-size:.68rem; letter-spacing:.06em; text-transform:uppercase; color:var(--glow); font-weight:700; }
.quick-card span.quick-link::after{ content:" \2197"; }

/* capabilities: hairline list instead of six identical bordered boxes */
#capabilities{ background:var(--tint-sand); }
.cap-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(240px,1fr)); gap:2.6rem 2.4rem; }
.cap-card{ padding:0; border:none; }
.cap-card svg{ width:26px; height:26px; margin-bottom:1.2rem; }
.cap-card h3{ font-family:var(--font-mono); font-weight:700; font-size:.82rem; letter-spacing:.05em; text-transform:uppercase; color:var(--black); margin-bottom:.6rem; }
.cap-card p{ color:var(--ink-soft); font-size:.95rem; }

/* industries */
.industry-row{ display:flex; flex-wrap:wrap; gap:1rem; }
.industry-chip{ border:1px solid var(--line-strong); border-radius:999px; padding:.7rem 1.3rem; font-family:var(--font-mono); font-size:.76rem; letter-spacing:.06em; text-transform:uppercase; }

/* approach: same hairline-list language as capabilities */
/* minmax dropped from 260 to 210 (2026-08-21): five approach-cards now
   that Professional Services has real content — at 260px min, four fit
   the row and the fifth orphaned itself onto its own half-empty row. 210px
   lets all five sit in one row at the site's own .wrap width (1140px). */
.approach-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(210px,1fr)); gap:0; border-top:1px solid var(--line-strong); border-left:1px solid var(--line); }
.approach-card{ border-right:1px solid var(--line); border-bottom:1px solid var(--line); padding:2.2rem 2rem; }
.approach-tag{ font-family:var(--font-mono); font-size:.68rem; letter-spacing:.08em; text-transform:uppercase; color:var(--glow); margin-bottom:1rem; display:block; }
.approach-card h3{ font-size:1.2rem; font-weight:600; margin-bottom:1.2rem; line-height:1.3; }
.approach-card ul{ list-style:none; display:flex; flex-direction:column; gap:.65rem; }
.approach-card li{ font-size:.88rem; color:var(--ink-soft); padding-left:1.2rem; position:relative; }
.approach-card li::before{ content:"\2013"; position:absolute; left:0; color:var(--ink-soft); }
.approach-link{ text-align:center; margin-top:2.6rem; }
.approach-link a{ font-family:var(--font-mono); font-size:.76rem; letter-spacing:.08em; text-transform:uppercase; text-decoration:none; color:var(--black); border-bottom:1px solid var(--black); padding-bottom:.2rem; transition:opacity .2s ease; }
.approach-link a:hover{ opacity:.7; }
#work .approach-link a{ color:var(--white); border-bottom-color:var(--white); }

/* pricing: featured plan is physically larger, and now the one place that
   carries the bright glow instead of a flat black fill */
.price-grid{ display:grid; grid-template-columns:1fr 1.15fr 1fr; gap:1.6rem; align-items:stretch; }
.price-card{ border:1px solid var(--line); border-radius:8px; padding:2.2rem 2rem; background:var(--surface); display:flex; flex-direction:column; }
.price-card.featured{ border-color:var(--glow); background:var(--glow); color:var(--bg); padding:2.8rem 2.2rem; }
.price-name{ font-family:var(--font-display); font-weight:600; font-size:1.4rem; margin-bottom:.5rem; }
.price-for{ font-family:var(--font-mono); font-size:.72rem; letter-spacing:.06em; text-transform:uppercase; color:var(--ink-soft); margin-bottom:1.6rem; display:block; }
.price-card.featured .price-for{ color:rgba(10,19,20,.68); }
.price-card ul{ list-style:none; display:flex; flex-direction:column; gap:.75rem; margin-bottom:1.8rem; flex:1; }
.price-card li{ font-size:.92rem; padding-left:1.2rem; position:relative; }
.price-card li::before{ content:"\2013"; position:absolute; left:0; color:var(--ink-soft); }
.price-card.featured li::before{ color:rgba(10,19,20,.6); }
.price-card .btn{ align-self:flex-start; }
.price-card.featured .btn-primary{ background:var(--bg); border-color:var(--bg); color:var(--glow); }
.price-card.featured .btn-primary:hover{ background:#000; border-color:#000; box-shadow:none; }
@media (max-width:860px){ .price-grid{ grid-template-columns:1fr; } .price-card.featured{ padding:2.2rem 2rem; order:-1; } }

/* faq */
#faq{ background:var(--tint-mint); }
.faq-list{ max-width:76ch; }
.faq-item{ border-bottom:1px solid var(--line); padding:1.4rem 0; }
.faq-item summary{ cursor:pointer; list-style:none; display:flex; justify-content:space-between; align-items:center; gap:1rem; font-family:var(--font-display); font-weight:600; font-size:1.15rem; color:var(--black); }
.faq-item summary::-webkit-details-marker{ display:none; }
.faq-item summary::after{ content:"+"; font-family:var(--font-mono); font-size:1.4rem; flex:none; transition:transform .2s ease; color:var(--glow); }
.faq-item[open] summary::after{ transform:rotate(45deg); }
.faq-item p{ margin-top:1rem; color:var(--ink-soft); font-size:.96rem; max-width:65ch; }

/* cta band */
.cta-band{ background:var(--dark); color:var(--white); text-align:center; }
.cta-band h2{ color:var(--white); font-size:clamp(2rem,4.4vw,3.4rem); margin-bottom:1.8rem; font-weight:600; }
.cta-band .actions{ justify-content:center; }
/* keeps the recessed ground from the base rule; only the border/label
   shift to --white for the band's deeper stone ground */
.cta-band .btn-outline{ border-color:rgba(247,240,227,.7); color:var(--white); }
.cta-band .btn-outline:hover{ background:rgba(var(--glow-rgb),.14); border-color:rgba(247,240,227,.72); color:var(--white); }

/* compare */
.compare-grid{ display:grid; grid-template-columns:1fr 1fr; gap:0; border:1px solid var(--line-strong); border-radius:8px; overflow:hidden; }
.compare-col{ padding:2.2rem 2rem; }
.compare-col.them{ background:var(--surface); }
.compare-col.us{ background:var(--dark); color:var(--white); }
.compare-label{ font-family:var(--font-mono); font-size:.7rem; letter-spacing:.1em; text-transform:uppercase; margin-bottom:1.5rem; display:block; }
.compare-col.them .compare-label{ color:var(--ink-soft); }
.compare-col.us .compare-label{ color:var(--white); }
.compare-col ul{ list-style:none; display:flex; flex-direction:column; gap:1.2rem; }
.compare-col li{ display:flex; align-items:center; gap:12px; font-size:.95rem; }
.compare-col.them li{ color:var(--ink-soft); }
.compare-col.us li{ color:rgba(255,255,255,.94); }
@media (max-width:680px){ .compare-grid{ grid-template-columns:1fr; } }

/* Animated icons (2026-08-21): real SVG marks instead of CSS ::before glyphs
   so each one can draw itself in — stroke-dashoffset from fully retracted to
   0 — the moment the comparison table scrolls into view, rather than just
   sitting there static. Reuses the section's existing .reveal/.in-view
   machinery (assets/site.js), no new JS. Staggered per row so the two
   columns draw in like a hand working down the list, not all at once. */
.cmp-icon{ flex:none; color:var(--ink-soft); }
.compare-col.us .cmp-icon{ color:var(--white); }
.cmp-icon line, .cmp-icon path{ stroke-dashoffset:16; transition:stroke-dashoffset .4s ease; }
.compare-grid.reveal.in-view .cmp-icon line, .compare-grid.reveal.in-view .cmp-icon path{ stroke-dashoffset:0; }
.compare-col li:nth-child(1) .cmp-icon line, .compare-col li:nth-child(1) .cmp-icon path{ transition-delay:.05s; }
.compare-col li:nth-child(2) .cmp-icon line, .compare-col li:nth-child(2) .cmp-icon path{ transition-delay:.12s; }
.compare-col li:nth-child(3) .cmp-icon line, .compare-col li:nth-child(3) .cmp-icon path{ transition-delay:.19s; }
.compare-col li:nth-child(4) .cmp-icon line, .compare-col li:nth-child(4) .cmp-icon path{ transition-delay:.26s; }
.compare-col li:nth-child(5) .cmp-icon line, .compare-col li:nth-child(5) .cmp-icon path{ transition-delay:.33s; }
@media (prefers-reduced-motion: reduce){ .cmp-icon line, .cmp-icon path{ transition:none; stroke-dashoffset:0; } }

/* Eyebrow "torch catch" (2026-08-21): the Work and Compare eyebrows briefly
   flare amber, like a torch just reached them, then settle back to the
   normal muted label color — one more moment of "this page is lit, not
   just dark," tied to the same scroll-reveal that already exists rather
   than a separate observer. Scoped to just these two (not every section
   eyebrow site-wide) to match the homepage-specific brief this shipped
   from. */
@keyframes eyebrowTorchCatch{
  /* text-shadow dropped: a shadow on text is repainted glyph by glyph every
     frame, and the colour change alone carries the same read. */
  0%   { color:var(--glow); }
  100% { color:var(--ink-soft); }
}
#work .section-head.reveal.in-view .eyebrow,
#compare .section-head.reveal.in-view .eyebrow{
  animation:eyebrowTorchCatch .9s ease-out forwards;
}
@media (prefers-reduced-motion: reduce){
  #work .section-head.reveal.in-view .eyebrow,
  #compare .section-head.reveal.in-view .eyebrow{ animation:none; }
}

/* work: each card gets a real visual panel (project's own palette, abstract
   mark) instead of being pure text — this is the section meant to prove
   design ability, so it can't be the plainest one on the site */
#work{ background:var(--dark); color:var(--white); }
#work .eyebrow{ color:rgba(255,255,255,.94); }
#work h2{ color:var(--white); }
.work-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(300px,1fr)); gap:1.6rem; }
.work-card{ display:block; text-decoration:none; border-radius:10px; overflow:hidden; background:var(--surface); border:1px solid transparent; transition:transform .25s ease, box-shadow .25s ease, border-color .25s ease; }
/* Motion pass: was a flat dark drop-shadow lift. This is the section that's
   explicitly meant to prove design ability (see the #work comment further
   down), so its one interactive moment gets a small warm-up instead of a
   generic card-hover shadow — border and a soft second glow shift toward
   the accent, on top of the same lift. */
.work-card:hover{ transform:translateY(-5px); border-color:rgba(var(--glow-rgb),.35); box-shadow:0 20px 40px rgba(0,0,0,.4), 0 0 26px rgba(var(--glow-rgb),.18); }
/* Round 2 on these cards: a flat, edge-to-edge screenshot still read as
   "just a screenshot" no matter how nice the browser-chrome bar on top of
   it was (real user feedback after round 1 shipped). Researched what
   actual agency portfolios do instead (Ramotion's work grid, Awwwards'
   agency-portfolio collection) — none of them use a full-bleed flat crop.
   Landed on the "photographed" device treatment below: small, tilted,
   floating with a real shadow and a color-matched glow behind it.

   Round 3: round 2 also shipped a second variant (.is-mark, a bare logo
   on a brand-color card) for whichever project had a strong standalone
   mark. That put two different visual languages in one grid at once —
   fine in isolation, inconsistent side by side (real user feedback).
   All six cards now use .is-device; .is-mark is kept only in case a
   future project has no live page to screenshot. */
.work-visual{ position:relative; overflow:hidden; display:flex; align-items:center; justify-content:center; }

/* --- Variant 1: logo-on-brand-color (fallback when there's nothing to screenshot) --- */
.work-visual.is-mark svg{ width:auto; height:50%; filter:drop-shadow(0 14px 22px rgba(0,0,0,.4)); }

/* --- Variant 2: floating angled device (default for every project) --- */
.work-visual.is-device::before{
  content:""; position:absolute; width:64%; aspect-ratio:1; border-radius:50%;
  background:radial-gradient(circle, var(--device-glow, rgba(255,255,255,.28)), transparent 72%);
  z-index:0;
}
.work-visual-device{
  position:relative; z-index:1; width:76%; border-radius:8px; overflow:hidden;
  transform:rotate(-5deg);
  box-shadow:0 30px 50px -20px rgba(0,0,0,.6), 0 12px 20px -10px rgba(0,0,0,.45);
  transition:transform .45s cubic-bezier(.16,1,.3,1);
}
.work-card:hover .work-visual-device{ transform:rotate(-2deg) translateY(-6px) scale(1.03); }
@media (prefers-reduced-motion:reduce){ .work-card:hover .work-visual-device{ transform:rotate(-5deg); } }
.work-visual-bar{ display:flex; gap:6px; align-items:center; padding:8px 10px; background:rgba(20,17,12,.92); position:relative; z-index:1; }
.work-visual-bar span{ width:7px; height:7px; border-radius:50%; display:block; }
.work-visual-bar span:nth-child(1){ background:#e0605a; }
.work-visual-bar span:nth-child(2){ background:#e0b24f; }
.work-visual-bar span:nth-child(3){ background:#5fae6e; }
.work-visual-frame{ aspect-ratio:8/5; position:relative; overflow:hidden; }
.work-visual img{ width:100%; height:100%; display:block; object-fit:cover; object-position:top; }

.work-visual.v-bakery{ background:radial-gradient(130% 150% at 15% -10%, var(--proj-bakery-2), var(--proj-bakery) 55%, #6E2A12 100%); --device-glow:rgba(193,80,46,.4); }
.work-visual.v-barber{ background:linear-gradient(135deg, #2B0F3D 0%, #150818 48%, var(--proj-barber-2) 100%); --device-glow:rgba(232,57,122,.35); }
.work-visual.v-pelagos{ background:linear-gradient(160deg, #EDE7DA 0%, var(--proj-pelagos) 68%, var(--proj-pelagos-2) 100%); --device-glow:rgba(176,141,87,.4); }
.work-visual.v-lumen{ background:radial-gradient(130% 150% at 85% -10%, #4A2A6B, var(--proj-lumen) 55%, #180D28 100%); --device-glow:rgba(212,175,55,.4); }
.work-visual.v-mike{ background:radial-gradient(130% 150% at 50% -15%, #2A0A0A, var(--proj-mike-2) 68%); --device-glow:rgba(226,64,47,.4); }
.work-visual.v-rina{ background:radial-gradient(130% 150% at 25% -10%, #F6DEE5, var(--proj-rina) 55%, var(--proj-rina-2) 100%); --device-glow:rgba(217,152,172,.45); }
.work-body{ padding:1.7rem 1.7rem 1.9rem; }
.work-tag{ font-family:var(--font-mono); font-size:.68rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); margin-bottom:.8rem; display:block; }
.work-card h3{ font-size:1.3rem; font-weight:600; color:var(--black); margin-bottom:.7rem; }
.work-card p{ color:var(--ink-soft); font-size:.9rem; margin-bottom:.6rem; }
.work-card p:last-of-type{ margin-bottom:1rem; }
.work-card p strong{ color:var(--black); font-weight:700; }
.work-link{ font-family:var(--font-mono); font-size:.7rem; letter-spacing:.06em; text-transform:uppercase; color:var(--glow); font-weight:700; }
.work-link::after{ content:" \2197"; }

/* blog: same card shell as work, own gradient set drawn from the core
   palette (not per-project colors, since these posts aren't tied to a
   single client) */
.blog-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(300px,1fr)); gap:1.6rem; }
.blog-card{ display:block; text-decoration:none; border-radius:10px; overflow:hidden; background:var(--surface); transition:transform .25s ease, box-shadow .25s ease; }
.blog-card:hover{ transform:translateY(-5px); box-shadow:0 20px 40px rgba(0,0,0,.4); }
.blog-visual{ aspect-ratio:16/10; position:relative; overflow:hidden; }
.blog-visual svg{ width:100%; height:100%; display:block; }
.blog-visual.v-cost{ background:radial-gradient(130% 150% at 15% -10%, var(--pastel-yellow), var(--accent) 55%, var(--accent-deep) 100%); }
/* v-instagram/v-bilingual (2026-08-21): both used to be near-black-on-black
   gradients with two 8%-opacity circles or three faint lines — after the
   --dark token fix above they'd have gone from muddy to nearly invisible.
   Given the same warm-gradient treatment as v-cost instead of a third
   near-black card, each shifted enough to read as its own post rather than
   a repeat of the first. */
.blog-visual.v-instagram{ background:radial-gradient(125% 150% at 85% 112%, var(--pastel-yellow) 0%, var(--glow-ambient) 48%, var(--dark) 100%); }
.blog-visual.v-bilingual{ background:linear-gradient(115deg, var(--glow) 0%, var(--glow-deep) 42%, var(--dark) 100%); }
.blog-body{ padding:1.7rem 1.7rem 1.9rem; }
.blog-tag{ font-family:var(--font-mono); font-size:.68rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); margin-bottom:.6rem; display:block; }
.blog-card h3{ font-size:1.3rem; font-weight:600; color:var(--black); margin-bottom:.7rem; }
.blog-card p{ color:var(--ink-soft); font-size:.9rem; margin-bottom:1rem; }
.blog-link{ font-family:var(--font-mono); font-size:.7rem; letter-spacing:.06em; text-transform:uppercase; color:var(--glow); font-weight:700; }
.blog-link::after{ content:" \2197"; }

/* article: single-column prose, narrower than the standard .wrap so long
   paragraphs stay readable — this is the "reading panel": a hair lighter
   than the page so long-form text isn't sitting on pure black */
.article{ background:var(--surface); border-radius:14px 14px 0 0; }
.article .wrap{ max-width:720px; }
.article-meta{ font-family:var(--font-mono); font-size:.72rem; letter-spacing:.06em; text-transform:uppercase; color:var(--ink-soft); margin-bottom:2.2rem; display:flex; gap:1.2rem; flex-wrap:wrap; }
.article h2{ font-size:clamp(1.5rem,2.6vw,1.9rem); margin:2.6rem 0 1.1rem; }
.article p{ color:var(--ink); font-size:1.04rem; margin-bottom:1.3rem; max-width:66ch; }
.article ul, .article ol{ margin:0 0 1.3rem 1.3rem; color:var(--ink); }
.article li{ margin-bottom:.5rem; font-size:1.02rem; max-width:64ch; }
.article a{ color:var(--glow); text-decoration-color:rgba(var(--glow-rgb),.4); text-underline-offset:3px; }
.article a:hover{ text-decoration-color:var(--glow); }
.article blockquote{ border-left:3px solid var(--glow); padding-left:1.4rem; font-style:italic; color:var(--ink-soft); margin:1.8rem 0; font-family:var(--font-display); font-size:1.2rem; max-width:60ch; }
.article-share{ margin-top:2.8rem; padding-top:1.8rem; border-top:1px solid var(--line); }
.back-link{ font-family:var(--font-mono); font-size:.72rem; letter-spacing:.06em; text-transform:uppercase; text-decoration:none; color:var(--black); border-bottom:1px solid var(--black); padding-bottom:.2rem; display:inline-block; }

/* process */
.process{ background:var(--dark); color:var(--white); }
.process .eyebrow{ color:rgba(255,255,255,.94); }
.process h2{ color:var(--white); }
.process-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(220px,1fr)); gap:1.8rem; }
.step{ border-left:1px solid rgba(255,255,255,.22); padding-left:1.4rem; }
.step-num{ font-family:var(--font-mono); font-size:.78rem; letter-spacing:.06em; color:rgba(255,255,255,.94); display:block; margin-bottom:.8rem; }
.step h3{ font-size:1.2rem; font-weight:600; margin-bottom:.6rem; color:var(--white); }
.step p{ color:rgba(255,255,255,.94); font-size:.92rem; }

/* why */
.why{ text-align:center; background:var(--tint-sky); }
.pull-quote{ font-family:var(--font-display); font-weight:600; font-style:italic; letter-spacing:-.01em; font-size:clamp(1.6rem,3.4vw,2.4rem); max-width:32ch; margin:0 auto 1.5rem; color:var(--black); }
.pull-quote span{ color:var(--black); }
.why p{ max-width:56ch; margin:0 auto; color:var(--ink-soft); }

/* contact */
#contact{ background:var(--tint-sand); }
.contact-section{ text-align:center; }
.contact-section .section-head{ margin-left:auto; margin-right:auto; text-align:center; }
.contact-section .actions{ justify-content:center; margin-bottom:1.8rem; }

footer{ padding:4.5rem 0 3rem; border-top:1px solid var(--line); background:var(--bg); }
footer .cols{ display:grid; grid-template-columns:1.4fr 1fr 1fr; gap:2.5rem; margin-bottom:2.8rem; }
footer .wordmark{ font-family:var(--font-display); font-weight:600; font-size:1.3rem; color:var(--black); }
footer .col h4{ font-family:var(--font-mono); font-size:.7rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-soft); margin-bottom:1.1rem; }
footer .col p{ color:var(--ink-soft); font-size:.9rem; max-width:32ch; }
footer .col a{ display:block; text-decoration:none; color:var(--ink-soft); font-size:.92rem; margin-bottom:.65rem; transition:color .2s ease; }
footer .col a:hover{ color:var(--glow); }
footer .bottom{ display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:1rem; padding-top:1.6rem; border-top:1px solid var(--line); }
footer .meta{ font-family:var(--font-mono); font-size:.7rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); }
@media (max-width:700px){ footer .cols{ grid-template-columns:1fr; } }

/* staggered card reveal: grid items cascade in one after another instead of all at once */
.pillars.reveal > *, .cap-grid.reveal > *, .approach-grid.reveal > *, .price-grid.reveal > *, .work-grid.reveal > *, .quick-grid.reveal > *, .industry-row.reveal > *, .process-grid.reveal > * { opacity:0; transform:translateY(14px); transition:opacity .55s ease, transform .55s ease; }
.pillars.reveal.in-view > *, .cap-grid.reveal.in-view > *, .approach-grid.reveal.in-view > *, .price-grid.reveal.in-view > *, .work-grid.reveal.in-view > *, .quick-grid.reveal.in-view > *, .industry-row.reveal.in-view > *, .process-grid.reveal.in-view > * { opacity:1; transform:none; }
.pillars.reveal > *:nth-child(1), .cap-grid.reveal > *:nth-child(1), .approach-grid.reveal > *:nth-child(1), .price-grid.reveal > *:nth-child(1), .work-grid.reveal > *:nth-child(1), .quick-grid.reveal > *:nth-child(1), .industry-row.reveal > *:nth-child(1), .process-grid.reveal > *:nth-child(1) { transition-delay:0.04s; }
.pillars.reveal > *:nth-child(2), .cap-grid.reveal > *:nth-child(2), .approach-grid.reveal > *:nth-child(2), .price-grid.reveal > *:nth-child(2), .work-grid.reveal > *:nth-child(2), .quick-grid.reveal > *:nth-child(2), .industry-row.reveal > *:nth-child(2), .process-grid.reveal > *:nth-child(2) { transition-delay:0.1s; }
.pillars.reveal > *:nth-child(3), .cap-grid.reveal > *:nth-child(3), .approach-grid.reveal > *:nth-child(3), .price-grid.reveal > *:nth-child(3), .work-grid.reveal > *:nth-child(3), .quick-grid.reveal > *:nth-child(3), .industry-row.reveal > *:nth-child(3), .process-grid.reveal > *:nth-child(3) { transition-delay:0.16s; }
.pillars.reveal > *:nth-child(4), .cap-grid.reveal > *:nth-child(4), .approach-grid.reveal > *:nth-child(4), .price-grid.reveal > *:nth-child(4), .work-grid.reveal > *:nth-child(4), .quick-grid.reveal > *:nth-child(4), .industry-row.reveal > *:nth-child(4), .process-grid.reveal > *:nth-child(4) { transition-delay:0.22s; }
.pillars.reveal > *:nth-child(5), .cap-grid.reveal > *:nth-child(5), .approach-grid.reveal > *:nth-child(5), .price-grid.reveal > *:nth-child(5), .work-grid.reveal > *:nth-child(5), .quick-grid.reveal > *:nth-child(5), .industry-row.reveal > *:nth-child(5), .process-grid.reveal > *:nth-child(5) { transition-delay:0.28s; }
.pillars.reveal > *:nth-child(6), .cap-grid.reveal > *:nth-child(6), .approach-grid.reveal > *:nth-child(6), .price-grid.reveal > *:nth-child(6), .work-grid.reveal > *:nth-child(6), .quick-grid.reveal > *:nth-child(6), .industry-row.reveal > *:nth-child(6), .process-grid.reveal > *:nth-child(6) { transition-delay:0.34s; }
@media (prefers-reduced-motion: reduce){ .pillars.reveal > *, .cap-grid.reveal > *, .approach-grid.reveal > *, .price-grid.reveal > *, .work-grid.reveal > *, .quick-grid.reveal > *, .industry-row.reveal > *, .process-grid.reveal > * { opacity:1; transform:none; transition:none; } }

/* arrow nudge on hover for "view/explore" links */
.work-link, .quick-link{ display:inline-block; }
.work-link::after, .quick-card span.quick-link::after{ display:inline-block; transition:transform .2s ease; }
.work-card:hover .work-link::after{ transform:translate(3px,-3px); }
.quick-card:hover span.quick-link::after{ transform:translate(3px,-3px); }

/* a slow point of light traveling along the three brightest wireframe
   lines — a "circuit signal" read, not a spin/tilt, and each line gets
   its own duration so they drift out of phase instead of pulsing in
   lockstep */
.line-flow{ stroke-dasharray:14 300; animation:lineFlow linear infinite; }
.line-flow-a{ animation-duration:7s; }
.line-flow-b{ animation-duration:9s; animation-direction:reverse; }
.line-flow-c{ animation-duration:11s; }
@keyframes lineFlow{ to{ stroke-dashoffset:-628; } }
@media (prefers-reduced-motion: reduce){ .line-flow{ animation:none; stroke-dasharray:none; } }
/* Perf fix (2026-08-16): paused by assets/site.js's IntersectionObserver
   once the backdrop scrolls out of view, instead of repainting a blurred
   SVG filter forever on every page regardless of scroll position. */
.hero-bg-scene.offscreen .line-flow{ animation-play-state:paused; }

/* z-index ties with .hero-mark are broken by DOM order (hero-bg-scene
   comes first in the markup), so the mark always paints in front of the
   wireframe backdrop without needing a higher number */
.hero-bg-scene{ position:absolute; z-index:1; pointer-events:none; }
.hero-bg-scene svg{ position:absolute; inset:0; width:100%; height:100%; display:block; overflow:visible; }

/* 2026-08-16: replaces the WebGL 3D render. Same mark geometry as the nav
   logo (just scaled up), rendered flat — costs nothing to keep smooth, has
   no browser/GPU-support fallback case to ever look broken in, and reads
   just as much like "the Nerwx mark" as a 3D render did. Motion is a soft
   glow halo (gradient-only, no filter:blur — see the note above the header
   logo's halo for why) plus a one-time rise-and-fade on page load; nothing
   continuous runs on it beyond the halo pulse. */
.hero-mark{ position:absolute; z-index:1; }
.hero-mark-svg{ position:absolute; inset:0; width:100%; height:100%; display:block; overflow:visible; opacity:0; animation:heroMarkIn .9s .15s cubic-bezier(.16,1,.3,1) forwards; }
.hero-mark-glow{
  position:absolute; z-index:-1; left:50%; top:50%; width:135%; height:135%;
  transform:translate(-50%,-50%); border-radius:50%; pointer-events:none;
  background:radial-gradient(circle, rgba(var(--glow-rgb),.42) 0%, rgba(var(--glow-rgb),.24) 32%, rgba(var(--glow-rgb),.09) 58%, transparent 80%);
  opacity:0; animation:heroMarkGlow 6s 1.1s ease-in-out infinite;
}
@keyframes heroMarkIn{ from{ opacity:0; transform:translateY(16px) scale(.96); } to{ opacity:1; transform:none; } }
@keyframes heroMarkGlow{ 0%,100%{ opacity:.35; } 50%{ opacity:.85; } }
@media (prefers-reduced-motion: reduce){
  .hero-mark-svg{ animation:none; opacity:1; }
  .hero-mark-glow{ animation:none; opacity:.5; }
}
.ambient-glow{ position:absolute; z-index:0; opacity:.85; transition:opacity .3s ease; pointer-events:none; background:radial-gradient(circle, rgba(var(--glow-rgb),.20) 0%, rgba(var(--glow-rgb),.13) 24%, rgba(var(--glow-ambient-rgb),.10) 48%, rgba(var(--glow-ambient-rgb),.04) 68%, transparent 85%); }

/* ---- Heritage redesign signature: Greek-key (meander) hairline, lit by a
   single torch-travel pass on load. Replaces the wireframe-mountain hero
   backdrop (assets/site.js's .hero-bg-scene / .line-flow machinery below is
   left in place for the subpages that still use it — this preview only
   touches the homepage hero markup, which no longer references it). One
   signature element, not two: the old floating .hero-mark N was dropped
   from the hero for this direction — the nav already carries the mark. ---- */
/* Shared irregular flicker curve — deliberately uneven keyframe spacing and
   value jumps (not a clean sine) so it reads as firelight, not a UI pulse.
   Reused by the meander torch and the header logo halo above, each at its
   own duration/delay so they drift in and out of phase with each other. */
@keyframes torchFlicker{
  0%   { opacity:.75; }
  8%   { opacity:.95; }
  15%  { opacity:.6;  }
  24%  { opacity:1;   }
  33%  { opacity:.7;  }
  44%  { opacity:.9;  }
  55%  { opacity:.65; }
  68%  { opacity:1;   }
  77%  { opacity:.7;  }
  88%  { opacity:.85; }
  100% { opacity:.75; }
}
@media (prefers-reduced-motion: reduce){
  .brand::before{ animation:none; opacity:.35; }
}


/* ---- Stronger pass (2nd round): the ambient ember flicker alone read as
   too polite for "dark, epic, torchlit stone" — this adds the moment that
   was missing: the hero actually catches light once on load, and the
   continuous flicker afterward has real contrast instead of a gentle
   shimmer. Both stay one-shot/GPU-cheap: the ignition plays once and never
   runs again, and the amplified flicker below is still opacity-only (no
   filter/blur), consistent with the site's own hard-won perf discipline
   (see the 2026-08-16 notes earlier in this file). */

/* louder flicker — this @keyframes redeclaration overrides the earlier
   torchFlicker (last declaration wins for a given keyframe name), wider
   swings (.4–1 instead of .6–1) so the meander and header halo read as
   genuinely reactive to something burning, not just softly breathing */
@keyframes torchFlicker{
  0%   { opacity:.7; }
  8%   { opacity:1;  }
  15%  { opacity:.42; }
  24%  { opacity:.95; }
  33%  { opacity:.55; }
  44%  { opacity:1;  }
  55%  { opacity:.48; }
  68%  { opacity:.92; }
  77%  { opacity:.5; }
  88%  { opacity:.8; }
  100% { opacity:.7; }
}

/* the ignition: page loads dark, a torch catches near the top of the
   meander strip, and the darkness is clipped away from that point —
   revealing the hero by light spreading outward, not by a plain fade. */
.hero-ignition{
  position:absolute; inset:0; z-index:5; pointer-events:none;
  background:#0b0906;
  clip-path:circle(150% at 88% 6%);
  -webkit-clip-path:circle(150% at 88% 6%);
  animation:ignitionReveal 1.7s cubic-bezier(.16,1,.3,1) .15s 1 forwards;
}
@keyframes ignitionReveal{
  0%   { clip-path:circle(150% at 88% 6%); -webkit-clip-path:circle(150% at 88% 6%); }
  100% { clip-path:circle(0% at 88% 6%); -webkit-clip-path:circle(0% at 88% 6%); }
}
.hero-ignition-flare{
  position:absolute; z-index:6; pointer-events:none;
  left:88%; top:6%; width:16px; height:16px; margin:-8px 0 0 -8px; border-radius:50%;
  background:radial-gradient(circle, rgba(255,224,168,1) 0%, rgba(238,155,0,.95) 20%, rgba(238,155,0,.4) 46%, transparent 72%);
  opacity:0; transform:scale(.3);
  animation:ignitionFlare 1.3s cubic-bezier(.16,1,.3,1) .05s 1 forwards;
}
@keyframes ignitionFlare{
  0%   { opacity:0;   transform:scale(.2); }
  12%  { opacity:1;   transform:scale(2.6); }
  30%  { opacity:.85; transform:scale(4.4); }
  100% { opacity:0;   transform:scale(10); }
}
@media (max-width:980px){
  .hero-ignition{ clip-path:circle(150% at 50% 4%); -webkit-clip-path:circle(150% at 50% 4%); }
  @keyframes ignitionReveal{
    0%   { clip-path:circle(150% at 50% 4%); -webkit-clip-path:circle(150% at 50% 4%); }
    100% { clip-path:circle(0% at 50% 4%); -webkit-clip-path:circle(0% at 50% 4%); }
  }
  .hero-ignition-flare{ left:50%; top:4%; }
}

/* hero copy stays hidden until the light reaches it, then rises in —
   staggered so it reads as sequential illumination, not a single group
   fade. Timed to start while the ignition is still mid-dissolve so the
   text feels lit by the same light, not delayed behind it. */
.hero .wrap > *{ opacity:0; transform:translateY(14px); animation:heroContentIn .75s cubic-bezier(.16,1,.3,1) forwards; }
.hero .tags{ animation-delay:.5s; }
.hero h1{ animation-delay:.72s; }
.hero .lede{ animation-delay:.95s; }
.hero .actions{ animation-delay:1.1s; }
@keyframes heroContentIn{ to{ opacity:1; transform:none; } }

@media (prefers-reduced-motion: reduce){
  .hero-ignition, .hero-ignition-flare{ display:none; }
  .hero .wrap > *{ opacity:1; transform:none; animation:none; }
}

/* ---- Motion pass, 4th addition: section entry catches light ----
   Every .reveal section already fades+rises into view via JS toggling
   .in-view (see the IntersectionObserver in site.js) — this hooks onto that
   same existing mechanism with zero new JS. The eyebrow label briefly
   flares amber then settles to its normal ink-soft color, so every section
   on the page reads as catching the same torchlight the hero opened with,
   not just a plain fade. One-shot per entry (already replays on re-entry
   both directions, matching the existing .reveal behavior); reduced-motion
   users get the site-wide .001ms override already set at the top of this
   file, same as every other animation here. */
.reveal.in-view .eyebrow{ animation:torchCatch .9s ease-out; }
@keyframes torchCatch{
  /* text-shadow dropped: a shadow on text is repainted glyph by glyph every
     frame, and the colour change alone carries the same read. */
  0%   { color:var(--glow); }
  100% { color:var(--ink-soft); }
}

/* ---- Hero intro, replaced (round 3): additive light-pass, not a veil ----
   The previous ignition (black clip-path circle + flare) hid the headline
   and CTA behind ~1.7s of darkness before they were legible/clickable —
   fine for a portfolio reel, a real risk for a homepage where an impatient
   visitor deciding whether to stay reads "content isn't there yet" as
   "leave." This version keeps content fully visible/clickable from frame
   one and only adds a warm light sweep on top once. */
.hero .wrap > *{ opacity:1 !important; transform:none !important; animation:none !important; }

.hero-lightpass{
  position:absolute; inset:0; z-index:4; pointer-events:none;
  background:linear-gradient(100deg,
    transparent 32%,
    rgba(255,214,140,.20) 45%,
    rgba(238,155,0,.30) 50%,
    rgba(255,214,140,.20) 55%,
    transparent 68%);
  mix-blend-mode:screen; /* only ever brightens what's underneath — can't obscure text */
  transform:translateX(-55%);
  opacity:0;
  animation:lightPass 1.4s cubic-bezier(.4,0,.2,1) .3s 1 forwards;
}
@keyframes lightPass{
  0%   { transform:translateX(-55%); opacity:0; }
  10%  { opacity:1; }
  60%  { opacity:1; }
  100% { transform:translateX(55%); opacity:0; }
}
@media (max-width:980px){
  .hero-lightpass{ background:linear-gradient(100deg, transparent 25%, rgba(255,214,140,.22) 46%, rgba(238,155,0,.32) 50%, rgba(255,214,140,.22) 54%, transparent 75%); }
}
@media (prefers-reduced-motion: reduce){ .hero-lightpass{ display:none; } }

/* ---- Length pass: tighter section rhythm ----
   Was a flat 7rem top+bottom on every section — ~112px of pure padding,
   times five sections, before any content. Cut to a responsive clamp so
   it's still generous on desktop but nowhere near as much dead scroll
   distance, especially on mobile where 7rem was proportionally huge. */
section{ padding:clamp(3.25rem, 7vw, 4.75rem) 0; }

/* compact lede + inline trust chips at the top of the merged Compare
   section (folded in from the removed About section — same claims, faster
   to scan as a line + three chips than as two paragraphs) */
.compare-lede{ max-width:56ch; color:var(--ink-soft); font-size:1.05rem; margin-top:1.1rem; }
.badges-inline{
  display:flex; flex-wrap:wrap; gap:.6rem;
  border-top:none; margin-top:1.3rem;
}
.badges-inline .badge{
  border-bottom:none; border:1px solid var(--line-strong); border-radius:999px;
  padding:.5rem 1rem; font-size:.7rem;
}


/* ---- Hero showcase card: real screenshots, auto-rotating slideshow ----
   2026-08-20: was a single static Bakery screenshot ("this one sample") —
   now cycles through all six real projects. The browser-chrome bar (traffic
   -light dots) stays a single constant element outside the crossfade, since
   it's decorative window-frame, not part of any one project — only the
   "page showing in the window" (image + caption + link) actually swaps,
   which also reads more literally like switching tabs in a real browser.
   Pure CSS: one shared @keyframes, each slide phase-shifted via a negative
   animation-delay, no JS needed. Also fixes a real accuracy bug: the old
   single-slide caption said "real client" under the Bakery, which is a
   concept/demo build, not a real client — every slide now uses the same
   industry-tag vocabulary already established in the Work section instead
   of a true/false client claim. */
.hero-showcase{
  position:absolute; top:50%; right:6%; z-index:2;
  width:min(30vw, 400px);
  transform:translateY(-52%) rotate(-3deg);
  border-radius:10px; overflow:hidden;
  border:1px solid var(--line-strong);
  box-shadow:0 30px 60px -20px rgba(0,0,0,.55), 0 0 40px -12px rgba(var(--glow-rgb),.18);
  background:var(--surface);
  transition:transform .3s ease, box-shadow .3s ease;
}
@media (hover:hover) and (pointer:fine){
  .hero-showcase:hover{
    transform:translateY(-52%) rotate(-1deg) scale(1.02);
    box-shadow:0 36px 70px -18px rgba(0,0,0,.6), 0 0 50px -8px rgba(var(--glow-rgb),.28);
  }
}
.hero-showcase-bar{ display:flex; gap:6px; align-items:center; padding:10px 12px; background:rgba(20,17,12,.92); position:relative; z-index:1; }
.hero-showcase-dot{ width:8px; height:8px; border-radius:50%; display:block; }
.hero-showcase-dot:nth-child(1){ background:#e0605a; }
.hero-showcase-dot:nth-child(2){ background:#e0b24f; }
.hero-showcase-dot:nth-child(3){ background:#5fae6e; }
.hero-showcase-slides{ position:relative; aspect-ratio:8/5; }
.hero-showcase-slide{
  position:absolute; inset:0; display:block; text-decoration:none;
  opacity:0; animation:heroShowcaseCycle 10.5s ease-in-out infinite;
}
.hero-showcase-slide img{ display:block; width:100%; height:100%; object-fit:cover; object-position:top; }
.hero-showcase-caption{
  position:absolute; left:12px; bottom:12px;
  font-family:var(--font-mono); font-size:.62rem; letter-spacing:.05em; text-transform:uppercase;
  color:var(--ink-soft); background:rgba(20,17,12,.78); padding:.4rem .6rem; border-radius:4px;
}
/* Trimmed to the three strongest projects (2026-08-20): three slides,
   3.5s each, one shared 10.5s loop — every slide runs the same keyframes,
   just started at a different negative offset so only one is ever at
   opacity:1 at a time. Duration must stay slide-count * 3.5s or the
   on-screen windows stop tiling cleanly (see the 6-slide version this
   replaced, same 3.5s-per-slide math, just a longer loop for more slides).

   Fixed (2026-08-21): the "on" window below only actually spanned 2%-17%
   of the loop (~1.8s) instead of the full 33.3% each slide needs (~3.5s)
   to hand off to the next one with no gap. With three slides offset by
   exactly a third of the loop each, that left every slide sitting at
   opacity:0 — all three at once, card visibly blank — for ~1.9s out of
   every 3.5s turn. Read as the whole showcase stalling/lagging, not as a
   slideshow. Widened the plateau to fill the full third, with the fade
   in/out happening right at the handoff boundary instead of long before
   it, so the next slide's fade-in starts the instant this one's fade-out
   ends — a real crossfade, no blank gap. */
@keyframes heroShowcaseCycle{
  0%      { opacity:0; }
  1%      { opacity:1; }
  32%     { opacity:1; }
  33.33%  { opacity:0; }
  100%    { opacity:0; }
}
@media (prefers-reduced-motion: reduce){
  .hero-showcase-slide{ animation:none; opacity:0; }
  .hero-showcase-slide:first-child{ opacity:1; }
}
/* Real fix (2026-08-20): this card was the single brightest, most visually
   rich element in the hero — a real colored screenshot with a light-cream
   background — and it was simply removed on mobile via display:none,
   leaving phones with a visibly emptier/dimmer hero than desktop for the
   exact same page. That's a real, concrete brightness gap between the two,
   not a rendering bug. Restored here in-flow below the CTA buttons
   (.hero's mobile layout is already a flex column — order:3 places it
   after .wrap's order:2), sized to the viewport instead of floating
   beside text there's no room for on a narrow screen. */
@media (max-width:980px){
  .hero-showcase{
    display:block; position:relative; order:3; top:auto; right:auto;
    width:min(86vw,420px); margin:.5rem auto 0;
    transform:rotate(-2deg);
  }
}

/* ---- Heritage redesign: interior-page hero backdrop, simplified ----
   Every subpage's .page-hero used the same wireframe-mountain SVG as the
   old homepage hero (cosmic-void network lines) — auto-recolors amber via
   tokens but is the wrong shape language for stone/torchlight regardless
   of color, and repeating the homepage's full meander signature on every
   single page would dilute it. Interior pages get the palette plus one
   quiet ambient glow instead — restrained on purpose. One deliberate
   exception below: Work gets a second, different motif because it's the
   showcase page; every other subpage stays glow-only. */
.page-hero-glow{
  position:absolute; z-index:0; left:50%; top:45%; width:min(60vw,760px); aspect-ratio:1;
  transform:translate(-50%,-50%); pointer-events:none;
  background:radial-gradient(circle, rgba(var(--glow-rgb),.16) 0%, rgba(var(--glow-rgb),.07) 40%, rgba(var(--glow-ambient-rgb),.05) 62%, transparent 80%);
}
@media (max-width:640px){ .page-hero-glow{ width:min(80vw,340px); } }

/* ---- Contact icons (2026-08-19): Instagram/WhatsApp/email wherever a
   contact link has room — monochrome, currentColor so they inherit each
   button/link's own color and hover state automatically instead of
   needing separate color tokens. */
.icon-inline{ width:1.05em; height:1.05em; flex:none; vertical-align:-.15em; }
.btn .icon-inline{ margin-right:.5rem; }
.footer-social{ display:flex; gap:.9rem; margin-top:1.1rem; }
.footer-social a{ color:var(--ink-soft); transition:color .2s ease; line-height:0; }
.footer-social a:hover{ color:var(--glow); }
.footer-social svg{ width:19px; height:19px; display:block; }

/* ---- Homepage animation pass (2026-08-21), ported from the Claude Design
   prototype "Nerwx Animated Homepage": scroll progress bar, sliding
   lang-switch pill (above), animated compare icons + eyebrow torch-catch
   (above), and this click-to-expand work-card modal. Hand-written vanilla
   JS/CSS matching the rest of the site, not the prototype's design-canvas
   runtime — that runtime (support.js) is a React-based prototyping tool,
   never meant to ship in a production static site. ---- */

/* Scroll progress bar — homepage only per the source prototype. */
/* Driven by transform:scaleX, not width. Animating width made the browser
   run layout on every scroll event; a transform stays on the compositor. */
#scrollProgress{ position:fixed; top:0; left:0; height:3px; z-index:80; background:var(--glow);
  box-shadow:0 0 10px rgba(var(--glow-rgb),.6); width:100%;
  transform:scaleX(0); transform-origin:0 50%; will-change:transform; }
@media (prefers-reduced-motion: reduce){ #scrollProgress{ transition:none; } }

/* Work-card modal: expands from the clicked card's own on-screen rect to a
   centered preview (top/left/width/height set inline by JS, transitioned
   via CSS) rather than just fading in centered — the card visibly becomes
   the modal instead of a new box appearing over it. The .work-card <a>
   itself stays a real link throughout (href unchanged, target="_blank"),
   so a middle-click/cmd-click still opens the real project directly; only
   a plain left-click is intercepted to open the modal instead. */
.work-modal{ position:fixed; inset:0; z-index:90; pointer-events:none; }
.work-modal:not(.open){ visibility:hidden; }
.work-modal.open{ pointer-events:auto; }
.work-modal-backdrop{ position:fixed; inset:0; background:rgba(10,8,5,0); transition:background .35s ease; cursor:pointer; }
.work-modal.open .work-modal-backdrop{ background:rgba(10,8,5,.6); }
.work-modal-flash{ position:fixed; z-index:2; pointer-events:none; border-radius:50%; width:16px; height:16px; margin:-8px 0 0 -8px; opacity:0; transform:scale(.3); }
.work-modal-flash.fire{ animation:workModalFlash .7s cubic-bezier(.16,1,.3,1) forwards; }
@keyframes workModalFlash{
  0%   { opacity:0;   transform:scale(.2); }
  12%  { opacity:1;   transform:scale(2.6); }
  30%  { opacity:.85; transform:scale(4.4); }
  100% { opacity:0;   transform:scale(10); }
}
.work-modal-box{ position:fixed; background:var(--surface); overflow:hidden; box-shadow:0 30px 80px rgba(0,0,0,.5); border:1px solid transparent; display:flex; flex-direction:column; }
.work-modal.open .work-modal-box{ border-color:rgba(var(--glow-rgb),.35); border-radius:10px; }
.work-modal-media{ width:100%; aspect-ratio:16/9; overflow:hidden; flex:none; }
.work-modal-img{ width:100%; height:100%; object-fit:cover; object-position:top; display:block; }
.work-modal-body{ padding:1.9rem 2rem; opacity:0; transition:opacity .3s ease .15s; overflow:auto; }
.work-modal.open .work-modal-body{ opacity:1; }
.work-modal-tag{ font-family:var(--font-mono); font-size:.68rem; letter-spacing:.08em; text-transform:uppercase; color:var(--glow); margin-bottom:.6rem; display:block; }
.work-modal-title{ font-family:var(--font-display); font-size:1.6rem; font-weight:600; color:var(--ink); margin-bottom:.7rem; }
.work-modal-desc{ font-size:.95rem; line-height:1.6; color:var(--ink-soft); margin-bottom:1.3rem; }
.work-modal-close{ position:absolute; top:14px; right:14px; width:32px; height:32px; border-radius:50%; background:rgba(20,17,12,.6); border:none; display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:20px; line-height:1; color:var(--ink); transition:background .2s ease; }
.work-modal-close:hover{ background:rgba(20,17,12,.85); }
@media (prefers-reduced-motion: reduce){
  .work-modal-box{ transition:none !important; }
  .work-modal-flash{ display:none; }
}


/* ---- Cinematic treatment, shared (2026-08-26) --------------------------
   The homepage and /work/ carry generated footage from one torchlit stone
   world. These rules let every other page use the same device without
   repeating the CSS: drop a .section-video + .section-scrim as the first
   children of a .page-hero or .cta-band and it inherits the look. Scenes
   are varied per page so the site doesn't read as one looping clip.
   The scrim fades to --bg top and bottom so a section always joins its
   neighbours cleanly regardless of what the footage is doing.           */
.page-hero, .cta-band{ position:relative; overflow:hidden; }
/* The generated footage is graded very dark at source (mean luminance
   21-40 of 255), so dropping opacity and stacking a scrim on top took it
   to ~18/255 on screen - effectively black. Opacity is near-full now and
   the clips get a real brightness lift; the scrims below do the work of
   protecting text instead. */
.section-video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0;
  opacity:.92; filter:brightness(1.45) contrast(1.04) saturate(1.06); }
.page-hero .section-video{ opacity:1; filter:brightness(1.5) contrast(1.05) saturate(1.06); }
.section-scrim{ position:absolute; inset:0; z-index:0;
  background:
    linear-gradient(180deg, var(--bg) 0%, rgba(20,17,12,.46) 12%, rgba(20,17,12,.46) 88%, var(--bg) 100%),
    linear-gradient(90deg, rgba(20,17,12,.55) 0%, rgba(20,17,12,.12) 42%, rgba(20,17,12,.12) 62%, rgba(20,17,12,.4) 100%); }
/* The page-hero band is short, so the section scrim's top+bottom fades to
   --bg overlapped in the middle and crushed the footage to a flat void.
   It gets a lighter scrim: enough contrast for the headline, still
   readable as a real place. */
.page-hero .section-scrim{
  background:
    linear-gradient(180deg, rgba(20,17,12,.42) 0%, rgba(20,17,12,.22) 45%, rgba(20,17,12,.72) 100%),
    linear-gradient(90deg, rgba(20,17,12,.62) 0%, rgba(20,17,12,.1) 58%, rgba(20,17,12,.3) 100%); }
.page-hero{ min-height:56vh; min-height:56svh; }
.page-hero .wrap, .cta-band .wrap{ position:relative; z-index:2; }
.page-hero h1, .cta-band h2{ text-shadow:0 4px 24px var(--shadow-ink-strong); }
.page-hero .lede, .page-hero .breadcrumb, .page-hero .eyebrow{ text-shadow:0 1px 12px var(--shadow-ink); }
@media (prefers-reduced-motion: reduce){ .section-video{ display:none; } }
