/* ==========================================================================
   LaserLair — site stylesheet
   Kinetic Refined design system. One file for every page.

   Structure:
   1. Tokens          2. Reset & base      3. Layout
   4. Nav             5. Buttons           6. Marquee
   7. Hero (home)     8. Sections          9. Trace stage
  10. Cards/packages 11. Steps & phases   12. Tables
  13. Details/FAQ    14. Shop             15. Articles
  16. Learn index    17. About            18. Footer
  19. Utilities & motion preferences
   ========================================================================== */

/* 1. TOKENS ---------------------------------------------------------------- */
:root{
  --bone:#E9E9E5;
  --surf:#F3F3F1;
  --ink:#0B0B0C;
  --ink-70:rgba(11,11,12,.70);
  --ink-65:rgba(11,11,12,.65);
  --ink-45:rgba(11,11,12,.58);   /* 4.57:1 on bone, WCAG AA */
  --ink-40:rgba(11,11,12,.58);   /* 4.57:1 on bone, WCAG AA */
  --hair:rgba(11,11,12,.15);
  --ink-16:rgba(11,11,12,.16);

  /* laser primaries */
  --r:#FF2A1F;
  --g:#00D95F;
  --b:#1B3CFF;

  /* Text-safe red. --r is the laser primary and is tuned for glow, not for
     reading: at 3.05:1 on --bone it fails WCAG AA for the 11px .lbl. This is
     the same hue darkened to 4.88:1, so a red label stays legible. */
  --r-ink:#C51A0F;

  --display:"Archivo Black",system-ui,-apple-system,sans-serif;
  --body:"Archivo",system-ui,-apple-system,sans-serif;
  --mono:"DM Mono",ui-monospace,SFMono-Regular,monospace;

  /* Foreground tokens. Text rules reference these rather than --ink directly,
     so a dark container can flip every descendant by redefining four variables.
     Inheritance beats specificity, which is why .article-body li b can no longer
     out-rank a dark container and render black on black. */
  --fg:var(--ink);
  --fg-strong:var(--ink);
  --fg-soft:var(--ink-70);
  --fg-mute:var(--ink-45);

  --pad:clamp(18px,4vw,56px);
  --pad-wide:clamp(20px,5vw,64px);
  --gap:clamp(80px,10vw,150px);
  --gap-lg:clamp(88px,11vw,168px);
  --maxw:1320px;
  --maxw-mid:1180px;
  --measure:740px;
}

/* 2. RESET & BASE -------------------------------------------------------- */
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:92px}
body{
  margin:0;background:var(--bone);color:var(--ink);
  font-family:var(--body);font-size:17.5px;line-height:1.7;
  -webkit-font-smoothing:antialiased;overflow-x:clip;
}
a{color:inherit}
img,video,svg{max-width:100%;display:block}
a:focus-visible,button:focus-visible,summary:focus-visible{
  outline:3px solid var(--b);outline-offset:2px;
}
.skip{
  position:absolute;left:-9999px;top:0;background:var(--ink);color:var(--bone);
  padding:14px 20px;z-index:99;font-family:var(--mono);font-size:12px;
  letter-spacing:.14em;text-transform:uppercase;text-decoration:none;
}
.skip:focus{left:0}

/* 3. LAYOUT -------------------------------------------------------------- */
.wrap{max-width:var(--maxw);margin:0 auto;padding-inline:var(--pad)}
.wrap-mid{max-width:var(--maxw-mid);margin:0 auto;padding-inline:var(--pad-wide)}
.art{max-width:var(--measure);margin:0 auto;padding-inline:var(--pad-wide)}
.narrow{max-width:840px}

.lbl{
  font-family:var(--mono);font-size:11px;letter-spacing:.17em;
  text-transform:uppercase;color:var(--fg-mute);margin:0;
}
/* Negative-sense label, e.g. the "Not included" column of the shop licensing
   grid. Colour alone is never the only signal: the word itself still reads
   "Not included", so this stays legible to colour-blind readers. */
.lbl.lbl--no{color:var(--r-ink)}
.rule{height:1px;background:var(--hair)}

/* 4. NAV ----------------------------------------------------------------- */
.nav{
  position:sticky;top:0;z-index:60;
  background:var(--bone);border-bottom:2px solid var(--ink);
}
.nav__in{
  display:flex;justify-content:space-between;align-items:center;
  gap:18px;flex-wrap:wrap;padding-block:16px;
}
.logo{font-family:var(--display);font-size:20px;letter-spacing:-.03em;text-decoration:none}
.nav__l{display:flex;gap:22px;font-size:14.5px;flex-wrap:wrap}
@media(max-width:599px){
  .nav__in{padding-block:11px;gap:8px}
  .nav__l{gap:14px;font-size:13px}
}
.nav__l a{text-decoration:none;color:var(--ink-70)}
.nav__l a:hover{color:var(--ink)}
.nav__l a[aria-current]{color:var(--ink);font-weight:600;border-bottom:2px solid var(--ink)}

/* Mobile nav: hamburger dropdown.
   .nav--hb is added by initMobileNav() in laserlair.js, never by the markup.
   Everything that hides the links is scoped under it, so with JS off or
   broken the full link row still renders and the site stays navigable.
   The panel is absolutely positioned against .nav, which is already
   position:sticky and therefore a containing block. */
.nav__burger{display:none}
@media(max-width:767px){
  .nav--hb .nav__in{flex-wrap:nowrap;gap:12px}

  .nav--hb .nav__burger{
    display:flex;flex-direction:column;justify-content:center;gap:5px;
    width:44px;height:38px;padding:0 9px;flex:none;
    background:var(--bone);border:2px solid var(--ink);cursor:pointer;
  }
  .nav--hb .nav__burger span{
    display:block;width:100%;height:2px;background:var(--ink);
    transition:transform .18s ease,opacity .18s ease;
  }
  .nav--hb.is-open .nav__burger span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  .nav--hb.is-open .nav__burger span:nth-child(2){opacity:0}
  .nav--hb.is-open .nav__burger span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}

  .nav--hb .nav__l{
    display:none;position:absolute;left:0;right:0;top:100%;
    flex-direction:column;gap:0;
    background:var(--bone);border-bottom:2px solid var(--ink);
    padding:2px var(--pad-wide) 14px;font-size:16.5px;
  }
  .nav--hb.is-open .nav__l{display:flex}
  .nav--hb .nav__l a{padding:14px 0;border-top:1px solid var(--hair)}
  /* the desktop current-page underline spans the whole row when stacked */
  .nav--hb .nav__l a[aria-current]{border-bottom:0}
}
@media(prefers-reduced-motion:reduce){
  .nav__burger span{transition:none}
}

