/* ============================================================================
   ART DIRECTION: "DICHROIC"

   Futurism by way of thin-film interference rather than neon signage. Dichroic
   glass splits light into cyan and violet depending on the angle you catch it
   at, and that is the whole scheme: one spectrum, two temperatures, nothing
   borrowed from 1999 cyberpunk.

   Three devices carry it:
     1. Notched HUD chips instead of printed slabs. Cut corners, hairline
        borders, a spectral wash inside.
     2. A spectral wash for the ground. Atmosphere, not graph paper.
     3. The pointer as an optical source. It throws Newton's rings, the
        interference fringes a thin dichroic film actually produces, cyan at
        the core falling to violet at the edge.

   Deep blue-black ground, cool white type, cyan on the prices because that is
   the hottest information on the page, violet on structure, magenta reserved
   entirely for caution.
   ============================================================================ */

@font-face{
  font-family:"Big Shoulders";
  src:url("fonts/big-shoulders.woff2") format("woff2");
  font-weight:100 900;          /* variable axis: kinetic type is driven off this */
  font-display:swap;
  font-style:normal;
}

:root{
  color-scheme:dark;

  --void:#07080F;       /* deep blue-black, the ground */
  --slab:#10121F;       /* raised surfaces */
  --seam:#242945;       /* hairlines */
  --bone:#E8ECFF;       /* type */
  --fissure:#9B6BFF;    /* violet: structure, edges, seals */
  --ember:#48F2E2;      /* cyan: prices and primary action only */
  --jade:#5CE6B0;       /* mint, positives only */
  --ash:#8E95BF;        /* secondary type */
  --magenta:#FF5FA8;    /* caution only */

  /* legacy aliases, see the note above */
  --narra:var(--void); --narra-2:var(--slab); --narra-3:var(--seam);
  --capiz:var(--bone); --lamp:var(--ember); --sili:var(--fissure);
  --dahon:var(--jade); --dim:var(--ash);
  --sili-warn:var(--magenta);

  --disp:"Big Shoulders","Arial Narrow",system-ui,sans-serif;
  --body:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --mono:ui-monospace,"Cascadia Mono",Consolas,Menlo,monospace;

  /* 4/8 rhythm */
  --s1:8px; --s2:16px; --s3:24px; --s4:40px; --s5:64px; --s6:104px;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--narra);color:var(--capiz);
  font:17px/1.65 var(--body);-webkit-font-smoothing:antialiased;
}
img{max-width:100%;display:block}
a{color:inherit}
h1,h2,h3,p,li,dt{overflow-wrap:break-word}
h1,h2,h3{text-wrap:balance}
a,button{touch-action:manipulation;-webkit-tap-highlight-color:rgba(72,242,226,.22)}
:focus-visible{outline:3px solid var(--lamp);outline-offset:3px}

.wrap{
  max-width:1180px;margin:0 auto;
  padding-left:max(22px,env(safe-area-inset-left));
  padding-right:max(22px,env(safe-area-inset-right));
}
.skip{position:absolute;left:-9999px;top:0;z-index:99;background:var(--lamp);color:var(--narra);
  padding:12px 20px;font:600 .8rem/1 var(--mono);text-decoration:none}
.skip:focus{left:12px;top:12px}

.eyebrow{font:600 .7rem/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--dim);margin:0 0 var(--s2)}

/* ---------------------------------------------------------------- lattice bar */
.bar{border-bottom:1px solid var(--narra-3);background:rgba(7,8,15,.88);backdrop-filter:blur(10px);
  position:sticky;top:0;z-index:20}
.bar .wrap{display:flex;align-items:center;gap:var(--s2);padding-top:12px;padding-bottom:12px}
/* DF monogram as an identification plate: bone slab, red seal edge, the same
   device as the section title cards so the mark and the page speak once. */
.mark{display:flex;align-items:center;gap:11px;text-decoration:none}
.mark .plate{
  display:inline-flex;align-items:center;color:var(--void);
  font-family:var(--disp);font-weight:800;font-size:1.32rem;line-height:1;
  letter-spacing:.02em;padding:7px 11px 6px;
  background:linear-gradient(135deg,var(--ember),var(--fissure));
  clip-path:polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px));
}
.mark b{font-family:var(--disp);font-weight:700;font-size:1.22rem;letter-spacing:.03em;
  line-height:1;text-transform:uppercase;color:var(--bone)}
/* Workflo is the studio, the name is the person behind it. The byline is the
   quieter of the two and it is the first thing to go when the bar gets tight. */
.mark .by{font:600 .64rem/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;
  font-style:normal;color:var(--ash);padding-left:11px;border-left:1px solid var(--seam)}
