
  :root{
    --bg:#FAFAF6;
    --bg-alt:#F1F2F0;
    --surface:#FFFFFF;
    --ink:#17191C;
    --ink-soft:#6B7280;
    --ink-faint:#9CA3AF;
    --line:#E5E7EB;
    --accent:#F97316;
    --accent-dim:#EA580C;
    --on-accent:#FFFFFF;
    --success:#16A34A;
    --danger:#DC2626;
    --dark:#17191C;
    --dark-alt:#22252B;
    --dark-ink:#FAFAF6;
    --dark-ink-soft:#9CA3AF;
    --dark-line:#31343B;
    --shadow:rgba(23,25,28,.10);
    --focus:#F97316;
    color-scheme: light;
  }
  @media (prefers-color-scheme: dark){
    :root:not([data-theme="light"]){
      --bg:#1B1D21;
      --bg-alt:#232529;
      --surface:#232529;
      --ink:#FAFAF6;
      --ink-soft:#9CA3AF;
      --ink-faint:#71717A;
      --line:#33363C;
      --accent:#FB923C;
      --accent-dim:#F97316;
      --on-accent:#FFFFFF;
      --success:#22C55E;
      --danger:#EF4444;
      --dark:#111214;
      --dark-alt:#191B1E;
      --dark-ink:#FAFAF6;
      --dark-ink-soft:#9CA3AF;
      --dark-line:#2B2D32;
      --shadow:rgba(0,0,0,.45);
      --focus:#FB923C;
      color-scheme: dark;
    }
  }
  :root[data-theme="dark"]{
    --bg:#1B1D21;
    --bg-alt:#232529;
    --surface:#232529;
    --ink:#FAFAF6;
    --ink-soft:#9CA3AF;
    --ink-faint:#71717A;
    --line:#33363C;
    --accent:#FB923C;
    --accent-dim:#F97316;
    --on-accent:#FFFFFF;
    --success:#22C55E;
    --danger:#EF4444;
    --dark:#111214;
    --dark-alt:#191B1E;
    --dark-ink:#FAFAF6;
    --dark-ink-soft:#9CA3AF;
    --dark-line:#2B2D32;
    --shadow:rgba(0,0,0,.45);
    --focus:#FB923C;
    color-scheme: dark;
  }

  *,*::before,*::after{ box-sizing:border-box; }
  html{ -webkit-text-size-adjust:100%; }
  body{
    margin:0;
    background:var(--bg);
    color:var(--ink);
    font-family:"Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    font-size:16px;
    line-height:1.6;
    -webkit-font-smoothing:antialiased;
  }
  /* Subtle film-grain texture for depth -- a fixed, ultra-low-opacity noise
     layer blended over everything. pointer-events:none keeps it inert, and
     the low z-index sits it below the header/modals/toast/loader (all
     explicitly higher) so it never gets in the way, just adds texture. */
  body::after{
    content:""; position:fixed; inset:0; z-index:1; pointer-events:none; opacity:.035; mix-blend-mode:overlay;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  }
  /* The fade/nudge transition lives on #page-view (a wrapper around the
     header+main+footer) rather than on body itself. #page-loader is a
     sibling of #page-view, not a descendant -- CSS opacity always caps every
     descendant's rendered opacity, with no way for a child to opt back out,
     so fading body directly would also fade the loader sitting on top of it
     out of view during the very moment it's supposed to be covering the
     page. Keeping the loader as an unrelated sibling avoids that entirely. */
  #page-view{
    /* No resting `transform` here on purpose (not even translateY(0)): any
       computed transform other than none turns this element into the
       containing block for its own fixed-position descendants -- exactly
       the trap the header's backdrop-filter fell into earlier -- which
       would confine the mobile nav overlay (position:fixed, lives inside
       the header inside this wrapper) to this box instead of the full
       viewport. Transitioning between the browser's implicit "none" and an
       explicit translateY() during .is-leaving/.page-enter still animates
       fine; it just isn't declared as a permanent resting value. */
    opacity:1;
    transition:opacity .22s cubic-bezier(.4,0,.2,1), transform .22s cubic-bezier(.4,0,.2,1);
  }
  /* Leaving this page (about to navigate away) and arriving on the next one
     use the same nudge-down-and-fade look, driven from opposite ends, so the
     two pages read as one continuous crossfade rather than two separate
     effects. .page-enter starts a fresh page invisible/offset; site.js
     removes it a frame after load to animate the page into place. */
  #page-view.is-leaving{ opacity:0; transform:translateY(-10px); }
  #page-view.page-enter{ opacity:0; transform:translateY(10px); }
  @media (prefers-reduced-motion: reduce){
    *{ animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; scroll-behavior:auto !important; }
  }
  img{ max-width:100%; display:block; }
  a{ color:inherit; }
  button{ font-family:inherit; }
  h1,h2,h3,p{ margin:0; }
  h1,h2,h3{ font-family:"Newsreader", Georgia, serif; text-wrap:balance; color:var(--ink); letter-spacing:-.015em; }
  .skip-link{
    position:absolute; left:-9999px; top:0; background:var(--accent); color:var(--on-accent);
    padding:.6rem 1rem; z-index:200; border-radius:0 0 6px 0;
  }
  .skip-link:focus{ left:0; }
  :focus-visible{ outline:2px solid var(--focus); outline-offset:3px; }

  /* ---------- Scroll reveal (subtle 3D tilt-in, not just a fade) ---------- */
  .reveal{
    opacity:0; transform:perspective(800px) translateY(24px) rotateX(6deg); transform-origin:50% 100%;
    transition:opacity .7s cubic-bezier(.16,.8,.24,1), transform .7s cubic-bezier(.16,.8,.24,1);
  }
  .reveal.is-visible{ opacity:1; transform:perspective(800px) translateY(0) rotateX(0deg); }

  .wrap{ max-width:1180px; margin-inline:auto; padding-inline:clamp(1.25rem,4vw,2.5rem); }
  .eyebrow{
    font-family:"IBM Plex Mono", monospace; font-size:.78rem; font-weight:500;
    letter-spacing:.12em; text-transform:uppercase; color:var(--accent);
    display:inline-flex; align-items:center; gap:.5em;
  }
  .eyebrow::before{ content:""; width:1.4em; height:1px; background:var(--accent); display:inline-block; }

  /* ---------- Logo ---------- */
  .logo{
    font-family:"Newsreader", Georgia, serif; font-style:italic; font-size:1.95rem; font-weight:500;
    display:inline-flex; align-items:baseline; letter-spacing:.01em; text-decoration:none; color:var(--ink);
    transition:opacity .2s ease;
  }
  .logo:hover{ opacity:.78; }
  .logo-dot{ color:var(--accent); cursor:default; }

  /* ---------- Header ----------
     Note: the frosted-glass blur lives on a ::before pseudo-element rather
     than directly on #site-header. A `backdrop-filter` on the header itself
     would make the header the containing block for ANY fixed-position
     descendant (like the mobile nav overlay below), confining it to the
     header's own small box instead of the full viewport. Keeping the filter
     on a separate, childless pseudo-element avoids that trap entirely. */
  #site-header{
    position:sticky; top:0; z-index:100; background:transparent; border-bottom:1px solid var(--line);
    isolation:isolate;
  }
  #site-header::before{
    content:""; position:absolute; inset:0; z-index:-1;
    background:color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter:blur(8px);
  }
  .nav-inner{ display:flex; align-items:center; justify-content:space-between; padding-block:1.1rem; }
  .nav-links{ display:flex; align-items:center; gap:2rem; list-style:none; margin:0; padding:0; }
  .nav-links a{
    position:relative;
    font-size:.85rem; font-weight:600; letter-spacing:.04em; text-transform:uppercase;
    text-decoration:none; color:var(--ink-soft); padding-bottom:.3rem; border-bottom:2px solid transparent;
    transition:color .15s, border-color .15s;
  }
  .nav-links a:hover{ color:var(--ink); }
  .nav-links a.active{ color:var(--ink); border-color:var(--accent); }
  /* Animated underline (a ::after overlay, independent of the .active item's
     own static border-bottom) that grows from the center on hover instead of
     the border simply appearing -- a small detail, but it's the single most-
     used interactive element on every page. */
  .nav-links a::after{
    content:""; position:absolute; left:0; right:0; bottom:-2px; height:2px; background:var(--accent);
    transform:scaleX(0); transform-origin:center; transition:transform .3s cubic-bezier(.16,.8,.24,1);
  }
  .nav-links a:hover::after{ transform:scaleX(1); }
  .nav-links a.active::after{ content:none; }
  .nav-toggle{
    display:none; background:none; border:1px solid var(--line); border-radius:0; width:48px; height:48px;
    align-items:center; justify-content:center; cursor:pointer; flex-direction:column; gap:5px;
    transition:border-color .2s ease, background .2s ease;
  }
  .nav-toggle:hover{ border-color:var(--ink-soft); background:var(--bg-alt); }
  body.mobile-nav-open .nav-toggle{ border-color:var(--accent); }
  .nav-toggle span{ width:22px; height:2px; background:var(--ink); display:block; transition:transform .2s ease, opacity .2s ease; }
  body.mobile-nav-open .nav-toggle span:nth-child(1){ transform:translateY(7px) rotate(45deg); }
  body.mobile-nav-open .nav-toggle span:nth-child(2){ opacity:0; }
  body.mobile-nav-open .nav-toggle span:nth-child(3){ transform:translateY(-7px) rotate(-45deg); }
  .nav-cta-item{ display:none; }
  @media (max-width:800px){
    .nav-toggle{ display:inline-flex; }
    .nav-links{
      position:fixed; top:var(--header-h, 65px); left:0; right:0; bottom:0; background:var(--bg);
      flex-direction:column; align-items:stretch; gap:0; border-top:1px solid var(--line);
      padding:.75rem clamp(1.25rem,4vw,2.5rem) 2.5rem; margin:0; z-index:90; overflow-y:auto;
      display:none; opacity:0; transition:opacity .18s ease;
    }
    .nav-links li{ display:block; }
    .nav-links a{
      display:flex; align-items:center; width:100%; padding-block:1.5rem; border-bottom:1px solid var(--line);
      font-size:1.55rem; letter-spacing:0; text-transform:none; font-family:"Newsreader", Georgia, serif; font-weight:500;
    }
    .nav-links a.active{ color:var(--accent); }
    body.mobile-nav-open{ overflow:hidden; }
    body.mobile-nav-open .nav-links{ display:flex; opacity:1; }
    body.mobile-nav-open .nav-links li{
      animation:navItemIn .45s cubic-bezier(.16,.8,.24,1) backwards;
    }
    body.mobile-nav-open .nav-links li:nth-child(1){ animation-delay:.04s; }
    body.mobile-nav-open .nav-links li:nth-child(2){ animation-delay:.09s; }
    body.mobile-nav-open .nav-links li:nth-child(3){ animation-delay:.14s; }
    body.mobile-nav-open .nav-links li:nth-child(4){ animation-delay:.19s; }
    body.mobile-nav-open .nav-links li:nth-child(5){ animation-delay:.24s; }
    body.mobile-nav-open .nav-links li:nth-child(6){ animation-delay:.29s; }
    .nav-cta-item{ display:block; border-bottom:none !important; margin-top:1.5rem; }
    .nav-links a.btn{
      width:100%; justify-content:center; padding-block:1.1rem; font-size:1.05rem; font-weight:600;
      font-family:"Work Sans", -apple-system, BlinkMacSystemFont, sans-serif; text-transform:none; letter-spacing:normal;
      color:var(--on-accent); border-bottom:none;
    }
    .nav-links a.btn:hover{ color:var(--on-accent); }
  }
  @keyframes navItemIn{ from{ opacity:0; transform:translateX(18px); } to{ opacity:1; transform:none; } }

  /* ---------- Sections ---------- */
  .section{ padding-block:clamp(3.5rem,7vw,6.5rem); }
  .section--alt{ background:var(--bg-alt); }
  .section--dark{ background:var(--dark); color:var(--dark-ink); }
  .section--dark h1, .section--dark h2, .section--dark h3{ color:var(--dark-ink); }
  .section--dark .eyebrow{ color:var(--accent); }
  /* The little rule before each eyebrow label draws in left-to-right the
     moment its section-head scrolls into view, riding the exact same
     is-visible toggle that already drives that container's own fade/tilt --
     no separate observer needed. */
  .section-head.reveal .eyebrow::before{ transform:scaleX(0); transform-origin:left; transition:transform .5s cubic-bezier(.16,.8,.24,1) .25s; }
  .section-head.reveal.is-visible .eyebrow::before{ transform:scaleX(1); }
  .section-head{ max-width:640px; margin-bottom:2.75rem; display:flex; flex-direction:column; gap:.9rem; }
  .section-head h2{ font-size:clamp(1.7rem,3.4vw,2.5rem); font-weight:600; }
  .section-head p{ color:var(--ink-soft); font-size:1.05rem; }
  .section--dark .section-head p{ color:var(--dark-ink-soft); }

  /* ---------- Buttons ---------- */
  .btn{
    display:inline-flex; align-items:center; gap:.55rem; padding:.85rem 1.5rem; border-radius:0;
    font-size:.92rem; font-weight:600; text-decoration:none; border:1px solid transparent; cursor:pointer;
    position:relative;
    transition:transform .18s cubic-bezier(.34,1.56,.64,1), box-shadow .2s ease, background .2s ease, color .3s ease, border-color .2s ease;
  }
  .btn:hover{ transform:translateY(-2px); }
  .btn:active{ transform:translateY(0) scale(.94); transition-duration:.08s; }
  .btn:disabled{ opacity:.45; cursor:not-allowed; transform:none !important; }
  .btn:disabled:hover{ transform:none; }
  /* Ripple (a .btn-ripple span is inserted on pointerdown by wireButtonRipples())
     and the accent sheen sweep both need their button to clip and host them. */
  .btn--accent, .btn--outline, .btn--ghost{ overflow:hidden; }
  .btn-ripple{
    position:absolute; border-radius:50%; background:currentColor; opacity:.45;
    transform:scale(0); pointer-events:none; animation:btnRippleOut .6s cubic-bezier(.25,.46,.45,.94) forwards;
  }
  @keyframes btnRippleOut{ to{ transform:scale(2.6); opacity:0; } }
  .btn--accent{ background:var(--accent); color:var(--on-accent); box-shadow:0 0 0 0 transparent; }
  .btn--accent::before{
    content:""; position:absolute; top:0; left:-75%; width:45%; height:100%;
    background:linear-gradient(115deg, transparent, rgba(255,255,255,.45), transparent);
    transform:skewX(-20deg); transition:left .55s cubic-bezier(.4,0,.2,1); pointer-events:none;
  }
  .btn--accent:hover{ background:var(--accent-dim); box-shadow:0 10px 24px -10px color-mix(in srgb, var(--accent) 65%, transparent); }
  .btn--accent:hover::before{ left:125%; }
  .btn--accent:active{ box-shadow:inset 0 2px 10px rgba(0,0,0,.2); }
  .btn--outline{
    border-color:var(--line); color:var(--ink);
    background:linear-gradient(var(--ink), var(--ink)) no-repeat right / 0% 100%;
    transition:background-size .3s cubic-bezier(.65,0,.35,1), color .3s ease .05s, border-color .2s ease, transform .18s cubic-bezier(.34,1.56,.64,1);
  }
  .btn--outline:hover{ background-size:100% 100%; background-position:left; color:var(--bg); border-color:var(--ink); }
  .section--dark .btn--outline{
    border-color:var(--dark-line); color:var(--dark-ink);
    background:linear-gradient(var(--dark-ink), var(--dark-ink)) no-repeat right / 0% 100%;
  }
  .section--dark .btn--outline:hover{ background-size:100% 100%; background-position:left; color:var(--dark); border-color:var(--dark-ink); }
  .btn--text{ padding:.4rem 0; border-radius:0; gap:.4rem; color:var(--ink); }
  .btn--text::before{
    content:""; position:absolute; left:0; bottom:-1px; width:0; height:1px; background:currentColor;
    transition:width .25s cubic-bezier(.4,0,.2,1);
  }
  .btn--text:hover::before{ width:100%; }
  .btn--text::after{ content:"→"; transition:transform .15s; }
  .btn--text:hover::after{ transform:translateX(3px); }
  .btn--text:active::after{ transform:translateX(5px); }
  .btn--sm{ padding:.55rem 1.05rem; font-size:.82rem; }
  .btn--ghost{
    background:transparent; border-color:var(--dark-line); color:var(--dark-ink);
    transition:background .2s ease, border-color .2s ease, transform .18s cubic-bezier(.34,1.56,.64,1);
  }
  .btn--ghost:hover{ background:var(--dark-alt); border-color:var(--dark-ink-soft); }
  .cta-row{ display:flex; flex-wrap:wrap; align-items:center; gap:1.5rem; margin-top:2rem; }

  /* ---------- Hero ---------- */
  .hero{ display:grid; grid-template-columns:1.05fr .95fr; gap:clamp(2rem,5vw,4rem); align-items:center; padding-block:clamp(3rem,7vw,5.5rem); position:relative; isolation:isolate; }
  @media (max-width:920px){ .hero{ grid-template-columns:1fr; } }
  .hero h1{ font-size:clamp(2.6rem,5.4vw,4.2rem); font-weight:600; line-height:1.04; margin-top:1rem; letter-spacing:-.025em; }
  .hero p.lede{ margin-top:1.4rem; max-width:46ch; color:var(--ink-soft); font-size:1.15rem; }
  .hero-visual{ position:relative; transform-style:preserve-3d; transition:transform .4s cubic-bezier(.16,.8,.24,1); }

  /* ---------- Hero entrance: a small cascade instead of one flat fade ----------
     #page-view already fades/nudges the whole page in over .22s (see near the
     top of this file); this adds a second, finer stagger on top of that for
     the hero's own children only, so the very first thing a visitor sees
     reads as a little cinematic instead of everything popping in at once.
     `backwards` (not `both`) on purpose: once each animation finishes it lets
     go of `transform` entirely and hands control back to the normal cascade,
     which matters for .hero-visual specifically since initHeroParallax() in
     site.js drives that element's `transform` afterward via inline style --
     a lingering `both`/`forwards` fill would keep re-asserting the
     animation's own end value over that inline style forever. */
  .hero > div:first-child > .eyebrow{ animation:heroItemIn .7s cubic-bezier(.16,.8,.24,1) .05s backwards; }
  .hero h1{ animation:heroItemIn .7s cubic-bezier(.16,.8,.24,1) .15s backwards; }
  .hero p.lede{ animation:heroItemIn .7s cubic-bezier(.16,.8,.24,1) .28s backwards; }
  .hero .cta-row{ animation:heroItemIn .7s cubic-bezier(.16,.8,.24,1) .4s backwards; }
  .hero-visual{ animation:heroVisualIn .8s cubic-bezier(.16,.8,.24,1) .22s backwards; }
  @keyframes heroItemIn{ from{ opacity:0; transform:perspective(600px) translateY(16px) rotateX(8deg); } to{ opacity:1; transform:none; } }
  @keyframes heroVisualIn{ from{ opacity:0; transform:perspective(1200px) rotateY(-10deg) translateX(24px); } to{ opacity:1; transform:none; } }

  /* Ambient glow that follows the pointer inside the hero only (see
     initHeroGlow in site.js) -- a small, contained detail rather than a
     page-wide effect, purely decorative (pointer-events:none) and hidden by
     default until the pointer is actually inside the section. */
  .hero-glow{
    position:absolute; top:0; left:0; width:420px; height:420px; margin:-210px 0 0 -210px; border-radius:50%;
    background:radial-gradient(circle, color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 70%);
    pointer-events:none; z-index:0; opacity:0; transition:opacity .4s ease;
  }
  .hero-glow.show{ opacity:1; }
  .hero > div, .hero-visual{ position:relative; z-index:1; }
  .hero-visual::before, .hero-visual::after{
    content:"+"; position:absolute; font-family:"IBM Plex Mono", monospace; font-size:1.1rem; color:var(--ink-faint);
    opacity:.6; pointer-events:none;
  }
  .hero-visual::before{ top:-1.3rem; left:-1.3rem; }
  .hero-visual::after{ bottom:-1.3rem; right:-1.3rem; }
  @media (max-width:920px){ .hero-visual::before, .hero-visual::after{ display:none; } }
  .spin-badge-wrap{
    position:absolute; top:-1.6rem; right:-1rem; width:108px; height:108px; z-index:2;
    display:flex; align-items:center; justify-content:center; color:var(--ink);
    transform-style:preserve-3d; perspective:400px;
  }
  .spin-badge{ animation:spin 16s linear infinite; color:inherit; }
  @keyframes spin{ to{ transform:rotate(360deg); } }
  /* A slow-orbiting 3D ring behind the badge -- purely decorative, adds depth
     to the hero without touching layout or copy. Tilted on X so the spin
     reads as an ellipse orbiting in space rather than a flat 2D circle. */
  .spin-badge-wrap::before{
    content:""; position:absolute; inset:-14px; border-radius:50%;
    border:1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
    transform:rotateX(62deg) rotateZ(0deg); animation:orbitRing 10s linear infinite;
  }
  @keyframes orbitRing{ to{ transform:rotateX(62deg) rotateZ(360deg); } }
  .spin-badge-wrap::after{
    content:"→"; position:absolute; width:34px; height:34px; border-radius:50%; background:var(--accent); color:var(--on-accent);
    display:flex; align-items:center; justify-content:center; font-size:.95rem; transform:rotate(45deg) translateZ(18px);
  }
  @media (max-width:920px){ .spin-badge-wrap{ display:none; } }
  @media (max-width:600px){
    .hero-visual{ max-width:280px; margin-inline:auto; }
    .section{ padding-block:clamp(2.5rem,9vw,4rem); }
    .card{ padding:1.5rem; }
    .value{ padding:1.25rem; }
  }

  .browser-card{
    background:var(--surface); border:1px solid var(--line); border-radius:16px; overflow:hidden;
    box-shadow:0 30px 60px -30px var(--shadow); aspect-ratio:4/3.1;
  }
  .browser-card__bar{ display:flex; align-items:center; gap:.4rem; padding:.85rem 1rem; border-bottom:1px solid var(--line); }
  .browser-card__bar span{ width:9px; height:9px; border-radius:50%; background:var(--line); }
  .browser-card__url{ margin-left:.6rem; flex:1; height:22px; border-radius:6px; background:var(--bg-alt); }
  .browser-card__body{ padding:1.4rem; display:flex; flex-direction:column; gap:.8rem; height:calc(100% - 54px); }
  .browser-card__hero{ height:34%; border-radius:10px; background:var(--accent); position:relative; overflow:hidden; }
  .browser-card__hero::after{ content:""; position:absolute; inset:0; background:repeating-linear-gradient(115deg, rgba(255,255,255,.14) 0 2px, transparent 2px 26px); }
  .browser-card__lines{ display:flex; flex-direction:column; gap:.55rem; flex:1; }
  .browser-card__lines span{ display:block; height:9px; border-radius:5px; background:var(--bg-alt); }
  .browser-card__lines span:nth-child(1){ width:78%; }
  .browser-card__lines span:nth-child(2){ width:92%; }
  .browser-card__lines span:nth-child(3){ width:60%; }
  .browser-card__grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:.6rem; }
  .browser-card__grid span{ display:block; aspect-ratio:1; border-radius:8px; background:var(--bg-alt); border:1px solid var(--line); }

  /* ---------- Stats ---------- */
  .stats{ display:grid; grid-template-columns:repeat(3,1fr); gap:2rem; }
  @media (max-width:720px){ .stats{ grid-template-columns:1fr; gap:2.25rem; } }
  .stat{ display:flex; flex-direction:column; gap:.35rem; }
  .stat .num{ font-family:"IBM Plex Mono", monospace; font-size:clamp(2rem,4vw,2.6rem); font-weight:600; color:var(--ink); font-variant-numeric:tabular-nums; }
  .stat .lbl{ font-size:.85rem; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-soft); }
  .stats .stat.reveal:nth-child(1){ transition-delay:0s; }
  .stats .stat.reveal:nth-child(2){ transition-delay:.1s; }
  .stats .stat.reveal:nth-child(3){ transition-delay:.2s; }

  /* ---------- Cards grid ---------- */
  .grid-3{ display:grid; grid-template-columns:repeat(3,1fr); gap:1.75rem; }
  @media (max-width:920px){ .grid-3{ grid-template-columns:1fr; } }
  .grid-3 .card.reveal:nth-child(1){ transition-delay:0s; }
  .grid-3 .card.reveal:nth-child(2){ transition-delay:.1s; }
  .grid-3 .card.reveal:nth-child(3){ transition-delay:.2s; }
  .card{ background:var(--surface); border:1px solid var(--line); border-radius:14px; padding:2rem; display:flex; flex-direction:column; gap:.9rem; }
  .card__mark{ width:2.6rem; height:2.6rem; border-radius:9px; background:var(--bg-alt); border:1px solid var(--line); position:relative; flex-shrink:0; }
  .card__mark::after{ content:""; position:absolute; inset:9px; border-radius:4px; background:var(--accent); }
  .card{ transition:transform .2s ease, box-shadow .2s ease, border-color .2s ease; }
  .card:hover{ transform:translateY(-3px); box-shadow:0 20px 40px -24px var(--shadow); border-color:color-mix(in srgb, var(--accent) 45%, var(--line)); }
  .card:hover .card__mark{ border-color:var(--accent); }

  /* ---------- Bold statement band ---------- */
  .section--bold{ background:var(--dark); color:var(--dark-ink); text-align:center; padding-block:clamp(2.75rem,5.5vw,4rem); }
  .section--bold h2{ color:var(--dark-ink); font-size:clamp(1.7rem,4vw,2.9rem); font-weight:600; max-width:20ch; margin-inline:auto; }
  .card h3{ font-size:1.18rem; font-weight:600; }
  .card p{ color:var(--ink-soft); font-size:.98rem; }

  /* ---------- CTA banner ---------- */
  .cta-banner{ position:relative; text-align:center; display:flex; flex-direction:column; align-items:center; gap:1.4rem; isolation:isolate; }
  .cta-banner::before{
    content:""; position:absolute; top:-60px; left:50%; width:380px; height:380px;
    background:radial-gradient(circle, color-mix(in srgb, var(--accent) 20%, transparent), transparent 70%);
    filter:blur(6px); pointer-events:none; z-index:-1;
    animation:ctaGlowPulse 6s ease-in-out infinite;
  }
  @keyframes ctaGlowPulse{
    0%,100%{ transform:translateX(-50%) scale(1); opacity:.75; }
    50%{ transform:translateX(-50%) scale(1.15); opacity:1; }
  }
  .cta-rule{ width:56px; height:2px; border-radius:2px; background:linear-gradient(90deg, transparent, var(--accent), transparent); }
  .cta-banner h2{ font-size:clamp(1.9rem,3.6vw,2.6rem); font-style:italic; font-weight:500; max-width:24ch; }

  /* ---------- Services (detail) ---------- */
  .service-row{ display:grid; grid-template-columns:.85fr 1.15fr; gap:clamp(2rem,5vw,4rem); align-items:start; padding-block:clamp(2.5rem,5vw,3.5rem); border-top:1px solid var(--line); }
  .service-row:first-of-type{ border-top:none; padding-top:0; }
  @media (max-width:860px){ .service-row{ grid-template-columns:1fr; } }
  .service-visual{ aspect-ratio:1.1; border-radius:14px; background:var(--bg-alt); border:1px solid var(--line); display:flex; align-items:center; justify-content:center; position:relative; overflow:hidden; }
  .service-visual .glyph{ width:52%; height:52%; position:relative; }
  .glyph-design span{ position:absolute; border-radius:6px; background:var(--accent); }
  .glyph-design span:nth-child(1){ width:60%; height:60%; top:0; left:0; opacity:.9; }
  .glyph-design span:nth-child(2){ width:42%; height:42%; bottom:0; right:0; background:var(--ink); opacity:.15; }
  .glyph-dev{ font-family:"IBM Plex Mono", monospace; font-size:2.4rem; font-weight:600; color:var(--accent); display:flex; align-items:center; justify-content:center; }
  .glyph-maint{ position:relative; }
  .glyph-maint::before,.glyph-maint::after{ content:""; position:absolute; border-radius:50%; border:5px solid var(--accent); }
  .glyph-maint::before{ inset:0; }
  .glyph-maint::after{ inset:22%; border-color:var(--ink); opacity:.18; }
  .service-copy h3{ font-size:1.55rem; font-weight:600; margin-bottom:.9rem; }
  .service-copy > p{ color:var(--ink-soft); margin-bottom:1.3rem; max-width:56ch; }
  .includes{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.65rem; }
  .includes li{ display:flex; gap:.7rem; font-size:.96rem; color:var(--ink); }
  .includes li::before{ content:"✓"; color:var(--success); flex-shrink:0; font-weight:700; }

  /* ---------- Process timeline ---------- */
  .timeline{ position:relative; display:flex; flex-direction:column; padding-left:2.5rem; }
  .timeline::before{ content:""; position:absolute; left:9px; top:10px; bottom:10px; width:2px; background:var(--line); transform-origin:top; }
  /* The connecting line draws itself in top-to-bottom once the whole
     timeline scrolls into view -- .timeline itself carries .reveal (added in
     site.js) purely so it shares the existing IntersectionObserver rather
     than needing a second one just for this. */
  .timeline.is-visible::before{ animation:timelineDraw 1.1s cubic-bezier(.16,.8,.24,1) .15s backwards; }
  @keyframes timelineDraw{ from{ transform:scaleY(0); } to{ transform:scaleY(1); } }
  .timeline-item{ position:relative; padding-block:1.5rem; }
  .timeline-item.reveal:nth-child(1){ transition-delay:0s; }
  .timeline-item.reveal:nth-child(2){ transition-delay:.1s; }
  .timeline-item.reveal:nth-child(3){ transition-delay:.2s; }
  .timeline-item.reveal:nth-child(4){ transition-delay:.3s; }
  .timeline-item.reveal:nth-child(5){ transition-delay:.4s; }
  .timeline-item::before{
    content:""; position:absolute; left:-2.5rem; top:1.75rem; width:20px; height:20px; border-radius:50%;
    background:var(--surface); border:3px solid var(--accent); z-index:1;
  }
  .timeline-item .step-no{ font-family:"IBM Plex Mono", monospace; color:var(--accent); font-size:.78rem; font-weight:600; letter-spacing:.08em; text-transform:uppercase; display:block; margin-bottom:.3rem; }
  .timeline-item h3{ font-size:1.15rem; font-weight:600; margin-bottom:.35rem; }
  .timeline-item p{ color:var(--ink-soft); font-size:.96rem; max-width:56ch; }

  /* ---------- About / values ---------- */
  .story{ display:flex; flex-direction:column; gap:1.1rem; max-width:66ch; font-size:1.06rem; color:var(--ink-soft); }
  .story p:first-child{ color:var(--ink); font-size:1.2rem; font-family:"Newsreader",serif; font-style:italic; font-weight:500; }
  .grid-values{ display:grid; grid-template-columns:1fr 1fr; gap:1.5rem; margin-top:2.75rem; }
  @media (max-width:720px){ .grid-values{ grid-template-columns:1fr; } }
  .grid-values .value.reveal:nth-child(1){ transition-delay:0s; }
  .grid-values .value.reveal:nth-child(2){ transition-delay:.1s; }
  .grid-values .value.reveal:nth-child(3){ transition-delay:.2s; }
  .grid-values .value.reveal:nth-child(4){ transition-delay:.3s; }
  .value{ padding:1.6rem; border:1px solid var(--line); border-radius:14px; background:var(--surface); }
  .value h3{ font-size:1.05rem; font-weight:600; margin-bottom:.5rem; }
  .value p{ color:var(--ink-soft); font-size:.95rem; }

  /* ---------- Portfolio ---------- */
  .grid-portfolio{ display:grid; grid-template-columns:1fr 1fr; gap:1.75rem; }
  @media (max-width:820px){ .grid-portfolio{ grid-template-columns:1fr; } }
  .grid-portfolio .p-card.reveal:nth-child(1),
  #portfolio-grid .p-card.reveal:nth-child(1){ transition-delay:0s; }
  .grid-portfolio .p-card.reveal:nth-child(2),
  #portfolio-grid .p-card.reveal:nth-child(2){ transition-delay:.1s; }
  #portfolio-grid .p-card.reveal:nth-child(3){ transition-delay:.2s; }
  #portfolio-grid .p-card.reveal:nth-child(4){ transition-delay:.3s; }
  #portfolio-grid .p-card.reveal:nth-child(5){ transition-delay:.4s; }
  #portfolio-grid .p-card.reveal:nth-child(6){ transition-delay:.5s; }
  .p-card{
    border:1px solid var(--line); border-radius:16px; overflow:hidden; background:var(--surface); position:relative;
    transition:transform .2s ease, box-shadow .2s ease, border-color .2s ease;
  }
  .p-card:hover{ transform:translateY(-4px); box-shadow:0 28px 50px -28px var(--shadow); border-color:color-mix(in srgb, var(--accent) 45%, var(--line)); }
  .p-thumb{ aspect-ratio:5/4; position:relative; overflow:hidden; background:var(--ink); }
  .p-thumb img{ width:100%; height:100%; object-fit:cover; transition:transform .5s ease; }
  .p-card:hover .p-thumb img{ transform:scale(1.04); }
  .p-thumb .p-thumb__fallback{
    position:absolute; inset:0; display:flex; align-items:center; justify-content:center; text-align:center;
    padding:1.5rem 2rem; font-family:"Newsreader",serif; font-style:italic; font-size:1.05rem; font-weight:500; color:rgba(255,255,255,.55);
    letter-spacing:.02em;
  }
  .p-overlay{
    position:absolute; inset:auto 0 0 0; padding:1.5rem 1.5rem 1.3rem;
    background:linear-gradient(0deg, rgba(10,10,10,.88) 0%, rgba(10,10,10,.35) 60%, transparent 100%);
    display:flex; flex-direction:column; gap:.3rem;
  }
  .p-overlay .eyebrow{ color:#FDBA74; font-size:.7rem; }
  .p-overlay .eyebrow::before{ background:#FDBA74; }
  .p-overlay h3{ color:#fff; font-size:1.25rem; font-weight:600; }
  .p-body{ padding:1.1rem 1.6rem 1.6rem; display:flex; flex-direction:column; gap:.5rem; }
  .p-body p{ color:var(--ink-soft); font-size:.95rem; }

  /* ---------- Contact ---------- */
  .contact-grid{ display:grid; grid-template-columns:.9fr 1.1fr; gap:clamp(2rem,5vw,4rem); align-items:start; }
  @media (max-width:900px){ .contact-grid{ grid-template-columns:1fr; } }
  .contact-card{ border:1px solid var(--line); border-radius:14px; background:var(--surface); padding:1.9rem; display:flex; flex-direction:column; gap:1.35rem; }
  .contact-line{ display:flex; align-items:flex-start; gap:.9rem; text-decoration:none; color:var(--ink); }
  .contact-line .k{ display:block; font-size:.72rem; text-transform:uppercase; letter-spacing:.08em; color:var(--ink-faint); margin-bottom:.15rem; }
  .contact-line .v{ font-weight:600; font-size:1.02rem; }
  .contact-line:hover .v{ color:var(--accent); }
  .icon-dot{ width:8px; height:8px; border-radius:50%; background:var(--accent); margin-top:.5rem; flex-shrink:0; }
  .field{ display:flex; flex-direction:column; gap:.4rem; margin-bottom:1.1rem; }
  .field label{ font-size:.82rem; font-weight:600; color:var(--ink-soft); }
  .field input, .field textarea, .field select{
    font:inherit; padding:.8rem .95rem; border:1px solid var(--line); border-radius:10px; background:var(--surface);
    color:var(--ink); resize:vertical;
  }
  .field select{
    appearance:none; cursor:pointer; padding-right:2.3rem;
    background-image:linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%), linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
    background-position:calc(100% - 20px) center, calc(100% - 15px) center;
    background-size:5px 5px, 5px 5px; background-repeat:no-repeat;
  }
  .field select:invalid{ color:var(--ink-faint); }
  .field input:focus, .field textarea:focus, .field select:focus{ border-color:var(--accent); }
  .field-row{ display:grid; grid-template-columns:1fr 1fr; gap:0 1rem; }
  @media (max-width:560px){ .field-row{ grid-template-columns:1fr; } }
  .form-note{ font-size:.82rem; color:var(--ink-faint); margin-top:.6rem; }
  .form-error{ font-size:.85rem; color:var(--danger); margin-top:.8rem; }
  .form-error a{ color:inherit; text-decoration:underline; }
  .contact-form-wrap{ position:relative; }
  #contact-form[hidden], .form-success[hidden]{ display:none; }

  /* ---------- Contact form success (checkmark draws itself in) ---------- */
  .form-success{
    display:flex; flex-direction:column; align-items:center; gap:1.1rem; text-align:center;
    padding:2.5rem 1.5rem; border:1px solid var(--line); border-radius:14px; background:var(--surface);
  }
  .success-check__circle{
    stroke:var(--success); stroke-width:2; fill:none;
    stroke-dasharray:151; stroke-dashoffset:151; animation:successCircle .5s ease-out forwards;
  }
  .success-check__mark{
    stroke:var(--success); stroke-width:3; stroke-linecap:round; stroke-linejoin:round; fill:none;
    stroke-dasharray:30; stroke-dashoffset:30; animation:successMark .35s ease-out .45s forwards;
  }
  .form-success p{
    font-weight:600; color:var(--ink); opacity:0; animation:successTextIn .4s ease-out .7s forwards;
  }
  @keyframes successCircle{ to{ stroke-dashoffset:0; } }
  @keyframes successMark{ to{ stroke-dashoffset:0; } }
  @keyframes successTextIn{ from{ opacity:0; transform:translateY(6px); } to{ opacity:1; transform:translateY(0); } }

  /* ---------- Footer ---------- */
  #site-footer{ background:var(--dark); color:var(--dark-ink); padding-block:3.5rem 1.75rem; position:relative; }
  #site-footer::before{
    content:""; position:absolute; top:0; left:0; right:0; height:2px;
    background:linear-gradient(90deg, transparent, var(--accent), transparent);
  }
  .footer-top{ display:grid; grid-template-columns:1.3fr 1fr 1fr; gap:2.5rem; padding-bottom:2.5rem; border-bottom:1px solid var(--dark-line); }
  @media (max-width:760px){ .footer-top{ grid-template-columns:1fr; gap:2rem; } }
  #site-footer .logo{ color:var(--dark-ink); }
  .footer-tagline{ margin-top:.9rem; color:var(--dark-ink-soft); max-width:32ch; font-size:.95rem; }
  .footer-col h4{ font-family:"IBM Plex Mono", monospace; font-size:.75rem; text-transform:uppercase; letter-spacing:.1em; color:var(--dark-ink-soft); margin-bottom:1rem; }
  .footer-col ul{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.7rem; }
  .footer-col a{ text-decoration:none; color:var(--dark-ink); font-size:.95rem; }
  .footer-col a:hover{ color:var(--accent); }
  .footer-bottom{ display:flex; justify-content:space-between; flex-wrap:wrap; gap:.75rem; padding-top:1.5rem; font-size:.82rem; color:var(--dark-ink-soft); }
  .footer-legal-links a{ color:var(--dark-ink-soft); text-decoration:underline; text-underline-offset:2px; }
  .footer-legal-links a:hover{ color:var(--accent); }
  .field-consent{ font-size:.85rem; }
  .field-consent a{ color:var(--accent); }

  /* ---------- Admin / editor ---------- */
  /* Base display/positioning for every modal overlay lives here; the actual
     show/hide + materialize-in animation is defined further below in the
     "Admin modals: materialize instead of popping" section, which overrides
     display to a constant `flex` and drives visibility via opacity instead. */
  #cookie-banner{ position:fixed; left:1rem; right:1rem; bottom:1rem; z-index:900; background:var(--dark); color:var(--dark-ink); border-radius:14px; padding:1.25rem 1.5rem; display:none; align-items:center; gap:1.25rem; flex-wrap:wrap; box-shadow:0 20px 45px -15px rgba(0,0,0,.5); max-width:860px; margin-inline:auto; border:1px solid var(--dark-line); }
  #cookie-banner.show{ display:flex; }
  #cookie-banner .cookie-banner__body{ flex:1 1 420px; }
  #cookie-banner h3{ margin:0 0 .4rem; font-size:.95rem; font-family:"IBM Plex Mono", monospace; text-transform:uppercase; letter-spacing:.06em; color:var(--dark-ink); }
  #cookie-banner p{ margin:0; font-size:.83rem; line-height:1.55; color:var(--dark-ink-soft); }
  #cookie-banner a{ color:var(--dark-ink); text-decoration:underline; text-underline-offset:2px; }
  #cookie-banner a:hover{ color:var(--accent); }
  #cookie-banner .cookie-banner__actions{ display:flex; gap:.6rem; flex-wrap:wrap; flex:0 0 auto; }
  @media (max-width:640px){ #cookie-banner .cookie-banner__actions{ width:100%; } #cookie-banner .cookie-banner__actions .btn{ flex:1 1 0; } }
  /* .btn--outline defaults to dark text/border for a light background --
     invisible on this banner's dark background, same reason .section--dark
     needs its own override elsewhere in this file. Mirror that override here. */
  #cookie-banner .btn--outline{
    border-color:var(--dark-line); color:var(--dark-ink);
    background:linear-gradient(var(--dark-ink), var(--dark-ink)) no-repeat right / 0% 100%;
  }
  #cookie-banner .btn--outline:hover{ background-size:100% 100%; background-position:left; color:var(--dark); border-color:var(--dark-ink); }
  .pw-box{ background:var(--surface); color:var(--ink); border-radius:14px; padding:2rem; width:100%; max-width:340px; box-shadow:0 30px 60px -20px rgba(0,0,0,.4); margin-block:auto; }
  .pw-box--wide{ max-width:440px; }
  .pw-box h3{ font-size:1.1rem; margin-bottom:.3rem; }
  .pw-box p{ font-size:.85rem; color:var(--ink-soft); margin-bottom:1.1rem; }
  .pw-box input{ width:100%; padding:.75rem .9rem; border:1px solid var(--line); border-radius:9px; font:inherit; margin-bottom:.8rem; }
  .pw-box .pw-error{ color:var(--danger); font-size:.82rem; min-height:1.2em; margin-bottom:.5rem; }
  .pw-box .pw-success{ color:var(--success); font-size:.82rem; min-height:1.2em; margin-bottom:.5rem; }
  .pw-box .pw-actions{ display:flex; gap:.6rem; justify-content:flex-end; }
  .pw-box .pw-hint{ font-size:.78rem; color:var(--ink-faint); margin-top:.9rem; }

  /* ---------- Demo en vivo (casos de éxito) ---------- */
  .p-demo-badge{ position:absolute; top:.7rem; left:.7rem; z-index:4; background:rgba(20,24,29,.82); color:#fff; border-radius:999px; padding:.35rem .8rem; font-size:.72rem; font-weight:600; letter-spacing:.02em; display:inline-flex; align-items:center; gap:.35rem; }
  .p-demo-badge::before{ content:""; width:6px; height:6px; border-radius:50%; background:#34d399; box-shadow:0 0 0 2px rgba(52,211,153,.35); }
  .p-demo-cta{ align-self:flex-start; margin-top:.2rem; }
  /* Deliberately bigger than a typical modal and a darker backdrop (see
     #demo-modal.open below) -- the goal is for a live demo to feel like a
     product you step into and explore, not a small popup preview. */
  #demo-modal .pw-box{ padding:0; max-width:min(1320px,96vw); width:100%; overflow:hidden; background:#1b1e23; box-shadow:0 40px 90px -20px rgba(0,0,0,.55); }
  #demo-modal.open{ background:rgba(8,9,11,.82); }
  .demo-chrome{ display:flex; align-items:center; gap:.5rem; padding:.7rem .9rem; background:#e7e7ea; }
  .demo-chrome .dot{ width:11px; height:11px; border-radius:50%; display:inline-block; flex-shrink:0; }
  .demo-chrome .dot--r{ background:#ff5f57; } .demo-chrome .dot--y{ background:#febc2e; } .demo-chrome .dot--g{ background:#28c840; }
  .demo-chrome .demo-url{ margin-left:.5rem; background:#fff; border-radius:999px; padding:.32rem 1rem; font-size:.78rem; color:#555; flex:1; max-width:340px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:var(--font-mono, monospace); }
  .demo-chrome .demo-modal-close{ margin-left:auto; border:none; background:none; font-size:1.4rem; line-height:1; cursor:pointer; color:#555; padding:.1rem .5rem; flex-shrink:0; }
  .demo-frame-wrap{ position:relative; width:100%; height:min(88vh,860px); background:#fff; }
  .demo-frame-wrap iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; }
  @media (max-width:720px){ .demo-frame-wrap{ height:78vh; } .demo-chrome .demo-url{ max-width:140px; } }

  .color-field{ display:flex; align-items:center; justify-content:space-between; gap:1rem; margin-bottom:.85rem; }
  .color-field label{ font-size:.88rem; color:var(--ink); }
  .color-field input[type="color"]{
    width:42px; height:32px; padding:0; border:1px solid var(--line); border-radius:7px; background:none; cursor:pointer;
  }

  body.is-admin [data-field]{ outline:1px dashed transparent; border-radius:3px; cursor:text; transition:outline-color .1s; }
  body.is-admin [data-field]:hover{ outline-color:var(--accent); }
  body.is-admin [data-field]:focus{ outline:1px solid var(--accent); background:color-mix(in srgb, var(--accent) 8%, transparent); }
  .edit-image-btn{ display:none; }
  body.is-admin .p-thumb{ position:relative; }
  body.is-admin .edit-image-btn{
    display:inline-flex; position:absolute; bottom:.7rem; right:.7rem; z-index:5; background:rgba(20,24,29,.82);
    color:#fff; border:none; border-radius:999px; padding:.45rem .9rem; font-size:.78rem; font-weight:600; cursor:pointer; gap:.4rem; align-items:center;
  }
  body.is-admin .p-card__remove{
    display:inline-flex; position:absolute; top:.7rem; right:.7rem; z-index:5; background:rgba(20,24,29,.82); color:#fff;
    border:none; border-radius:50%; width:28px; height:28px; align-items:center; justify-content:center; cursor:pointer; font-size:.95rem;
  }
  .p-card__remove{ display:none; }
  /* Sits just left of the remove (×) button so both stay together in the
     card's top-right corner instead of one floating alone elsewhere. */
  .p-card__move-up{ display:none; }
  body.is-admin .p-card__move-up{
    display:inline-flex; position:absolute; top:.7rem; right:2.9rem; z-index:5; background:rgba(20,24,29,.82); color:#fff;
    border:none; border-radius:50%; width:28px; height:28px; align-items:center; justify-content:center; cursor:pointer; font-size:.95rem;
  }
  .p-card__move-up:hover{ background:var(--accent); }
  /* "Demo en vivo asociada" -- lets an admin attach one of the pre-built
     interactive demos (see DEMO_META in site.js) to any project, admin-only
     and otherwise invisible so it never affects what a visitor sees. */
  .demo-key-field{ display:none; }
  body.is-admin .demo-key-field{
    display:flex; align-items:center; gap:.5rem; margin-top:.8rem; padding-top:.8rem;
    border-top:1px dashed var(--line); font-size:.72rem; text-transform:uppercase; letter-spacing:.04em; color:var(--ink-soft);
  }
  body.is-admin .demo-key-select{
    font:inherit; text-transform:none; letter-spacing:normal; padding:.3rem .5rem; border-radius:7px;
    border:1px solid var(--line); background:var(--bg); color:var(--ink); cursor:pointer; max-width:100%;
  }
  .admin-add-card{
    display:none; border:1px dashed var(--line); border-radius:14px; align-items:center; justify-content:center;
    min-height:180px; background:transparent; color:var(--ink-soft); cursor:pointer; font-size:.95rem; font-weight:600;
  }
  body.is-admin .admin-add-card{ display:flex; }

  #admin-bar{
    position:fixed; left:0; right:0; bottom:0; z-index:900; background:var(--dark); color:var(--dark-ink);
    display:none; align-items:center; justify-content:space-between; gap:1rem; padding:.9rem clamp(1rem,4vw,2rem);
    border-top:1px solid var(--dark-line); box-shadow:0 -10px 30px -15px rgba(0,0,0,.4); flex-wrap:wrap;
  }
  body.is-admin #admin-bar{ display:flex; }
  #admin-bar .admin-bar__label{ font-size:.85rem; display:flex; align-items:center; gap:.5rem; }
  #admin-bar .admin-bar__label .pulse{ width:8px; height:8px; border-radius:50%; background:var(--accent); }
  #admin-bar .admin-bar__actions{ display:flex; gap:.6rem; flex-wrap:wrap; }
  /* Same fix as #cookie-banner .btn--outline above: .btn--outline defaults
     to dark text/border for a light background, which is invisible on this
     bar's dark background. */
  #admin-bar .btn--outline{
    border-color:var(--dark-line); color:var(--dark-ink);
    background:linear-gradient(var(--dark-ink), var(--dark-ink)) no-repeat right / 0% 100%;
  }
  #admin-bar .btn--outline:hover{ background-size:100% 100%; background-position:left; color:var(--dark); border-color:var(--dark-ink); }
  body.is-admin{ padding-bottom:64px; }

  #toast{
    position:fixed; left:50%; bottom:5.5rem; transform:translate(-50%,10px); background:var(--ink); color:var(--bg);
    padding:.8rem 1.2rem; border-radius:10px; font-size:.88rem; opacity:0; pointer-events:none; transition:opacity .2s, transform .2s;
    z-index:1100; max-width:min(90vw,420px); text-align:center;
  }
  #toast.show{ opacity:1; transform:translate(-50%,0); }

  /* ---------- Page loader (shown on open/refresh) ---------- */
  #page-loader{
    position:fixed; inset:0; z-index:3000; background:var(--bg);
    display:flex; align-items:center; justify-content:center;
    transition:opacity .32s ease, visibility .32s ease;
  }
  #page-loader.is-hidden{ opacity:0; visibility:hidden; pointer-events:none; }
  #page-loader .loader-content{ display:flex; flex-direction:column; align-items:center; gap:1.3rem; }
  #page-loader .loader-mark{
    font-family:"Newsreader", Georgia, serif; font-style:italic; font-size:clamp(2.4rem,7vw,3.4rem); font-weight:500; color:var(--ink);
    display:inline-flex; align-items:baseline; letter-spacing:.01em;
    animation:loaderMarkIn .6s cubic-bezier(.16,.8,.24,1) both;
  }
  #page-loader .loader-tag{
    font-family:"IBM Plex Mono", monospace; font-size:.72rem; font-weight:500; letter-spacing:.14em; text-transform:uppercase;
    color:var(--ink-faint); animation:loaderMarkIn .6s cubic-bezier(.16,.8,.24,1) .12s both;
  }
  #page-loader .loader-mark .logo-dot{
    font-style:normal; color:var(--accent); animation:loaderPulse .9s ease-in-out infinite;
  }
  #page-loader .loader-bar{ width:120px; height:2px; background:var(--line); overflow:hidden; position:relative; }
  #page-loader .loader-bar span{
    position:absolute; top:0; left:0; height:100%; width:36%; background:var(--accent);
    animation:loaderSweep 1.15s cubic-bezier(.45,0,.2,1) infinite;
  }
  @keyframes loaderPulse{ 0%,100%{ opacity:1; transform:scale(1); } 50%{ opacity:.3; transform:scale(1.4); } }
  @keyframes loaderMarkIn{ from{ opacity:0; transform:translateY(10px); } to{ opacity:1; transform:none; } }
  @keyframes loaderSweep{
    0%{ left:-36%; }
    60%{ left:100%; }
    100%{ left:100%; }
  }

  /* ---------- Scroll progress bar ---------- */
  #scroll-progress{
    position:fixed; top:0; left:0; height:3px; width:0%; background:linear-gradient(90deg, var(--accent), var(--accent-dim));
    z-index:2000; transition:width .12s linear; pointer-events:none;
  }

  /* ---------- Route fade (single-page hash router only; kept for stylesheet parity) ---------- */
  #app{ transition:opacity .2s cubic-bezier(.4,0,.2,1), transform .2s cubic-bezier(.4,0,.2,1); }
  #app.route-fade-out{ opacity:0; transform:translateY(8px); }

  /* ---------- Interactive 3D layer (tilt / magnetic buttons) ----------
     Purely additive: site.js drives these with inline `transform` on
     pointermove (see initTiltEffects / initMagneticButtons), reusing each
     element's own resting transition for the settle-back on pointerleave.
     .js-tilting swaps in a fast, linear transition ONLY while the pointer is
     actively moving over the element, so the tilt tracks the cursor almost
     1:1 (per Apple's "respond during the interaction, not just at the end")
     -- removing the class hands control back to the slower default
     transition already defined above, which is what makes the release feel
     like a soft, springy settle instead of a snap. Desktop-only: gated in JS
     by (hover:hover) and (pointer:fine), same media condition duplicated
     here so nothing sets `transform-style` on a touch device that will never
     run the JS anyway. */
  @media (hover:hover) and (pointer:fine){
    .card, .p-card, .value, .browser-card, .service-visual{ transform-style:preserve-3d; }
    .js-tilting{ transition:transform 70ms linear !important; }
  }

  /* ---------- Admin editing: richer hover/focus affordance ----------
     Slightly more tactile than a flat dashed outline -- a soft lift + glow
     that matches the rest of the site's depth language instead of looking
     like a plain form-validation outline. */
  body.is-admin [data-field]{
    outline:1px dashed transparent; border-radius:4px; cursor:text;
    transition:outline-color .15s ease, background-color .15s ease, box-shadow .15s ease;
  }
  body.is-admin [data-field]:hover{
    outline-color:color-mix(in srgb, var(--accent) 70%, transparent);
    box-shadow:0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent);
  }
  body.is-admin [data-field]:focus{
    outline:1px solid var(--accent); background:color-mix(in srgb, var(--accent) 8%, transparent);
    box-shadow:0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent);
  }

  /* ---------- Admin modals: materialize instead of popping ----------
     Same "no display:none <-> transition" trick already used for
     #page-loader above: keep `display:flex` at all times once open is
     possible and drive visibility purely through opacity/visibility/scale,
     so the browser can actually animate the change. Modals stay centered
     (transform-origin:center) since they aren't anchored to a trigger. */
  .modal-overlay{
    position:fixed; inset:0; background:rgba(10,12,14,.6); z-index:1000; padding:1.5rem; overflow-y:auto;
    display:flex; align-items:center; justify-content:center;
    opacity:0; visibility:hidden; pointer-events:none;
    transition:opacity .22s ease, visibility 0s linear .22s, background-color .22s ease;
  }
  .modal-overlay.open{
    opacity:1; visibility:visible; pointer-events:auto; transition:opacity .22s ease, visibility 0s linear;
  }
  .modal-overlay .pw-box{
    transform:scale(.95) translateY(6px); opacity:0;
    transition:transform .28s cubic-bezier(.16,.8,.24,1), opacity .22s ease;
    transform-origin:center;
  }
  .modal-overlay.open .pw-box{ transform:scale(1) translateY(0); opacity:1; }
  #demo-modal .pw-box{ transform:scale(.97) translateY(10px); }
  #demo-modal.open .pw-box{ transform:scale(1) translateY(0); }

  /* ---------- Admin bar: unsaved-changes state ----------
     The pulse dot switches from accent (idle) to a warmer, slightly larger
     pulse when there are changes not yet written to Firestore -- a glanceable
     "this isn't live yet" signal instead of only the button label changing. */
  #admin-bar .admin-bar__label .pulse{ transition:background-color .2s ease, transform .2s ease; }
  #admin-bar.has-unsaved .admin-bar__label .pulse{
    background:var(--danger); transform:scale(1.35);
    animation:adminPulseWarn 1.1s ease-in-out infinite;
  }
  @keyframes adminPulseWarn{ 0%,100%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--danger) 45%, transparent); } 50%{ box-shadow:0 0 0 6px transparent; } }
  #admin-bar .admin-bar__status{ font-size:.78rem; color:var(--dark-ink-soft); margin-left:.35rem; }

  /* ---------- Admin: drag-and-drop image upload ---------- */
  body.is-admin .p-thumb.is-dragover{
    outline:2px dashed var(--accent); outline-offset:-2px; background:color-mix(in srgb, var(--accent) 18%, var(--ink));
  }