/* 5. BUTTONS ------------------------------------------------------------- */
.btn{
  display:inline-flex;align-items:center;font-family:var(--body);font-weight:600;
  font-size:15px;text-transform:uppercase;letter-spacing:.04em;
  padding:16px 28px;text-decoration:none;border:2px solid transparent;border-radius:0;
  transition:background .15s ease,color .15s ease,border-color .15s ease;
}
/* Solid only. Every button is white on black on a light surface, and black on
   white inside a dark section. There is no outlined variant: --fill and
   --ghost render identically and the class names are kept purely so existing
   markup does not have to be rewritten. */
.btn--fill,.btn--ghost{background:var(--ink);color:var(--bone);border-color:var(--ink)}
.btn--fill:hover,.btn--ghost:hover{background:var(--r);border-color:var(--r);color:var(--bone)}
.acts{display:flex;gap:10px;flex-wrap:wrap;margin-top:38px}

/* inverted context (dark sections) */
.on-ink .btn--fill,.on-ink .btn--ghost{background:var(--bone);color:var(--ink);border-color:var(--bone)}
.on-ink .btn--fill:hover,.on-ink .btn--ghost:hover{background:var(--r);border-color:var(--r);color:var(--bone)}

/* The copy-address control is injected by initMailCopy() as a real <button>,
   so it needs the element defaults an <a> never had. Confirmation goes green
   rather than swapping in a tick, because the label already says "Copied"
   and a second signal saying the same thing is noise. */
button.btn{cursor:pointer;-webkit-appearance:none;appearance:none;line-height:normal}
button.btn[data-copied],
.on-ink button.btn[data-copied]{background:var(--g);border-color:var(--g);color:var(--ink)}

/* 6. MARQUEE ------------------------------------------------------------- */
.mq{border-block:2px solid var(--ink);overflow:hidden;padding-block:12px}
/* The track holds four identical copies of the item set and shifts by exactly
   one copy before repeating, so the loop is seamless. Six copies rather than
   two because one copy measures roughly 1130px: with two the track ran out on
   any screen wider than about 1280px and the strip went blank before snapping
   back. Six leaves five copies (~5670px) covering the viewport at all times,
   which clears ultrawide displays. */
.mq__t{display:flex;width:max-content;animation:mq-move 52s linear infinite;will-change:transform}
@media (prefers-reduced-motion:reduce){.mq__t{animation:none}}
.mq__t span{
  font-family:var(--display);font-size:clamp(14px,1.8vw,19px);
  text-transform:uppercase;padding-inline:20px;white-space:nowrap;
}
.mq__t i{font-style:normal;color:var(--ink-40)}
@keyframes mq-move{from{transform:translateX(0)}to{transform:translateX(-16.6667%)}}

/* 7. HERO (home) --------------------------------------------------------- */
/* Hero: two columns from 900px (type left, loop right); a single centered
   column below that. Background stays bone; the loop reads as a dark panel. */
.hero-home__in{
  display:grid;gap:clamp(30px,5vw,64px);grid-template-columns:1fr;
  align-items:center;padding-block:clamp(40px,7vw,96px);
}
@media(min-width:900px){
  .hero-home__in{grid-template-columns:1.12fr .88fr;gap:clamp(48px,6vw,96px)}
  /* keeps the title clear of the video even at the widest fill */
  .hero__copy{padding-right:clamp(12px,1.6vw,28px)}
}
.hero__copy{min-width:0}

/* The button row spans exactly the width the title fills, and the buttons
   share that width between them. flex-grow does the fitting, so there is no
   breakpoint to maintain: three fit on one line when the column is wide,
   otherwise they wrap and whatever lands on each line fills it. Nothing can
   overflow, because wrapping is the release valve. */
.hero__copy .acts{
  display:flex;flex-wrap:wrap;gap:10px;
  width:calc(var(--hero-fill) * 100%);
}
.hero__copy .acts > .btn{
  flex:1 1 auto;min-width:0;justify-content:center;text-align:center;
  padding-inline:clamp(14px,2.2cqw,28px);
}

/* ---------------------------------------------------------------------------
   HERO TITLE

   The three words are set to render at the same width, filling the line.
   Since they have different letter counts they need different sizes, and that
   size has to track the container: the title spans the wrap's content width
   in the single-column layout but only its grid column on desktop.

   This is done with container query units. `.hero__copy` is the query
   container, so 1cqw is 1% of the title's own width, and each word gets a
   constant that converts container width into the font size that fills it.
   The browser recalculates that natively, so resizing is smooth with no
   script involved.

   Deriving a constant, if the words ever change:

     ink   = sum of the glyph advances in em, plus letter-spacing between
             them. Tracking applies BETWEEN glyphs, not after the last one,
             so it is multiplied by (letters - 1). Getting this wrong is what
             clipped the final letter previously.
     const = 98 / ink        (98 rather than 100 leaves 2% of slack, which is
                              invisible but absorbs metric differences between
                              Archivo Black and the fallback font)

   Archivo Black advances used: A .722  N .784  I .324  M .925  T .653
   E .661  D .752  G .781  R .732  P .700  H .792  C .723  S .679  L .620
--------------------------------------------------------------------------- */
/* How much of the container the title fills. The loop reads the same value on
   phones so the two blocks line up, and calibrateHeroTitle() reads it too, so
   changing it here moves both together. */
.hero-home{--hero-fill:.97}
.hero__copy{container-type:inline-size}

.h-xl{
  font-family:var(--display);margin:16px 0 0;max-width:none;
  line-height:.86;letter-spacing:-.045em;text-transform:uppercase;
  /* last-resort fallback: no container query units at all */
  font-size:clamp(34px,6.4vw,88px);
}
.h-xl .hw{
  display:block;
  /* --fit is the container-width percentage that makes this word fill the
     line. calibrateHeroTitle() measures the real font and sets it. The
     defaults below are estimates from published advance widths and are
     deliberately undersized, so a no-script render is small rather than
     clipped. */
  font-size:calc(var(--fit) * 1cqw);
}
.hw--animated{--fit:16.83}
.hw--laser   {--fit:27.21}
.hw--graphics{--fit:17.13}