/* Hidden at 960 rather than 900. Between roughly 905 and 930 the byline was
   still on screen and the bar needed about 13px more than it had, which pushed
   the button off the right edge and gave the whole page a horizontal scrollbar. */
@media (max-width:960px){.mark .by{display:none}}
/* A flex child will not shrink below its content unless told it may. Without
   this the name is a hard floor on how narrow the bar can get. */
.mark{min-width:0}
.mark b{min-width:0;overflow-wrap:anywhere}
.bar nav{margin-left:auto;display:flex;gap:var(--s3);font:600 .74rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase}
.bar nav a{position:relative;color:var(--dim);text-decoration:none;padding:6px 0;border-bottom:2px solid transparent;transition:color .18s ease}
/* The link is 26px tall, which is a fine size to read and a poor one to hit with
   a thumb. Rather than pad it out and make the bar taller, the hit area is
   extended past the text. Nothing moves; the target reaches 44px. */
.bar nav a::before{content:"";position:absolute;inset:-10px -6px;}
.bar nav a:hover{color:var(--capiz);border-bottom-color:var(--lamp)}
.bar .book{
  margin-left:var(--s3);color:var(--narra);background:var(--lamp);text-decoration:none;
  font:700 .72rem/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;
  padding:11px 16px;border-radius:2px;transition:background-color .16s ease;
}
.bar .book:hover{background:#7DF7EB}
/* 34px and 35px respectively. Extended rather than padded, so the bar keeps its
   height and the button keeps its proportions. */
.bar .book{position:relative}
.bar .book::before{content:"";position:absolute;inset:-5px 0}
.mark{position:relative}
.mark::before{content:"";position:absolute;inset:-5px 0}
/* Phones previously got no navigation at all. A short four-item nav does not
   need a hamburger; it needs to be visible. Wraps to its own scrollable row.

   This used to start at 700px, which left everything from 705 to 855 with a
   nine-item nav still inline and nowhere to put it: the bar wanted 764px and
   the button hung as much as 122px past the right edge. Measured across the
   range rather than guessed, the nav needs its own row from 900 down. */
@media (max-width:900px){
  /* The nav row now carries its own 15px of vertical padding, so the wrap's
     bottom padding was stacking on top of it and leaving 13px of dead space
     under the links against 1px above them. */
  .bar .wrap{flex-wrap:wrap;gap:10px;padding-bottom:0}
  .bar .book{margin-left:auto}
  .bar nav{
    order:3;flex:1 0 100%;margin:0 -22px;padding:0 22px;gap:20px;
    border-top:1px solid var(--seam);overflow-x:auto;scrollbar-width:none;
  }
  .bar nav::-webkit-scrollbar{display:none}
  /* overflow-x:auto makes this row a clipping context, so the hit extender used
     at desktop gets cut off vertically and the target came out at 36px. Here the
     padding is real, and the row loses its own padding to keep the bar the same
     height it was. */
  .bar nav a{white-space:nowrap;padding:15px 0;display:inline-flex;align-items:center}
  .bar nav a::before{content:none}
  /* The row was built for four links and now carries ten: 680px of them in a
     375px rail. With the scrollbar hidden the six off-screen ones leave no
     trace, so the row ends on a half-cut word and reads as broken rather than
     swipeable. A fade on whichever side still has links behind it says which
     way to push, and costs no height. */
  .bar nav{--fl:0px;--fr:0px;
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 var(--fl),#000 calc(100% - var(--fr)),transparent 100%);
            mask-image:linear-gradient(90deg,transparent 0,#000 var(--fl),#000 calc(100% - var(--fr)),transparent 100%)}
  .bar nav.more-l{--fl:26px}
  .bar nav.more-r{--fr:26px}
}

/* ============================================================== THE FISSURE
   The plates run cold until something heats them. A fractured lattice sits at
   the edge of visibility across the whole page; an identical copy, molten, is
   masked to a disc that follows the pointer. Three gradient angles rather than
   a square grid, so it reads as cracks in a shell instead of graph paper.
   ============================================================================ */
/* Ambient: a deep spectral wash so the ground is atmosphere, not graph paper. */
.field{
  position:fixed;inset:0;pointer-events:none;z-index:-2;
  background:
    radial-gradient(70% 54% at 8% 2%, rgba(155,107,255,.26), transparent 60%),
    radial-gradient(58% 50% at 94% 22%, rgba(72,242,226,.18), transparent 58%),
    radial-gradient(80% 58% at 44% 102%, rgba(255,95,168,.14), transparent 64%);
}

/* Pointer: Newton's rings. Thin-film interference is the actual physics behind a
   dichroic surface, so the cursor makes fringes rather than revealing a grid.
   The pattern travels WITH the element, which is why there is no
   background-attachment here: the rings are centred on the pointer, not the page. */
/* ========================================================== DISPERSION
   A prism separates light by wavelength, and it separates it more the harder
   you bend the beam. So the pointer is three colour channels stacked: at rest
   they sit on top of each other and read as one white-ish source, and the
   faster you move the further they slide apart, smearing into cyan, violet and
   magenta before snapping back together when you stop.

   The container also eases toward the pointer rather than tracking it exactly,
   which is what gives the whole thing weight. Everything here is transform and
   opacity, deliberately: the previous version put the signature effect on
   @property interpolation and it silently did nothing.
   ============================================================================ */
.torch{
  position:fixed;pointer-events:none;z-index:-1;
  left:0;top:0;width:460px;height:460px;margin:-230px 0 0 -230px;
  --x:50vw; --y:38vh; --sx:0px; --sy:0px;
  transform:translate3d(var(--x),var(--y),0);
  will-change:transform;
}
.torch i{
  position:absolute;inset:0;border-radius:50%;
  mix-blend-mode:screen;
  will-change:transform;
}
/* cyan leads, magenta trails, violet holds the centre */
.torch i.c{background:radial-gradient(circle at center,rgba(72,242,226,.55),rgba(72,242,226,.12) 34%,transparent 52%);
  transform:translate3d(calc(var(--sx) * -1),calc(var(--sy) * -1),0)}
.torch i.m{background:radial-gradient(circle at center,rgba(255,95,168,.48),rgba(255,95,168,.10) 34%,transparent 52%);
  transform:translate3d(var(--sx),var(--sy),0)}
.torch i.v{background:radial-gradient(circle at center,rgba(155,107,255,.50),rgba(155,107,255,.11) 32%,transparent 50%)}

/* Idle breath so the source is alive when the pointer is parked. */
@media (prefers-reduced-motion:no-preference){
  .torch i.v{animation:pulse 3.4s ease-in-out infinite alternate}
}
@keyframes pulse{from{transform:scale(.84);opacity:.7}to{transform:scale(1.06);opacity:1}}

/* Nothing to track on touch, so the source rests off-centre. The fringes still
   propagate, so it is alive without a pointer. */
@media (hover:none){
  .torch{--tx:78vw; --ty:26vh}
}

.window{position:relative;padding:var(--s5) 0 var(--s6);overflow:hidden}

/* ============================================================== KINETIC GRID
   A lattice that bends toward the pointer and rings out from a click. It lives
   inside the hero rather than over the page, and it paints no ground of its
   own, so the .field wash and the .torch prism read straight through it. Under
   the scrim at z-index 1 and the type at z-index 2, which is what lets the
   bottom of it dissolve into the page floor. kinetic-grid.js drives it.
   ========================================================================== */
.window .lattice{
  position:absolute;inset:0;z-index:0;
  display:block;width:100%;height:100%;
  pointer-events:none;
}
/* The field is faint enough to sit under body copy, so the hero only needs a soft
   floor to seat the type on, not the heavy scrim the old solid panes required. */
.window::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,rgba(7,8,15,.5) 0%,rgba(7,8,15,.16) 42%,transparent 72%,var(--narra) 100%);
}
@media (max-width:820px){.window{padding-top:var(--s4)}}
@media (prefers-reduced-motion:no-preference){
  .field,.torch,.window .lattice{animation:wake 1.3s ease-out backwards}
}
@keyframes wake{from{opacity:0}}

.window .wrap{position:relative;z-index:2}
.window h1{
  font-family:var(--disp);font-weight:750;
  font-size:clamp(2.5rem,7.4vw,5.4rem);line-height:.92;letter-spacing:-.008em;
  margin:0 0 var(--s3);text-transform:uppercase;max-width:17ch;
}
.window h1 .lit{
  color:var(--lamp);
  text-shadow:0 0 22px rgba(72,242,226,.22);   /* light through shell, not neon */
}
.window .rule{width:64px;height:2px;background:var(--lamp);margin:0 0 var(--s3);opacity:.8}
/* Kinetic type: the headline gains weight as the page is entered. Zero JS, and
   it degrades to a fixed weight where scroll timelines are unsupported. */
/* The variable-weight move used to be tied to scroll, where nobody saw it. It
   now happens on arrival, staggered with the rest of the hero. */
@media (prefers-reduced-motion:no-preference){
  .window .wrap > *{animation:heroIn .8s cubic-bezier(.2,.8,.25,1) backwards}
  .window .eyebrow{animation-delay:.06s}
  .window h1{animation:heroType 1s cubic-bezier(.2,.8,.25,1) .12s backwards}
  .window .rule{animation-delay:.5s}
  .window .lede{animation-delay:.58s}
  .window .cta{animation-delay:.68s}
  .window .note{animation-delay:.78s}
}
@keyframes heroIn{from{opacity:0;transform:translateY(20px)}}
@keyframes heroType{from{opacity:0;transform:translateY(26px);font-weight:300;letter-spacing:.06em}}

.window .lede{font-size:clamp(1.04rem,2.3vw,1.26rem);color:var(--dim);max-width:46ch;margin:0 0 var(--s4)}
.window .lede b{color:var(--capiz);font-weight:600}

.cta{display:flex;flex-wrap:wrap;gap:12px;align-items:center}
.btn{
  display:inline-flex;align-items:center;min-height:48px;padding:14px 28px;
  font:700 .98rem/1 var(--body);text-decoration:none;border-radius:2px;
  transition:transform .16s ease,background-color .16s ease,color .16s ease;
}
.btn-lit{background:var(--lamp);color:var(--narra)}
.btn-lit:hover{background:#7DF7EB}
.btn-ghost{color:var(--capiz);box-shadow:inset 0 0 0 1px var(--narra-3)}
.btn-ghost:hover{background:var(--narra-2);box-shadow:inset 0 0 0 1px var(--lamp)}
.btn:active{transform:translateY(1px)}
/* Capped like every other block of prose on this page. Uncapped it ran the
   full 1180px of the wrap, which is about 187 characters a line. */
.note{font:.76rem/1.5 var(--mono);color:var(--dim);margin:var(--s2) 0 0;max-width:74ch}

/* ---------------------------------------------------------------- sections */
.sec{padding:var(--s6) 0;border-top:1px solid var(--narra-3)}
/* A small label above each heading. Gives the page an index-like structure
   without resorting to 01 / 02 / 03, which every studio template already uses. */
/* The title card. White slab, heavy condensed type, red seal edge, monospace
   designation alongside. The one device the whole theme hangs on. */
.label{display:flex;align-items:center;flex-wrap:wrap;gap:8px 16px;margin:0 0 var(--s3)}
.label span{
  display:inline-flex;align-items:center;color:var(--bone);
  background:linear-gradient(135deg,rgba(72,242,226,.17),rgba(155,107,255,.12));
  box-shadow:inset 0 0 0 1px rgba(72,242,226,.42);
  font-family:var(--disp);font-weight:800;font-size:.82rem;
  letter-spacing:.11em;text-transform:uppercase;padding:6px 14px;
  clip-path:polygon(0 0,calc(100% - 9px) 0,100% 9px,100% 100%,9px 100%,0 calc(100% - 9px));
}
.label i{
  font-style:normal;font:600 .66rem/1.4 var(--mono);letter-spacing:.2em;
  text-transform:uppercase;color:var(--ash);
}
.sec h2{
  font-family:var(--disp);font-weight:750;text-transform:uppercase;
  font-size:clamp(2rem,5.2vw,3.5rem);line-height:.94;letter-spacing:-.012em;margin:0 0 var(--s2);
}
.sec .sub{color:var(--dim);max-width:58ch;margin:0 0 var(--s5)}

/* reveal-on-enter, scroll-timeline driven, guarded so unsupported browsers just show it */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .rise{animation:rise linear both;animation-timeline:view();animation-range:entry 0% entry 46%}
  }
}
@keyframes rise{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}

