/* Hey Mehdi — stylesheet
   Edit this file directly; there is no build step. */

/* ===================================================================
   HEY MEHDI — portfolio
   Single-file build. Sections: hero, about, work, services, band,
   testimonials, pricing, faq, project form, cta, footer.
   =================================================================== */

:root{
  --black:#000000;
  --ink:#050505;
  --ink-2:#0b0b0b;
  --line:rgba(255,255,255,.09);
  --line-2:rgba(255,255,255,.055);
  --white:#f4f2f0;
  --dim:#8a8683;
  --dimmer:#4e4b49;
  --orange:#ff4a10;
  --orange-soft:#ff6b2c;
  --ease:cubic-bezier(.16,1,.3,1);
  --ease-2:cubic-bezier(.65,.05,.2,1);
  --gut:clamp(16px,4.6vw,72px);
  --nav-h:64px;
  --maxw:1440px;
  --r:2px;

  /* ---------- TYPE ----------------------------------------------------
     Two voices, deliberately different.

     --display  Bricolage Grotesque. A variable grotesque with narrow
                apertures, an optical-size axis and real quirk in the
                letterforms. Used for every heading and the wordmark; it
                carries the personality.
     --body     Geist. Neutral, tall x-height, built for screens. It gets
                out of the way so the display face can be loud.
     --mono     Geist Mono. Same skeleton as Geist, so the small tracked
                labels read as part of the system rather than an add-on.
     ------------------------------------------------------------------- */
  --display:'Bricolage Grotesque','Helvetica Neue',Helvetica,Arial,sans-serif;
  --body:'Geist','Inter Tight',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  --mono:'Geist Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
/* NOTE: deliberately no `scroll-behavior:smooth` here. app.js runs its own
   easing tween with window.scrollTo() on every frame; with CSS smooth
   scrolling enabled each of those calls started a *separate* smooth
   animation, they fought one another, and in-page links appeared dead. */

body{
  margin:0;
  background:var(--black);
  color:var(--white);
  font-family:var(--body);
  font-weight:400;
  font-size:16px;
  line-height:1.5;
  letter-spacing:-.008em;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  overflow-x:hidden;
}
body.is-locked{overflow:hidden}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
/* in-page destinations carry no href — nothing shows in the status bar — so the
   pointer affordance has to be restored by hand */
[data-goto]{cursor:pointer}
button,input,select,textarea{font:inherit;color:inherit}
::selection{background:var(--orange);color:#fff}

/* ---------- layout primitives ---------- */
.wrap{width:100%;max-width:var(--maxw);margin:0 auto;padding:0 var(--gut)}
.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:clamp(12px,1.6vw,24px)}
.col-main{grid-column:4 / 13}
.col-side{grid-column:1 / 4}
@media (max-width:900px){
  .col-main,.col-side{grid-column:1 / 13}
}

/* decorative page rules */
.rules{position:fixed;inset:0;pointer-events:none;z-index:1;max-width:var(--maxw);overflow:hidden;
  margin:0 auto;padding:0 var(--gut);display:flex;justify-content:space-between}
.rules span{width:1px;background:var(--line-2);height:100%}
@media (max-width:760px){.rules span:nth-child(2),.rules span:nth-child(4){display:none}}

/* ---------- type ---------- */
.mono{
  font-family:var(--mono);
  font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--dim);
  font-weight:400;
}
/* every heading uses the display face; opsz keeps the quirk readable
   at small sizes and lets it get wilder as the type grows */
.h-xl,.h-lg,.h-md,.wordmark,.ptitle,.cta h2,.band-word,.years,
.wcard-title,.pnext-t,.stat .num,.big-stat .n,.price .amt,.pstats .n,
.svc h3,.plan h3,.faq-q,.tst-quote,.tst-mark span,.logo,.f-mark .logo{
  font-family:var(--display);
  font-variation-settings:'opsz' 40;
  font-weight:400;
}
.h-xl{
  font-size:clamp(36px,5.2vw,72px);
  line-height:1.03;letter-spacing:-.038em;margin:0;
  font-variation-settings:'opsz' 72;
}
.h-lg{font-size:clamp(29px,3.5vw,49px);line-height:1.07;letter-spacing:-.032em;margin:0;
  font-variation-settings:'opsz' 56}
.h-md{font-size:clamp(21px,2.1vw,29px);line-height:1.16;letter-spacing:-.028em;margin:0;
  font-variation-settings:'opsz' 32}
.muted{color:var(--dimmer)}
.dim{color:var(--dim)}
.body-lg{font-size:clamp(15px,1.15vw,17px);line-height:1.6;color:var(--dim);letter-spacing:-.004em;
  font-family:var(--body)}

/* ---------- section header (/ ABOUT  ...  N.01) ---------- */
.sec{position:relative;padding:clamp(70px,9vw,140px) 0;z-index:2}
section[id]{scroll-margin-top:calc(var(--nav-h) + 12px)}
.sec-head{
  display:flex;justify-content:space-between;align-items:baseline;
  border-top:1px solid var(--line);padding-top:14px;margin-bottom:clamp(40px,5.5vw,88px);
}

/* ---------- crosshair marks ---------- */
.plus{position:absolute;width:11px;height:11px;opacity:.34;pointer-events:none}
.plus::before,.plus::after{content:'';position:absolute;background:var(--white)}
.plus::before{left:5px;top:0;width:1px;height:11px}
.plus::after{top:5px;left:0;height:1px;width:11px}

/* ============================================================================
   NAV
   The row is a fixed --nav-h tall and centres its children, rather than being
   sized by padding plus whatever the tallest child happens to be. Two reasons:
   the bar is compact and predictable, and --nav-h is then *true*, so the hero
   can offset against it correctly.
   ========================================================================== */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:60;
  transition:transform .6s var(--ease),background .4s ease,backdrop-filter .4s ease;
}
.nav.hidden{transform:translateY(-102%)}
.nav.solid{background:rgba(0,0,0,.66);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  box-shadow:0 1px 0 rgba(255,255,255,.06)}
.nav .wrap{
  height:var(--nav-h);
  display:flex;align-items:center;justify-content:space-between;gap:16px;
}
.nav-right{display:flex;align-items:center;gap:8px;flex:0 0 auto}
/* ---------- LOGO LOCKUP ----------
   "Hey Mehdi" is ONE tag containing ONE bare text node — there is no inner
   element around either word, so the two words are physically the same run
   of glyphs and cannot differ in size, weight, tracking or baseline.
   The brand accent is a pseudo-element full stop appended after the text,
   which lives outside the text node and therefore can't affect its metrics.
   Scale the whole lockup with a single font-size on .logo. */
.logo{
  display:inline-block;
  font-size:19px;font-weight:500;line-height:1;letter-spacing:-.035em;
  color:var(--white);white-space:nowrap;
  transition:color .45s var(--ease);
}
.logo::after{content:'.';color:var(--orange);transition:color .45s var(--ease)}
.logo.lg{font-size:clamp(25px,2.7vw,36px)}
a.logo:hover{color:var(--orange)}
a.logo:hover::after{color:var(--white)}
.nav-links{display:flex;gap:28px;align-items:center}
.nav-links a{font-size:14px;color:var(--white);transition:opacity .3s ease;position:relative}
.nav-links a::after{
  content:'';position:absolute;left:0;bottom:-5px;width:100%;height:1px;background:currentColor;
  transform:scaleX(0);transform-origin:right;transition:transform .5s var(--ease);
}
.nav-links a:hover::after{transform:scaleX(1);transform-origin:left}

/* ============================================================================
   .cbtn — THE button component. Every button on the site is one of these.

   Gradient pill, inset top highlight, resting glow, and a circular arrow badge
   whose arrow swaps on hover: the first slides out right while a second slides
   in from the left. The label never disappears at any width — it only tightens
   — so the button reads identically everywhere.

   Variants
     .cbtn            primary, gradient orange
     .cbtn.ghost      secondary, hairline border, fills on hover
     .cbtn.wide       full width of its container
     .cbtn.lg         larger, for the closing call to action
     .nav-cta         the header instance (smaller, set by --cta-h)

   The arrow badge is injected by app.js into any .cbtn that lacks one, so the
   markup stays clean and no instance can drift. Buttons work without JS —
   they just don't get the arrow.
   ========================================================================== */
.cbtn{
  --cta-h:44px;
  display:inline-flex;align-items:center;justify-content:space-between;gap:14px;
  flex:0 0 auto;height:var(--cta-h);padding:0 6px 0 21px;
  border:0;border-radius:999px;white-space:nowrap;cursor:pointer;
  color:#fff;font-family:var(--body);font-size:14.5px;font-weight:500;
  letter-spacing:-.008em;line-height:1;text-align:left;
  position:relative;overflow:hidden;isolation:isolate;
  background:linear-gradient(140deg,#ff6127 0%,#ff4a10 46%,#e83c05 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.26),
    inset 0 -1px 0 rgba(0,0,0,.14),
    0 3px 12px -4px rgba(255,74,16,.55);
  transition:box-shadow .5s var(--ease),transform .45s var(--ease),
             filter .4s ease,background .5s var(--ease),color .4s var(--ease);
}
.cbtn:hover{
  transform:translate(var(--mtx,0px),calc(var(--mty,0px) - 1.5px));
  filter:saturate(1.06);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.34),
    inset 0 -1px 0 rgba(0,0,0,.14),
    0 10px 26px -8px rgba(255,74,16,.8);
}
.cbtn:active{transform:translate(var(--mtx,0px),var(--mty,0px)) scale(.975)}
.cbtn:disabled{opacity:.55;cursor:not-allowed;filter:saturate(.6)}
.cbtn:disabled:hover{transform:none;box-shadow:inset 0 1px 0 rgba(255,255,255,.2)}
.cbtn>*{position:relative;z-index:2}
.cbtn::before{content:none}
/* pointer spotlight, positioned by app.js via --mx / --my */
.cbtn::after{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:1;
  background:radial-gradient(100px circle at var(--mx,50%) var(--my,50%),rgba(255,255,255,.30),transparent 62%);
  opacity:0;transition:opacity .4s var(--ease);
}
.cbtn:hover::after{opacity:1}

.cbtn-label{display:block}
.cbtn-arw{
  width:32px;height:32px;border-radius:999px;flex:0 0 auto;
  background:rgba(255,255,255,.20);
  position:relative;overflow:hidden;
  transition:background .45s var(--ease);
}
.cbtn:hover .cbtn-arw{background:rgba(255,255,255,.34)}
.cbtn-arw svg{
  position:absolute;top:50%;left:50%;margin:-7px 0 0 -7px;
  transition:transform .52s var(--ease);
}
.cbtn-arw svg:nth-child(1){transform:translateX(0)}
.cbtn-arw svg:nth-child(2){transform:translateX(-190%)}
.cbtn:hover .cbtn-arw svg:nth-child(1){transform:translateX(190%)}
.cbtn:hover .cbtn-arw svg:nth-child(2){transform:translateX(0)}

/* --- secondary --- */
.cbtn.ghost{
  background:transparent;color:var(--white);
  box-shadow:inset 0 0 0 1px var(--line);
}
.cbtn.ghost .cbtn-arw{background:rgba(255,255,255,.10)}
.cbtn.ghost:hover{
  background:var(--white);color:#0a0a0a;filter:none;
  box-shadow:inset 0 0 0 1px var(--white),0 8px 22px -10px rgba(0,0,0,.9);
}
.cbtn.ghost:hover .cbtn-arw{background:rgba(10,10,10,.12)}
.cbtn.ghost::after{
  background:radial-gradient(110px circle at var(--mx,50%) var(--my,50%),rgba(255,255,255,.12),transparent 65%);
}

/* --- sizes --- */
.cbtn.wide{width:100%;justify-content:space-between}
.cbtn.lg{--cta-h:54px;font-size:16px;padding:0 7px 0 26px;gap:18px}
.cbtn.lg .cbtn-arw{width:40px;height:40px}
.cbtn.lg .cbtn-arw svg{margin:-8px 0 0 -8px}

/* --- the header instance --- */
.nav-cta{--cta-h:38px;font-size:13.5px;padding:0 5px 0 17px;gap:10px}
.nav-cta .cbtn-arw{width:28px;height:28px}
.nav-cta .cbtn-arw svg{margin:-6.5px 0 0 -6.5px}