/* Single-column hero (below 900px): everything centered inside the standard
   .wrap gutters, same as every other section. Title fills the content width
   through the cqw sizing; buttons center beneath it; the loop centers beneath
   the buttons at its contained size. No full-bleed special case. */
@media(max-width:899px){
  .hero__copy{text-align:center}
  .hero__copy > .acts{justify-content:center}
  .hero__loop{margin-inline:auto}
}

/* stroke and split offsets in em, so they hold their proportion
   now that each word sets its own font size */
.l-out{color:transparent;-webkit-text-stroke:.02em var(--ink)}

/* 8. SECTIONS ------------------------------------------------------------ */
.sec{padding-block:var(--gap);border-bottom:2px solid var(--ink)}
.sec--quiet{padding-block:var(--gap-lg);border-bottom:none;border-top:1px solid var(--hair)}
.sec--flush{padding-top:0}
.sec--ink{background:var(--ink);color:var(--bone);border-bottom:2px solid var(--ink)}
.sec--ink .lbl{color:rgba(233,233,229,.55)}   /* 5.33:1 on ink */
.sec--ink h2,.sec--ink h3{color:var(--bone)}
.sec--ink .lede{color:rgba(233,233,229,.7)}

h1{
  font-family:var(--display);text-transform:uppercase;
  font-size:clamp(30px,5vw,58px);line-height:.98;letter-spacing:-.042em;
  margin:24px 0 0;max-width:18ch;
}
h2{
  font-family:var(--display);text-transform:uppercase;
  font-size:clamp(28px,5.2vw,58px);line-height:.94;letter-spacing:-.04em;
  margin:16px 0 0;max-width:17ch;
}
h2.h-mid{font-size:clamp(23px,3.4vw,38px);line-height:1.02;max-width:19ch}
h3{font-family:var(--display);text-transform:uppercase;letter-spacing:-.03em}
.lede{font-size:clamp(16.5px,1.7vw,19px);color:var(--fg-soft);max-width:36em;margin:24px 0 0}

.grid2{display:grid;gap:clamp(34px,5vw,72px);grid-template-columns:1fr}
@media(min-width:920px){.grid2{grid-template-columns:1.25fr .75fr;align-items:start}}
.grid-why{display:grid;gap:clamp(34px,5vw,70px);margin-top:56px;grid-template-columns:1fr}
@media(min-width:960px){.grid-why{grid-template-columns:1.05fr .95fr}}

.body p{font-size:18px;line-height:1.78;color:var(--fg-soft);margin:24px 0 0;max-width:36em}
.body p b{color:var(--fg-strong);font-weight:600}
.body p:first-of-type{margin-top:30px}

/* 9. TRACE STAGE (the animated laser demo) ------------------------------- */
.stage{background:var(--ink);color:var(--bone);padding:24px;border:2px solid var(--ink);margin:0}
.meter{
  display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:16px;
  padding-top:13px;border-top:1px solid rgba(233,233,229,.24);
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
  text-transform:uppercase;color:rgba(233,233,229,.45);
}
.meter b{color:var(--bone);font-weight:500}
.cap{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  color:rgba(233,233,229,.45);margin:11px 0 0;text-align:center;
}
.cap i{color:#FF6A60;font-style:normal}

/* 10. CARDS / PACKAGES --------------------------------------------------- */
.pk{display:grid;gap:20px;margin-top:56px;grid-template-columns:1fr}
@media(min-width:820px){.pk{grid-template-columns:repeat(2,1fr)}}
.card{
  display:flex;flex-direction:column;background:var(--surf);
  border:2px solid var(--ink);padding:clamp(28px,3.4vw,44px);
}
.card__t{
  display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-40);
}
.card h3{font-size:clamp(30px,3.6vw,40px);letter-spacing:-.04em;margin:26px 0 0}
.price{
  font-family:var(--display);font-size:clamp(48px,6vw,68px);
  letter-spacing:-.05em;margin:10px 0 0;line-height:1;
}
.price em{
  display:block;font-family:var(--mono);font-style:normal;font-size:11px;
  letter-spacing:.15em;text-transform:uppercase;color:var(--ink-40);margin-top:14px;
}
.desc{font-size:16.5px;margin:22px 0 0;color:var(--fg-soft);max-width:32em}
.card ul{list-style:none;margin:30px 0 0;padding:0;font-size:15.5px}
.card li{padding:13px 0;border-top:1px solid var(--ink-16);color:var(--fg-soft)}
.card .foot{margin-top:auto;padding-top:34px}
.card .foot .btn{width:100%;justify-content:center;padding-inline:12px}

.vat{font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:var(--ink-40);margin:28px 0 0}

/* "EVERYTHING ELSE" BLOCK ------------------------------------------------
   A bordered aside at the end of the fixed-price section, pointing anything
   that cannot be priced from a card at the services page. The four steps
   above it belong to Trace and Reveal, so they sit outside this border
   rather than inside it. */
.offer{border:2px solid var(--ink);margin-top:clamp(48px,6vw,80px)}
.offer__head{
  display:grid;gap:18px;padding:clamp(26px,3vw,38px);
  grid-template-columns:1fr;align-items:center;
}
@media(min-width:820px){.offer__head{grid-template-columns:1fr auto}}
.offer__head h3{font-size:clamp(19px,2.3vw,26px);letter-spacing:-.035em;margin:0}
.offer__head p{margin:12px 0 0;color:var(--fg-soft);max-width:46em;font-size:16px}