/* ---------------------------------------------------------------- the board */
.board{
  background:var(--narra-2);border:1px solid var(--narra-3);border-radius:3px;
  padding:clamp(26px,4.4vw,52px);position:relative;overflow:hidden;
}
.board::before{                       /* lamp behind the shell */
  content:"";position:absolute;left:-10%;right:-10%;top:-60%;height:120%;
  background:radial-gradient(50% 50% at 50% 50%,rgba(72,242,226,.13),transparent 70%);
  pointer-events:none;
}
.board-head{display:flex;flex-wrap:wrap;gap:6px 16px;align-items:baseline;
  padding-bottom:var(--s3);border-bottom:1px solid rgba(72,242,226,.32);position:relative}
.board-head h2{color:var(--capiz);margin:0;font-size:clamp(1.6rem,3.8vw,2.5rem)}
.board-head .tl{font:600 .7rem/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--dim)}
.board-note{color:var(--dim);max-width:56ch;margin:var(--s3) 0 var(--s4);position:relative}

.line{padding:var(--s3) 0;border-bottom:1px solid rgba(233,237,243,.1);position:relative}
.line:last-of-type{border-bottom:0;padding-bottom:0}
.line-top{display:grid;grid-template-columns:1fr auto;gap:2px 14px;align-items:baseline}
.line-name{font-family:var(--disp);font-weight:700;font-size:clamp(1.3rem,3.4vw,1.75rem);
  text-transform:uppercase;letter-spacing:.01em;min-width:0}
