/* ---------- Tokens ---------- */
  :root{
    /* Shared by every velvet surface, so the data URI exists once. */
    --velvet-grain:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='160' height='160' filter='url(%23n)' opacity='0.55'/></svg>");
    --purple-900:#2C0E63;
    --purple-700:#5B21B6;
    --purple-600:#7C3AED;
    --purple-500:#8B5CF6;
    --purple-100:#EFE9FB;
    --white:#FFFFFF;
    --ink:#15121F;
    --ink-soft:#5B5568;
    --line:#E4DEF0;

    --bg:var(--white);
    --surface:var(--purple-100);
    --text:var(--ink);
    --text-soft:var(--ink-soft);
    --border:var(--line);
    --accent:var(--purple-700);
    --accent-strong:var(--purple-600);
    --on-accent:var(--white);
    --header-bg:rgba(255,255,255,0.78);
    --spider-color:#000000;
    --bug-dark:#1C1526;
    /* Plain bugs (not butterflies) get their own outline token so it can
       turn a soft grey against dark/spider surfaces without touching the
       butterflies' brown. Light mode has no need to tell them apart. */
    --bug-dark-plain:#1C1526;

    --font-display:'Sora','Segoe UI',system-ui,-apple-system,sans-serif;
    --font-body:'Manrope','Segoe UI',system-ui,-apple-system,sans-serif;

    box-sizing:border-box;
    padding-top:env(safe-area-inset-top,0px);
    padding-bottom:env(safe-area-inset-bottom,0px);
  }

  @media (prefers-color-scheme:dark){
    :root:not([data-theme="light"]):not([data-theme="spider"]){
      --bg:#0E0B17;
      --surface:#1A1526;
      --text:#F4F1FA;
      --text-soft:#B7AFC9;
      --border:#2C2540;
      --accent:#A78BFA;
      --accent-strong:#C4B5FD;
      --on-accent:#14101F;
      --header-bg:rgba(14,11,23,0.72);
      --spider-color:#FFFFFF;
      --bug-dark:#2E1B0E;
      --bug-dark-plain:#FFFFFF;
    }
  }
  :root[data-theme="dark"]{
    --bg:#0E0B17;
    --surface:#1A1526;
    --text:#F4F1FA;
    --text-soft:#B7AFC9;
    --border:#2C2540;
    --accent:#A78BFA;
    --accent-strong:#C4B5FD;
    --on-accent:#14101F;
    --header-bg:rgba(14,11,23,0.72);
    --spider-color:#FFFFFF;
    --bug-dark:#2E1B0E;
    --bug-dark-plain:#FFFFFF;
  }

  html{ scroll-padding-top:env(safe-area-inset-top,0px); }
  @media (prefers-reduced-motion:no-preference){ html{ scroll-behavior:smooth; } }

  *,*::before,*::after{ box-sizing:inherit; }
  body{
    margin:0;
    background:var(--bg);
    color:var(--text);
    font-family:var(--font-body);
    -webkit-font-smoothing:antialiased;
    transition:background .35s ease, color .35s ease;
  }
  img,svg{ display:block; max-width:100%; }
  a{ color:inherit; }

  .container{
    width:100%;
    max-width:1160px;
    margin-inline:auto;
    padding-inline:24px;
  }

  h1,h2,h3{
    font-family:var(--font-display);
    margin:0;
    letter-spacing:-0.01em;
  }

  /* ---------- Header ---------- */
  .site-header{
    position:sticky;
    top:env(safe-area-inset-top,0px);
    z-index:50;
    background:var(--header-bg);
    backdrop-filter:blur(10px);
    -webkit-backdrop-filter:blur(10px);
    border-bottom:1px solid var(--border);
    overflow:visible;
  }
  .nav-inner{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:20px;
    height:76px;
  }
  .brand{
    display:flex;
    align-items:baseline;
    gap:6px;
    font-family:var(--font-display);
    font-weight:700;
    font-size:1.2rem;
    text-decoration:none;
    color:var(--text);
    white-space:nowrap;
  }
  .brand-sub{
    font-family:var(--font-body);
    font-weight:500;
    font-size:0.72rem;
    color:var(--text-soft);
  }
  .nav-links{
    display:flex;
    gap:28px;
    font-size:0.95rem;
    font-weight:500;
  }
  .nav-links a{
    text-decoration:none;
    color:var(--text-soft);
    position:relative;
    padding-bottom:2px;
  }
  .nav-links a::after{
    content:"";
    position:absolute; left:0; bottom:-2px;
    width:0; height:1.5px;
    background:var(--accent);
    transition:width .25s ease;
  }
  .nav-links a:hover{ color:var(--text); }
  .nav-links a:hover::after{ width:100%; }

  .nav-actions{
    display:flex;
    align-items:center;
    gap:14px;
  }

  /* Below the tablet breakpoint (.nav-links is hidden there) this button
     opens .nav-dropdown; hidden entirely on desktop where the inline links
     already do the job. */
  .nav-toggle{
    display:none;
    width:38px; height:38px;
    border-radius:999px;
    border:1px solid var(--border);
    background:transparent;
    color:var(--text);
    cursor:pointer;
    align-items:center; justify-content:center;
  }
  .nav-toggle:hover{ border-color:var(--accent); color:var(--accent); }
  .nav-dropdown{
    display:none;
    position:absolute;
    top:100%; left:0; right:0;
    flex-direction:column;
    padding:8px 24px 18px;
    background:var(--bg);
    border-bottom:1px solid var(--border);
    box-shadow:0 12px 24px rgba(0,0,0,0.18);
  }
  .nav-dropdown.open{ display:flex; }
  .nav-dropdown a{
    padding:12px 0;
    text-decoration:none;
    color:var(--text-soft);
    font-weight:500;
    border-bottom:1px solid var(--border);
  }
  .nav-dropdown a:last-child{ border-bottom:0; }
  .nav-dropdown a:hover,
  .nav-dropdown a[aria-current="page"]{ color:var(--text); }

  .theme-toggle{
    width:38px; height:38px;
    border-radius:999px;
    border:1px solid var(--border);
    background:transparent;
    color:var(--text);
    font-size:1rem;
    cursor:pointer;
    display:flex; align-items:center; justify-content:center;
    transition:border-color .2s ease, transform .2s ease;
  }
  .theme-toggle:hover{ border-color:var(--accent); transform:translateY(-1px); }

  .btn{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    padding:10px 20px;
    border-radius:8px;
    font-family:var(--font-body);
    font-weight:600;
    font-size:0.95rem;
    text-decoration:none;
    border:1px solid transparent;
    cursor:pointer;
    transition:transform .2s ease, box-shadow .2s ease, background .2s ease;
    white-space:nowrap;
  }
  .btn-primary{
    background:var(--accent);
    color:var(--on-accent);
  }
  .btn-primary:hover{
    background:var(--accent-strong);
    transform:translateY(-1px);
    box-shadow:0 8px 20px -8px var(--accent);
  }

  /* ---------- Spider rig ---------- */
  .cta-wrap{ position:relative; display:inline-flex; }
  .spider-rig{
    position:absolute;
    top:100%;                 /* the button's lower edge */
    left:50%;
    transform:translateX(-50%);
    opacity:var(--fade, 1);   /* driven by scroll position */
    pointer-events:auto;
    z-index:40;
    cursor:pointer;
  }
  .pendulum{
    transform-origin:top center;
    width:3px;
    opacity:0;
    --thread-len:104px;
  }
  .thread{
    width:2px;
    height:var(--thread-len);
    margin:0 auto;
    background:linear-gradient(to bottom, var(--border), var(--accent));
  }
  /* Spider sits below the thread in normal flow, so retracting the thread's
     height is what carries it upward — it reads as climbing the line. */
  .pendulum.climbed .thread{ height:calc(var(--thread-len) * 0.5); }
  /* Holds the rig visible once the swing-in animation's fill is released. */
  .pendulum.landed{ opacity:1; }
  .spider{
    --spider-w:58px;
    --px:calc(var(--spider-w) / 15);   /* one pixel of the 15-wide sprite grid */
    display:block;
    /* The thread parent is only 3px wide, and the global img,svg max-width:100%
       would crush the spider to that. Opt out and centre it on the thread. */
    width:var(--spider-w);
    height:auto;
    max-width:none;
    /* Pull up by the single empty row above the body so the thread ends on its
       back, passing between the raised front legs. */
    margin:calc(var(--px) * -1) 0 0 calc(50% - var(--spider-w) / 2);
    color:var(--spider-color);
    filter:drop-shadow(0 6px 10px rgba(0,0,0,0.12));
  }

  /* ---------- Speech bubble (8-bit) ---------- */
  .spider-say{
    --p:4px;                        /* one bubble pixel */
    --bubble-ink:var(--spider-color);
    position:absolute;
    right:calc(100% + var(--p) * 11); /* clear of the spider, tail reaching toward it */
    bottom:0;                         /* level with the spider */
    display:none;
    width:max-content;
    max-width:min(178px, 46vw);
    padding:10px 12px;
    background:var(--bg);
    color:var(--bubble-ink);
    font-family:"Press Start 2P","Segoe UI Emoji","Apple Color Emoji","Noto Color Emoji","Courier New",monospace;
    font-size:8px;
    line-height:1.9;
    text-transform:lowercase;
    pointer-events:none;
    /* Offset copies build a blocky frame and leave the corners notched, which
       is what reads as pixel art rather than a rounded speech balloon. */
    box-shadow:
      0 calc(var(--p) * -1) 0 0 var(--bubble-ink),
      0 var(--p) 0 0 var(--bubble-ink),
      calc(var(--p) * -1) 0 0 0 var(--bubble-ink),
      var(--p) 0 0 0 var(--bubble-ink);
  }
  /* An emoji on its own needs room to read; the pixel font has no glyph for it. */
  .spider-say.is-emoji{ font-size:22px; line-height:1.1; padding:8px 12px; }
  /* Stepped tail: columns of 5, 3 and 1 pixels marching toward the spider. */
  .spider-say::after{
    content:"";
    position:absolute;
    left:calc(100% + var(--p));   /* start beyond the frame so all 3 steps show */
    bottom:calc(var(--p) * 4);
    width:var(--p);
    height:var(--p);
    background:var(--bubble-ink);
    box-shadow:
      0 calc(var(--p) * -2) 0 0 var(--bubble-ink),
      0 calc(var(--p) * -1) 0 0 var(--bubble-ink),
      0 var(--p) 0 0 var(--bubble-ink),
      0 calc(var(--p) * 2) 0 0 var(--bubble-ink),
      var(--p) calc(var(--p) * -1) 0 0 var(--bubble-ink),
      var(--p) 0 0 0 var(--bubble-ink),
      var(--p) var(--p) 0 0 var(--bubble-ink),
      calc(var(--p) * 2) 0 0 0 var(--bubble-ink);
  }
  .spider-say.show{ display:block; }
  /* steps() keeps the pop chunky instead of smoothly easing. */
  @keyframes bubblePop{
    0%   { transform:scale(0.35); opacity:0; }
    100% { transform:scale(1); opacity:1; }
  }
  @media (prefers-reduced-motion:no-preference){
    .spider-say.show{ animation:bubblePop .16s steps(4) both; transform-origin:right bottom; }
  }

  /* ---------- Welcome box (8-bit, shown once per session) ---------- */
  /* Built the same way as the spider's speech bubble: flat fill, a frame made
     of offset box-shadows so the corners stay notched, and the pixel font. */
  .welcome{
    position:fixed;
    inset:0;
    z-index:100;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:24px 16px;
    background:rgba(8,4,16,0.62);
    -webkit-backdrop-filter:blur(3px);
    backdrop-filter:blur(3px);
  }
  .welcome[hidden]{ display:none; }
  html.welcome-open{ overflow:hidden; }
  .welcome-box{
    --p:6px;
    --ink:var(--spider-color);
    position:relative;
    width:min(560px, 100%);
    padding:36px 32px 0;
    background:var(--bg);
    color:var(--ink);
    text-align:center;
    box-shadow:
      0 calc(var(--p) * -1) 0 0 var(--ink),
      0 var(--p) 0 0 var(--ink),
      calc(var(--p) * -1) 0 0 0 var(--ink),
      var(--p) 0 0 0 var(--ink);
  }
  .welcome-text,
  .welcome-ok,
  .welcome-say{
    font-family:"Press Start 2P","Segoe UI Emoji","Apple Color Emoji","Noto Color Emoji","Courier New",monospace;
  }
  .welcome-text{
    margin:0;
    font-size:14px;
    line-height:2.1;
  }
  .welcome-ok{
    --p:4px;
    margin-top:28px;
    padding:12px 20px;
    border:0;
    background:var(--accent);
    color:var(--on-accent);
    font-size:12px;
    cursor:pointer;
    box-shadow:
      0 calc(var(--p) * -1) 0 0 var(--ink),
      0 var(--p) 0 0 var(--ink),
      calc(var(--p) * -1) 0 0 0 var(--ink),
      var(--p) 0 0 0 var(--ink);
  }
  .welcome-ok:hover{ background:var(--accent-strong); }
  .welcome-ok:focus-visible{ outline:2px dashed var(--accent); outline-offset:8px; }
  .welcome-ok:active{ transform:translateY(2px); }

  /* The strip along the bottom edge the spider walks; feet on the frame.
     Tall enough that its speech bubble fits below the button, not over it. */
  .welcome-lane{
    position:relative;
    height:96px;
    margin-top:12px;
  }
  .welcome-crawler{
    --spider-w:40px;
    position:absolute;
    bottom:0;
    left:10%;
    width:var(--spider-w);
  }
  .welcome-spider{
    display:block;
    padding:0;
    border:0;
    background:none;
    color:var(--spider-color);
    cursor:pointer;
  }
  .welcome-spider-art{
    display:block;
    width:var(--spider-w);
    height:auto;
    max-width:none;
  }
  .welcome-spider:focus-visible{ outline:2px dashed var(--accent); outline-offset:4px; }

  .welcome-say{
    --p:3px;
    position:absolute;
    bottom:calc(100% + 10px);
    display:none;
    width:max-content;
    max-width:200px;
    padding:9px 11px;
    background:var(--bg);
    color:var(--ink);
    font-size:8px;
    line-height:1.9;
    text-align:left;
    text-transform:lowercase;
    pointer-events:none;
    box-shadow:
      0 calc(var(--p) * -1) 0 0 var(--ink),
      0 var(--p) 0 0 var(--ink),
      calc(var(--p) * -1) 0 0 0 var(--ink),
      var(--p) 0 0 0 var(--ink);
  }
  .welcome-say.show{ display:block; }
  .welcome-say.say-right{ left:0; }
  .welcome-say.say-left{ right:0; }
  /* Two-step tail dropping toward the spider's head. */
  .welcome-say::after{
    content:"";
    position:absolute;
    top:calc(100% + var(--p));
    width:var(--p);
    height:var(--p);
    background:var(--ink);
    box-shadow:0 var(--p) 0 0 var(--ink);
  }
  .welcome-say.say-right::after{ left:16px; }
  .welcome-say.say-left::after{ right:16px; }

  @keyframes crawl{
    from{ left:0; }
    to  { left:calc(100% - var(--spider-w)); }
  }
  /* Legs-down/legs-up bob, stepped so it reads as a walk cycle. */
  @keyframes scuttle{
    50%{ transform:translateY(-2px); }
  }
  @media (prefers-reduced-motion:no-preference){
    .welcome:not([hidden]) .welcome-box{ animation:bubblePop .2s steps(4) both; }
    .welcome-crawler{ animation:crawl 9s steps(90) infinite alternate; }
    .welcome-spider-art{ animation:scuttle .3s steps(2) infinite; }
    .welcome-crawler.paused,
    .welcome-crawler.paused .welcome-spider-art{ animation-play-state:paused; }
    .welcome-say.show{ animation:bubblePop .16s steps(4) both; transform-origin:bottom center; }
  }
  @media (max-width:520px){
    .welcome-box{ --p:4px; padding:28px 18px 0; }
    .welcome-text{ font-size:11px; line-height:2.1; }
    .welcome-ok{ font-size:11px; margin-top:22px; }
  }

  @keyframes pendulumSwing{
    0%   { transform:rotate(-62deg); opacity:0; }
    8%   { opacity:1; }
    26%  { transform:rotate(34deg); }
    46%  { transform:rotate(-20deg); }
    64%  { transform:rotate(11deg); }
    80%  { transform:rotate(-5deg); }
    92%  { transform:rotate(2deg); }
    100% { transform:rotate(0deg); opacity:1; }
  }
  /* Startled swing. The whole rig pivots about its anchor, so the thread stays
     welded to the spider for the entire arc. */
  @keyframes spookSwing{
    0%   { transform:rotate(0deg); }
    12%  { transform:rotate(calc(var(--swing, 20) * 1deg)); }
    32%  { transform:rotate(calc(var(--swing, 20) * -0.62deg)); }
    52%  { transform:rotate(calc(var(--swing, 20) * 0.38deg)); }
    70%  { transform:rotate(calc(var(--swing, 20) * -0.21deg)); }
    86%  { transform:rotate(calc(var(--swing, 20) * 0.09deg)); }
    100% { transform:rotate(0deg); }
  }
  @media (prefers-reduced-motion:no-preference){
    .pendulum.swing-in{ animation:pendulumSwing 2.3s cubic-bezier(.36,.07,.19,.97) both; }
    .thread{ transition:height 1.15s cubic-bezier(.45,.05,.2,1); }
    .pendulum.spooked{ animation:spookSwing 1.6s cubic-bezier(.36,.07,.19,.97); }
  }
  .pendulum.settled{ opacity:1; transform:rotate(0deg); }
  .pendulum.settled .thread{ height:calc(var(--thread-len) * 0.5); }

  @media (max-width:640px){
    .pendulum{ --thread-len:76px; }
    .spider{ --spider-w:46px; }
  }

  /* Tablet and down: the inline links give way to the dropdown behind
     .nav-toggle rather than disappearing outright. */
  @media (max-width:860px){
    .nav-links{ display:none; }
    .nav-toggle{ display:flex; }
  }
  /* Header collapses further on narrow screens: CTA stays reachable */
  @media (max-width:720px){
    .nav-inner{ height:64px; gap:12px; }
    .nav-actions{ gap:10px; }
    .nav-actions .btn{ padding:9px 16px; font-size:0.88rem; }
  }
  @media (max-width:440px){
    /* A third header button (nav-toggle) joined theme-toggle and the CTA,
       which is one too many at phone width without trimming further. */
    .nav-actions{ gap:6px; }
    .nav-toggle, .theme-toggle{ width:34px; height:34px; }
    .nav-actions .btn{ padding:8px 12px; font-size:0.84rem; }
  }
  /* With "agency" showing, the header row needs 419px; below that it pushed
     the CTA off the right edge (390px phones were 5px over). */
  @media (max-width:420px){
    .brand-sub{ display:none; }
  }
  @media (max-width:380px){
    /* The header row needs ~374px at the 440px sizes, so 360px phones scrolled
       sideways. A 16px gutter plus a slight trim fits it down to 320px. */
    .container{ padding-inline:16px; }
    .nav-inner{ gap:8px; }
    .brand{ font-size:1rem; }
    .nav-actions{ gap:4px; }
    .nav-toggle, .theme-toggle{ width:32px; height:32px; }
    .nav-actions .btn{ padding:8px 10px; font-size:0.8rem; }
  }

  /* ---------- Hero web (case studies) ---------- */
  .hero-web{
    position:absolute;
    /* Drawn 1:1 (see build/gen2.js) in a fixed box whose right edge sits 150px
       right of the page centre, vertically centred like the copy. So every
       point of the web, bugs included, keeps the same offset from the text on
       any screen; narrower screens just crop more off the left. */
    top:50%;
    right:calc(50% - 150px);
    width:1700px;
    height:800px;
    translate:0 -50%;
    /* app.js shrinks it toward the hub as the hero scrolls away */
    scale:var(--web-s, 1);
    transform-origin:1100px 400px;
    color:var(--accent);
    pointer-events:none;      /* silk is decoration; only the bugs are clickable */
    z-index:1;
  }
  .hero-web svg{ width:100%; height:100%; display:block; overflow:visible; }

  /* currentColor drives the bug body, so each client sets its own --bug. */
  .web-bug{ pointer-events:auto; cursor:pointer; color:var(--bug, #F0782A); }
  .web-bug .bug-hover{
    transform-box:fill-box;
    transform-origin:center;
    transition:transform .18s cubic-bezier(.22,1,.36,1);
  }
  .web-bug:hover .bug-hover,
  .web-bug:focus-visible .bug-hover{ transform:scale(1.18); }
  .bug-label{
    font-family:var(--font-body);
    font-size:9px;
    font-weight:600;
    letter-spacing:0.02em;
    fill:var(--text);
    opacity:0;
    transition:opacity .18s ease;
  }
  .web-bug:hover .bug-label,
  .web-bug:focus-visible .bug-label{ opacity:1; }

  /* Below this there is no gutter left beside the text column, so rather than
     dropping the web it spans the full hero and sits behind the copy, faded
     right back. The bugs go: behind a headline they would be unreachable as
     links and would fight the CTA for taps. The portfolio section carries the
     case studies on small screens instead. */
  @media (max-width:1149px){
    .hero-web{
      right:auto;
      left:calc(50% - 1100px);  /* hub at the horizontal centre */
      opacity:0.5;
      pointer-events:none;
    }
    .hero-web .web-bug{ display:none; }
  }

  /* ---------- Hero ---------- */
  .hero{
    position:relative;
    overflow:hidden;
    padding:96px 0 120px;
  }
  /* Where the web and its bugs show, the hero takes the first screen (within
     limits) so the web has room; the copy sits in the vertical middle, level
     with the web's hub. Written as the exact complement of the max-width:1149px
     rule below, so a fractional width (browser zoom) can't fall between them. */
  @media not all and (max-width:1149px){
    .hero{
      min-height:clamp(640px, calc(100svh - 76px), 780px);
      display:flex;
      align-items:center;
      padding:72px 0;
    }
  }
  .hero-bg{
    position:absolute;
    top:-40px; right:-60px;
    width:520px; height:520px;
    opacity:0.35;
    pointer-events:none;
  }
  .hero-bg svg{ color:var(--accent); }

  .hero-inner{
    position:relative;
    z-index:2;
    max-width:640px;
    container-type:inline-size;   /* lets the h1 size itself to this column */
  }
  .hero h1{
    /* "Websites, ads, content;" can't wrap and runs about 11.7x the font size
       wide, so the size is capped to what the column can hold (cqw = this
       column's width). */
    font-size:min(clamp(2.3rem, 4.4vw + 1rem, 3.6rem), calc(100cqw / 12));
    font-weight:700;
    line-height:1.08;
    color:var(--text);
  }
  /* "Websites, ads, content;" is one unbroken line; the rest always starts
     on the line below it, smaller. */
  .hero-h1-top{ display:block; white-space:nowrap; }
  .hero-h1-sub{ display:block; font-size:0.72em; }
  .hero-word{
    display:inline-block;
    white-space:nowrap;
  }
  .hero-letter{
    display:inline-block;
    transform-origin:center bottom;
    transition:transform 0.2s ease-out;
    will-change:transform;
  }
  .hero-sub{
    margin-top:12px;
    font-size:1.08rem;
    line-height:1.65;
    color:var(--text-soft);
    max-width:52ch;
  }
  .hero-actions{
    margin-top:14px;
    display:flex;
    align-items:center;
    gap:18px;
  }
  .hero-caps{
    margin-top:40px;
    max-width:480px;
    overflow:hidden;
    -webkit-mask-image:linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
    mask-image:linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
  }
  .hero-caps-track{
    display:flex;
    width:max-content;
    animation:hero-caps-scroll 16s linear infinite;
  }
  .hero-caps-group{
    display:flex;
    align-items:center;
    gap:10px;
    padding-right:10px;
    font-size:0.9rem;
    white-space:nowrap;
    color:var(--border);
  }
  .hero-caps-group span:not(.dot){ color:var(--text-soft); }
  @keyframes hero-caps-scroll{
    from{ transform:translateX(0); }
    to{ transform:translateX(-50%); }
  }
  @media (prefers-reduced-motion:reduce){
    .hero-caps-track{ animation:none; }
  }

  /* ---------- Services ---------- */
  .services{
    padding:100px 0;
    background:var(--surface);
    border-top:1px solid var(--border);
    border-bottom:1px solid var(--border);
    transition:background .35s ease, border-color .35s ease;
  }
  .services-head{ max-width:560px; margin-bottom:48px; }
  .services-head h2{
    font-size:clamp(1.8rem, 2.6vw + 1rem, 2.5rem);
    font-weight:700;
  }
  .services-head p{
    margin-top:14px;
    color:var(--text-soft);
    font-size:1.05rem;
    line-height:1.6;
  }

  .service-row{
    display:grid;
    grid-template-columns:56px 1fr 240px;
    gap:28px;
    align-items:start;
    padding:32px 18px;
    border-top:1px solid var(--border);
    border-radius:10px;
    transition:background .25s ease;
  }
  .service-row:last-of-type{ border-bottom:1px solid var(--border); }
  .service-row:hover{ background:var(--bg); }

  .service-icon{ color:var(--accent); padding-top:4px; }

  .service-name{
    font-family:var(--font-display);
    font-size:1.35rem;
    font-weight:700;
    margin:0 0 10px;
  }
  .service-desc{
    color:var(--text-soft);
    line-height:1.65;
    max-width:50ch;
    margin:0;
  }
  .service-tags{
    font-size:0.84rem;
    color:var(--text-soft);
    line-height:1.9;
    text-align:right;
    padding-top:6px;
  }

  .service-teaser{
    margin-top:6px;
    padding:16px 18px;
    display:flex;
    align-items:center;
    gap:10px;
    color:var(--text-soft);
    font-size:0.9rem;
  }
  .service-teaser .dot{
    width:6px; height:6px;
    border-radius:999px;
    background:var(--text-soft);
    opacity:.55;
    flex-shrink:0;
  }

  @media (max-width:760px){
    .service-row{ grid-template-columns:1fr; gap:12px; padding:26px 4px; }
    .service-tags{ text-align:left; padding-top:0; }
  }

  /* ---------- Process ---------- */
  .process{ padding:100px 0; }
  .process-head{ max-width:560px; margin-bottom:60px; }
  .process-head h2{
    font-size:clamp(1.8rem, 2.6vw + 1rem, 2.5rem);
    font-weight:700;
  }
  .process-head p{
    margin-top:14px;
    color:var(--text-soft);
    font-size:1.05rem;
    line-height:1.6;
  }

  .process-list{ max-width:680px; }
  .process-step{
    display:grid;
    grid-template-columns:34px 1fr;
    gap:26px;
    position:relative;
    padding-bottom:52px;
  }
  .process-step:last-child{ padding-bottom:0; }

  .step-marker{ position:relative; display:flex; justify-content:center; }
  .step-marker::before{
    content:"";
    position:absolute;
    top:34px; bottom:-52px;
    width:2px;
    background:linear-gradient(to bottom, var(--border), var(--border));
  }
  .process-step:last-child .step-marker::before{ display:none; }

  .step-node{
    width:34px; height:34px;
    border-radius:999px;
    border:1.5px solid var(--accent);
    background:var(--bg);
    display:flex; align-items:center; justify-content:center;
    font-family:var(--font-display);
    font-weight:700;
    font-size:0.82rem;
    color:var(--accent);
    z-index:1;
    flex-shrink:0;
  }

  .step-title{
    font-family:var(--font-display);
    font-weight:700;
    font-size:1.3rem;
    margin:0 0 8px;
  }
  .step-desc{
    color:var(--text-soft);
    line-height:1.65;
    max-width:56ch;
    margin:0;
  }

  /* ---------- Contact ---------- */
  .contact{
    padding:100px 0;
    background:linear-gradient(135deg, var(--purple-900), var(--purple-700));
    color:#fff;
  }
  .contact-inner{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:60px;
    align-items:start;
  }
  .contact-copy h2{
    font-size:clamp(1.9rem, 3vw + 1rem, 2.6rem);
    font-weight:700;
    color:#fff;
  }
  .contact-copy p{
    margin-top:16px;
    color:rgba(255,255,255,0.78);
    line-height:1.6;
    max-width:44ch;
  }
  .contact-email{
    margin-top:28px;
    display:inline-flex;
    align-items:center;
    gap:8px;
    color:#fff;
    font-weight:600;
    text-decoration:none;
    border-bottom:1px solid rgba(255,255,255,0.4);
    padding-bottom:2px;
  }
  .contact-email:hover{ border-color:#fff; }

  .contact-form{ display:flex; flex-direction:column; gap:16px; }
  .form-row{ display:flex; flex-direction:column; gap:6px; }
  .form-row label{ font-size:0.85rem; color:rgba(255,255,255,0.75); }
  .form-row input, .form-row select, .form-row textarea{
    background:rgba(255,255,255,0.08);
    border:1px solid rgba(255,255,255,0.28);
    border-radius:8px;
    padding:12px 14px;
    color:#fff;
    font-family:var(--font-body);
    font-size:0.95rem;
    outline:none;
    transition:border-color .2s ease, background .2s ease;
  }
  .form-row input::placeholder, .form-row textarea::placeholder{ color:rgba(255,255,255,0.45); }
  .form-row input:focus, .form-row select:focus, .form-row textarea:focus{
    border-color:#fff;
    background:rgba(255,255,255,0.14);
  }
  .form-row select option{ color:#111; }
  .contact-form textarea{ resize:vertical; min-height:110px; }

  .btn-light{ align-self:flex-start; background:#fff; color:var(--purple-900); }
  .btn-light:hover{
    background:rgba(255,255,255,0.88);
    transform:translateY(-1px);
    box-shadow:0 10px 24px -10px rgba(0,0,0,0.5);
  }
  .btn-light:disabled{ opacity:0.7; cursor:default; transform:none; }

  /* Off-screen rather than display:none, which some bots know to skip. */
  .form-trap{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
  .form-status{
    margin:2px 0 0;
    font-size:0.88rem;
    min-height:1.2em;
  }
  .form-status.is-success{ color:#fff; font-weight:600; }
  .form-status.is-error{ color:#FFD9D9; }

  @media (max-width:820px){
    .contact-inner{ grid-template-columns:1fr; gap:36px; }
  }

  /* ---------- Footer ---------- */
  .site-footer{
    border-top:1px solid var(--border);
    padding:32px 0;
  }
  .footer-inner{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:16px;
    flex-wrap:wrap;
    font-size:0.85rem;
    color:var(--text-soft);
  }
  .footer-brand{
    font-family:var(--font-display);
    font-weight:700;
    color:var(--text);
  }
  .footer-nav{ display:flex; gap:22px; font-size:0.85rem; }
  .footer-nav a{ text-decoration:none; color:var(--text-soft); transition:color .2s ease; }
  .footer-nav a:hover{ color:var(--text); }

  /* ---------- Portfolio ---------- */
  .portfolio{
    padding:96px 0 72px;
    background:var(--surface);
    border-top:1px solid var(--border);
    border-bottom:1px solid var(--border);
    transition:background .35s ease, border-color .35s ease;
    overflow:hidden;
  }
  .portfolio-stage{
    max-width:1060px;
    margin-inline:auto;
    padding-inline:24px;
    color:var(--accent);
  }
  .portfolio-stage svg{ width:100%; height:auto; display:block; }
  /* Kept in CSS rather than the transform attribute so phones can size it up. */
  .bug-scale{ transform:scale(var(--bug-scale, 2.6)); }

  /* A handful of bugs are called out as more prominent: 30% larger than the
     rest, traced with a tight rim of their own colour rather than a soft
     bloom — a wide blur washed out into the body and made it harder to
     read, not easier. The descendant selector out-specifies .bug-scale so
     it wins regardless of source order, and still respects the responsive
     --bug-scale base. */
  .bug-featured .bug-scale{
    transform:scale(calc(var(--bug-scale, 2.6) * 1.3));
    /* Filter primitives run in the sprite's own tiny (~11-21 unit) drawing
       space, before the scale above blows it up — a blur that looks small
       here still gets multiplied ~3-4x on screen, so these radii are kept
       far smaller than they look. */
    filter:
      drop-shadow(0 0 0.12px var(--bug))
      drop-shadow(0 0 0.12px var(--bug))
      drop-shadow(0 0 0.3px color-mix(in srgb, var(--bug) 40%, transparent));
  }

  /* Glitter overlay for the silver, winged bug: a few pixels of shine held
     static for reduced-motion, twinkling on a stagger otherwise. */
  .sparkle{
    fill:#fff;
    opacity:0.55;
    transform-box:fill-box;
    transform-origin:center;
  }
  @media (prefers-reduced-motion:no-preference){
    .sparkle{ opacity:0; animation:bugTwinkle 2.6s ease-in-out infinite; }
    .sparkle.s2{ animation-delay:.65s; }
    .sparkle.s3{ animation-delay:1.3s; }
    .sparkle.s4{ animation-delay:1.95s; }
    @keyframes bugTwinkle{
      0%,100%{ opacity:0.15; transform:scale(0.3); }
      50%{ opacity:1; transform:scale(1.1); }
    }
  }

  /* Names for phones, where the hover labels can never appear. */
  .portfolio-list{
    display:none;
    list-style:none;
    margin:24px 0 0;
    padding:0 24px;
  }
  .portfolio-list a{
    display:flex;
    align-items:center;
    gap:10px;
    padding:11px 0;
    text-decoration:none;
    color:var(--text);
    font-weight:600;
    font-size:0.95rem;
    border-bottom:1px solid var(--border);
  }
  .portfolio-list .swatch{
    width:12px; height:12px;
    flex:none;
    background:var(--bug);
  }

  /* Scaling the whole frame down to phone width would leave the bugs a few
     pixels across, so below this the web keeps a set height and crops instead.
     All three bugs sit inside the middle band that survives the crop. */
  @media (max-width:760px){
    .portfolio-stage{ padding-inline:0; }
    .portfolio-stage svg{ height:clamp(400px, 64vh, 520px); }
    .portfolio .web-bug{ --bug-scale:3.3; }   /* bigger tap targets */
    .portfolio-list{ display:block; }
    /* A label long enough to read would run off the cropped frame, so the list
       carries the names instead. */
    .portfolio .bug-label{ display:none; }
  }
  .portfolio-head{ max-width:600px; margin-bottom:12px; }
  .portfolio-head h2{
    font-size:clamp(1.8rem, 2.6vw + 1rem, 2.5rem);
    font-weight:700;
  }
  .portfolio-head p{
    margin-top:14px;
    color:var(--text-soft);
    font-size:1.05rem;
    line-height:1.6;
  }

  /* ---------- Focus ---------- */
  a:focus-visible, button:focus-visible{
    outline:2px solid var(--accent);
    outline-offset:2px;
    border-radius:4px;
  }

  /* ---------- Inner pages ---------- */
  .page-hero{
    padding:88px 0 56px;
    border-bottom:1px solid var(--border);
  }
  /* Portfolio's second section is the web itself, not a new block of copy, so
     the hero runs straight into it: no rule on either side, the same surface,
     and less of a gap. */
  .page-hero:has(+ .portfolio){ border-bottom:none; }
  .page-hero + .portfolio{
    padding-top:8px;
    border-top:none;
    background:var(--bg);
  }
  .page-eyebrow{
    margin:0 0 14px;
    font-size:0.78rem;
    font-weight:700;
    letter-spacing:0.14em;
    text-transform:uppercase;
    color:var(--accent);
  }
  .page-hero h1{
    margin:0;
    font-size:clamp(2.1rem, 4vw + 1rem, 3.4rem);
    font-weight:800;
    line-height:1.06;
    max-width:14ch;
  }
  .page-lede{
    margin:20px 0 0;
    max-width:56ch;
    color:var(--text-soft);
    font-size:1.08rem;
    line-height:1.65;
  }

  /* Teaser cards on the home page */
  .teaser-grid{
    display:grid;
    grid-template-columns:repeat(3, 1fr);
    gap:22px;
    margin-top:12px;
  }
  .teaser-card{
    padding:26px 24px;
    border:1px solid var(--border);
    border-radius:12px;
    background:var(--bg);
    transition:border-color .2s ease, transform .2s ease;
  }
  .teaser-card:hover{ border-color:var(--accent); transform:translateY(-2px); }
  .teaser-card h3{
    margin:0 0 10px;
    font-size:1.05rem;
    font-weight:700;
  }
  .teaser-card p{
    margin:0;
    color:var(--text-soft);
    font-size:0.95rem;
    line-height:1.6;
  }

  /* ---------- Pixel scenes in the "What we do" cards ---------- */
  /* Each card keeps a stage along its bottom; the scene sits in it. */
  /* Scenes are 36x21 pixels of art at 4px each. */
  .teaser-card.has-scene{ position:relative; padding-bottom:150px; }
  .scene{
    position:absolute;
    bottom:20px;
    left:50%;
    width:144px;
    height:84px;
    margin-left:-72px;
    padding:0;
    border:0;
    background:none;
    color:var(--spider-color);
    cursor:pointer;
  }
  .scene-art{ display:block; width:144px; height:84px; overflow:visible; }
  .scene:focus-visible,
  .web-spider:focus-visible{ outline:2px dashed var(--accent); outline-offset:6px; }
  /* Transforms on these SVG groups are in grid units: 1px = one pixel of art. */
  .sc-hop, .sc-type, .sc-brush, .sc-painter, .sc-blob{ transform-box:fill-box; }

  /* Web Development: a corner web spun out from the bottom-right corner. */
  .scene-web{
    position:absolute;
    right:0;
    bottom:0;
    width:168px;
    height:140px;
    pointer-events:none;
  }
  .scene-web-silk{ position:absolute; inset:0; width:100%; height:100%; overflow:visible; }
  .silk-radials{ stroke:var(--accent); stroke-width:1; opacity:0.35; }
  .silk-spiral{ stroke:var(--accent); stroke-width:1.2; opacity:0.85; stroke-dasharray:1; stroke-dashoffset:0; }
  .web-spider{
    position:absolute;
    top:0;
    left:0;
    width:60px;                     /* 4px per pixel, like the other scenes */
    height:48px;                    /* 12 rows: 2 of bow above the 10 of body */
    padding:0;
    border:0;
    background:none;
    color:var(--spider-color);
    cursor:pointer;
    pointer-events:auto;
    offset-rotate:0deg;
    offset-anchor:50% 58%;          /* ride on her body's centre, not the bow's */
    offset-distance:83.5%;          /* at rest: the end of the finished spiral */
  }
  .scene-sprite{ display:block; width:60px; height:48px; }

  /* The spider's speech bubble, in the empty stage to the left of the web. */
  .px-say{
    --p:3px;
    --ink:var(--spider-color);
    position:absolute;
    left:24px;
    bottom:34px;
    display:none;
    max-width:calc(100% - 215px);   /* clear of the 168px web */
    min-width:130px;
    padding:9px 11px;
    background:var(--bg);
    color:var(--ink);
    font-family:"Press Start 2P","Courier New",monospace;
    font-size:8px;
    line-height:1.9;
    pointer-events:none;
    box-shadow:
      0 calc(var(--p) * -1) 0 0 var(--ink),
      0 var(--p) 0 0 var(--ink),
      calc(var(--p) * -1) 0 0 0 var(--ink),
      var(--p) 0 0 0 var(--ink);
  }
  .px-say.show{ display:block; }
  /* Two-step tail pointing right, toward the web. */
  .px-say::after{
    content:"";
    position:absolute;
    left:calc(100% + var(--p));
    bottom:calc(var(--p) * 2);
    width:var(--p);
    height:var(--p);
    background:var(--ink);
    box-shadow:var(--p) 0 0 0 var(--ink);
  }

  /* Meta Ads: dollars float up out of the monitor. */
  .sc-dollar{
    position:absolute;
    top:14px;                      /* the monitor's top edge */
    width:6px;
    height:14px;
    opacity:0.9;
    pointer-events:none;
  }
  .sc-dollar-art{ display:block; width:6px; height:14px; }

  /* Content: the canvas fills in stroke by stroke; poked, the painter
     turns into paint. */
  .eyes-shut{ opacity:0; }
  .sc-blob{ opacity:0; }
  /* The state swap still happens with reduced motion, just without the
     shake and squish. */
  .scene-paint.poked .sc-painter{ opacity:0; }
  .scene-paint.poked .sc-blob{ opacity:1; }
  .scene-paint.poked .eyes-shut{ opacity:1; }

  @media (prefers-reduced-motion:no-preference){
    /* Spinning: thread draws for 70% of the loop while the spider rides its
       tip (83.5% of the spider's route is the spiral), holds, then fades as
       the spider walks back down the border to the hub. */
    .silk-spiral{ animation:silkSpin 9s linear infinite; }
    .web-spider{ animation:silkWalk 9s linear infinite; }
    .scene-sprite{ animation:scuttle .3s steps(2) infinite; }

    .sc-type{ animation:typing .5s steps(2) infinite; }
    .sc-dollar{ animation:cashUp 2.4s steps(12) infinite; opacity:0; }
    .scene-meta.boost .sc-dollar{ animation-duration:.7s; }
    .scene-meta.boost .sc-hop{ animation:hop .45s steps(3) 1; }

    .sc-brush{ animation:dab .6s steps(2) infinite; }
    .sc-stroke{ animation:6s steps(1) infinite; opacity:0; }
    .sc-stroke.s1{ animation-name:stroke1; }
    .sc-stroke.s2{ animation-name:stroke2; }
    .sc-stroke.s3{ animation-name:stroke3; }
    .sc-stroke.s4{ animation-name:stroke4; }
    .scene-paint.poked .sc-painter{ animation:shake .1s steps(2) 5, vanish 2.5s steps(1) forwards; }
    .scene-paint.poked .sc-blob{ animation:blobIn 2.5s steps(1) forwards, squish .3s steps(3) .5s both; transform-origin:bottom center; }

    .px-say.show{ animation:bubblePop .16s steps(4) both; transform-origin:right bottom; }
  }
  @keyframes silkSpin{
    0%   { stroke-dashoffset:1; opacity:0.85; }
    70%  { stroke-dashoffset:0; opacity:0.85; }
    88%  { stroke-dashoffset:0; opacity:0.85; }
    100% { stroke-dashoffset:0; opacity:0; }
  }
  @keyframes silkWalk{
    0%   { offset-distance:0%; }
    70%  { offset-distance:83.5%; }
    88%  { offset-distance:83.5%; }
    100% { offset-distance:100%; }
  }
  @keyframes typing{ 50%{ transform:translateY(1px); } }
  @keyframes hop{ 40%{ transform:translateY(-4px); } 100%{ transform:none; } }
  @keyframes cashUp{
    0%   { transform:translate(0, 0); opacity:0; }
    15%  { opacity:0.95; }
    100% { transform:translate(var(--dx, 0px), -40px); opacity:0; }
  }
  @keyframes dab{ 50%{ transform:translate(-1px, 1px); } }
  @keyframes stroke1{ 0%{ opacity:0; } 12%{ opacity:1; } 94%{ opacity:0; } }
  @keyframes stroke2{ 0%{ opacity:0; } 32%{ opacity:1; } 94%{ opacity:0; } }
  @keyframes stroke3{ 0%{ opacity:0; } 52%{ opacity:1; } 94%{ opacity:0; } }
  @keyframes stroke4{ 0%{ opacity:0; } 72%{ opacity:1; } 94%{ opacity:0; } }
  @keyframes shake{ 0%{ transform:translateX(-1px); } 100%{ transform:translateX(1px); } }
  @keyframes vanish{ 0%{ opacity:1; } 20%{ opacity:0; } 100%{ opacity:0; } }
  @keyframes blobIn{ 0%{ opacity:0; } 20%{ opacity:1; } 100%{ opacity:1; } }
  @keyframes squish{ 0%{ transform:scale(1.4, 0.5); } 100%{ transform:scale(1, 1); } }
  @media (max-width:820px){
    .teaser-grid{ grid-template-columns:1fr; }
  }

  .section-link{
    display:inline-flex;
    align-items:center;
    gap:8px;
    margin-top:34px;
    font-weight:600;
    text-decoration:none;
    color:var(--accent);
  }
  .section-link:hover{ text-decoration:underline; }
  .section-link--center{
    display:flex;
    justify-content:center;
    margin:26px auto 0;
  }

  /* Services detail */
  .svc-wrap{ padding:80px 0; }
  .svc{
    padding:40px 0;
    border-bottom:1px solid var(--border);
  }
  .svc:last-of-type{ border-bottom:0; }
  .svc h2{
    font-size:clamp(1.5rem, 2vw + 1rem, 2rem);
    font-weight:700;
  }
  .svc-lede{
    margin:14px 0 0;
    max-width:62ch;
    color:var(--text-soft);
    font-size:1.05rem;
    line-height:1.65;
  }
  .svc-list{
    margin:22px 0 0;
    padding-left:20px;
    max-width:66ch;
    color:var(--text-soft);
    line-height:1.7;
  }
  .svc-list li{ margin-bottom:8px; }
  .svc-note{
    margin:20px 0 0;
    font-size:0.9rem;
    color:var(--text-soft);
    opacity:0.85;
    font-style:italic;
  }

  /* Portfolio case write-ups — each card carries a screenshot of the
     client's own homepage behind a scrim, so the text stays on a fixed
     dark surface regardless of theme (a card can sit on a light photo in
     light mode otherwise, which white text would lose). */
  .cases{ padding:16px 0 80px; display:flex; flex-direction:column; gap:18px; }
  .case{
    position:relative;
    isolation:isolate;
    display:flex;
    gap:18px;
    padding:32px 28px;
    border-radius:18px;
    overflow:hidden;
    color:#fff;
  }
  .case::before,
  .case::after{ content:""; position:absolute; inset:0; z-index:-1; }
  .case::before{
    background-image:var(--case-bg);
    background-size:cover;
    background-position:var(--case-pos, center 15%);   /* per card: pos in pages.js */
  }
  .case::after{
    background:linear-gradient(100deg, rgba(9,6,18,0.93) 0%, rgba(9,6,18,0.88) 40%, rgba(9,6,18,0.62) 100%);
  }
  .case-swatch{
    width:14px; height:14px;
    margin-top:7px;
    flex:none;
    border-radius:3px;
    background:var(--bug);
    box-shadow:0 0 0 2px rgba(255,255,255,0.55);
  }
  /* min-width:0 lets the text column wrap within the card instead of
     forcing the flex row (and the whole page) wider. */
  .case-body{ flex:1; min-width:0; }
  .case-type{
    margin:0 0 6px;
    font-size:0.76rem;
    font-weight:700;
    letter-spacing:0.12em;
    text-transform:uppercase;
    color:rgba(255,255,255,0.72);
  }
  .case-body h2{
    margin:0;
    font-size:1.3rem;
    font-weight:700;
    color:#fff;
  }
  .case-body h2 a{ color:#fff; text-decoration:none; }
  .case-body h2 a:hover{ color:var(--accent-strong); }
  .case-body p{
    margin:12px 0 0;
    max-width:62ch;
    color:rgba(255,255,255,0.82);
    line-height:1.65;
  }

  /* Closing call to action on inner pages */
  .page-cta{
    padding:76px 0;
    background:var(--surface);
    border-top:1px solid var(--border);
    text-align:center;
  }
  .page-cta h2{
    font-size:clamp(1.5rem, 2vw + 1rem, 2rem);
    font-weight:700;
  }
  .page-cta p{
    margin:14px auto 26px;
    max-width:52ch;
    color:var(--text-soft);
    line-height:1.65;
  }

  /* Contact CTA block on the home page */
  .contact-cta{ text-align:center; }
  .contact-cta h2{
    font-size:clamp(1.8rem, 2.6vw + 1rem, 2.5rem);
    font-weight:700;
  }
  .contact-cta p{
    margin:16px auto 28px;
    max-width:54ch;
    line-height:1.65;
    opacity:0.92;
  }
  /* Button on its own line, email underneath. */
  .contact-cta-actions{
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:20px;
  }
  .contact-cta-actions .contact-email{ margin-top:0; }
  /* .btn-light left-aligns itself for the contact form; centre it here. */
  .contact-cta-actions .btn{ align-self:center; }

  /* Home page only: Services above it already uses --surface, so the portfolio
     block drops to --bg and the two sections stay visually separate. */
  .portfolio--alt{ background:var(--bg); }

  /* ---------- Bugs flying in on load ---------- */
  /* They start large, blurred and off-axis — as if passing the viewer — then
     recede and settle onto the web. Scaling about the bug's own centre is what
     sells the depth; the blur clearing is the focus pulling in.
     No-JS safe: this is pure CSS, so if anything else fails the bugs still land. */
  .bug-fly{
    transform-box:fill-box;
    transform-origin:center;
  }
  @keyframes bugFlyIn{
    0%{
      opacity:0;
      filter:blur(5px);
      transform:translate(var(--fly-x,0), var(--fly-y,0)) scale(7) rotate(var(--fly-r,0deg));
    }
    45%{ opacity:1; }
    100%{
      opacity:1;
      filter:blur(0);
      transform:translate(0,0) scale(1) rotate(0deg);
    }
  }
  @media (prefers-reduced-motion:no-preference){
    .bug-fly{
      animation:bugFlyIn 1s cubic-bezier(.19,.82,.3,1) both;
      animation-delay:var(--fly-d, 0s);
    }
  }

  /* ---------- Neon highlight ---------- */
  /* The reference effect is high-chroma colour against a dark background, not a
     glow — a text-shadow on a headline mostly just smears it. The shadow here is
     barely-there in dark mode and absent in light, where a glow on a white page
     reads as a mistake rather than an effect. */
  :root{
    --neon:#6D28D9;
    --neon-glow:transparent;
  }
  @media (prefers-color-scheme:dark){
    :root:not([data-theme="light"]):not([data-theme="spider"]){
      --neon:#C08BFF;
      --neon-glow:rgba(192,139,255,0.28);
    }
  }
  :root[data-theme="dark"]{
    --neon:#C08BFF;
    --neon-glow:rgba(192,139,255,0.28);
  }

  .neon{
    color:var(--neon);
    text-shadow:0 0 28px var(--neon-glow);
  }

  /* ---------- Spider theme ---------- */
  /* A third palette alongside light and dark: deeper and more saturated, with a
     purple hero that carries a velvet texture. */
  :root[data-theme="spider"]{
    --bg:#150A28;
    --surface:#1E0E37;
    --text:#F7F2FF;
    --text-soft:#C6B3E6;
    --border:#3A1F63;
    --accent:#C08BFF;
    --accent-strong:#DCC0FF;
    --on-accent:#1A0730;
    --header-bg:rgba(21,10,40,0.82);
    --spider-color:#FFFFFF;
    --bug-dark:#2A1810;
    --bug-dark-plain:#FFFFFF;
    --neon:#E6C2FF;
    --neon-glow:rgba(230,194,255,0.34);
  }

  /* Velvet is really two things: a deep pile that shifts tone across the surface,
     and a fine nap that catches light. The layered radial gradients do the first,
     the grain overlay does the second. */
  :root[data-theme="spider"] .hero{
    background:
      radial-gradient(120% 95% at 18% -10%, rgba(190,140,255,0.30), transparent 62%),
      radial-gradient(85% 75% at 88% 8%, rgba(138,63,232,0.34), transparent 66%),
      radial-gradient(130% 120% at 50% 115%, rgba(60,20,110,0.55), transparent 70%),
      linear-gradient(162deg, #43137F 0%, #2C0A54 52%, #1B0636 100%);
  }
  :root[data-theme="spider"] .hero::after{
    content:"";
    position:absolute;
    inset:0;
    z-index:0;                /* over the gradient, under the web and the copy */
    pointer-events:none;
    opacity:0.5;
    mix-blend-mode:soft-light;
    background-image:var(--velvet-grain);
    background-size:160px 160px;
  }
  /* A soft sheen along the lower edge, the way light sits on pile fabric. */
  :root[data-theme="spider"] .hero::before{
    content:"";
    position:absolute;
    left:0; right:0; bottom:0;
    height:38%;
    z-index:0;
    pointer-events:none;
    background:linear-gradient(to top, rgba(12,4,24,0.55), transparent);
  }
  :root[data-theme="spider"] .hero-inner{ position:relative; z-index:2; }

  /* The inner pages have their own hero block; give it the same velvet so the
     theme is coherent across the site rather than a home-page-only trick. */
  :root[data-theme="spider"] .page-hero{
    position:relative;
    overflow:hidden;
    border-bottom-color:rgba(255,255,255,0.10);
    background:
      radial-gradient(120% 140% at 15% -30%, rgba(190,140,255,0.26), transparent 62%),
      radial-gradient(80% 120% at 92% 0%, rgba(138,63,232,0.30), transparent 66%),
      linear-gradient(162deg, #3B1070 0%, #260949 60%, #1B0636 100%);
  }
  :root[data-theme="spider"] .page-hero::after{
    content:"";
    position:absolute;
    inset:0;
    pointer-events:none;
    opacity:0.45;
    mix-blend-mode:soft-light;
    background-image:var(--velvet-grain);
    background-size:160px 160px;
  }
  :root[data-theme="spider"] .page-hero > .container{ position:relative; z-index:1; }
  /* Vertical here so the whole bottom edge lands on one colour for the
     portfolio section below to pick up from. */
  :root[data-theme="spider"] .page-hero:has(+ .portfolio){
    background:
      radial-gradient(120% 140% at 15% -30%, rgba(190,140,255,0.26), transparent 62%),
      radial-gradient(80% 120% at 92% 0%, rgba(138,63,232,0.30), transparent 66%),
      linear-gradient(180deg, #3B1070 0%, #260949 60%, #1B0636 100%);
  }

  /* ---------- Velvet on other sections (spider theme) ---------- */
  /* Not every section: the services list and the case write-ups stay flat so the
     reading surface is calm. These three are the display moments, and each gets
     its light from a different corner so the page does not look stamped. */

  /* Portfolio — light from the right, so the web reads against a darker left. */
  :root[data-theme="spider"] .portfolio{
    position:relative;
    background:
      radial-gradient(95% 120% at 88% 10%, rgba(176,120,255,0.20), transparent 64%),
      radial-gradient(120% 130% at 20% 100%, rgba(52,16,98,0.55), transparent 68%),
      linear-gradient(196deg, #2A0A50 0%, #1D0739 58%, #17052E 100%);
    border-color:rgba(255,255,255,0.09);
  }
  :root[data-theme="spider"] .portfolio > *{ position:relative; z-index:1; }
  /* Under the portfolio page's hero, start from the exact colour the hero's
     gradient ends on and keep the glow off the top edge, or the seam shows. */
  :root[data-theme="spider"] .page-hero + .portfolio{
    background:
      radial-gradient(95% 110% at 85% 60%, rgba(176,120,255,0.16), transparent 64%),
      linear-gradient(180deg, #1B0636 0%, #17052E 100%);
  }
  :root[data-theme="spider"] .page-hero + .portfolio::after{ opacity:0.45; }

  /* Contact — already a gradient; deepen it and give it the same nap. */
  :root[data-theme="spider"] .contact{
    position:relative;
    overflow:hidden;
    background:
      radial-gradient(90% 120% at 12% 0%, rgba(200,150,255,0.26), transparent 60%),
      linear-gradient(135deg, #3E1178 0%, #5B21B6 55%, #3A0F73 100%);
  }
  :root[data-theme="spider"] .contact > *{ position:relative; z-index:1; }

  /* Closing CTA on inner pages — light from below, so it lifts off the page. */
  :root[data-theme="spider"] .page-cta{
    position:relative;
    overflow:hidden;
    background:
      radial-gradient(110% 150% at 50% 130%, rgba(176,120,255,0.24), transparent 62%),
      linear-gradient(180deg, #1B0636 0%, #250947 100%);
    border-top-color:rgba(255,255,255,0.09);
  }
  :root[data-theme="spider"] .page-cta > *{ position:relative; z-index:1; }

  :root[data-theme="spider"] .portfolio::after,
  :root[data-theme="spider"] .contact::after,
  :root[data-theme="spider"] .page-cta::after{
    content:"";
    position:absolute;
    inset:0;
    z-index:0;
    pointer-events:none;
    opacity:0.4;
    mix-blend-mode:soft-light;
    background-image:var(--velvet-grain);
    background-size:160px 160px;
  }

  /* ---------- Sparkles ("What we do", "Tell us what you're building") ---------- */
  /* Two tiles of tiny pixel stars used as masks over a flat colour, so the
     colour can follow the theme. Different tile sizes keep the repeat from
     lining up, and they twinkle out of phase. Kept faint on purpose.
     Last in the file: the spider theme's ".contact > *" rule would otherwise
     pull this layer back into flow. */
  .has-sparkles{ position:relative; isolation:isolate; }
  .has-sparkles > .container{ position:relative; z-index:1; }
  :root .has-sparkles > .sparkles{
    position:absolute;
    inset:0;
    z-index:0;
    pointer-events:none;
    --star:#FFFFFF;
  }
  /* Light mode's "What we do" sits on a pale surface, where white would vanish. */
  :root[data-theme="light"] .services > .sparkles{ --star:var(--purple-600); }
  .sparkles::before,
  .sparkles::after{
    content:"";
    position:absolute;
    inset:0;
    background:var(--star);
    -webkit-mask-repeat:repeat;
    mask-repeat:repeat;
  }
  .sparkles::before{
    opacity:0.16;
    -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240' shape-rendering='crispEdges'><rect x='36' y='52' width='2' height='2'/><rect x='34' y='52' width='2' height='2'/><rect x='38' y='52' width='2' height='2'/><rect x='36' y='50' width='2' height='2'/><rect x='36' y='54' width='2' height='2'/><rect x='168' y='118' width='2' height='2'/><rect x='166' y='118' width='2' height='2'/><rect x='170' y='118' width='2' height='2'/><rect x='168' y='116' width='2' height='2'/><rect x='168' y='120' width='2' height='2'/><rect x='96' y='196' width='2' height='2'/><rect x='94' y='196' width='2' height='2'/><rect x='98' y='196' width='2' height='2'/><rect x='96' y='194' width='2' height='2'/><rect x='96' y='198' width='2' height='2'/><rect x='120' y='24' width='2' height='2'/><rect x='210' y='40' width='2' height='2'/><rect x='20' y='150' width='2' height='2'/><rect x='150' y='210' width='2' height='2'/><rect x='70' y='110' width='2' height='2'/><rect x='220' y='190' width='2' height='2'/></svg>");
    mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240' shape-rendering='crispEdges'><rect x='36' y='52' width='2' height='2'/><rect x='34' y='52' width='2' height='2'/><rect x='38' y='52' width='2' height='2'/><rect x='36' y='50' width='2' height='2'/><rect x='36' y='54' width='2' height='2'/><rect x='168' y='118' width='2' height='2'/><rect x='166' y='118' width='2' height='2'/><rect x='170' y='118' width='2' height='2'/><rect x='168' y='116' width='2' height='2'/><rect x='168' y='120' width='2' height='2'/><rect x='96' y='196' width='2' height='2'/><rect x='94' y='196' width='2' height='2'/><rect x='98' y='196' width='2' height='2'/><rect x='96' y='194' width='2' height='2'/><rect x='96' y='198' width='2' height='2'/><rect x='120' y='24' width='2' height='2'/><rect x='210' y='40' width='2' height='2'/><rect x='20' y='150' width='2' height='2'/><rect x='150' y='210' width='2' height='2'/><rect x='70' y='110' width='2' height='2'/><rect x='220' y='190' width='2' height='2'/></svg>");
  }
  .sparkles::after{
    opacity:0.1;
    -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300' shape-rendering='crispEdges'><rect x='250' y='60' width='2' height='2'/><rect x='248' y='60' width='2' height='2'/><rect x='252' y='60' width='2' height='2'/><rect x='250' y='58' width='2' height='2'/><rect x='250' y='62' width='2' height='2'/><rect x='60' y='250' width='2' height='2'/><rect x='58' y='250' width='2' height='2'/><rect x='62' y='250' width='2' height='2'/><rect x='60' y='248' width='2' height='2'/><rect x='60' y='252' width='2' height='2'/><rect x='180' y='190' width='2' height='2'/><rect x='178' y='190' width='2' height='2'/><rect x='182' y='190' width='2' height='2'/><rect x='180' y='188' width='2' height='2'/><rect x='180' y='192' width='2' height='2'/><rect x='30' y='30' width='2' height='2'/><rect x='140' y='80' width='2' height='2'/><rect x='270' y='150' width='2' height='2'/><rect x='100' y='160' width='2' height='2'/><rect x='200' y='270' width='2' height='2'/><rect x='20' y='200' width='2' height='2'/><rect x='230' y='20' width='2' height='2'/></svg>");
    mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300' shape-rendering='crispEdges'><rect x='250' y='60' width='2' height='2'/><rect x='248' y='60' width='2' height='2'/><rect x='252' y='60' width='2' height='2'/><rect x='250' y='58' width='2' height='2'/><rect x='250' y='62' width='2' height='2'/><rect x='60' y='250' width='2' height='2'/><rect x='58' y='250' width='2' height='2'/><rect x='62' y='250' width='2' height='2'/><rect x='60' y='248' width='2' height='2'/><rect x='60' y='252' width='2' height='2'/><rect x='180' y='190' width='2' height='2'/><rect x='178' y='190' width='2' height='2'/><rect x='182' y='190' width='2' height='2'/><rect x='180' y='188' width='2' height='2'/><rect x='180' y='192' width='2' height='2'/><rect x='30' y='30' width='2' height='2'/><rect x='140' y='80' width='2' height='2'/><rect x='270' y='150' width='2' height='2'/><rect x='100' y='160' width='2' height='2'/><rect x='200' y='270' width='2' height='2'/><rect x='20' y='200' width='2' height='2'/><rect x='230' y='20' width='2' height='2'/></svg>");
  }
  @keyframes twinkleA{ 0%,100%{ opacity:0.16; } 50%{ opacity:0.05; } }
  @keyframes twinkleB{ 0%,100%{ opacity:0.04; } 50%{ opacity:0.14; } }
  @media (prefers-reduced-motion:no-preference){
    .sparkles::before{ animation:twinkleA 5s ease-in-out infinite; }
    .sparkles::after { animation:twinkleB 7s ease-in-out infinite; }
  }

  /* Contact page hero: a deeper purple than the other page heroes, with the
     same faint sparkles as the home page sections (spider theme only for the
     colour; light mode needs purple stars on its pale hero). */
  :root[data-theme="spider"] .page-hero--contact{
    background:
      radial-gradient(120% 140% at 15% -30%, rgba(170,120,240,0.16), transparent 62%),
      radial-gradient(80% 120% at 92% 0%, rgba(110,45,200,0.20), transparent 66%),
      linear-gradient(162deg, #2A0A52 0%, #1B0636 60%, #13042A 100%);
  }
  :root[data-theme="light"] .page-hero--contact > .sparkles{ --star:var(--purple-600); }

  /* ---------- Privacy policy (legal prose) ---------- */
  .legal{ padding:64px 0 96px; }
  .legal-inner > *{ max-width:720px; }   /* short lines, still aligned with the hero */
  .legal h2{ font-size:1.35rem; margin:40px 0 12px; }
  .legal h2:first-child{ margin-top:0; }
  .legal p, .legal li{ color:var(--text-soft); line-height:1.7; }
  .legal p + p, .legal p + ul, .legal ul + p{ margin-top:12px; }
  .legal ul{ padding-left:1.2em; display:grid; gap:8px; }
  .legal strong{ color:var(--text); }
  .legal a{ color:var(--accent); }
  .form-privacy{ font-size:0.85rem; opacity:0.8; margin-top:-4px; }
  .form-privacy a{ color:inherit; }

  /* ---------- Why "Spider Web"? (home, below the hero) ---------- */
  .why{ padding:88px 0; text-align:center; }
  .why-inner{ max-width:720px; }
  .why h2{ font-size:clamp(1.8rem, 3vw + 1rem, 2.6rem); margin-bottom:18px; }
  .why p{ color:var(--text-soft); line-height:1.7; }
  .why-lede{ font-size:1.2rem; color:var(--text) !important; }
  .why-cta{ font-family:var(--font-display); font-weight:700; font-size:1.3rem; color:var(--text) !important; margin:14px 0 18px; }