/* 11. STEPS & PHASES ---------------------------------------------------- */
.steps{display:grid;gap:0;margin-top:56px;border-top:2px solid var(--ink);grid-template-columns:1fr}
@media(min-width:760px){.steps{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1060px){.steps{grid-template-columns:repeat(4,1fr)}}
.step{padding:30px 28px 36px;border-bottom:2px solid var(--ink)}
@media(min-width:1060px){
  .step{border-right:2px solid var(--ink)}
  .step:last-child{border-right:none}
}
.step em{
  font-family:var(--mono);font-style:normal;font-size:10.5px;
  letter-spacing:.16em;color:var(--ink-40);
}
.step h3{font-size:20px;margin:14px 0 10px}
.step p{margin:0;font-size:15.5px;color:var(--fg-soft)}

.phases{margin-top:52px;border-top:1px solid var(--hair)}
.phase{display:grid;gap:14px;padding:28px 0;border-bottom:1px solid var(--hair);grid-template-columns:1fr}
@media(min-width:760px){.phase{grid-template-columns:70px 240px 1fr;gap:32px;align-items:baseline}}
.phase em{font-family:var(--mono);font-style:normal;font-size:11px;letter-spacing:.16em;color:var(--ink-45)}
.phase h3{font-size:17px;margin:0}
.phase p{margin:0;color:var(--fg-soft);font-size:16.5px;max-width:40em}
@media(min-width:820px){}

@media(min-width:820px){}

/* Scope cards (services page).
   ---------------------------------------------------------------------------
   Replaces an earlier text-row list. That list assumed every item was the same
   height, so adding images to three of five broke the row rules and left the
   two text-only items stranded against tall image rows.

   Two grids instead of one: the three illustrated disciplines lead as equal
   cards, and the two that have no image follow as a deliberate two-up text
   row rather than as gaps in the first grid.

   The artwork is laser-on-black, so the image sits in its own dark panel with
   a hard border. That is the same treatment the work gallery and the simulator
   stage use, and it stops the black frames reading as holes in the page. */
.scope{display:grid;gap:clamp(24px,3vw,38px);grid-template-columns:1fr;margin-top:52px}
@media(min-width:640px){.scope{grid-template-columns:repeat(2,1fr)}}
@media(min-width:980px){.scope{grid-template-columns:repeat(3,1fr)}}

.scope__c{margin:0}
.scope__m{
  aspect-ratio:1/1;background:#030304;border:2px solid var(--ink);
  overflow:hidden;margin-bottom:20px;
}
.scope__m img{
  width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .6s cubic-bezier(.2,.7,.2,1);
}
.scope__c:hover .scope__m img{transform:scale(1.04)}
.scope__c h2{
  font-family:var(--body);font-size:17.5px;font-weight:600;line-height:1.3;
  letter-spacing:0;text-transform:none;margin:0;max-width:none;
}
.scope__c p{margin:10px 0 0;color:var(--fg-soft);font-size:16px;max-width:34em}

/* 12. TABLES & SPEC LISTS ---------------------------------------------- */
.tw{overflow-x:auto;margin-top:28px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:460px;font-size:15.5px}
caption{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--ink-45);text-align:left;padding-bottom:12px;
}
th,td{
  text-align:left;padding:12px 14px 12px 0;border-bottom:1px solid var(--hair);
  vertical-align:top;line-height:1.55;
}
th{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-45);border-bottom:2px solid var(--ink);font-weight:400;
}
td code,p code{font-family:var(--mono);font-size:14px;background:var(--surf);padding:2px 6px}
tbody tr:hover{background:var(--surf)}

.facts{margin-top:44px;border-top:2px solid var(--ink)}
.fact{display:grid;gap:6px;padding:20px 0;border-bottom:1px solid var(--hair);grid-template-columns:1fr}
@media(min-width:700px){.fact{grid-template-columns:230px 1fr;gap:32px;align-items:baseline}}
.fact dt{
  font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-45);margin:0;
}
.fact dd{margin:0;font-size:17px}

.cost{margin-top:48px;border-top:2px solid var(--ink)}
.row{display:grid;gap:10px;padding:26px 0;border-bottom:1px solid var(--hair);grid-template-columns:1fr;align-items:baseline}
@media(min-width:700px){.row{grid-template-columns:1fr auto;gap:32px}}
.row h3{font-size:18px;margin:0}
.row p{margin:8px 0 0;color:var(--fg-soft);font-size:16px;max-width:42em}
.row .fig{font-family:var(--display);font-size:clamp(24px,3vw,32px);letter-spacing:-.04em;white-space:nowrap}
/* price rows that link out (the two fixed-price logo products) keep the row
   look; color and underline are reset from the global anchor styles and the
   figure carries the affordance on hover */
.row--link{text-decoration:none;color:inherit}
.row--link:hover h3{text-decoration:underline;text-underline-offset:3px}
.row--link:hover .fig{color:var(--r)}
.row--link:focus-visible{outline:2px solid var(--ink);outline-offset:4px}

.row .fig em{
  font-family:var(--mono);font-style:normal;font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-45);display:block;margin-bottom:4px;font-weight:400;
}
.note{
  font-family:var(--mono);font-size:11.5px;letter-spacing:.05em;
  color:var(--ink-45);margin:26px 0 0;max-width:52em;line-height:1.8;
}

/* 13. DETAILS / FAQ ---------------------------------------------------- */
.faq{margin-top:48px;border-top:2px solid var(--ink);max-width:62em}
details{border-bottom:1px solid var(--ink-16)}
summary{
  cursor:pointer;list-style:none;padding:24px 44px 24px 0;position:relative;
  font-family:var(--display);text-transform:uppercase;
  font-size:clamp(15.5px,1.8vw,20px);letter-spacing:-.03em;
}
summary::-webkit-details-marker{display:none}
summary::after{
  content:"+";position:absolute;right:4px;top:50%;transform:translateY(-50%);
  font-family:var(--body);font-weight:600;font-size:26px;color:var(--ink-40);
}
details[open] summary::after{content:"–"}
details p{margin:0 0 26px;color:var(--fg-soft);max-width:46em}

/* 14. SHOP ------------------------------------------------------------- */
@media(min-width:660px){}
@media(min-width:1040px){}

.prod{display:flex;flex-direction:column;background:var(--surf);border:2px solid var(--ink)}
.prod h3{font-size:24px;letter-spacing:-.04em;margin:14px 0 0;line-height:1.05}
@media(min-width:880px){}
@media(min-width:880px){}