.leader{display:none}
.line-price{font-family:var(--disp);font-weight:800;color:var(--lamp);
  font-size:clamp(1.5rem,4vw,2.1rem);white-space:nowrap;font-variant-numeric:tabular-nums}
@media (min-width:620px){
  .line-top{display:flex;gap:14px}
  .line-name{flex:0 1 auto}
  .leader{display:block;flex:1 1 24px;border-bottom:1px dotted rgba(233,237,243,.22);transform:translateY(-.3em);min-width:18px}
}
.line-desc{color:var(--dim);font-size:.96rem;margin:10px 0 0;max-width:62ch}
.stamp{display:inline-block;font:600 .68rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--lamp);border:1px solid rgba(72,242,226,.38);padding:5px 10px;border-radius:2px;margin-top:14px}
.stamp.free{color:var(--narra);background:var(--dahon);border-color:var(--dahon)}

/* ---------------------------------------------------------------- work: bento */
/* Panel size carries importance, per the 2026 bento pattern. Two panels are
   double-width because they are the two the buyer should look at first. */
.bento{display:grid;grid-template-columns:repeat(6,1fr);gap:var(--s2)}
.card{position:relative;grid-column:span 6;background:var(--narra-2);border:1px solid var(--narra-3);border-radius:3px;
  display:flex;flex-direction:column;transition:border-color .2s ease,transform .2s ease}