/* Below 860px the inline links are replaced by the burger and the overlay menu
   at the end of this file. The header CTA goes with them: the menu carries a
   full-width Start button, which is a bigger and better-placed target than a
   38px pill squeezed next to the logo. */
@media (max-width:860px){.nav-links{display:none}}

/* ===================== HERO ===================== */
/* ---------- HERO: resolution-independent backdrop ----------
   The photo's orange backdrop is rebuilt as a CSS radial gradient (colours
   sampled straight off the original file). Vector = never soft, at any size.
   The photograph is then scaled by HEIGHT only, centred, with its left and
   right edges feathered into that gradient. Consequence: on a large monitor
   the photo is DOWNscaled and the extra width is pure gradient, so the
   portrait is never stretched past its native pixels. */
.hero{
  position:relative;height:100svh;min-height:600px;overflow:hidden;
  background:radial-gradient(128% 84% at 50% 69%,
    #f0560f 0%, #e04806 15%, #ca3b03 32%, #ad2f02 50%,
    #8b2202 66%, #6c1802 80%, #551302 91%, #470f01 100%);
}
.hero-media{position:absolute;inset:0;overflow:hidden;display:flex;justify-content:center;align-items:flex-start}
.hero-media img{
  height:112%;width:auto;max-width:none;flex:0 0 auto;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 18%,#000 82%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 18%,#000 82%,transparent 100%);
  transform:scale(1.08);opacity:0;
  transition:transform 2.4s var(--ease),opacity 1.6s ease;
  filter:saturate(1.03);
}
.hero.ready .hero-media img{transform:scale(1);opacity:1}
/* vertical scrim: keeps the nav legible up top and lands the wordmark on black */
.hero-media::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,
    rgba(0,0,0,.46) 0%, rgba(0,0,0,0) 22%, rgba(0,0,0,.05) 44%,
    rgba(0,0,0,.52) 73%, rgba(0,0,0,.93) 91%, #000 100%);
}
/* fine grain — hides gradient banding on large / 10-bit displays */
.hero::after{
  content:'';position:absolute;inset:0;z-index:2;pointer-events:none;opacity:.06;
  mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* Meta labels sit in normal flow inside .wrap, so they always respect the
   page gutter. (They used to be position:absolute with left/right:0, which
   resolves against the padding box and ignored the gutter entirely — that
   is why they hugged the screen edges.) */
.hero-inner{
  position:relative;z-index:3;height:100%;
  display:flex;flex-direction:column;
  padding-top:calc(var(--nav-h) + clamp(18px,5vh,64px));
  padding-bottom:clamp(22px,3.2vw,46px);
}
/* Stays a two-column row at every width — the right-hand label belongs on
   the right, the same as desktop. Only the type size gives way. */
.hero-meta{
  margin-top:auto;display:flex;justify-content:space-between;align-items:flex-start;
  gap:clamp(10px,4vw,40px);
}
.hero-meta>*{min-width:0}
.hero-meta .mono{color:rgba(255,255,255,.88);line-height:1.95}
.hero-meta .right{text-align:right}
/* ---------- hero name lockup ----------
   Structure:  [Hey, I am]  [ Mehdi · Bagheri ]
   "Mehdi" and "Bagheri" live in .hero-name-row, so they stay side by side at
   every width. Only the "Hey, I am" label moves — it drops above the row on
   narrow screens, with real space beneath it instead of sitting on the
   wordmark's cap height. */
.hero-name{
  margin-top:clamp(26px,9vh,104px);
  display:flex;align-items:flex-end;justify-content:center;
  gap:clamp(6px,1.6vw,22px);width:100%;
}
.hero-name-row{
  display:flex;align-items:flex-end;justify-content:center;
  gap:clamp(7px,1.4vw,20px);min-width:0;
}
.hero-name .side{
  font-family:var(--body);
  font-size:clamp(12.5px,1.3vw,18px);letter-spacing:-.015em;font-weight:400;
  padding-bottom:clamp(6px,1vw,16px);white-space:nowrap;color:rgba(255,255,255,.94);
}
.wordmark{
  font-size:clamp(58px,15.4vw,238px);line-height:.8;letter-spacing:-.052em;
  margin:0;white-space:nowrap;
  font-variation-settings:'opsz' 96;
  font-weight:500;
}
.hero-scroll{
  position:absolute;bottom:clamp(22px,3.2vw,46px);right:var(--gut);z-index:4;
  display:flex;align-items:center;gap:9px;pointer-events:none;
}
.hero-scroll .dotline{width:1px;height:34px;background:linear-gradient(180deg,rgba(255,255,255,.9),transparent);animation:drop 2.2s var(--ease) infinite}
@keyframes drop{0%{transform:scaleY(0);transform-origin:top}45%{transform:scaleY(1);transform-origin:top}55%{transform:scaleY(1);transform-origin:bottom}100%{transform:scaleY(0);transform-origin:bottom}}

/* ===================== ABOUT ===================== */
.about-row{display:flex;gap:clamp(24px,4vw,64px);align-items:flex-start}
.about-portrait{
  width:clamp(110px,11vw,150px);flex:0 0 auto;aspect-ratio:1/1.03;overflow:hidden;background:#151515;
}
.about-portrait img{width:100%;height:100%;object-fit:cover;filter:saturate(.92)}
.statement span.g{color:var(--dimmer)}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,3vw,40px);margin-top:clamp(48px,6vw,96px)}
.stat .num{font-size:clamp(38px,4.6vw,64px);line-height:1;letter-spacing:-.045em;display:block}
.stat .lb{margin-top:14px;font-size:14px}
.stat .sub,.sub{color:var(--dimmer);font-size:13.5px;margin-top:2px}
@media (max-width:700px){.about-row{flex-direction:column}.stats{grid-template-columns:1fr 1fr}}

/* ===================== WORK (cover grid) ===================== */
.work-top{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;margin-bottom:clamp(28px,3.4vw,48px)}
.years{font-size:clamp(26px,3vw,40px);letter-spacing:-.03em}
.tag{font-size:11.5px;color:var(--dim);border:1px solid var(--line);padding:4px 10px;border-radius:999px;white-space:nowrap}

/* align-items:start is load-bearing. A grid row defaults to `stretch`, so the
   shorter card in a row was being stretched to the height of the taller one —
   its box (and therefore its hover area) ran on into empty space below the
   tags. That is why hovering under a card still lit it up. */
.work-grid{
  display:grid;grid-template-columns:1fr 1fr;align-items:start;
  gap:clamp(16px,2.4vw,40px) clamp(14px,1.8vw,28px);
}
.wcard{display:block;position:relative;cursor:pointer}
.wcard:nth-child(even){margin-top:clamp(0px,4.4vw,72px)}
.wcard.wide{grid-column:1 / -1;margin-top:clamp(10px,2vw,32px)}

.wcard-media{
  position:relative;overflow:hidden;background:#111;aspect-ratio:16/10;
  border:1px solid var(--line);
}
.wcard.wide .wcard-media{aspect-ratio:21/9}
.wcard-media img{
  width:100%;height:100%;object-fit:cover;
  transform:scale(1.03);transition:transform 1.1s var(--ease),filter .8s var(--ease);
  filter:saturate(.94) brightness(.92);
}
.wcard.is-hot .wcard-media img{transform:scale(1.075);filter:saturate(1.04) brightness(1)}
.wcard-media::after{
  content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.2),rgba(0,0,0,0) 45%,rgba(0,0,0,.5));
  opacity:.9;transition:opacity .7s var(--ease);
}
.wcard.is-hot .wcard-media::after{opacity:.55}
.wcard-idx{
  position:absolute;top:16px;left:18px;z-index:3;color:rgba(255,255,255,.72);
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
}
/* the "View" affordance lives in the cursor (a filled disc with the word in
   it), not as a pill on the card — one label, not two */
.wcard-foot{display:flex;justify-content:space-between;align-items:flex-start;gap:18px;padding-top:16px}
.wcard-title{
  font-size:clamp(19px,1.9vw,26px);letter-spacing:-.028em;line-height:1.14;margin:0 0 10px;font-weight:400;
  transition:color .45s var(--ease);
}
.wcard.is-hot .wcard-title{color:var(--orange)}
.wcard-tags{display:flex;flex-wrap:wrap;gap:6px}
.wcard-yr{color:var(--dimmer);font-size:13px;flex:0 0 auto;padding-top:3px}
@media (max-width:780px){
  .work-grid{grid-template-columns:1fr;gap:34px}
  .wcard:nth-child(even){margin-top:0}
  .wcard.wide{margin-top:0}
  .wcard.wide .wcard-media{aspect-ratio:16/10}
}

/* ===================== SERVICES ===================== */
.svc-grid{display:grid;grid-template-columns:repeat(2,1fr);border-top:1px solid var(--line);border-left:1px solid var(--line)}
/* Top-aligned, with ONE fixed gap under the icon.
   This used to be justify-content:space-between, which pinned the icon to the
   top and the text to the bottom. Grid rows are as tall as their tallest cell,
   so every card with less copy than its neighbour got a bigger gap injected —
   and no two titles in a row started on the same line. Any leftover height now
   falls at the bottom of the card, where it is invisible, instead of in the
   middle of the card, where it was the only thing you could see. */
.svc{
  padding:clamp(24px,3.2vw,46px);border-right:1px solid var(--line);border-bottom:1px solid var(--line);
  min-height:clamp(210px,22vw,290px);
  display:flex;flex-direction:column;gap:clamp(20px,2.4vw,34px);
  position:relative;overflow:hidden;transition:background .6s var(--ease);
}
.svc::before{
  content:'';position:absolute;inset:0;
  background:radial-gradient(80% 70% at 20% 100%, rgba(255,74,16,.14), transparent 70%);
  opacity:0;transition:opacity .7s var(--ease);
}
.svc:hover::before{opacity:1}
.svc-ico{
  width:30px;height:30px;flex:0 0 auto;      /* never stretched by the column */
  position:relative;z-index:2;color:var(--white);
  transition:transform .7s var(--ease),color .5s ease;
}
.svc:hover .svc-ico{transform:translateY(-4px) rotate(-6deg);color:var(--orange)}
.svc-body{position:relative;z-index:2}
.svc h3{font-size:clamp(17px,1.6vw,22px);letter-spacing:-.025em;font-weight:400;margin:0 0 8px}
.svc p{margin:0;font-size:14px;color:var(--dim);line-height:1.5;max-width:34ch}
/* The number sits opposite the icon, so it is centred against the icon's box
   rather than hung from the same top edge — a 10px line and a 30px glyph
   sharing a top edge do not read as being on the same row. */
.svc-n{
  position:absolute;top:clamp(24px,3.2vw,46px);right:clamp(24px,3.2vw,46px);z-index:2;
  height:30px;display:flex;align-items:center;
}
@media (max-width:700px){.svc-grid{grid-template-columns:1fr}}

/* ===================== BAND ===================== */
.band{position:relative;height:clamp(360px,58vw,660px);overflow:hidden;background:#181512}
.band img{width:100%;height:100%;object-fit:cover;object-position:50% 34%;filter:grayscale(.35) contrast(1.03);transform:scale(1.1)}
.band::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.35),rgba(0,0,0,.05) 40%,rgba(0,0,0,.72))}
.band-word{
  position:absolute;left:0;right:0;top:52%;transform:translateY(-50%);z-index:3;text-align:center;
  font-size:clamp(46px,10vw,150px);line-height:.9;letter-spacing:-.05em;
}
.band-meta{position:absolute;left:0;right:0;bottom:26px;z-index:3;display:flex;justify-content:space-between;gap:16px}
.band-meta .mono{color:rgba(255,255,255,.78)}