.lic{display:grid;gap:0;margin-top:52px;border-top:2px solid var(--ink);grid-template-columns:1fr}
@media(min-width:820px){.lic{grid-template-columns:repeat(3,1fr)}}
.lic__c{padding:28px 26px 32px;border-bottom:2px solid var(--ink)}
@media(min-width:820px){
  .lic__c{border-right:2px solid var(--ink)}
  .lic__c:last-child{border-right:none}
}
.lic__c h3{font-size:20px;letter-spacing:-.035em;margin:12px 0 0}
/* :not(.lbl) matters. Without it this rule (0,1,1) outranks .lbl (0,1,0) and
   silently repaints the card labels at 15.5px in body grey, so "Included" and
   "Not included" rendered larger than every other .lbl on the site and ignored
   their own colour. Scope it to real paragraphs and the label styling holds. */
.lic__c p:not(.lbl){color:var(--fg-soft);font-size:15.5px;margin:12px 0 0}
.lic__c .lbl{margin:0}

/* 15. ARTICLES --------------------------------------------------------- */
.article-body{font-size:18px;line-height:1.78}
.top-art{padding-block:clamp(52px,7vw,92px) clamp(28px,4vw,44px)}
.top-art h1{font-size:clamp(27px,4.4vw,50px);line-height:1.02;max-width:none}
.meta{
  display:flex;gap:20px;flex-wrap:wrap;margin-top:28px;padding-top:20px;
  border-top:1px solid var(--hair);font-family:var(--mono);font-size:11px;
  letter-spacing:.13em;text-transform:uppercase;color:var(--ink-45);
}
.tldr{background:var(--ink);color:var(--bone);padding:clamp(24px,3.4vw,36px);margin-top:40px}
.tldr .lbl{color:rgba(233,233,229,.45)}
.tldr ul{list-style:none;margin:20px 0 0;padding:0}
.tldr li{
  padding:12px 0;border-top:1px solid rgba(233,233,229,.24);
  color:rgba(233,233,229,.82);font-size:17px;line-height:1.65;
}
.tldr p{color:rgba(233,233,229,.82);margin:18px 0 0;font-size:17px;line-height:1.68}
.tldr b{color:var(--bone);font-weight:600}

.article-body h2{font-size:clamp(21px,2.8vw,30px);line-height:1.08;margin:64px 0 0;max-width:none}
.article-body h3{font-size:17px;margin:40px 0 0}
.article-body p{margin:20px 0 0;color:var(--fg-soft)}
.article-body p b,.article-body li b{color:var(--fg-strong);font-weight:600}
.lead{font-size:20px;color:var(--fg-soft);margin-top:26px}
.key{border-left:3px solid var(--ink);padding:2px 0 2px 22px;margin-top:24px}
.key p{margin:0;color:var(--fg-strong);font-weight:500}
.warn{background:var(--surf);border-left:3px solid var(--r);padding:22px 24px;margin-top:32px}

/* Sourced pull-quote. Used where an article rests on a named primary source
   rather than on the author's own authority, so the attribution is part of the
   component rather than an afterthought in the surrounding prose. */
.quote{margin:32px 0 0;padding:24px 26px;background:var(--surf);border-left:3px solid var(--g)}
.quote p{margin:0;font-size:19px;line-height:1.55;letter-spacing:-.015em;color:var(--fg-strong)}
.quote cite{
  display:block;margin-top:16px;font-style:normal;font-family:var(--mono);
  font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--fg-mute);
}
.warn p{margin:0;color:var(--fg-soft);font-size:16.5px}
.warn b{color:var(--fg-strong)}
.stepn{
  font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-45);margin:56px 0 0;
}
.stepn + h2{margin-top:12px}

ul.plain{margin:20px 0 0;padding:0;list-style:none}
ul.plain li{padding:11px 0 11px 20px;border-top:1px solid var(--hair);position:relative;color:var(--fg-soft)}
ul.plain li::before{content:"";position:absolute;left:0;top:22px;width:9px;height:1px;background:var(--ink-45)}
ol.num{margin:20px 0 0;padding:0;list-style:none;counter-reset:n}
ol.num li{counter-increment:n;padding:16px 0 16px 44px;border-top:1px solid var(--hair);position:relative;color:var(--fg-soft)}
ol.num li::before{
  content:counter(n,decimal-leading-zero);position:absolute;left:0;top:18px;
  font-family:var(--mono);font-size:11px;letter-spacing:.12em;color:var(--ink-45);
}

.faqs{margin-top:32px}
.faqs details:first-of-type{border-top:2px solid var(--ink)}
.faqs summary{font-size:16px;padding:22px 44px 22px 0}
.faqs summary::after{font-size:24px}
.faqs details p{margin:0 0 24px}

.cta-box{background:var(--surf);border:2px solid var(--ink);padding:clamp(26px,3.4vw,40px);margin-top:72px}
.cta-box h2{margin:14px 0 0}
.cta-box .btn{margin-top:26px}
.src{margin-top:56px;padding-top:24px;border-top:1px solid var(--hair);font-size:15px;color:var(--ink-45)}
.src a{color:var(--ink-70)}

/* 16. LEARN INDEX ------------------------------------------------------ */
.list{border-top:2px solid var(--ink)}
.item{
  display:grid;gap:14px;padding:clamp(28px,3.6vw,42px) 0;
  border-bottom:1px solid var(--hair);grid-template-columns:1fr;
  text-decoration:none;transition:background .15s;
}
@media(min-width:860px){.item{grid-template-columns:150px 1fr auto;gap:38px;align-items:baseline}}
.item:hover{background:var(--surf)}
.item__k{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-45);
}
.item h2{font-size:clamp(19px,2.5vw,27px);line-height:1.1;margin:0;max-width:none}
.item p{margin:12px 0 0;color:var(--fg-soft);font-size:16.5px;max-width:44em}
.item__go{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-45);white-space:nowrap;
}
.item:hover .item__go{color:var(--ink)}
@media(min-width:900px){
}