.card:hover{border-color:var(--fissure);transform:translateY(-3px)}
/* HUD registration marks, on demand */
.card::before,.card::after{
  content:"";position:absolute;width:15px;height:15px;z-index:3;pointer-events:none;
  opacity:0;transition:opacity .2s ease;
}
.card::before{top:0;left:0;border-top:2px solid var(--ember);border-left:2px solid var(--ember)}
.card::after{bottom:0;right:0;border-bottom:2px solid var(--ember);border-right:2px solid var(--ember)}
.card:hover::before,.card:hover::after,
.card:focus-within::before,.card:focus-within::after{opacity:1}
@media (min-width:760px){
  .card{grid-column:span 2}
  .card.wide{grid-column:span 4}
  .card.half{grid-column:span 3}
}
/* Hover reveal, the pattern the Ceros survey kept turning up: the designation
   the card already carries, surfaced on demand instead of cluttering the grid.
   Comes from a data-desig attribute, so no extra markup per card. */
.card figure{margin:0;position:relative;background:var(--narra);overflow:hidden;aspect-ratio:16/10}
/* A preview, not a poster.
   16:10 alone keeps the shape consistent and lets the height run away with the
   width: a full-width panel was rendering its screenshot at 1134x709, three
   times the height of a small card's, so the two biggest panels each filled a
   screen and pushed the sentence explaining them below the fold. Capping the
   height gives the grid a rhythm the eye can hold, and because these images are
   already object-fit:cover from the top, the crop lands on the hero, which is
   the part of a website worth previewing anyway. The sizes still descend, so
   panel size still says which work matters most. */
.card.wide figure{max-height:400px}
.card.full figure{max-height:520px}
.card figure::after{
  content:attr(data-desig);
  position:absolute;left:0;bottom:0;
  background:var(--fissure);color:var(--void);   /* black on the seal: 4.99:1, white was 3.42:1 */
  font:700 .62rem/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;
  padding:8px 12px;
  transform:translateY(100%);
  transition:transform .22s cubic-bezier(.2,.7,.3,1);
}
.card:hover figure::after,.card:focus-within figure::after{transform:none}
@media (prefers-reduced-motion:reduce){.card figure::after{transition:none}}
.card img{width:100%;height:100%;object-fit:cover;object-position:top;border-bottom:1px solid var(--narra-3);
  transition:transform .45s cubic-bezier(.2,.7,.3,1)}
.card video{width:100%;height:100%;object-fit:cover;object-position:center;display:block;
  border-bottom:1px solid var(--narra-3);background:var(--void)}
/* the caste line-ups are a 2.85:1 strip; in a 16:10 window they would sit in a bar of dead
   space, so those two figures take the clip's own shape and the pad crops away. */
.card figure.wideclip{aspect-ratio:57/20}

/* the interactive model. model-viewer paints its own canvas, so the slab and the hairline
   are ours and everything inside the box is the element's. */
.viewer{border:1px solid var(--seam);border-radius:3px;background:var(--slab);overflow:hidden;
  margin-bottom:var(--s5)}
.viewer model-viewer{display:block;width:100%;height:min(58vh,520px);
  --poster-color:transparent;background:var(--slab)}