/* ===================== TESTIMONIALS ===================== */
.tst{display:grid;grid-template-columns:250px 1fr;gap:clamp(24px,4vw,56px);align-items:start}
/* client monogram instead of a stock face — honest, and on-brand */
.tst-mark{
  aspect-ratio:1/1.05;border:1px solid var(--line);background:var(--ink);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;
  position:relative;overflow:hidden;
}
.tst-mark::before{
  content:'';position:absolute;inset:0;
  background:radial-gradient(70% 60% at 50% 108%,rgba(255,74,16,.16),transparent 70%);
}
.tst-mark span{
  position:relative;font-size:clamp(38px,4.4vw,58px);letter-spacing:-.04em;line-height:1;
  color:var(--white);transition:opacity .5s var(--ease);
}
.tst-mark em{position:relative;font-style:normal}
.tst-slide{display:none}
.tst-slide.on{display:block;animation:fadeUp .7s var(--ease)}
@keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
.tst-quote{font-size:clamp(18px,1.85vw,25px);line-height:1.38;letter-spacing:-.025em;font-style:italic;font-weight:300;margin:22px 0 30px}
.tst-foot{display:flex;justify-content:space-between;align-items:flex-end;gap:20px}
.tst-name{font-size:14.5px}
.tst-role{color:var(--dimmer);font-size:13.5px}
.arrows{display:flex;gap:8px}
.arw{width:42px;height:42px;border-radius:999px;border:1px solid var(--line);background:none;cursor:pointer;
  display:grid;place-items:center;transition:background .45s var(--ease),border-color .45s ease,transform .45s var(--ease)}