/* showreel */
.reel__in{padding-block:var(--gap);text-align:center}
.reel__box{
  position:relative;aspect-ratio:1;max-width:620px;margin:38px auto 0;
  background:#030304;color:var(--bone);border:1px solid rgba(233,233,229,.22);
  display:grid;place-items:center;overflow:hidden;
}
/* contain, not cover: the source is 1:1 and nothing should be cropped */
.reel__box video,.reel__box iframe{position:absolute;inset:0;width:100%;height:100%;border:0;object-fit:contain;background:#030304}
.reel__cap{
  font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  color:rgba(233,233,229,.55);margin:18px 0 0;
}

/* hero loop: square source, so the panel is square and the video is contained */
.hero__loop{
  position:relative;aspect-ratio:1;width:100%;max-width:420px;
  background:#030304;border:1px solid var(--ink-16);overflow:hidden;
}
.hero__loop video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
@media(min-width:900px){
  .hero__loop{max-width:none;margin-left:auto}
}
@media (prefers-reduced-motion:reduce){
  .hero__loop video{display:none}
  .hero__loop{background:#030304 url(../media/hero/disco-loop-poster.jpg) center/cover}
}

/* recognition: copy beside the still from the placed show */
.recog{display:grid;gap:32px;grid-template-columns:1fr;align-items:center}
@media(min-width:900px){.recog{grid-template-columns:1.05fr .95fr;gap:56px}}
.recog__t{min-width:0}
.recog__t h2{margin-top:14px}
.recog__t .lede{margin-top:20px}
.recog__m{margin:0}
.recog__m img{
  display:block;width:100%;height:auto;background:#030304;
  border:1px solid var(--ink-16);
}
.recog__m figcaption{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-45);margin-top:12px;
}

/* ---------------------------------------------------------------------------
   SELLFY EMBED

   The store arrives as a cross-origin iframe, which means it cannot report its
   own content height and cannot be auto-sized from this page. So the frame is
   given a tall, viewport-relative height and the store scrolls inside it. That
   is the trade-off of the quick embed route; a hand-built grid hitting the
   Sellfy product URLs is the version that flows with the page.

   Sellfy's snippet ships width/height attributes. The rules below override
   them so the frame stays responsive regardless of what is pasted in.
--------------------------------------------------------------------------- */
.sellfy-embed{
  width:100%;height:clamp(900px,150vh,2200px);
  margin-top:32px;border:0;padding:2px;background:var(--surface);
}
.sellfy-embed iframe{
  display:block;width:100% !important;height:100% !important;border:0;
}

/* ---------------------------------------------------------------------------
   DARK CONTAINERS

   Every container that paints a dark background re-points the foreground
   tokens rather than restating colors on each child. Descendants pick the
   right color up by inheritance, whatever their own specificity, which is
   what stops dark-on-dark text appearing when a later rule wins.

   Add new dark containers to this list. Do not chase the bug with more
   specific color rules; that is what caused it the first time.
--------------------------------------------------------------------------- */
.sec--ink, .on-ink, .tldr, .stage, .reel__box, .shot, .shots, .sellfy-embed{
  --fg:#E9E9E5;
  --fg-strong:#FFFFFF;
  --fg-soft:rgba(233,233,229,.74);
  --fg-mute:rgba(233,233,229,.55);
  color:var(--fg);
}
.sec--ink :is(h1,h2,h3,h4), .tldr :is(h1,h2,h3){color:var(--fg)}
.sec--ink :is(b,strong), .tldr :is(b,strong){color:var(--fg-strong)}

/* article figures ------------------------------------------------------- */
.art-fig{margin:40px 0 0}
.art-fig img{
  display:block;width:100%;height:auto;background:#030304;
  border:1px solid var(--ink-16);
}
.art-fig figcaption{
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  color:var(--ink-45);margin-top:10px;line-height:1.55;text-transform:none;
}
.art-figrow{display:grid;gap:16px;grid-template-columns:1fr;margin-top:8px}
@media(min-width:720px){.art-figrow{grid-template-columns:repeat(3,1fr)}}
.art-figrow .art-fig{margin-top:32px}
.art-ol{margin:24px 0 0;padding-left:1.3em;color:var(--fg-soft);font-size:17.5px;line-height:1.7}
.art-ol li{margin-top:14px;padding-left:.2em}
.art-ol b{color:var(--fg-strong);font-weight:600}

.wide-fig{margin:44px 0 0}
.wide-fig img{display:block;width:100%;height:auto;border:1px solid rgba(233,233,229,.22)}
.wide-fig figcaption{
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  color:var(--fg-mute);margin-top:12px;
}

/* FAQ group headings: the list is long enough that four labeled runs read
   faster than fifteen undifferentiated rows */
.faq__grp{margin-top:52px}
.faq__grp + .faq{margin-top:14px}

/* package preview slot. Holds the inline SVG previews below, and still
   accepts an <img>/<video> unchanged if real footage replaces them. */
.card__m{
  aspect-ratio:16/10;background:#030304;border:1px solid var(--ink-16);
  display:grid;place-items:center;text-align:center;margin-bottom:22px;overflow:hidden;
}
.card__m img,.card__m video{width:100%;height:100%;object-fit:cover;display:block}
.card__m span{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(233,233,229,.5);line-height:2;
}
.card__m em{font-style:normal;color:rgba(233,233,229,.3);letter-spacing:.08em}

/* 17. ABOUT PORTRAIT ------------------------------------------------------ */
.portrait{
  aspect-ratio:4/5;background:#030304;border:1px solid var(--ink);
  overflow:hidden;
}
.portrait img{width:100%;height:100%;object-fit:cover;display:block}

/* 18. FOOTER ---------------------------------------------------------- */
footer{
  border-top:2px solid var(--ink);padding-block:30px;
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-45);
}
.f__in{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap}
.legal{
  font-family:var(--mono);font-size:11px;letter-spacing:.05em;color:var(--ink-45);
  text-transform:none;line-height:1.8;margin:20px 0 0;max-width:60em;
}
/* A link inheriting --ink-45 would sit at the same weight as the fine print
   around it and read as plain text. --ink-70 is 6.84:1 on --bone, so it is
   visibly darker than its surroundings as well as underlined. */
footer a{color:var(--ink-70)}
footer a:hover{color:var(--ink)}
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{
    animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;
  }
}
.tldr li, .tldr p{color:var(--fg-soft)}
.meter b{color:var(--fg)}
.meter, .cap{color:var(--fg-mute)}

/* 21. WORK & GALLERY ----------------------------------------------------- */
.proj{padding-block:clamp(52px,7vw,92px);border-bottom:1px solid var(--hair)}
.proj:last-of-type{border-bottom:none}
.proj__head{display:grid;gap:18px;grid-template-columns:1fr;align-items:end}
/* client and runtime stay below the description at every width, so the
   reading order is the same on a phone and a desktop */