/* before the element upgrades it is an unknown tag with no box of its own, so the still
   render stands in until the script has arrived. */
.viewer model-viewer:not(:defined){display:block;width:100%;height:min(58vh,520px);
  background:var(--slab)}
/* Only the ant has a still to stand in for it. Scoped by id because the shared
   selector handed the ant's poster to the Godzilla viewer too, so the pipeline
   section showed an ant until the module arrived. */
#ant:not(:defined){background:var(--slab) url("models/ant-poster.jpg") center/contain no-repeat}
/* The AR button is a light-DOM child of the element. Until the module defines
   model-viewer there is no shadow root to slot it into, so it is laid out as an
   ordinary absolute child and resolves against .viewer rather than the canvas:
   it lands on top of the caption paragraph, several hundred pixels from where it
   belongs. On a phone the module is a megabyte behind a scroll observer, so that
   window is long enough to see and tap. It has nothing to activate before then,
   so keep it out of the layout entirely. */
.viewer model-viewer:not(:defined) .ar-cta{display:none}
.viewer-bar{display:flex;flex-wrap:wrap;align-items:center;gap:10px var(--s3);
  padding:12px var(--s3);border-top:1px solid var(--seam)}
.castes{display:flex;gap:8px}
.caste{font:700 .68rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--ash);background:transparent;border:1px solid var(--seam);border-radius:2px;
  padding:10px 15px;cursor:pointer;
  transition:color .16s ease,border-color .16s ease,background-color .16s ease}
.caste:hover{color:var(--bone);border-color:var(--fissure)}
.caste.on{color:var(--void);background:var(--ember);border-color:var(--ember)}
.caste:focus-visible{outline:2px solid var(--ember);outline-offset:3px}
.viewer-hint{margin:0 0 0 auto;font:600 .64rem/1 var(--mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ash)}
.viewer-note{margin:0;padding:4px var(--s3) 18px;color:var(--ash);font-size:.92rem;max-width:74ch}
.viewer-note b{color:var(--bone)}
@media (max-width:759px){.viewer-hint{margin:0}}
@media (max-width:759px){.viewer model-viewer{height:360px}}
.card:hover img{transform:scale(1.05)}
.card-body{padding:var(--s3) var(--s3) calc(var(--s3) + 4px)}
.card h3{font-family:var(--disp);font-weight:700;font-size:1.42rem;text-transform:uppercase;
  letter-spacing:.01em;margin:0 0 4px;line-height:1.05}
.kind{font:600 .66rem/1.5 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin:0 0 12px}
.kind b{color:var(--lamp);font-weight:700}
/* The bento's double-width panels gave their body copy a 144 character line,
   which is roughly twice a comfortable measure. The cap matches the rest of
   the page's secondary prose; a wide card is wide because the panel matters,
   not because the sentence should stretch to fill it. */
.card p{margin:0;font-size:.94rem;color:var(--dim);max-width:64ch}

/* The three real builds open in a new tab. The line is the visible affordance; the
   pseudo-element stretches it over the whole card so the image is clickable too, and it
   sits under the ::before/::after registration marks (z-index 3) rather than over them. */
.visit{display:inline-block;margin-top:16px;font:700 .68rem/1 var(--mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ember);text-decoration:none;
  border-bottom:1px solid transparent;padding-bottom:4px}
.visit::before{content:"";position:absolute;inset:0;z-index:2}
.visit::after{content:"\2009\2192"}
.card.is-live:hover .visit{border-bottom-color:var(--ember)}
.visit:focus-visible{outline:2px solid var(--ember);outline-offset:4px;border-radius:2px}

/* Secondary link on a card that already has a .visit. The .visit::before overlay
   stretches over the whole card at z-index 2 to make the image clickable, so
   anything meant to stay separately clickable has to sit above it. */
.note-link{
  position:relative;z-index:4;display:inline-block;margin-top:12px;
  font:600 .74rem/1.45 var(--body);color:var(--dim);text-decoration:none;
  border-bottom:1px solid var(--seam);padding-bottom:2px;
  transition:color .16s ease,border-color .16s ease;
}
.note-link:hover{color:var(--sili);border-bottom-color:var(--sili)}
.note-link::after{content:"\2009\2192"}
.note-link:focus-visible{outline:2px solid var(--sili);outline-offset:3px;border-radius:2px}
@media (prefers-reduced-motion:reduce){.card.is-live:hover img{transform:none}}

/* ---------------------------------------------------------------- steps */
.steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:var(--s4);counter-reset:s}
/* the capability list: nine short items, so it wants a tighter rhythm than the 3-step
   process grid it borrows from, and a rule under the stack note that closes the section. */