.arw:hover{background:var(--white);color:#000;border-color:var(--white)}
.arw:active{transform:scale(.94)}
.big-stat{margin-top:clamp(48px,6vw,86px);display:flex;align-items:flex-end;gap:20px}
.big-stat .n{font-size:clamp(46px,6vw,82px);line-height:.9;letter-spacing:-.05em}
@media (max-width:760px){.tst{grid-template-columns:1fr}.tst-mark{max-width:180px}}

/* logo marquee */
.marq{margin-top:clamp(44px,5vw,72px);overflow:hidden;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:26px 0;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent)}
.marq-track{display:flex;gap:clamp(40px,6vw,92px);width:max-content;animation:slide 34s linear infinite}
.marq:hover .marq-track{animation-play-state:paused}
@keyframes slide{to{transform:translateX(-50%)}}
.marq-track span{font-size:clamp(17px,1.7vw,23px);letter-spacing:-.02em;color:#565351;white-space:nowrap;transition:color .4s ease}
.marq-track span:hover{color:var(--white)}

/* ===================== PRICING ===================== */
.price-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(12px,1.4vw,20px)}
.plan{
  border:1px solid var(--line);padding:clamp(22px,2.6vw,36px);display:flex;flex-direction:column;
  position:relative;overflow:hidden;background:var(--ink);
  transition:border-color .6s var(--ease),transform .6s var(--ease),background .6s var(--ease);
}
.plan::before{
  content:'';position:absolute;left:0;right:0;top:0;height:1px;background:var(--orange);
  transform:scaleX(0);transform-origin:left;transition:transform .8s var(--ease);
}
.plan:hover{transform:translateY(-6px);border-color:rgba(255,255,255,.2)}
.plan:hover::before{transform:scaleX(1)}
.plan.feat{background:#0d0b0a;border-color:rgba(255,74,16,.32)}
.plan.feat::before{transform:scaleX(1)}
.plan-top{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
.badge{font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--orange);
  border:1px solid rgba(255,74,16,.36);padding:4px 9px;border-radius:999px;white-space:nowrap;
  font-family:var(--mono)}
.plan h3{font-size:17px;font-weight:400;letter-spacing:-.02em;margin:0}
.plan .blurb{color:var(--dim);font-size:13.5px;margin:8px 0 0;line-height:1.5;min-height:40px}
.price{display:flex;align-items:flex-end;gap:8px;margin:26px 0 4px}
.price .amt{font-size:clamp(36px,4vw,54px);line-height:.92;letter-spacing:-.05em}
.price .per{color:var(--dimmer);font-size:13px;padding-bottom:5px}
.plan .from{color:var(--dimmer);font-size:12.5px;margin:0 0 24px}
.feats{list-style:none;margin:0 0 30px;padding:22px 0 0;border-top:1px solid var(--line);display:grid;gap:11px;flex:1}
/* two children only: the tick and one text block. A bare <strong> used to
   become a third flex item and inherit the gap, which opened a hole in the
   middle of the line. */
.feats li{display:flex;gap:11px;font-size:13.8px;color:#c9c5c2;line-height:1.45}
.feats li>span{flex:1;min-width:0}
.feats li strong{color:var(--white);font-weight:500}
.feats svg{flex:0 0 auto;margin-top:4px;color:var(--orange)}
/* the old .btn component was replaced by .cbtn (see above) */
.price-note{margin-top:28px;display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;
  border-top:1px solid var(--line);padding-top:18px}
@media (max-width:960px){.price-grid{grid-template-columns:1fr;gap:14px}}

/* ===================== FAQ ===================== */
.faq{border-top:1px solid var(--line)}
.faq-item{border-bottom:1px solid var(--line)}
.faq-q{
  width:100%;background:none;border:0;cursor:pointer;text-align:left;
  padding:clamp(18px,2.2vw,28px) 0;display:flex;justify-content:space-between;align-items:center;gap:20px;
  font-size:clamp(15px,1.5vw,20px);letter-spacing:-.02em;transition:color .4s ease;
}
.faq-q:hover{color:var(--orange)}
.faq-ico{position:relative;width:13px;height:13px;flex:0 0 auto}
.faq-ico::before,.faq-ico::after{content:'';position:absolute;background:currentColor;transition:transform .55s var(--ease),opacity .35s ease}
.faq-ico::before{top:6px;left:0;width:13px;height:1.2px}
.faq-ico::after{left:6px;top:0;width:1.2px;height:13px}
.faq-item.open .faq-ico::after{transform:rotate(90deg);opacity:0}
.faq-a{max-height:0;overflow:hidden;transition:max-height .7s var(--ease)}
.faq-a p{margin:0 0 clamp(18px,2.2vw,28px);color:var(--dim);font-size:14.6px;line-height:1.6;max-width:66ch}

/* ===================== FORM ===================== */
.form-shell{display:grid;grid-template-columns:1fr 1.35fr;gap:clamp(28px,4.6vw,80px);align-items:start}
.form-aside p{color:var(--dim);font-size:14.5px;line-height:1.6;margin:18px 0 0;max-width:36ch}
.aside-list{list-style:none;padding:22px 0 0;margin:26px 0 0;border-top:1px solid var(--line);display:grid;gap:13px}
.aside-list li{display:flex;gap:12px;align-items:baseline;font-size:13.8px;color:#c9c5c2}
.aside-list .k{color:var(--dimmer);font-family:var(--mono);font-size:10.5px;letter-spacing:.1em}
form{display:grid;gap:2px}
.f-row{display:grid;grid-template-columns:1fr 1fr;gap:2px}
.field{position:relative;background:var(--ink);border:1px solid var(--line);transition:border-color .45s ease,background .45s ease}
.field:focus-within{border-color:rgba(255,74,16,.5);background:#0a0806}
/* Floating label.
   At rest it has to read as a placeholder, so it is centred on the field —
   a fixed `top` left it sitting high, because the input's padding is
   deliberately top-heavy (26px/12px) to leave room for the floated state. */
.field label{
  position:absolute;left:16px;top:50%;
  transform:translateY(-50%);
  font-size:14.5px;color:var(--dimmer);pointer-events:none;
  transform-origin:left center;
  transition:top .4s var(--ease),transform .4s var(--ease),
             font-size .4s var(--ease),color .3s ease;
}
.field.filled label,.field:focus-within label{
  top:11px;transform:translateY(0) scale(.76);color:var(--dim);
  transform-origin:left top;
}
/* a textarea's label belongs at the top of the box, not the middle of it */
.field:has(textarea) label{top:15px;transform:none;transform-origin:left top}
.field:has(textarea).filled label,.field:has(textarea):focus-within label{
  top:11px;transform:scale(.76);
}
/* real placeholders share the value's baseline, and stay quiet */
.field input::placeholder,.field textarea::placeholder{color:var(--dimmer);opacity:1}
.field input,.field textarea,.field select{
  width:100%;background:none;border:0;outline:none;padding:26px 16px 12px;font-size:14.5px;letter-spacing:-.01em;
  /* display:block matters for the textarea: as an inline-block it sat on the
     parent's text baseline, and the line box reserved a few pixels of descender
     space beneath it. The browser draws the resize grip at the bottom of the
     textarea, so that phantom gap left the grip floating above the corner. */
  display:block;vertical-align:bottom;
}
.field textarea{resize:vertical;min-height:132px;line-height:1.5}
.field select{appearance:none;cursor:pointer;color:var(--white)}
.field select option{background:#0b0b0b;color:#f4f2f0}
.field .caret{position:absolute;right:16px;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--dimmer)}
.field.err{border-color:#c2341a}
.err-msg{display:none;color:#e0503a;font-size:11.5px;padding:0 16px 9px;font-family:var(--mono);letter-spacing:.05em}
.field.err .err-msg{display:block}
.chips{display:flex;flex-wrap:wrap;gap:7px;padding:16px}
.chip{
  border:1px solid var(--line);border-radius:999px;padding:8px 15px;font-size:13px;color:var(--dim);
  cursor:pointer;background:none;transition:all .4s var(--ease);
}
.chip:hover{color:var(--white);border-color:rgba(255,255,255,.3)}
.chip.on{background:var(--white);color:#000;border-color:var(--white)}
.chip-wrap{background:var(--ink);border:1px solid var(--line)}
.chip-wrap>.mono{padding:15px 16px 0;display:block}
.sent{
  display:none;border:1px solid rgba(255,74,16,.34);background:#0c0908;padding:clamp(26px,3.4vw,44px);text-align:center;
}
.sent.on{display:block;animation:fadeUp .8s var(--ease)}
.sent .tick{width:46px;height:46px;border-radius:999px;border:1px solid var(--orange);display:grid;place-items:center;margin:0 auto 20px;color:var(--orange)}
@media (max-width:900px){.form-shell{grid-template-columns:1fr}.f-row{grid-template-columns:1fr}}

/* ===================== CTA + FOOTER ===================== */
.cta{text-align:center;padding:clamp(80px,10vw,150px) 0 0}
.cta h2{font-size:clamp(38px,7.4vw,104px);line-height:.96;letter-spacing:-.045em;font-weight:400;margin:0}
.cta .cbtn{margin-top:38px}
.cta-strip{display:flex;justify-content:center;gap:clamp(10px,1.6vw,26px);margin-top:clamp(48px,6vw,88px);padding-bottom:clamp(40px,5vw,70px)}
.cta-strip figure{margin:0;width:clamp(88px,13vw,190px);aspect-ratio:1/1.24;overflow:hidden;background:#141414;
  transition:transform .8s var(--ease)}
.cta-strip figure img{width:100%;height:100%;object-fit:cover}
.cta-strip figure:nth-child(1){transform:rotate(-7deg) translateY(12px)}
.cta-strip figure:nth-child(2){transform:rotate(2deg)}
.cta-strip figure:nth-child(3){transform:rotate(7deg) translateY(12px)}
.cta-strip figure:nth-child(1):hover{transform:rotate(-3deg) translateY(0) scale(1.04)}
.cta-strip figure:nth-child(2):hover{transform:rotate(0) scale(1.04)}
.cta-strip figure:nth-child(3):hover{transform:rotate(3deg) translateY(0) scale(1.04)}
.cta-strip figure img{transition:filter .8s var(--ease)}
.cta-strip figure:hover img{filter:saturate(1.1)}

footer{border-top:1px solid var(--line);position:relative;z-index:2}
.f-top{display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:clamp(24px,3vw,48px);padding:clamp(44px,5vw,72px) 0}
.f-top h4{margin:0;font-weight:500}
.f-brand{align-self:start}
.f-blurb{font-size:14px;color:var(--dim);line-height:1.55;margin:18px 0 0;max-width:34ch}
.f-col{display:grid;gap:9px;align-content:start}
/* #fMenu exists only as somewhere for the renderer to write; display:contents
   takes it out of the box tree so its links are grid items of .f-col again.
   Without this they were all inside one grid cell, laid out inline, which is
   why they ended up on a single line. */
.f-col > #fMenu{display:contents}
.f-col a,.f-col span{font-size:14px;color:var(--dim);transition:color .35s ease;width:fit-content}
.f-col a:hover{color:var(--white)}
.f-bot{border-top:1px solid var(--line);display:flex;justify-content:space-between;gap:16px;padding:20px 0;flex-wrap:wrap}
/* The wordmark is deliberately cropped: it sits low enough that its bottom
   edge runs off the page, which makes it read as a printed mark bleeding off
   the sheet rather than a heading that happens to be at the bottom. */
.f-mark{overflow:hidden;padding:0 var(--gut)}
/* ---------- oversized footer wordmark ----------
   Two stacked copies of the same lockup:
     .logo      the base, in near-black — always readable, never touched
     .fm-glow   an identical copy in orange, masked to a soft circle that
                follows the pointer, so the letters light up as you sweep across
   The base layer means nothing can vanish if masks are unsupported — the
   previous version painted the text with background-clip and a transparent
   fill, so any failure there wiped the wordmark. */
.fm-row{display:flex;align-items:flex-start;justify-content:center;
  margin-bottom:-0.17em;padding-top:.06em}
.f-mark{--mx:50%;--my:50%;overflow:hidden;cursor:default}
.fm-stack{position:relative;display:inline-block;cursor:pointer}
.f-mark .logo,.fm-glow{
  font-family:var(--display);font-weight:500;
  font-size:clamp(38px,11.6vw,186px);line-height:.78;letter-spacing:-.05em;
  white-space:nowrap;display:block;
}
.f-mark .logo{
  color:#141312;
  transition:color 1s var(--ease),letter-spacing .9s var(--ease);
}
.fm-glow{
  position:absolute;inset:0;pointer-events:none;
  color:var(--orange);
  opacity:0;
  /* asymmetric timing: appears quickly, leaves slowly. A fast flick out used
     to read as a flicker because the fade was short enough to be caught
     mid-way by the next enter. */
  transition:opacity .85s var(--ease);
  -webkit-mask-image:radial-gradient(circle 190px at var(--gx,50%) var(--gy,50%),
    #000 0%,rgba(0,0,0,.55) 42%,transparent 72%);
  mask-image:radial-gradient(circle 190px at var(--gx,50%) var(--gy,50%),
    #000 0%,rgba(0,0,0,.55) 42%,transparent 72%);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  will-change:opacity;
}
/* the giant footer lockup drops the accent full stop — at that size it read
   as a stray dot rather than part of the mark */
.f-mark .logo::after,.fm-glow::after{content:none}
/* .is-lit is set by app.js, not :hover — the pointer engine already knows
   exactly when the mark is under the cursor, and a class lets the fade finish
   without being retriggered by sub-pixel enter/leave chatter at the edges. */
.f-mark.is-lit .fm-glow{opacity:1;transition:opacity .3s var(--ease)}
.f-mark.is-lit .logo{color:#1e1c1a;letter-spacing:-.046em}
/* no mask support: keep the base layer only, rather than risk empty text */
@supports not ((mask-image:radial-gradient(#000,#000)) or (-webkit-mask-image:radial-gradient(#000,#000))){
  .fm-glow{display:none}
}
@media (hover:none),(pointer:coarse){.fm-glow{display:none}}
@media (max-width:560px){
  .fm-glow{-webkit-mask-image:radial-gradient(circle 110px at var(--gx,50%) var(--gy,50%),#000 0%,transparent 70%);
    mask-image:radial-gradient(circle 110px at var(--gx,50%) var(--gy,50%),#000 0%,transparent 70%)}
}
@media (max-width:800px){.f-top{grid-template-columns:1fr 1fr}.f-brand{grid-column:1/3}}

/* ===================== PROJECT VIEW (in-app route) =====================
   Full-screen panel that slides over the page. No navigation ever leaves
   the document — routing is pushState + popstate only. */
.pview{
  position:fixed;inset:0;z-index:80;background:var(--black);
  overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;
  transform:translateY(2.5%);opacity:0;visibility:hidden;
  transition:transform .8s var(--ease),opacity .55s var(--ease),visibility .8s;
  -webkit-overflow-scrolling:touch;
}
.pview.on{transform:none;opacity:1;visibility:visible}
.pbar{
  position:sticky;top:0;z-index:10;background:rgba(0,0,0,.72);
  backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  border-bottom:1px solid var(--line);
}
.pbar .wrap{display:flex;align-items:center;justify-content:space-between;gap:16px;height:var(--nav-h)}
/* A hairline under the bar while the cover image is still decoding. It is a
   real wait — a full-width photograph — and an indeterminate sweep is the
   honest way to show a wait whose length is unknown. */
.pbar::after{
  content:'';position:absolute;left:0;bottom:-1px;height:2px;width:100%;
  background:linear-gradient(90deg,transparent,var(--orange),transparent);
  transform:translateX(-100%);opacity:0;
}
.pview.loading .pbar::after{opacity:1;animation:pload 1.15s var(--ease) infinite}
@keyframes pload{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
/* the body waits with it, so nothing appears half-built */
#pbodyMount{transition:opacity .5s ease}
.pview.loading #pbodyMount{opacity:0}

/* ---- moving from one project to the next ----
   Out to the left, in from the right: the direction says "forward", which a
   cross-fade cannot. */
.pview.swap #pbodyMount{
  opacity:0;transform:translateX(-3%);
  transition:opacity .3s ease,transform .35s var(--ease);
}
#pbodyMount{transition:opacity .5s ease,transform .6s var(--ease)}
.pview.swap-in #pbodyMount{
  opacity:0;transform:translateX(3%);transition:none;
}
.pback{
  display:inline-flex;align-items:center;gap:10px;font-size:14px;cursor:pointer;
  background:none;border:0;padding:0;color:var(--white);
}
.pback .c{
  width:34px;height:34px;border-radius:999px;border:1px solid var(--line);display:grid;place-items:center;
  transition:background .45s var(--ease),border-color .45s ease,transform .45s var(--ease);
}
.pback:hover .c{background:var(--white);color:#000;border-color:var(--white);transform:translateX(-3px)}

/* This element carries .wrap, which supplies the page gutter as
   `padding:0 var(--gut)`. A three-value shorthand here set the horizontal side
   to 0 and won on source order, so the title, kicker and meta table sat flush
   against the screen edges while the rest of the page was inset. Vertical
   longhands only — the same mistake as .pre-in and .menu-in. */
.phero{
  padding-top:clamp(44px,6vw,96px);
  padding-bottom:clamp(28px,3.4vw,52px);
}
.pkicker{display:flex;justify-content:space-between;gap:16px;border-top:1px solid var(--line);padding-top:14px;margin-bottom:clamp(26px,3.2vw,48px)}
.ptitle{font-size:clamp(38px,7vw,96px);line-height:.98;letter-spacing:-.045em;font-weight:400;margin:0}
.psub{font-size:clamp(16px,1.5vw,21px);line-height:1.45;color:var(--dim);margin:22px 0 0;max-width:52ch;letter-spacing:-.012em}
.pmeta{
  display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(14px,2vw,28px);
  border-top:1px solid var(--line);margin-top:clamp(34px,4vw,60px);padding-top:22px;
}
.pmeta div>span{display:block}
.pmeta div>span+span{font-size:14px;margin-top:8px;color:#d3cfcc;line-height:1.4;letter-spacing:-.01em}
.pshot{margin:clamp(28px,3.6vw,60px) 0;overflow:hidden;background:#0e0e0e;border:1px solid var(--line)}
.pshot img{width:100%;height:auto;display:block}
.pcap{display:flex;justify-content:space-between;gap:16px;margin-top:12px}
.pbody{display:grid;grid-template-columns:1fr 1.5fr;gap:clamp(24px,4vw,72px);padding:clamp(24px,3vw,50px) 0}
.pbody p{margin:0 0 18px;color:var(--dim);font-size:15.4px;line-height:1.62;max-width:62ch}
.pbody p:last-child{margin-bottom:0}
.pbody strong{color:var(--white);font-weight:400}
.pgrid2{display:grid;grid-template-columns:1fr 1fr;gap:clamp(14px,1.8vw,26px)}
.pstats{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(18px,2.6vw,40px);
  border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:clamp(28px,3.4vw,52px) 0;margin:clamp(20px,2.6vw,40px) 0}
.pstats .n{font-size:clamp(32px,3.8vw,52px);line-height:1;letter-spacing:-.045em;display:block}
.pstats .l{font-size:13.6px;color:var(--dim);margin-top:11px;line-height:1.4}
.pnext{
  display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;
  padding:clamp(34px,4.4vw,72px) 0 clamp(60px,7vw,110px);cursor:pointer;
}
.pnext-t{font-size:clamp(28px,4.4vw,60px);letter-spacing:-.04em;line-height:1.02;transition:color .5s var(--ease)}
.pnext:hover .pnext-t{color:var(--orange)}
.pnext-thumb{width:clamp(96px,11vw,168px);aspect-ratio:16/11;overflow:hidden;flex:0 0 auto;border:1px solid var(--line)}
.pnext-thumb img{width:100%;height:100%;object-fit:cover;transition:transform .9s var(--ease)}
.pnext:hover .pnext-thumb img{transform:scale(1.08)}
@media (max-width:820px){
  .pmeta{grid-template-columns:1fr 1fr}
  .pbody{grid-template-columns:1fr;gap:18px}
  .pgrid2{grid-template-columns:1fr}
  .pstats{grid-template-columns:1fr;gap:24px}
}

/* ============================================================================
   CURSOR
   Two layers driven by a spring in app.js.

     .cur-dot   the pointer itself. Visible in EVERY state — it never blinks
                out. In the text state it stretches into the I-beam rather than
                being replaced by one, so there is always exactly one thing
                following your hand.
     .cur-ring  a companion that lags behind and squashes along its direction
                of travel.

   Colour is pure black or pure white — never a blend. app.js samples the
   background under the pointer and sets --cur-ink / --cur-paper accordingly,
   so the cursor is always maximum contrast against whatever it is over. The
   old version used mix-blend-mode:difference, which produced muddy greys over
   mid-tone surfaces like the orange hero.

   States
     .is-link   ring grows, gains a second hairline
     .is-media  ring fills; the dot inverts and stays at the centre; the label
                sits in a pill BELOW the ring so it never fights the dot
     .is-text   dot becomes the I-beam, ring steps aside
   ========================================================================== */
:root{--cur-ink:#ffffff;--cur-paper:#000000}
body.cur-dark{--cur-ink:#000000;--cur-paper:#ffffff}

.cur-dot,.cur-ring{
  position:fixed;top:0;left:0;pointer-events:none;z-index:210;
  will-change:transform;
  transform:translate3d(-100px,-100px,0);
  opacity:0;
}
body.cur-on .cur-dot,body.cur-on .cur-ring{opacity:1}

.cur-dot{
  width:6px;height:6px;margin:-3px 0 0 -3px;border-radius:99px;
  background:var(--cur-ink);
  transition:width .32s var(--ease),height .32s var(--ease),margin .32s var(--ease),
             border-radius .32s var(--ease),background .25s ease,opacity .25s ease;
}
.cur-ring{
  width:34px;height:34px;margin:-17px 0 0 -17px;border-radius:99px;
  border:1px solid var(--cur-ink);
  display:grid;place-items:center;
  opacity:0;
  transition:width .5s var(--ease),height .5s var(--ease),margin .5s var(--ease),
             border-radius .45s var(--ease),background .4s var(--ease),
             border-color .3s ease,border-width .3s var(--ease),opacity .35s ease;
}
body.cur-on .cur-ring{opacity:.5}

/* the ring's second hairline, used on links */
.cur-ring::before{
  content:'';position:absolute;inset:4px;border-radius:inherit;
  border:1px solid var(--cur-ink);
  opacity:0;transform:scale(.8);
  transition:opacity .35s var(--ease),transform .45s var(--ease);
}

/* --- link / button --------------------------------------------------- */
.cur-ring.is-link{width:50px;height:50px;margin:-25px 0 0 -25px}
body.cur-on .cur-ring.is-link{opacity:.85}
.cur-ring.is-link::before{opacity:.35;transform:none}
.cur-dot.is-link{width:5px;height:5px;margin:-2.5px 0 0 -2.5px}

/* --- magnetic snap --------------------------------------------------- */
/* --- magnetic buttons keep the normal growing ring ---
   Snapping the ring to the element's own box drew what read as a stray outline
   around something that already has an edge, and the soft-wash alternative was
   so faint the circle looked like it had vanished. Buttons use .is-link. */

/* --- project cover ---
   The ring becomes a solid disc with the word inside it. This is the whole
   cursor in that state, so the dot steps aside — a dot in the middle of the
   word would just collide with it. */
.cur-ring.is-media{
  width:84px;height:84px;margin:-42px 0 0 -42px;
  background:var(--cur-ink);border-color:transparent;
}
body.cur-on .cur-ring.is-media{opacity:1}
.cur-ring.is-media::before{opacity:0}
.cur-dot.is-media{opacity:0}
.cur-label{
  font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--cur-paper);white-space:nowrap;
  opacity:0;transform:scale(.8);
  transition:opacity .28s var(--ease),transform .4s var(--ease);
}
.cur-ring.is-media .cur-label{opacity:1;transform:none}

/* --- reading / typing: the dot BECOMES the caret --------------------- */
.cur-dot.is-text{
  width:2px;height:23px;margin:-11.5px 0 0 -1px;border-radius:1px;
}
body.cur-on .cur-ring.is-text{opacity:0}
.cur-ring.is-text{width:24px;height:24px;margin:-12px 0 0 -12px}

/* hide the native pointer wherever ours is doing the work */
@media (hover:hover) and (pointer:fine){
  body.cur-on,body.cur-on a,body.cur-on button,body.cur-on .wcard,
  body.cur-on .faq-q,body.cur-on .plan,body.cur-on .cs,body.cur-on .cs-opt{cursor:none}
}
/* Capability only — never width. A desktop window narrowed to 400px still has
   a mouse, so the custom cursor must stay. It only goes away on devices that
   genuinely have no fine pointer. */
@media (hover:none),(pointer:coarse){
  .cur-dot,.cur-ring{display:none}
}
@media (prefers-reduced-motion:reduce){
  .cur-dot,.cur-ring{display:none}
}

/* ============================================================================
   HOVER — the interactive layer the cursor plays against
   ========================================================================== */

/* --- spotlight fill: a warm light that tracks the pointer inside buttons.
       --mx / --my are set by app.js on pointermove. --- */
.cbtn,.plan,.svc,.chip{--mx:50%;--my:50%}
.chip::after{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(120px circle at var(--mx) var(--my),rgba(255,255,255,.16),transparent 65%);
  opacity:0;transition:opacity .4s var(--ease);z-index:3;
}
.chip:hover::after{opacity:1}

/* --- services: the glow follows the pointer instead of sitting bottom-left --- */
.svc::before{
  background:radial-gradient(260px circle at var(--mx) var(--my),rgba(255,74,16,.20),transparent 70%);
}

/* --- pricing plans: same idea, cooler --- */
.plan::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(320px circle at var(--mx) var(--my),rgba(255,255,255,.055),transparent 70%);
  opacity:0;transition:opacity .5s var(--ease);
}
.plan:hover::after{opacity:1}

/* --- project cards: subtle 3D tilt + counter-parallax on the cover ---
       --rx/--ry (deg) and --px/--py (%) come from app.js --- */
.wcard{--rx:0deg;--ry:0deg;--px:50%;--py:50%}
.wcard-media{
  transform:perspective(900px) rotateX(var(--rx)) rotateY(var(--ry));
  transform-style:preserve-3d;
  transition:transform .5s var(--ease);
}
.wcard-media img{object-position:var(--px) var(--py)}
.wcard.is-hot .wcard-media{transition:transform .12s linear}

/* --- nav links: letters lift in sequence --- */
.lift{display:inline-block;white-space:pre}
.lift span{
  display:inline-block;
  transition:transform .5s var(--ease),color .4s ease;
  transition-delay:calc(var(--i) * 22ms);
}
.nav-links a:hover .lift span{transform:translateY(-3px)}

/* --- pull toward the pointer, for the big CTA and plan buttons ---
   app.js writes --mtx / --mty rather than `transform`, so :hover and :active
   transforms still compose with the drift instead of being overridden. */
.cbtn,[data-magnet]{--mtx:0px;--mty:0px}
[data-magnet]{transform:translate(var(--mtx),var(--mty));transition:transform .5s var(--ease)}
/* .cbtn already animates transform for its hover lift; the drift composes into
   the same declaration so the two never fight.
   NOTE: never put data-rv on a .cbtn — the reveal's `transform:none` has higher
   specificity and would cancel the drift. Wrap the button instead. */
.cbtn{transform:translate(var(--mtx),var(--mty))}

/* the footer wordmark's pointer effect lives with the footer styles above —
   this is where the old background-clip version used to be. It painted the
   text transparent, so any failure in the gradient made the wordmark vanish;
   the replacement stacks a masked overlay over an always-visible base. */

/* ===================== REVEAL ===================== */
[data-rv]{opacity:0;transform:translateY(26px);transition:opacity 1s var(--ease),transform 1.1s var(--ease)}
[data-rv].in{opacity:1;transform:none}

/* ============================================================================
   SECTION ENTRANCE

   app.js adds .sec-in to a section the frame it comes into view, and everything
   below hangs off that one class. Four things, offset from each other, so the
   section assembles rather than appearing:

     the label      slides in from the left
     the number     from the right
     the rule       an accent hairline sweeps across it
     the heading    lifts word by word out of its own mask
     the grid       children rise on a stagger

   Transform and opacity only. Nothing here animates a layout property, so a
   section arriving mid-scroll cannot cause a reflow while the page is moving.
   ========================================================================== */

/* ---- the rule and its two labels ---- */
.sec-head{position:relative}
/* the accent sweep rides on top of the existing border-top */
.sec-head::before{
  content:'';position:absolute;left:0;right:0;top:-1px;height:1px;
  background:linear-gradient(90deg,var(--orange),rgba(255,74,16,0) 62%);
  transform:scaleX(0);transform-origin:left;
  transition:transform 1.4s var(--ease) .1s;
}
.sec-in .sec-head::before{transform:scaleX(1)}

.sec-head > .mono{
  opacity:0;transition:opacity .7s ease .12s,transform .9s var(--ease) .12s;
}
.sec-head > .mono:first-child{transform:translateX(-14px)}
.sec-head > .mono:last-child{transform:translateX(14px)}
.sec-in .sec-head > .mono{opacity:1;transform:none}

/* ---- the heading, word by word ----
   The padding is what stops an overflow:hidden inline-block from clipping the
   descenders off g, y and p; the equal negative margin puts the baseline back
   where it was, so the line spacing of the heading is untouched. */
.w{
  display:inline-block;overflow:hidden;vertical-align:baseline;
  padding-bottom:.16em;margin-bottom:-.16em;
}
.w > span{
  display:inline-block;
  transform:translateY(108%);
  transition:transform 1.05s var(--ease);
}
.sec-in .w > span,.cta.sec-in .w > span{
  transform:none;
  transition-delay:calc(var(--wi,0) * 42ms + .16s);
}

/* ---- the grids below, staggered ----
   Written out per child rather than generated: a stagger index cannot be
   computed in CSS, and the alternative is carrying one in the markup AND in the
   renderer and keeping the two in step. These lists are bounded, so a handful of
   rules costs less than that.

   Only the three lists that had no reveal of their own are touched here. The
   plans, the figures and the closing photo strip already carry [data-rv] with
   their own delays; adding a second treatment would fight the first. */
.svc-grid .svc{
  opacity:0;transform:translateY(20px);
  transition:opacity .9s var(--ease),transform 1s var(--ease);
}
.svc-grid.in .svc{opacity:1;transform:none}
.svc-grid.in .svc:nth-child(1){transition-delay:.04s}
.svc-grid.in .svc:nth-child(2){transition-delay:.10s}
.svc-grid.in .svc:nth-child(3){transition-delay:.16s}
.svc-grid.in .svc:nth-child(4){transition-delay:.22s}
.svc-grid.in .svc:nth-child(5){transition-delay:.28s}
.svc-grid.in .svc:nth-child(6){transition-delay:.34s}
.svc-grid.in .svc:nth-child(n+7){transition-delay:.40s}

.faq .faq-item{opacity:0;transform:translateY(16px);
  transition:opacity .8s var(--ease),transform .9s var(--ease)}
.faq.in .faq-item{opacity:1;transform:none}
.faq.in .faq-item:nth-child(1){transition-delay:.05s}
.faq.in .faq-item:nth-child(2){transition-delay:.10s}
.faq.in .faq-item:nth-child(3){transition-delay:.15s}
.faq.in .faq-item:nth-child(4){transition-delay:.20s}
.faq.in .faq-item:nth-child(5){transition-delay:.25s}
.faq.in .faq-item:nth-child(6){transition-delay:.30s}
.faq.in .faq-item:nth-child(7){transition-delay:.35s}
.faq.in .faq-item:nth-child(n+8){transition-delay:.40s}

.aside-list li{opacity:0;transform:translateX(-12px);
  transition:opacity .7s var(--ease),transform .8s var(--ease)}
.sec-in .aside-list li{opacity:1;transform:none}
.sec-in .aside-list li:nth-child(1){transition-delay:.22s}
.sec-in .aside-list li:nth-child(2){transition-delay:.30s}
.sec-in .aside-list li:nth-child(3){transition-delay:.38s}
.sec-in .aside-list li:nth-child(4){transition-delay:.46s}

/* ---- a project page gets the same treatment as a section ----
   .psec is what app.js marks up in the injected markup, and the observer that
   watches .sec watches these too, so the rules above (the sweeping rule, the
   sliding label, the word-by-word lift) all apply without being written twice. */
.psec{position:relative}
.pkicker{position:relative}
.pkicker::before{
  content:'';position:absolute;left:0;right:0;top:-1px;height:1px;
  background:linear-gradient(90deg,var(--orange),rgba(255,74,16,0) 62%);
  transform:scaleX(0);transform-origin:left;
  transition:transform 1.4s var(--ease) .1s;
}
.psec.sec-in .pkicker::before{transform:scaleX(1)}
.pkicker .mono{opacity:0;transition:opacity .7s ease .12s,transform .9s var(--ease) .12s}
.pkicker .mono:first-child{transform:translateX(-14px)}
.pkicker .mono:last-child{transform:translateX(14px)}
.psec.sec-in .pkicker .mono{opacity:1;transform:none}

/* ---- the band: the word settles out of a blur as the plate arrives ---- */
.band-word{
  opacity:0;filter:blur(14px);
  transform:translateY(-50%) scale(1.06);
  transition:opacity 1.1s ease .1s,filter 1.4s var(--ease) .1s,transform 1.6s var(--ease) .1s;
}
.band.sec-in .band-word{opacity:1;filter:none;transform:translateY(-50%) scale(1)}
.band-meta .mono{opacity:0;transition:opacity .9s ease .45s}
.band.sec-in .band-meta .mono{opacity:1}

/* ---- the marquee only runs once it is worth watching ---- */
.marq-track{animation-play-state:paused}
.sec-in .marq-track{animation-play-state:running}

@media (prefers-reduced-motion:reduce){
  .sec-head::before,.sec-head > .mono,.w > span,.band-word,.band-meta .mono,
  .svc-grid .svc,.faq .faq-item,.aside-list li{
    transition:none!important;opacity:1!important;filter:none!important;
    transform:none!important;
  }
  .band-word{transform:translateY(-50%)!important}
  .marq-track{animation-play-state:running}
}
.rv-w{display:inline-block;overflow:hidden;vertical-align:top}
.rv-w>i{display:inline-block;font-style:normal;transform:translateY(105%);transition:transform 1s var(--ease)}
.in .rv-w>i,.rv-w.in>i{transform:none}
@media (prefers-reduced-motion:reduce){
  [data-rv],.rv-w>i{opacity:1!important;transform:none!important;transition:none!important}
  .marq-track,.hero-scroll .dotline{animation:none!important}
  .hero-media img{transform:none!important;opacity:1!important}
}

/* ====================================================================
   RESPONSIVE LAYER
   Tiers, largest to smallest. Everything above this point is the
   desktop baseline; these blocks only ever simplify.
     1200  small laptop
     1024  tablet landscape
      860  tablet portrait  (top nav gives way to the bottom bar)
      700  large phone
      560  phone
      420  small phone
      360  smallest phone (iPhone SE / Android compact)
   ==================================================================== */

/* ------------------------------------------------ 1200 small laptop */
@media (max-width:1200px){
  .nav-links{gap:22px}
  .nav-links a{font-size:13.5px}
  .col-main{grid-column:3 / 13}
  .col-side{grid-column:1 / 3}
}

/* --------------------------------------------- 1024 tablet landscape */
@media (max-width:1024px){
  :root{--nav-h:60px}
  .nav-links{gap:18px}
  .nav-cta{--cta-h:36px;font-size:13px;padding:0 5px 0 15px;gap:9px}
  .nav-cta .arw-c{width:26px;height:26px}
  .col-main,.col-side{grid-column:1 / 13}
  .grid{gap:14px}
  .price-grid{grid-template-columns:1fr 1fr}
  .price-grid .plan:last-child{grid-column:1 / -1}
  .pmeta{grid-template-columns:1fr 1fr;gap:20px}
  .form-shell{grid-template-columns:1fr 1.15fr}
}

/* ----------------------------------------- 860 tablet portrait / nav */
@media (max-width:860px){
  :root{--nav-h:58px}
  /* The menu carries Start, so a pill here would be a duplicate — and a full
     pill next to a 17px logo made the header top-heavy. The header becomes the
     logo and the burger, which is all it needs to be. */
  .nav-right{display:none}
  .nav .wrap{gap:10px}
  .nav-links{display:none}
  .rules{display:none}
  .hero-meta .mono{line-height:1.85}
  .band-word{top:50%}
  .price-grid{grid-template-columns:1fr}
  .price-grid .plan:last-child{grid-column:auto}
  .plan .blurb{min-height:0}
  .pbar .logo{display:none}
}

/* -------------------------------------------------- 700 large phone */
@media (max-width:700px){
  .sec{padding:clamp(52px,11vw,78px) 0}
  .sec-head{margin-bottom:30px}
  .hero{min-height:520px}
  /* label drops above the name, with room to breathe; Mehdi + Bagheri
     stay on one line and keep their shared baseline */
  .hero-name{flex-direction:column;align-items:flex-start;gap:0;margin-top:clamp(22px,5vh,48px)}
  .hero-name .side{padding-bottom:0;font-size:13px;margin-bottom:14px}
  .hero-name-row{justify-content:flex-start;width:100%;gap:8px}
  .hero-name-row .side{margin-bottom:0;padding-bottom:clamp(5px,1.4vw,10px)}
  .hero-scroll{display:none}
  .wordmark{font-size:clamp(58px,20vw,120px)}

  .about-row{flex-direction:column;gap:22px}
  .about-portrait{width:98px}
  .stats{grid-template-columns:1fr 1fr;gap:26px 18px;margin-top:44px}
  .stat .num{font-size:clamp(32px,10vw,44px)}

  .work-top{flex-direction:column;align-items:flex-start;gap:10px}
  .svc{min-height:0;padding:24px 20px}
  .svc-n{top:24px;right:20px}

  .tst{grid-template-columns:1fr;gap:22px}
  .tst-mark{max-width:150px}
  .tst-quote{font-size:17px;margin:18px 0 22px}
  .big-stat{flex-direction:column;align-items:flex-start;gap:10px;margin-top:44px}

  .form-shell{grid-template-columns:1fr;gap:28px}
  .f-row{grid-template-columns:1fr}

  .cta-strip{gap:8px}
  .f-top{grid-template-columns:1fr;gap:30px}
  .f-brand{grid-column:auto}
  .f-bot{flex-direction:column;gap:8px}

  .phero{padding:32px 0 22px}
  .pbody{grid-template-columns:1fr;gap:14px}
  .pgrid2{grid-template-columns:1fr}
  .pstats{grid-template-columns:1fr;gap:22px;padding:26px 0}
  .pnext{flex-direction:row;align-items:center;padding:32px 0 64px}
}

/* -------------------------------------------------------- 560 phone */
@media (max-width:560px){
  :root{--nav-h:56px}
  .logo{font-size:17px}
  .nav-cta{--cta-h:34px;font-size:12.5px;padding:0 4px 0 13px;gap:8px}
  .nav-cta .arw-c{width:26px;height:26px}

  .h-xl{font-size:clamp(26px,7.4vw,34px)}
  .h-lg{font-size:clamp(24px,6.8vw,31px)}
  .h-md{font-size:clamp(19px,5.4vw,24px)}
  .mono{font-size:9.8px;letter-spacing:.12em}

  .years{font-size:26px}
  .work-grid{gap:28px}
  .wcard-media{aspect-ratio:4/3}
  .wcard.wide .wcard-media{aspect-ratio:4/3}
  .wcard-title{font-size:18px}
  .wcard-foot{gap:12px}
  .tag{font-size:11px;padding:3px 9px}

  .band{height:clamp(280px,64vw,360px)}
  .marq-track{gap:34px}
  .marq{padding:20px 0}

  .plan{padding:22px 18px}
  .price .amt{font-size:34px}
  .feats{margin-bottom:24px;padding-top:18px}
  .cbtn{--cta-h:42px;font-size:14px;padding:0 5px 0 18px}

  .faq-q{font-size:15px;padding:16px 0}
  .faq-a p{font-size:14px}

  .field input,.field textarea,.field select{padding:24px 14px 11px;font-size:15px}
  .field label{left:14px}
  .field.filled label,.field:focus-within label{top:10px}
  .chips{padding:14px;gap:6px}
  .chip{padding:7px 13px;font-size:12.5px}

  .cta h2{font-size:clamp(30px,9.4vw,42px)}
  .cta-strip{margin-top:38px;padding-bottom:34px}
  .cta-strip figure{width:30%}

  .ptitle{font-size:clamp(30px,9vw,44px)}
  .psub{font-size:15px;margin-top:16px}
  .pmeta{grid-template-columns:1fr 1fr;gap:16px;margin-top:26px}
  .pnext-t{font-size:clamp(22px,7vw,32px)}
}

/* -------------------------------------------------- 420 small phone */
@media (max-width:420px){
  /* the CTA keeps its label at every width — only the metrics tighten, so the
     button never changes shape between breakpoints */
  :root{--nav-h:54px}
  .nav .wrap{gap:8px}
  .nav-cta{--cta-h:33px;font-size:12px;padding:0 4px 0 12px;gap:7px}
  .nav-cta .arw-c{width:25px;height:25px}
  .nav-right{gap:4px}

  .wordmark{font-size:clamp(44px,17vw,72px)}
  .hero-name .side{font-size:12.5px;margin-bottom:11px}
  .hero-name-row{gap:6px}
  .hero-meta .mono{line-height:1.7;font-size:9px;letter-spacing:.08em}
  .stats{grid-template-columns:1fr;gap:22px}
  .about-portrait{width:84px}

  .svc{padding:20px 16px}
  .svc p{font-size:13.4px}
  .price-note{flex-direction:column;gap:8px}
  .arw{width:38px;height:38px}
  .tst-mark{max-width:126px}
  .tst-mark span{font-size:34px}

  .pmeta{grid-template-columns:1fr}
  .pnext{flex-direction:column;align-items:flex-start;gap:18px}
  .pnext-thumb{width:120px}
  .pback span:last-child{display:none}

  .cta-strip figure{width:31%}
  .f-mark .logo,.fm-glow{font-size:clamp(30px,12.4vw,48px)}
}

/* ---------------------------------------------- 360 smallest phone */
@media (max-width:360px){
  :root{--gut:14px;--nav-h:52px}
  .logo{font-size:16px}
  .nav-cta{--cta-h:31px;font-size:11.5px;padding:0 3px 0 11px;gap:6px}
  .nav-cta .arw-c{width:24px;height:24px}
  .wordmark{font-size:clamp(38px,15.5vw,54px)}
  .hero-name .side{font-size:11.5px;margin-bottom:9px}
  .hero-name-row{gap:5px}
  .hero-name-row .side{padding-bottom:4px}
  .h-xl{font-size:24px}
  .h-lg{font-size:22px}
  .mono{font-size:9.2px;letter-spacing:.1em}
  .price .amt{font-size:30px}
  .feats li{font-size:13px}
  .cta h2{font-size:27px}
}

/* --------------------------------- short landscape (phone rotated) */
@media (max-height:520px) and (orientation:landscape){
  .hero{height:auto;min-height:0;padding:calc(var(--nav-h) + 26px) 0 34px}
  .hero-media img{height:100%}
  .hero-inner{height:auto;padding-top:0}
  .hero-meta{margin-top:0}
  .hero-name{margin-top:22px}
  .hero-name .side{margin-bottom:8px}
  .wordmark{font-size:clamp(44px,10.5vw,82px)}
}

/* ------------------------------------------- input zoom / touch fix */
@media (hover:none){
  /* iOS zooms any input under 16px on focus */
  .field input,.field textarea,.field select{font-size:16px}
  .wcard-media img{transform:none}
  .cur{display:none}
  /* comfortable tap targets — but NOT the header CTA, which sets its own
     height and centres its contents */
  .chip,.faq-q,.arw{min-height:44px}
  .faq-q{align-items:center}
}

/* ============================================================================
   PLAN ORDER MODAL
   Opened from a pricing plan. Separate from the general project brief — these
   submissions land in their own "Orders" inbox in the admin panel.
   ========================================================================== */
.mo-back{
  position:fixed;inset:0;z-index:90;background:rgba(0,0,0,.72);
  backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);
  opacity:0;visibility:hidden;transition:opacity .45s var(--ease),visibility .45s;
  display:grid;place-items:center;padding:clamp(12px,3vw,40px);overflow-y:auto;
}
.mo-back.on{opacity:1;visibility:visible}
.mo{
  width:100%;max-width:620px;background:#0a0a09;border:1px solid var(--line);
  border-radius:16px;position:relative;
  transform:translateY(22px) scale(.985);opacity:0;
  transition:transform .6s var(--ease),opacity .45s var(--ease);
  max-height:calc(100dvh - 24px);display:flex;flex-direction:column;
}
.mo-back.on .mo{transform:none;opacity:1}
.mo-head{
  padding:clamp(20px,2.6vw,30px) clamp(20px,2.8vw,34px) 0;
  display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex:0 0 auto;
}
.mo-head h3{margin:10px 0 0;font-family:var(--display);font-weight:500;
  font-size:clamp(22px,2.6vw,31px);letter-spacing:-.03em;line-height:1.1;
  font-variation-settings:'opsz' 36}
.mo-head p{margin:9px 0 0;color:var(--dim);font-size:14px;line-height:1.55;max-width:44ch}
.mo-x{
  width:36px;height:36px;flex:0 0 auto;border-radius:999px;border:1px solid var(--line);
  background:none;cursor:pointer;display:grid;place-items:center;
  transition:background .4s var(--ease),border-color .4s ease,transform .4s var(--ease);
}
.mo-x:hover{background:var(--white);color:#000;border-color:var(--white);transform:rotate(90deg)}
.mo-body{padding:clamp(18px,2.4vw,26px) clamp(20px,2.8vw,34px);overflow-y:auto;flex:1 1 auto}

/* plan switcher inside the modal */
.mo-plans{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:22px}
.mo-plan{
  border:1px solid var(--line);background:var(--ink);
  padding:13px 12px;cursor:pointer;text-align:left;
  transition:border-color .35s var(--ease),background .35s var(--ease),transform .35s var(--ease);
}
.mo-plan:hover{border-color:rgba(255,255,255,.24);transform:translateY(-2px)}
.mo-plan.on{border-color:var(--orange);background:#120c08}
.mo-plan b{display:block;font-weight:500;font-size:13.5px;letter-spacing:-.015em}
.mo-plan i{display:block;font-style:normal;color:var(--dim);font-size:12.5px;margin-top:4px}
.mo-plan.on i{color:var(--orange)}

/* The only difference from the form on the landing page is how the boxes are
   spaced: there they are grid cells with a 2px gap, here they are siblings.
   Nothing else may differ — a rounded input inside the modal and a square one
   forty pixels up the page is the same form contradicting itself. */
.mo-body .field{margin-bottom:2px}
.mo-body .chip-wrap{margin-bottom:2px}
.mo-body>.mono{display:block;margin:20px 0 9px}
.mo-sent{text-align:center;padding:clamp(24px,4vw,44px) 0}
.mo-sent .tick{
  width:48px;height:48px;border-radius:999px;border:1px solid var(--orange);
  display:grid;place-items:center;margin:0 auto 18px;color:var(--orange);
}
.mo-sent h4{margin:0 0 10px;font-family:var(--display);font-weight:500;
  font-size:clamp(20px,2.3vw,26px);letter-spacing:-.028em}
@media (max-width:560px){
  .mo{border-radius:14px}
  .mo-plans{grid-template-columns:1fr}
}

/* ============================================================================
   CUSTOM SELECT
   A native <select>'s dropdown list is drawn by the operating system — the blue
   highlight, the white panel, the font, none of it is reachable from CSS. So
   the real <select> stays in the DOM as the source of truth (form value,
   validation, keyboard, no-JS fallback) and app.js renders this listbox over
   it. Choosing an option writes back to the <select> and fires `change`, so
   every existing behaviour keeps working.
   ========================================================================== */
.field.has-cs select{
  /* kept for the form and for screen readers, but visually replaced */
  opacity:0;pointer-events:none;
}
.cs{
  position:absolute;inset:0;display:flex;align-items:flex-end;
  padding:0 38px 12px 16px;cursor:pointer;
  font-size:14.5px;letter-spacing:-.01em;color:var(--white);
  background:none;border:0;text-align:left;width:100%;
}
.cs-value{
  display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  transition:color .3s ease;
}
.cs-value.is-empty{color:transparent}
.field .caret{transition:transform .45s var(--ease),color .3s ease}
.field.cs-open .caret{transform:translateY(-50%) rotate(180deg);color:var(--orange)}
.field.cs-open{border-color:rgba(255,74,16,.5);background:#0a0806;z-index:30}

/* ---- the panel ---- */
.cs-panel{
  position:absolute;left:-1px;right:-1px;top:calc(100% + 6px);z-index:40;
  background:rgba(11,10,10,.94);
  backdrop-filter:blur(20px) saturate(1.3);-webkit-backdrop-filter:blur(20px) saturate(1.3);
  border:1px solid rgba(255,255,255,.13);
  box-shadow:0 24px 60px -18px rgba(0,0,0,.95),0 0 0 1px rgba(0,0,0,.5),
             inset 0 1px 0 rgba(255,255,255,.07);
  padding:6px;
  max-height:min(300px,52vh);overflow-y:auto;overscroll-behavior:contain;
  opacity:0;visibility:hidden;transform:translateY(-8px) scale(.985);
  transform-origin:top center;
  transition:opacity .28s var(--ease),transform .38s var(--ease),visibility .38s;
}
.cs-panel.up{top:auto;bottom:calc(100% + 6px);transform-origin:bottom center}
.field.cs-open .cs-panel{opacity:1;visibility:visible;transform:none}
/* ---- options ---- */
.cs-opt{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:10px 12px;border:0;background:none;cursor:pointer;
  font-size:14.2px;letter-spacing:-.01em;color:#c8c4c1;text-align:left;
  position:relative;transition:color .2s ease,background .2s ease;
}
.cs-opt::before{
  content:'';position:absolute;left:0;top:50%;width:2px;height:0;
  background:var(--orange);border-radius:2px;transform:translateY(-50%);
  transition:height .3s var(--ease);
}
.cs-opt:hover,.cs-opt.is-active{background:rgba(255,255,255,.055);color:var(--white)}
.cs-opt.is-active::before{height:16px}
.cs-opt .cs-tick{
  margin-left:auto;flex:0 0 auto;color:var(--orange);
  opacity:0;transform:scale(.6);transition:opacity .25s var(--ease),transform .35s var(--ease);
}
.cs-opt.is-sel{color:var(--white)}
.cs-opt.is-sel .cs-tick{opacity:1;transform:none}
.cs-opt.is-placeholder{color:var(--dimmer)}

/* staggered entrance, capped so long lists don't crawl in */
.field.cs-open .cs-opt{animation:csIn .34s var(--ease) both}
.field.cs-open .cs-opt:nth-child(1){animation-delay:.02s}
.field.cs-open .cs-opt:nth-child(2){animation-delay:.045s}
.field.cs-open .cs-opt:nth-child(3){animation-delay:.07s}
.field.cs-open .cs-opt:nth-child(4){animation-delay:.09s}
.field.cs-open .cs-opt:nth-child(5){animation-delay:.11s}
.field.cs-open .cs-opt:nth-child(n+6){animation-delay:.13s}
@keyframes csIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}

@media (prefers-reduced-motion:reduce){
  .cs-panel{transition:opacity .01s,visibility .01s}
  .field.cs-open .cs-opt{animation:none}
}
@media (max-width:560px){
  .cs{font-size:16px;padding-bottom:10px}
  .cs-opt{padding:12px 12px;font-size:15px}
  .cs-panel{max-height:min(46vh,280px)}
}

/* ---------- footer social row ---------- */
.f-social{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:4px}
.f-social a{
  display:inline-flex;align-items:center;justify-content:center;
  width:36px;height:36px;border-radius:999px;
  border:1px solid var(--line);color:var(--dim);
  transition:color .35s var(--ease),border-color .35s var(--ease),
             background .35s var(--ease),transform .45s var(--ease);
}
.f-social a:hover{
  color:#0a0a0a;background:var(--white);border-color:var(--white);
  transform:translateY(-2px);
}
.f-social a svg{width:16px;height:16px}
.f-social:empty{display:none}

/* ============================================================================
   REASSURANCE NOTE
   Sits directly under a primary action, not beside it: the button gets the full
   width it deserves and the promise reads as a consequence of pressing it.
   Green because it is a reassurance, not a warning.
   ========================================================================== */
.note-ok{
  display:flex;align-items:center;gap:10px;margin:0;
  padding:11px 14px;border-radius:10px;
  background:rgba(63,191,114,.07);
  border:1px solid rgba(63,191,114,.24);
  color:#8ee0ac;font-size:13.2px;line-height:1.45;letter-spacing:-.005em;
}
.note-ok svg{flex:0 0 auto;color:#3fbf72}
.note-ok span{min-width:0}

/* both footers stack: action first, promise underneath */
.form-foot{display:flex;flex-direction:column;align-items:stretch;gap:12px;margin-top:20px}
.form-foot .cbtn{width:100%}
.mo-foot{
  padding:16px clamp(20px,2.8vw,34px) clamp(20px,2.6vw,28px);
  border-top:1px solid var(--line);
  display:flex;flex-direction:column;align-items:stretch;gap:12px;
  flex:0 0 auto;background:#0a0a09;border-radius:0 0 16px 16px;
}
.mo-foot .cbtn{width:100%}
@media (max-width:560px){
  .note-ok{font-size:12.6px;padding:10px 12px}
}

/* ============================================================================
   BURGER + OVERLAY MENU  (below 860px)

   The trigger is two lines of deliberately different length. They equalise and
   cross into an X — the asymmetry at rest is what stops it reading as the
   default three-bar icon everyone ships.

   The panel is built from three layers moving at different speeds, which is the
   whole reason it feels considered rather than assembled:

     1. .menu-sheet   slides down over the page          (transform)
     2. .menu-in      trails behind it and settles       (transform)
     3. each link     lifts out of its own mask, staggered

   All three are compositor-only properties, so this stays smooth on a phone.
   Nothing animates height, and nothing animates a filter.

   Out is faster than in and loses the stagger — waiting for a considered
   animation to finish before the page responds is what makes menus feel slow.
   ========================================================================== */
.burger{
  display:none;
  position:relative;z-index:2;
  width:44px;height:44px;flex:0 0 auto;      /* a real tap target */
  margin-right:-10px;                        /* optically flush with the gutter */
  border:0;background:none;padding:0;cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.burger i{
  position:absolute;left:50%;top:50%;
  height:1.6px;border-radius:2px;background:var(--white);
  transition:width .5s var(--ease),transform .55s var(--ease),background .4s ease;
}
.burger i:nth-child(1){width:23px;transform:translate(-50%,calc(-50% - 4.5px))}
.burger i:nth-child(2){width:15px;transform:translate(-50%,calc(-50% + 4.5px))}
/* they line up before they cross — a two-stage tell that it is one control */
.burger:hover i:nth-child(2),.burger:focus-visible i:nth-child(2){width:23px}
body.menu-open .burger i{width:21px;background:var(--white)}
body.menu-open .burger i:nth-child(1){transform:translate(-50%,-50%) rotate(45deg)}
body.menu-open .burger i:nth-child(2){transform:translate(-50%,-50%) rotate(-45deg)}

/* ---------------------------------------------------------------- the panel */
/* Stacking, in order of who has to be on top of whom:
     .nav        60   the header
     .ck         70   the cookie notice
     .pview      80   a project page
     .mo-back    90   the order modal
     .menu       95   this — above all of those
     .pre       150   the loading screen, over the lot
     .cur-*     210   the custom cursor, above even that: the native pointer is
                      hidden on a fine-pointer device, so anything painted over
                      the cursor leaves the visitor with no pointer at all

   The header has to rise above the sheet while it is open, or the control that
   closes it would be painted behind it: .nav has its own z-index, which makes
   it a stacking context, so a z-index on .burger cannot escape 60 on its own. */
.menu{position:fixed;inset:0;z-index:95;overflow:hidden;pointer-events:none}
.menu.on{pointer-events:auto}
body.menu-open .nav{z-index:96}
/* while it is open the header must not slide away, whatever the scroll says */
body.menu-open .nav.hidden{transform:none}
/* and it must not paint its own dark bar over the sheet */
body.menu-open .nav.solid{background:none;backdrop-filter:none;-webkit-backdrop-filter:none;box-shadow:none}

.menu-sheet{
  position:absolute;inset:0;
  display:flex;flex-direction:column;
  background:#0a0908;
  transform:translateY(-100%);
  transition:transform .78s var(--ease);
  will-change:transform;
}
.menu.on .menu-sheet{transform:none}
/* a hairline of accent along the bottom edge as it lands */
.menu-sheet::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:2px;
  background:linear-gradient(90deg,var(--orange),transparent 72%);
  transform:scaleX(0);transform-origin:left;
  transition:transform .9s var(--ease) .12s;
}
.menu.on .menu-sheet::after{transform:scaleX(1)}

.menu-in{
  flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  display:flex;flex-direction:column;
  padding-top:calc(var(--nav-h) + clamp(14px,3.4vh,30px));
  padding-bottom:calc(clamp(20px,3.4vh,34px) + env(safe-area-inset-bottom));
  transform:translateY(26%);
  transition:transform .78s var(--ease);
}
.menu.on .menu-in{transform:none}

/* every child fades as a group, so nothing pops before the sheet has arrived */
.menu-in > *{opacity:0;transition:opacity .5s ease}
.menu.on .menu-in > *{opacity:1;transition-delay:.16s}

/* ------------------------------------------------------------------ header */
.menu-head{display:flex;align-items:center;gap:12px;margin-bottom:clamp(14px,2.6vh,26px)}
.menu-rule{
  flex:1 1 auto;height:1px;background:var(--line);
  transform:scaleX(0);transform-origin:left;
  transition:transform .9s var(--ease) .3s;
}
.menu.on .menu-rule{transform:scaleX(1)}

/* ------------------------------------------------------------------- links */
.menu-list{display:flex;flex-direction:column;border-top:1px solid var(--line-2)}
.menu-i{
  position:relative;display:grid;
  grid-template-columns:auto 1fr auto;align-items:center;gap:clamp(12px,3.4vw,20px);
  padding:clamp(11px,2.1vh,19px) 0;
  border-bottom:1px solid var(--line-2);
  color:var(--white);
  -webkit-tap-highlight-color:transparent;
}
/* The wash sweeps in from the left on press, under the text. It bleeds out to
   the screen edge by cancelling the wrapper's gutter — a highlight that stops
   at the text column reads as a box, not as a row being selected. */
.menu-i::before{
  content:'';position:absolute;
  left:calc(var(--gut) * -1);right:calc(var(--gut) * -1);
  top:0;bottom:0;z-index:-1;
  background:linear-gradient(90deg,rgba(255,74,16,.14),transparent 62%);
  transform:scaleX(0);transform-origin:left;
  transition:transform .55s var(--ease);
}
.menu-i:active::before{transform:scaleX(1)}

.menu-n{
  font-family:var(--mono);font-size:10px;letter-spacing:.14em;
  color:var(--dimmer);line-height:1;
  transition:color .4s ease;
}
.menu-i:active .menu-n{color:var(--orange)}

/* the mask each label lifts out of */
.menu-t{display:block;overflow:hidden;min-width:0}
.menu-t > span{
  display:block;
  font-family:var(--display);font-weight:500;
  font-size:clamp(27px,8.6vw,44px);line-height:1.04;letter-spacing:-.035em;
  transform:translateY(112%);
  transition:transform .85s var(--ease);
}
.menu.on .menu-t > span{transform:none;transition-delay:calc(var(--i,0) * 62ms + .24s)}

.menu-arw{
  display:grid;place-items:center;width:26px;height:26px;flex:0 0 auto;
  color:var(--dim);
  opacity:0;transform:translateX(-7px);
  transition:opacity .5s ease,transform .6s var(--ease),color .4s ease;
}
.menu-arw svg{width:15px;height:15px}
.menu.on .menu-arw{opacity:.65;transform:none;transition-delay:calc(var(--i,0) * 62ms + .42s)}
.menu-i:active .menu-arw{opacity:1;color:var(--orange);transform:translateX(3px)}

/* ------------------------------------------------------------------ footer */
.menu-foot{margin-top:auto;padding-top:clamp(20px,3.6vh,38px);display:grid;gap:clamp(12px,2vh,18px)}
.menu-foot .mono{display:block;margin-bottom:11px}
.menu-social{display:flex;gap:9px;flex-wrap:wrap}
.menu-social a{
  width:38px;height:38px;display:grid;place-items:center;
  border:1px solid var(--line);border-radius:11px;color:var(--dim);
  transition:color .35s ease,border-color .35s ease,background .35s ease,transform .35s var(--ease);
}
.menu-social a svg{width:16px;height:16px}
.menu-social a:active{color:var(--white);border-color:rgba(255,255,255,.28);background:rgba(255,255,255,.05);transform:scale(.94)}
.menu-mail{
  font-size:15px;color:var(--white);width:fit-content;
  border-bottom:1px solid rgba(255,255,255,.22);padding-bottom:2px;
}
.menu-note{color:var(--dimmer);margin:2px 0 0;line-height:1.7}

/* ----------------------------------------------------------------- switches */
@media (max-width:860px){
  .burger{display:block}
}
/* Above 860px the real nav is on screen, so the panel must never be reachable —
   including by a stale .on class left behind by a resize mid-animation. */
@media (min-width:861px){
  .menu{display:none}
}
/* a phone in landscape has almost no height: shrink the type, drop the note */
@media (max-height:520px) and (orientation:landscape){
  .menu-t > span{font-size:clamp(21px,4.4vh,28px)}
  .menu-i{padding:clamp(7px,1.5vh,12px) 0}
  .menu-note{display:none}
  .menu-social a{width:34px;height:34px}
}
@media (prefers-reduced-motion:reduce){
  .menu-sheet,.menu-in,.menu-t > span,.menu-arw,.menu-rule,.menu-sheet::after,.menu-in > *{
    transition-duration:.01s !important;transition-delay:0s !important;
  }
  .menu-t > span{transform:none}
}

/* ============================================================================
   LOADING SCREEN

   Held together by three rules:

     1. It can never trap the visitor. <html class="is-loading"> is in the
        markup so it paints on the first frame, a <noscript> rule removes it
        when scripting is off, and the animation below retires it after 12
        seconds if app.js never runs. A full-screen cover with a single way out
        is a broken site.
     2. The number is honest. app.js counts real work — images decoded, the two
        API calls — rather than animating 0 to 100 on a timer.
     3. It leaves the same way the menu arrives, in layers: the content lifts
        faster than the panel, and an accent seam sweeps up the screen as the
        last of it clears. Same vocabulary, opposite direction.
   ========================================================================== */
.pre{
  position:fixed;inset:0;z-index:150;display:none;
  background:#080706;
  transform:translateY(0);
  transition:transform 1.3s var(--ease);
}
html.is-loading .pre{
  display:block;
  /* dead man's switch: no JS, no exit — so CSS retires it on its own */
  animation:pre-failsafe 0s linear 12s forwards;
}
@keyframes pre-failsafe{to{opacity:0;visibility:hidden;pointer-events:none}}

/* the seam that sweeps up the screen behind it */
.pre::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:2px;
  background:linear-gradient(90deg,transparent,var(--orange) 30%,var(--orange) 70%,transparent);
  opacity:0;transition:opacity .45s ease;
}
html.pre-out .pre{transform:translateY(-100%)}
html.pre-out .pre::after{opacity:1}

/* This element also carries .wrap, which supplies the page gutter through
   `padding:0 var(--gut)`. Only the vertical padding may be set here — a
   three-value `padding` shorthand would set the horizontal side to 0 and win on
   source order, which is exactly what put this content on the screen edges. */
.pre-in{
  height:100%;display:flex;flex-direction:column;justify-content:space-between;
  padding-top:calc(clamp(18px,3vh,34px) + env(safe-area-inset-top));
  padding-bottom:calc(clamp(18px,3vh,34px) + env(safe-area-inset-bottom));
  transition:transform 1.3s var(--ease);
}
html.pre-out .pre-in{transform:translateY(-34%)}

.pre-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.pre-pct{
  font-size:12px;color:var(--white);
  font-variant-numeric:tabular-nums;font-feature-settings:'tnum' 1;
}
.pre-pct::after{content:'%';color:var(--dimmer);margin-left:2px}

/* the wordmark lifts out of a mask, once, on arrival */
.pre-mid{flex:1 1 auto;display:flex;align-items:center;min-height:0}
.pre-mask{display:block;overflow:hidden;padding-bottom:.08em}
.pre-mask .logo{
  display:block;font-size:clamp(38px,7.6vw,116px);letter-spacing:-.045em;line-height:1;
  transform:translateY(108%);
  transition:transform 1.5s var(--ease) .22s;
}
html:not(.pre-ready) .pre-mask .logo{transform:translateY(108%)}
html.pre-ready .pre-mask .logo{transform:none}

.pre-foot{align-items:flex-end}
.pre-rule{
  display:block;position:relative;flex:1 1 auto;max-width:min(46vw,420px);
  height:1px;background:var(--line);overflow:hidden;
}
/* No transition: app.js eases this value itself and writes it every frame, so a
   CSS one would only chase a moving target and lag behind the digits. */
.pre-rule i{
  position:absolute;inset:0;transform-origin:left;background:var(--orange);
  transform:scaleX(var(--p,0));
}
@media (max-width:560px){
  .pre-foot{flex-direction:column;align-items:flex-start;gap:12px}
  .pre-rule{max-width:none;width:100%}
}
@media (prefers-reduced-motion:reduce){
  .pre,.pre-in,.pre-mask .logo{transition-duration:.01s}
  .pre-mask .logo{transform:none}
}

/* ============================================================================
   COOKIE NOTICE

   A glyph, two lines, two answers. Nothing else — no label, no accent rule, no
   heading-plus-paragraph, no per-category switches, no third button. Each of
   those was on screen at one point and each was furniture: what the notice has
   to say is one sentence, and the answer is yes or no.

   Accept allows everything, Decline allows only the record of that decision.
   window.hmConsent() reports which, for whatever gets added later.
   ========================================================================== */
.ck{
  position:fixed;z-index:70;
  left:max(14px,env(safe-area-inset-left));
  bottom:calc(14px + env(safe-area-inset-bottom));
  width:min(348px,calc(100vw - 28px));
  transition:transform .5s var(--ease),opacity .3s ease;
}
.ck-card{
  display:grid;grid-template-columns:auto 1fr;gap:13px;
  background:rgba(13,12,11,.92);
  backdrop-filter:blur(22px) saturate(1.35);-webkit-backdrop-filter:blur(22px) saturate(1.35);
  border:1px solid rgba(255,255,255,.1);border-radius:15px;
  padding:16px 17px 15px;
  box-shadow:0 24px 56px -22px rgba(0,0,0,.92),inset 0 1px 0 rgba(255,255,255,.06);
  transform:translateY(18px) scale(.975);opacity:0;
  transition:transform .75s var(--ease),opacity .45s ease;
}
.ck.on .ck-card{transform:none;opacity:1}
.ck.out .ck-card{transform:translateY(10px);opacity:0;transition-duration:.36s,.24s}

.ck-glyph{color:var(--orange);width:19px;height:19px;margin-top:1px}
.ck-glyph svg{width:100%;height:100%;display:block}
.ck-body{min-width:0}
.ck-lead{margin:0;font-size:14.4px;font-weight:500;color:var(--white);letter-spacing:-.014em;line-height:1.35}
.ck-note{margin:4px 0 0;font-size:12.9px;line-height:1.5;color:var(--dim)}

.ck-acts{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-top:14px}
.ck-quiet{
  border:0;background:none;padding:0 0 1px;cursor:pointer;
  font-size:12.9px;color:var(--dim);
  border-bottom:1px solid rgba(255,255,255,.18);
  transition:color .3s ease,border-color .3s ease;
}
.ck-quiet:hover{color:var(--white);border-color:var(--white)}

/* out of the way of a project page or the order modal, both of which are modal */
body.is-locked .ck{transform:translateY(calc(100% + 24px));opacity:0;pointer-events:none}

@media (max-width:420px){
  .ck{width:calc(100vw - 20px);left:10px;bottom:calc(10px + env(safe-area-inset-bottom))}
  .ck-card{padding:14px 15px 13px;gap:11px}
}
@media (prefers-reduced-motion:reduce){
  .ck,.ck-card{transition-duration:.01s}
}

/* a small instance of the shared button, for use inside cards */
.cbtn.sm{--cta-h:38px;font-size:13px;padding:0 5px 0 16px;gap:10px}
.cbtn.sm .cbtn-arw{width:28px;height:28px}
.cbtn.sm .cbtn-arw svg{margin:-6.5px 0 0 -6.5px}

/* ============================================================================
   SCROLLBAR

   One width and one colour for every scrollable thing on the site: the page,
   the project view, the menu sheet, the order modal, a select panel, a
   textarea. Declared with the universal selector rather than element by
   element, so a new scrollable panel inherits it instead of arriving 3px wider
   than the last one — which is how the page ended up at 9px and its panels at
   6px and 8px.

   The bar is kept, styled rather than hidden. Hiding it is the fashion and it
   costs a real affordance: it is the only thing that says how long the page is
   and the only way to drag to a position.

   Resting colour is neutral. The accent appears only under the pointer, and
   muted at that — a permanently orange bar is a loud piece of chrome competing
   with the one accent the design already spends carefully.

   Firefox takes the two standard properties; WebKit needs its pseudo-elements.
   A browser that supports neither keeps its default, which is fine, because
   nothing here is load-bearing.
   ========================================================================== */
:root{
  --sb-w:6px;
  /* A softened accent rather than the accent itself: #ff4a10 at full strength is
     a signal colour and a bar that never moves has no business shouting. Lifted
     toward #ff7a4d and held under half opacity, it reads as warm rather than as
     a warning, and the hover state has somewhere to go. */
  --sb-thumb:rgba(255,122,77,.38);
  --sb-thumb-hot:rgba(255,122,77,.72);
}
html{scrollbar-width:thin;scrollbar-color:var(--sb-thumb) transparent}
/* :not(textarea) is load-bearing — see the note below */
*:not(textarea)::-webkit-scrollbar{width:var(--sb-w);height:var(--sb-w)}
*:not(textarea)::-webkit-scrollbar-track{background:transparent}
*:not(textarea)::-webkit-scrollbar-thumb{
  background:var(--sb-thumb);border-radius:99px;
  transition:background .3s ease;
}
*:not(textarea)::-webkit-scrollbar-thumb:hover{background:var(--sb-thumb-hot)}
*:not(textarea)::-webkit-scrollbar-corner{background:transparent}

/* A textarea is left out of all of the above, and that is the whole fix.

   In WebKit the resize grip is a scrollbar PART, and styling any scrollbar part
   switches the element to the custom scrollbar rendering path — which does not
   draw the native grip at all. Every attempt to win that fight made it worse:
   sizing the box up left an invisible 12px square, and drawing hairlines into
   the slot by hand looked like a hatched patch rather than a corner.

   There is no way to opt one part back into the native rendering. So the
   textarea opts out of the custom scrollbar instead, and gets the browser's own
   scrollbar and its own grip — the one that was there before any of this, drawn
   properly for the platform.

   The cost is that a long message shows a default-width scrollbar inside the
   box. That is a rare, minor inconsistency; a permanently broken resize handle
   on the one control people actually drag is not. */
textarea{scrollbar-width:auto}