.proj__head h2{font-size:clamp(24px,3.6vw,40px);line-height:1.0;margin:14px 0 0;max-width:20ch}
.proj__head p{margin:16px 0 0;color:var(--fg-soft);max-width:40em;font-size:17px}
.proj__meta{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--mono);
  font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-45)}

/* Project stills: a carousel on a dark stage.
   ---------------------------------------------------------------------------
   These projects run to eighteen stills. Laid out as a full mosaic each one
   became a wall several screens tall and the page stopped being scannable, so
   the stills now live in a horizontal, scroll-snapped carousel: a project
   occupies a fixed, predictable band of the page regardless of how many
   images it holds.

   It is a real scroll container, not a transform track. That means trackpad
   and touch swipe work with no JavaScript at all, keyboard focus moving into
   an off-screen still scrolls it into view by itself, and the arrows added by
   initCarousel() are an enhancement rather than the only way through.

   The dark stage matters as much as the layout. These are light-on-black
   laser frames; on the bone background with hairline borders they read as a
   contact sheet. On black the frames bleed into the field and the gaps
   disappear, which is how the work actually looks in a room.
--------------------------------------------------------------------------- */
.gal{position:relative;margin-top:36px}
.shots{
  display:flex;
  gap:clamp(6px,.9vw,14px);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scroll-behavior:smooth;
  padding:clamp(10px,1.5vw,22px);
  background:#030304;border:1px solid var(--ink);
  scrollbar-width:thin;scrollbar-color:rgba(233,233,229,.32) #030304;
}
.shots::-webkit-scrollbar{height:8px}
.shots::-webkit-scrollbar-track{background:#030304}
.shots::-webkit-scrollbar-thumb{background:rgba(233,233,229,.32)}

/* Flex-basis rather than a column count, so the last visible still is clipped
   mid-tile: a cut edge is the clearest possible signal that there is more to
   the right. aspect-ratio keeps every tile square from that width. */
.shot{
  margin:0;position:relative;flex:0 0 78%;aspect-ratio:1/1;
  scroll-snap-align:start;
}
@media(min-width:620px){.shot{flex-basis:42%}}
@media(min-width:980px){.shot{flex-basis:30%}}
/* Promotion to a 2x2 tile was a grid idea and has no meaning in a single row.
   The class is left on the markup so the layout can be reverted. */
.shot--wide{grid-column:auto;grid-row:auto}

/* Arrows. Hidden where a pointer cannot hover, because a touch device already
   has the better gesture and the overlay only covers the artwork. */
.gal__nav{
  position:absolute;top:50%;transform:translateY(-50%);z-index:2;
  width:46px;height:68px;padding:0;border:0;cursor:pointer;
  background:rgba(3,3,4,.74);color:var(--bone);
  display:grid;place-items:center;
  transition:background .15s ease,opacity .2s ease;
}
.gal__nav:hover{background:var(--r)}
.gal__nav:focus-visible{outline:2px solid var(--g);outline-offset:2px}
.gal__nav--prev{left:clamp(10px,1.5vw,22px)}
.gal__nav--next{right:clamp(10px,1.5vw,22px)}
.gal__nav[disabled]{opacity:0;pointer-events:none}
.gal__nav::before{
  content:"";width:13px;height:13px;
  border-top:2px solid currentColor;border-right:2px solid currentColor;
}
.gal__nav--prev::before{transform:rotate(-135deg);margin-left:5px}
.gal__nav--next::before{transform:rotate(45deg);margin-right:5px}
@media(hover:none){.gal__nav{display:none}}
@media(prefers-reduced-motion:reduce){.shots{scroll-behavior:auto}}

.shot__open{
  display:block;width:100%;height:100%;padding:0;border:0;
  background:#030304;cursor:zoom-in;position:relative;overflow:hidden;
}
.shot__open img{
  width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .6s cubic-bezier(.2,.7,.2,1),opacity .3s;
}
.shot__open:hover img,.shot__open:focus-visible img{transform:scale(1.05)}
.shot__open:focus-visible{outline:2px solid var(--g);outline-offset:-2px}

/* Caption sits on the frame and stays visible, rather than appearing on
   hover: a hover-only caption is invisible on every touch device. */
.shot figcaption{
  position:absolute;left:0;right:0;bottom:0;pointer-events:none;
  padding:26px 12px 10px;
  font-family:var(--mono);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:rgba(233,233,229,.62);
  background:linear-gradient(to top,rgba(3,3,4,.85),rgba(3,3,4,0));
  transition:color .25s;
}
.shot:hover figcaption{color:var(--bone)}
.shot--wide figcaption{font-size:11px;padding:34px 16px 14px}

/* expand affordance */
.shot__open::after{
  content:"";position:absolute;top:10px;right:10px;width:22px;height:22px;
  border-top:2px solid var(--bone);border-right:2px solid var(--bone);
  opacity:0;transition:opacity .25s;
}
.shot__open:hover::after,.shot__open:focus-visible::after{opacity:.85}

/* Lightbox -------------------------------------------------------------- */
.lb{
  width:min(94vw,1080px);max-width:none;max-height:94vh;
  padding:0;border:2px solid var(--bone);background:#030304;color:var(--bone);
  overflow:visible;
}
.lb::backdrop{background:rgba(3,3,4,.9)}
.lb__fig{margin:0;display:grid;place-items:center}
.lb__img{display:block;width:100%;height:auto;max-height:82vh;object-fit:contain}
.lb__cap{
  font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:rgba(233,233,229,.72);
  padding:14px 16px;text-align:center;
}
.lb__close{
  position:absolute;top:-2px;right:-2px;width:46px;height:46px;
  background:var(--bone);color:var(--ink);border:2px solid var(--ink);
  font-size:24px;line-height:1;cursor:pointer;z-index:2;
}
.lb__close:hover{background:var(--r);color:var(--bone);border-color:var(--r)}
.lb__nav{
  position:absolute;top:calc(50% - 26px);width:52px;height:52px;
  display:grid;place-items:center;cursor:pointer;padding:0;
  background:var(--bone);border:2px solid var(--ink);color:var(--ink);
}
.lb__nav:hover{background:var(--ink);color:var(--bone)}
.lb__nav[disabled]{opacity:0;pointer-events:none}
.lb__nav::before{content:"";width:12px;height:12px;
  border-top:2px solid currentColor;border-right:2px solid currentColor}
.lb__nav--prev{left:-26px}
.lb__nav--prev::before{transform:rotate(-135deg);margin-left:5px}
.lb__nav--next{right:-26px}
.lb__nav--next::before{transform:rotate(45deg);margin-right:5px}
@media(max-width:720px){
  .lb__nav--prev{left:6px}
  .lb__nav--next{right:6px}
}
@media(min-width:700px){}
@media(min-width:1100px){}

/* offer routes */
.routes{display:grid;gap:0;margin-top:56px;border-top:2px solid var(--ink);grid-template-columns:1fr}
@media(min-width:760px){.routes{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1100px){.routes{grid-template-columns:repeat(4,1fr)}}
.route{padding:30px 26px 34px;border-bottom:2px solid var(--ink);
  display:flex;flex-direction:column;text-decoration:none}
@media(min-width:1100px){
  .route{border-right:2px solid var(--ink)}
  .route:last-child{border-right:none}
}
.route:hover{background:var(--surf)}
.route em{font-family:var(--mono);font-style:normal;font-size:10.5px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink-45)}
.route h3{font-size:clamp(20px,2.3vw,25px);letter-spacing:-.035em;margin:14px 0 10px}
.route p{margin:0;font-size:15.5px;color:var(--fg-soft)}
.route span{margin-top:20px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--ink-45)}
.route:hover span{color:var(--ink)}