.caps{grid-template-columns:repeat(auto-fit,minmax(268px,1fr));gap:var(--s3) var(--s4)}
.caps .step h3{font-size:1.2rem}
.caps .step p{font-size:.92rem}
.sub.stack{margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--seam)}
.step{counter-increment:s}
.step::before{content:counter(s,decimal-leading-zero);display:block;font:700 .82rem/1 var(--mono);
  letter-spacing:.14em;color:var(--lamp);padding-bottom:12px;border-bottom:1px solid var(--narra-3);margin-bottom:16px}
.step h3{font-family:var(--disp);font-weight:700;font-size:1.4rem;text-transform:uppercase;margin:0 0 8px;line-height:1.05}
.step p{margin:0;color:var(--dim);font-size:.96rem}

/* ---------------------------------------------------------------- two columns */
.cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--s5)}
.cols h3{font:600 .7rem/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--dim);margin:0 0 var(--s2)}
.cols ul{margin:0;padding:0;list-style:none}
.cols li{padding:12px 0 12px 28px;border-bottom:1px solid var(--narra-3);position:relative;font-size:.98rem}
.cols li::before{position:absolute;left:0;top:12px;font-family:var(--mono);font-weight:700}
.yes li::before{content:"+";color:var(--dahon)}
.add li{display:flex;gap:14px;align-items:baseline}
.add li::before{content:"+";color:var(--lamp)}
.add li b{flex:1;font-weight:400}
.add li span{font:700 .84rem/1 var(--mono);color:var(--lamp);white-space:nowrap;font-variant-numeric:tabular-nums}
.caveat{margin:var(--s4) 0 0;padding:var(--s3) var(--s3);border-left:2px solid var(--magenta);
  color:var(--dim);font-size:.95rem;max-width:72ch;background:rgba(255,95,168,.05)}
.caveat b{color:var(--capiz);font-weight:600}

/* ---------------------------------------------------------------- close */
.close{border-top:1px solid var(--narra-3);position:relative;overflow:hidden}
.close::before{content:"";position:absolute;left:50%;top:0;width:min(900px,120%);height:100%;
  transform:translateX(-50%);
  background:radial-gradient(60% 70% at 50% 0%,rgba(155,107,255,.14),transparent 70%);pointer-events:none}
/* the beam crossing sits off the right edge. No blend mode here on purpose: the
   torch is already screen blended, and a second one dragged it into the same
   group and flared its resting glow. The shader writes its own alpha instead. */
.close .beam,
.window .vortex,
.window .beam{
  position:absolute;inset:0;z-index:0;
  display:block;width:100%;height:100%;
  pointer-events:none;
}
/* In the hero the beam sits under the dot field: ground, then texture, then the
   scrim, then the type. It is first in the markup, which is what puts it there. */
.close .wrap{padding-top:var(--s6);padding-bottom:var(--s6);position:relative}
.close h2{font-family:var(--disp);font-weight:750;text-transform:uppercase;color:var(--capiz);
  font-size:clamp(1.9rem,4.8vw,3.2rem);line-height:.96;letter-spacing:-.006em;margin:0 0 var(--s3);max-width:20ch}
.close p{color:var(--dim);max-width:50ch;margin:0 0 var(--s4)}
.who{display:flex;gap:var(--s3);align-items:center;margin-top:var(--s5);padding-top:var(--s3);border-top:1px solid var(--narra-3)}
.who .photo{width:66px;height:66px;flex:none;border-radius:50%;background:var(--narra-2);
  border:1px dashed var(--narra-3);display:flex;align-items:center;justify-content:center;
  font:600 .52rem/1.25 var(--mono);color:var(--dim);text-align:center;padding:6px}
.who p{margin:0;font-size:.95rem}
footer{border-top:1px solid var(--narra-3)}
footer .wrap{padding:var(--s3) 22px var(--s5);font:.74rem/1.6 var(--mono);color:var(--dim);letter-spacing:.04em}

/* "Reduce" means reduce, not eliminate. Travel stops: no marquee scroll, no
   propagating fringes, no slide-in entrances, since those are the vestibular
   ones. What stays is a slow opacity breath, which is non-vestibular and keeps
   the page from reading as a dead screenshot for anyone with the OS setting on.
   Hover and focus feedback also stays; removing it makes a page feel broken. */
@media (prefers-reduced-motion:reduce){
  .band .track,.window .wrap > *,.window h1,.rise,.card figure::after{animation:none!important}
  .rise{opacity:1!important;transform:none!important}
  .card:hover img{transform:none}
  .torch i.c,.torch i.m{display:none}
  .torch{animation:softbreath 7s ease-in-out infinite alternate!important}
  .field{animation:softbreath 11s ease-in-out infinite alternate!important}
}
@keyframes softbreath{from{opacity:.5}to{opacity:1}}

/* Real client work takes the full width of the grid. */
@media (min-width:760px){.card.full{grid-column:span 6}}
/* A slot that reads as reserved rather than broken while a screenshot is missing. */
.pending{
  display:flex;flex-direction:column;justify-content:center;gap:6px;
  min-height:170px;padding:var(--s3);
  background:
    repeating-linear-gradient(-45deg,rgba(155,107,255,.07) 0 12px,transparent 12px 24px),
    var(--slab);
  border-bottom:1px solid var(--seam);
}
.pending b{font-family:var(--disp);font-weight:800;text-transform:uppercase;font-size:1.15rem;letter-spacing:.03em}
.pending span{font:600 .68rem/1.5 var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--ash)}


/* =============================================================== SEAM BAND
   A full-bleed break in the rhythm, and the one place the fissure is stated at
   scale instead of only appearing under the cursor. The page was nine identical
   containers in a row; this interrupts it twice.
   ============================================================================ */
.band{
  position:relative;overflow:hidden;
  border-top:1px solid var(--seam);border-bottom:1px solid var(--seam);
  background:
    repeating-linear-gradient(90deg,rgba(72,242,226,.3) 0 1px,transparent 1px 84px),
    linear-gradient(90deg,rgba(155,107,255,.20),rgba(72,242,226,.14),rgba(255,95,168,.16),rgba(155,107,255,.20)),
    var(--void);
  padding:16px 0;
}
.band .track{display:flex;width:max-content}
.band .track span{
  white-space:nowrap;padding-right:0;
  font:700 .74rem/1 var(--mono);letter-spacing:.34em;text-transform:uppercase;
  color:var(--bone);opacity:.92;
}
@media (prefers-reduced-motion:no-preference){
  .band .track{animation:marquee 30s linear infinite}
}
@keyframes marquee{to{transform:translateX(-50%)}}

/* ============================================================== TOOL ROWS
   The tools used to repeat the sample-site grid card for card, so the page said
   the same visual sentence twice. A dense list changes the pace and is honest
   about these being secondary, while the thumbnails keep the evidence.
   ============================================================================ */
.rows{border-top:1px solid var(--seam)}
.row{
  display:grid;grid-template-columns:132px 1fr auto;gap:4px 22px;align-items:start;position:relative;
  padding:18px 0;border-bottom:1px solid var(--seam);
}
.row img{grid-row:1/3;width:132px;height:84px;object-fit:cover;object-position:top;border:1px solid var(--seam)}
.row h3{font-family:var(--disp);font-weight:700;font-size:1.34rem;text-transform:uppercase;
  letter-spacing:.01em;margin:0;line-height:1.05;align-self:center}
.row .t{font:600 .64rem/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--ash);align-self:center;white-space:nowrap}
.row .visit{grid-column:2/4;margin-top:10px;font-size:.64rem}
.row p{grid-column:2/4;margin:0;color:var(--ash);font-size:.94rem;max-width:74ch}
/* A secondary link in a row is still a row child, so without a column it lands
   in the image column and reads as stray body text sitting under the thumbnail.
   It sits beside the primary link instead, in the same track as the copy. */
.row .visit + .note-link{grid-column:2/4;justify-self:start;margin-top:6px;
  font:600 .64rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase}
@media (max-width:640px){
  .row{grid-template-columns:96px 1fr;gap:4px 16px}
  .row img{width:96px;height:62px}
  /* nowrap makes this label a hard minimum width, so a longer one than the
     designer happened to try pushes the whole grid past the viewport. On a
     phone it wraps instead of dictating the layout. */
  .row .t{grid-column:2;align-self:start;margin-top:2px;white-space:normal}
  .row h3{align-self:start}
  .row p{grid-column:1/3;margin-top:8px}
  .row .visit + .note-link{grid-column:1/3}
}

/* The reserved slot must not inherit the 16:10 card ratio, or a full-width card
   with no screenshot yet stretches into 700px of empty stripes. */
.card figure.reserved{aspect-ratio:auto}

/* ---------- picture to 3D to AR: the AR call to action ----------
   model-viewer only reveals this button when the device can actually place the
   model, so it is invisible on desktop by design. */
.ar-cta {
  position: absolute;
  right: 14px;
  bottom: 14px;
  min-height: 44px;
  padding: 0 18px;
  border: 0;
  border-radius: 2px;
  background: #f4f1ea;
  color: #14110f;
  font: 600 12px/44px "IBM Plex Mono", ui-monospace, monospace;
  letter-spacing: .12em;
  text-transform: uppercase;
  cursor: pointer;
}

.ar-cta:hover { background: #fff; }