/* 22. SCANNER SIMULATOR --------------------------------------------------
   The interactive widget in assets/laser-sim.js. Stage is dark because a
   laser only exists against darkness; controls sit on bone below it.
   The render is static: no animation, no afterimage. .sim__beam is the path
   the mirrors actually trace, .sim__ideal is the artwork it was asked for.
   ---------------------------------------------------------------------- */
.sim{border:2px solid var(--ink);background:var(--surf);margin-top:44px}

.sim__stage{
  background:#030304;color:var(--bone);border-bottom:2px solid var(--ink);
  padding:clamp(14px,2.4vw,28px);
}
.sim__svg{display:block;width:100%;height:auto}

.sim__ideal path{stroke:rgba(233,233,229,.20);stroke-width:.8;stroke-dasharray:2 3}
.sim__travel path{stroke:var(--r);stroke-opacity:.42;stroke-width:.5;stroke-dasharray:1.5 3}
.sim__beam path{
  stroke:#4BFF9B;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;fill:none;
  filter:drop-shadow(0 0 2.5px rgba(75,255,155,.55));
}
.sim__dots circle{stroke:none}
.sim__dots circle.d{fill:rgba(233,233,229,.55)}
.sim__dots circle.a{fill:var(--r)}
.sim__dots circle.t{fill:rgba(27,60,255,.75)}

.sim__meter{
  display:flex;flex-wrap:wrap;gap:10px 26px;align-items:baseline;
  padding:14px clamp(14px,2.4vw,24px);border-bottom:2px solid var(--ink);
  background:var(--ink);color:rgba(233,233,229,.5);
  font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;
}
.sim__meter b{color:var(--bone);font-weight:500}

.sim__controls{
  display:grid;gap:0;grid-template-columns:1fr;
  border-bottom:2px solid var(--ink);
}
@media(min-width:860px){.sim__controls{grid-template-columns:repeat(3,1fr)}}
.sim__ctl{padding:18px 20px 20px;border-bottom:1px solid var(--hair)}
.sim__ctl:last-child{border-bottom:none}
@media(min-width:860px){
  .sim__ctl{border-bottom:none;border-right:1px solid var(--hair)}
  .sim__ctl:last-child{border-right:none}
}
.sim__ctl label{
  display:flex;justify-content:space-between;align-items:baseline;gap:10px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-45);
}
.sim__ctl output{color:var(--ink);font-weight:500;letter-spacing:.06em}
.sim__hint{
  display:block;margin-top:8px;font-size:12.5px;line-height:1.5;color:var(--ink-45);
}

.sim input[type=range]{
  -webkit-appearance:none;appearance:none;width:100%;margin:14px 0 0;
  background:transparent;height:20px;cursor:pointer;
}
.sim input[type=range]::-webkit-slider-runnable-track{
  height:2px;background:var(--ink);border:none;
}
.sim input[type=range]::-moz-range-track{height:2px;background:var(--ink)}
.sim input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:16px;height:16px;margin-top:-7px;
  background:var(--ink);border:none;border-radius:0;
}
.sim input[type=range]::-moz-range-thumb{
  width:16px;height:16px;background:var(--ink);border:none;border-radius:0;
}
.sim input[type=range]:focus-visible::-webkit-slider-thumb{outline:3px solid var(--b);outline-offset:2px}
.sim input[type=range]:focus-visible::-moz-range-thumb{outline:3px solid var(--b);outline-offset:2px}

/* View toggles sit above the sliders and left-aligned: they change how the
   frame is drawn, not what it contains, so they read as a legend for the
   stage rather than as another parameter. */
.sim__foot{
  display:flex;padding:16px clamp(14px,2.4vw,24px);
  border-bottom:1px solid var(--hair);
}
.sim__toggles{
  display:flex;gap:18px;flex-wrap:wrap;font-family:var(--mono);
  font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-45);
}
.sim__toggles label{display:inline-flex;align-items:center;gap:7px;cursor:pointer}
.sim__toggles input{accent-color:var(--ink);width:14px;height:14px}

.sim__key{
  display:flex;gap:20px;flex-wrap:wrap;margin-top:16px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;
  text-transform:uppercase;color:rgba(233,233,229,.72);
}
.sim__key i{display:inline-block;width:16px;height:2px;vertical-align:middle;margin-right:7px}
.sim__key .k-beam i{background:var(--g)}
.sim__key .k-blank i{background:var(--r)}
.sim__key .k-anchor i{background:var(--r);height:6px;width:6px;border-radius:50%}

/* project route: the shop card sitting in the project stream */
.proj--route{border-bottom:none}
@media(min-width:880px){}

