/* ==========================================================================
   HECHCREATES, homepage

   SIZING NOTE
   The Figma frames are 1728 px wide, a 16" MacBook comp. Those pixels are
   not CSS pixels: dropped in 1:1 they render ~20% oversized on a typical
   1440 px screen, and 414 px cards fit fewer than four per row where a
   streaming UI shows five or six.

   So the design's *proportions and hierarchy* are kept, but the absolute
   values are rebased against measured Netflix density at 1440 px:
   body 16px, row headings 24px/500, cards 224–270px, gaps 0–16px.
   Original design values are noted alongside each token.
   ========================================================================== */

:root{
  /* ---- colour (unchanged, read from the source files) ---------------- */
  --ground:        #1E1B1B;
  --ground-tint:   #242D0F;
  --surface:       #1D1A1A;
  --placeholder:   #D9D9D9;

  --accent:        #81E821;
  --accent-deep:   #5AA810;
  --pill:          #3E5B31;

  --search-fill:   rgba(62, 91, 49, .47);
  --search-edge:   rgba(129, 232, 33, .47);

  --white:         #FFFFFF;
  --ink-invert:    #000000;
  --ink-dim:       rgba(255,255,255,.62);
  --ink-faint:     rgba(255,255,255,.38);

  /* ---- type scale ----------------------------------------------------- */
  --fs-nav:      15px;   /* design 20 */
  --fs-body:     15px;   /* design 20 */
  --fs-row:      20px;   /* design 24 */
  --fs-hero-bio: 15px;   /* design 20 */
  --fs-btn:      15px;   /* design 22 */
  --fs-search:   14px;   /* design 16 */
  --fs-peek:     14px;   /* design 18 */
  --fs-peek-ttl: 18px;   /* design 24 */
  --fs-sheet:    15px;   /* design 20 */
  --fs-sheet-ttl:30px;   /* design 40 */

  /* ---- layout --------------------------------------------------------- */
  --frame:      1920px;  /* design comp was 1728; cards now set the density */
  --gutter:     56px;    /* design 69 */
  --card-w:     260px;   /* design 414 */
  --card-h:     171px;   /* design 272, ratio 1.522 preserved */
  --card-gap:   12px;    /* design 20 */
  --card-r:     8px;     /* design 14 */
  --row-gap:    42px;    /* design 66 */
  --head-gap:   16px;    /* design 28 */

  --hero-r:     14px;    /* design 24 */
  --hero-pad:   56px;    /* design 90 */
  --hero-title: 104px;   /* HECH.svg is 2.81:1, so this is ~292px wide */
  --hero-offset:124px;   /* masthead 22 + nav 40 + hero pad 34 + 28 below */
  --hero-ratio: 1.56;    /* shape of the hero media, for the stacked phone
                            layout only. 1.5 = the placeholder portrait;
                            set 1.778 when the 16:9 showreel lands. */
  --reel-ratio: 16 / 9;  /* every project media frame, hover panel and sheet
                            banner alike. Showreels are 16:9, so both frames
                            take one uncropped. */

  --nav-brand:  132px;   /* design 186 */
  --peek-w:     420px;   /* design 640 */

  --ease: cubic-bezier(.22,.61,.36,1);
}

/* Card size and gutter step down with the viewport, the way a streaming
   grid does, the number of visible cards stays roughly 5–6 throughout. */
@media (max-width:1599px){
  :root{ --gutter:48px; --card-w:240px; --card-h:158px; }
}
@media (max-width:1279px){
  :root{
    --gutter:40px; --card-w:216px; --card-h:142px; --card-gap:10px;
    --hero-pad:44px; --hero-title:88px; --peek-w:376px;
    --row-gap:36px;
  }
}
@media (max-width:1023px){
  :root{
    --gutter:32px; --card-w:200px; --card-h:131px;
    --fs-row:18px; --hero-pad:34px; --hero-title:74px;
    --fs-sheet-ttl:26px;
  }
}
@media (max-width:767px){
  :root{
    --gutter:20px; --card-w:172px; --card-h:113px; --card-gap:8px;
    --fs-nav:14px; --fs-body:14px; --fs-row:16px; --fs-hero-bio:14px;
    --fs-btn:14px; --fs-sheet:14px; --fs-sheet-ttl:22px;
    --hero-pad:20px; --hero-title:min(56px, 7.6dvh); --nav-brand:112px;
    --row-gap:30px; --head-gap:12px;
  }
}

/* ==========================================================================
   base
   ========================================================================== */

*,*::before,*::after{ box-sizing:border-box; }

html{ scroll-behavior:smooth; }

body{
  margin:0;
  min-height:100vh;
  background:var(--ground);
  color:var(--white);
  font-family:'Manrope',system-ui,-apple-system,'Segoe UI',sans-serif;
  font-size:var(--fs-body);
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}

body::before{
  content:'';
  position:fixed; inset:0; z-index:-2;
  background:linear-gradient(158deg, var(--ground) 0%, var(--ground) 42%, var(--ground-tint) 165%);
}

/* Grain, present as a full-frame layer in every screen export. */
body::after{
  content:'';
  position:fixed; inset:0; z-index:60;
  pointer-events:none;
  opacity:.05;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>");
}

img{ max-width:100%; display:block; }
button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
a{ color:inherit; text-decoration:none; }

:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; border-radius:4px; }

.visually-hidden{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}

.shell{
  width:100%;
  max-width:var(--frame);
  margin-inline:auto;
  padding-inline:var(--gutter);
}

/* ==========================================================================
   loader
   ========================================================================== */

#loader{
  position:fixed; inset:0; z-index:100;
  display:grid; place-items:center;
  background:var(--ground);
  transition:opacity .5s var(--ease), visibility .5s;
}
#loader[hidden]{ display:none; }
#loader.is-done{ opacity:0; visibility:hidden; }

.loader__stage{ display:grid; place-items:center; }
.loader__stage > *{ grid-area:1 / 1; }

.loader__mark{ width:52px; height:auto; }
.loader__type{ width:200px; height:auto; }

.loader__mark,.loader__type{ opacity:0; }
.loader__mark{ animation:beat-mark 2.1s var(--ease) forwards; }
.loader__type{ animation:beat-type 2.1s var(--ease) forwards; }

@keyframes beat-mark{
  0%   { opacity:0; transform:translateY(8px) scale(.94); }
  16%  { opacity:1; transform:none; }
  46%  { opacity:1; transform:none; }
  58%  { opacity:0; transform:scale(1.06); }
  100% { opacity:0; }
}
@keyframes beat-type{
  0%,52% { opacity:0; transform:translateY(6px); }
  66%    { opacity:1; transform:none; }
  88%    { opacity:1; transform:none; }
  100%   { opacity:0; }
}

/* ==========================================================================
   nav
   ========================================================================== */

.masthead{
  position:sticky; top:0; z-index:50;
  padding-block:22px 0;                /* design 48 */
  background:linear-gradient(var(--ground) 58%, rgba(30,27,27,0));
  transition:background .3s var(--ease), padding .3s var(--ease);
}
.masthead.is-stuck{
  background:rgba(30,27,27,.9);
  backdrop-filter:blur(14px);
  padding-block:10px;
  box-shadow:0 1px 0 rgba(255,255,255,.06);
}

.nav{
  display:flex; align-items:center; gap:22px;
  height:40px;                          /* design 52 */
}

.nav__brand{
  display:flex; align-items:center;
  width:var(--nav-brand); flex:none;
  height:26px;                          /* design 37 */
}
.nav__brand svg{ width:100%; height:auto; }
.nav__brand svg path{ fill:var(--accent); }

.nav__links{
  display:flex; align-items:center; gap:28px;
  list-style:none; margin:0; padding:0;
  flex:1 1 auto;
}
.nav__link{
  display:inline-flex; align-items:center; justify-content:center;
  height:30px; padding-inline:14px;     /* design 35 / 22 */
  border-radius:8px;
  font-size:var(--fs-nav); font-weight:500;   /* medium, inactive */
  color:var(--white);
  white-space:nowrap;
  transition:background .18s var(--ease);
}
.nav__link:hover{ background:rgba(255,255,255,.07); }
.nav__link[aria-current="page"]{ background:var(--pill); font-weight:700; }

.search{
  position:relative;
  width:260px; height:38px; flex:none;  /* design 351 × 52 */
}
.search__input{
  width:100%; height:100%;
  padding:0 40px 0 16px;
  border-radius:10px;
  border:1.5px solid var(--search-edge);
  background:var(--search-fill);
  color:var(--white);
  font:inherit; font-size:var(--fs-search);
  transition:border-color .2s var(--ease), background .2s var(--ease);
}
.search__input::placeholder{ color:var(--ink-dim); }
.search__input:focus{
  outline:none;
  border-color:var(--accent);
  background:rgba(62,91,49,.62);
}
.search__icon{
  position:absolute; right:13px; top:50%;
  translate:0 -50%;
  width:17px; height:17px;
  stroke:var(--white); stroke-width:2; fill:none;
  pointer-events:none;
}

.nav__toggle{ display:none; }

/* ==========================================================================
   hero
   ========================================================================== */

.hero{ padding-top:34px; }             /* design 73 */

/* The hero card is the about-me panel: full content width, full viewport
   height below the nav. Its media is a showreel, so it is meant to be filled
   edge to edge, `cover` crops to whatever shape the card ends up.
   --hero-offset is the nav block plus the breathing room under the card. */
.hero__card{
  position:relative;
  border-radius:var(--hero-r);
  overflow:hidden;
  width:100%;
  height:calc(100vh - var(--hero-offset));
  height:calc(100dvh - var(--hero-offset));   /* dvh dodges mobile browser chrome */
  min-height:420px;
  background:var(--surface);
  display:flex; align-items:flex-end;
  isolation:isolate;
}

.hero__media{ position:absolute; inset:0; z-index:-1; }
.hero__media img,
.hero__media video{
  position:relative; z-index:0;
  width:100%; height:100%;
  object-fit:cover;
  /* The plate carries 17% of its height as air above his head. Anchoring the
     crop near the top spends a card's overflow on the floor instead, so no
     viewport, however short, takes the top of his head off. */
  object-position:center 26%;
  /* The plate is a light studio backdrop; this seats it in the dark shell
     without crushing the subject, who is already the darkest thing in frame. */
  filter:brightness(.84) contrast(1.06) saturate(.94);
}
/* Depth of field rather than a smudge: full strength at the left edge, where
   the copy sits, and gone before it reaches the subject. */
.hero__media::before{
  content:'';
  position:absolute; inset:0; z-index:1;
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
  -webkit-mask-image:linear-gradient(90deg, #000 0%, rgba(0,0,0,.8) 26%, transparent 56%);
  mask-image:linear-gradient(90deg, #000 0%, rgba(0,0,0,.8) 26%, transparent 56%);
}
/* Scrim so the copy holds over the photograph. */
.hero__media::after{
  content:'';
  position:absolute; inset:0; z-index:2;
  background:
    /* Keeps a little tint all the way across so the bright upper right of the
       backdrop does not blow out against the shell. */
    linear-gradient(90deg, rgba(20,17,17,.93) 0%, rgba(20,17,17,.6) 40%, rgba(20,17,17,.22) 72%, rgba(20,17,17,.12) 100%),
    linear-gradient(0deg, rgba(20,17,17,.82) 0%, rgba(20,17,17,0) 46%);
}

.hero__body{
  padding:0 var(--hero-pad) 34px;
  max-width:620px;                      /* design 900 */
  min-width:0;
}

.hero__title{ margin:0 0 16px; }
.hero__title img{ height:var(--hero-title); width:auto; }

/* Flex-wrapped: as inline text with no whitespace between the spans there
   would be no break opportunity, forcing the hero wide on small screens. */
.hero__roles{
  margin:0 0 16px;
  display:flex; flex-wrap:wrap; align-items:baseline;
  font-size:var(--fs-body); font-weight:700;
  letter-spacing:.01em;
}
.hero__roles span{ white-space:nowrap; }
.hero__roles i{ font-style:normal; color:var(--ink-faint); padding-inline:7px; }

.hero__bio{
  margin:0 0 22px;
  font-size:var(--fs-hero-bio); font-weight:400;
  color:rgba(255,255,255,.9);
  max-width:52ch;
}

.btn-play{
  display:inline-flex; align-items:center; gap:10px;
  height:44px; padding-inline:22px;     /* design 68 × 206 */
  border-radius:8px;
  background:var(--white); color:var(--ink-invert);
  font-size:var(--fs-btn); font-weight:700;
  transition:transform .18s var(--ease), background .18s var(--ease);
}
.btn-play:hover{ background:#E8E6E4; transform:translateY(-2px); }
.btn-play svg{ width:14px; height:16px; fill:currentColor; flex:none; }

/* ==========================================================================
   project rows
   ========================================================================== */

.rows{
  padding-top:var(--row-gap);
  padding-bottom:80px;
  display:flex; flex-direction:column;
  gap:var(--row-gap);
}

.row__head{
  display:flex; align-items:center; gap:10px;
  margin-bottom:var(--head-gap);
}
.row__title{
  margin:0;
  font-size:var(--fs-row); font-weight:600;
  letter-spacing:-.01em;
}
.row__count{
  font-size:12px; font-weight:500;
  color:var(--ink-faint);
  font-variant-numeric:tabular-nums;
}

/* Track bleeds to the viewport edge so the next card peeks, as designed. */
.row__viewport{
  position:relative;
  margin-right:calc((100vw - min(100vw, var(--frame))) / -2 - var(--gutter));
}

.row__track{
  display:flex; gap:var(--card-gap);
  overflow-x:auto; overflow-y:visible;
  scroll-snap-type:x mandatory;
  padding-block:22px;
  margin-block:-22px;
  padding-right:var(--gutter);
  scrollbar-width:none;
}
.row__track::-webkit-scrollbar{ display:none; }

.row__nav{
  position:absolute; top:0; bottom:0;
  z-index:6;
  width:44px;
  display:grid; place-items:center;
  background:linear-gradient(90deg, rgba(20,17,17,.85), rgba(20,17,17,0));
  opacity:0; transition:opacity .2s var(--ease);
}
.row__nav[data-dir="next"]{
  right:0; left:auto;
  background:linear-gradient(270deg, rgba(20,17,17,.85), rgba(20,17,17,0));
}
.row__nav[data-dir="prev"]{ left:0; }
.row__viewport:hover .row__nav:not([disabled]){ opacity:1; }
.row__nav svg{ width:20px; height:20px; stroke:var(--white); stroke-width:2.5; fill:none; }
.row__nav[disabled]{ opacity:0 !important; pointer-events:none; }

/* ---- search results ----
   One grid of cards, no shelf titles: a search crosses the rows, so the rows
   would only repeat projects. Cards keep the 1.522 card ratio while the
   columns flex. */
.results{ padding-top:var(--row-gap); }
.results__count{
  margin:0 0 var(--head-gap);
  font-size:12px; font-weight:500;
  color:var(--ink-faint);
  font-variant-numeric:tabular-nums;
}
.results__grid{
  display:grid;
  /* 48% keeps two cards per row on a phone, where a full-width card would
     turn twelve results into a very long scroll. */
  grid-template-columns:repeat(auto-fill, minmax(min(var(--card-w), 48%), 1fr));
  gap:calc(var(--card-gap) + 10px) var(--card-gap);
}
.results__grid .card{ width:auto; }
.results__grid .card__art{ height:auto; aspect-ratio:1.522; }

/* ---- card ---- */

.card{
  flex:none;
  width:var(--card-w);
  scroll-snap-align:start;
  border:0; padding:0;
  text-align:left;
  border-radius:var(--card-r);
  transition:transform .22s var(--ease);
}
.card__art{
  width:100%; height:var(--card-h);
  border-radius:var(--card-r);
  background:var(--placeholder);
  overflow:hidden;
  position:relative;
}
.card__art img{ width:100%; height:100%; object-fit:cover; }

.card.is-empty .card__art::after{
  content:attr(data-label);
  position:absolute; inset:0;
  display:grid; place-items:center;
  padding:0 10px;
  font-size:11px; font-weight:600; letter-spacing:.07em;
  text-transform:uppercase; text-align:center;
  color:rgba(30,27,27,.34);
}

.card:hover,.card:focus-visible{ transform:translateY(-3px); }

/* ---- hover-expand panel ---- */

.peek{
  position:fixed; z-index:40;
  width:var(--peek-w);
  border-radius:var(--card-r);
  background:var(--surface);
  box-shadow:0 18px 46px rgba(0,0,0,.62);
  overflow:hidden;
  opacity:0; transform:scale(.92);
  transform-origin:center top;
  transition:opacity .18s var(--ease), transform .18s var(--ease);
  pointer-events:none;
}
.peek.is-open{ opacity:1; transform:none; pointer-events:auto; }
.peek[hidden]{ display:none; }

/* Tap layouts (no hover, or the mobile breakpoint): the panel is opened by
   tapping a card and presented centred over a scrim, with a close control.
   Driven by a class rather than a media query, because a hover-less tablet
   can be much wider than the mobile breakpoint. */
.peek.is-centred{
  left:50%; right:auto; top:50%;
  width:min(420px, calc(100vw - 32px));
  max-height:calc(100vh - 48px);
  max-height:calc(100dvh - 48px);
  overflow-y:auto;
  transform:translate(-50%,-50%) scale(.94);
  transform-origin:center;
}
.peek.is-centred.is-open{ transform:translate(-50%,-50%) scale(1); }
.peek.is-centred .peek__close{ display:grid; }

.peek__art{
  aspect-ratio:var(--reel-ratio);
  background:var(--placeholder);
  overflow:hidden;
  position:relative;
}
.peek__art img,
.peek__art video{ width:100%; height:100%; object-fit:cover; display:block; }

.peek__close{
  display:none;                          /* pointer devices close by moving away */
  position:absolute; top:12px; right:12px;
  width:34px; height:34px;
  place-items:center;
  border-radius:50%;
  background:rgba(20,17,17,.82);
  z-index:2;
}
.peek__close svg{ width:15px; height:15px; stroke:var(--accent); stroke-width:2.5; }

.peek-scrim{
  position:fixed; inset:0; z-index:39;
  background:rgba(8,7,7,.72);
}
.peek-scrim[hidden]{ display:none; }

.peek__body{ padding:14px 18px 18px; }

.peek__title{ margin:0 0 8px; font-size:var(--fs-peek-ttl); font-weight:600; }
.peek__title img{ height:18px; width:auto; }
/* Coming-soon slots: the doodle is the card, the title slot says what it is,
   and there is no project to play. */
.peek__soon{
  display:inline-block;
  font-size:15px; font-weight:700; letter-spacing:.01em; line-height:18px;
  color:var(--accent);
}
.peek.is-soon .peek__actions{ display:none; }
.peek.is-soon .peek__rule{ margin-bottom:10px; }
.card.is-soon{ cursor:default; }

.peek__rule{
  height:2px; margin:0 0 12px;
  background:var(--accent); border-radius:2px;
}

.peek__actions{ display:flex; align-items:center; gap:10px; margin-bottom:12px; }

.btn-play--sm{
  height:34px; padding-inline:15px;
  font-size:14px; border-radius:7px; gap:8px;
}
.btn-play--sm svg{ width:11px; height:13px; }

.peek__synopsis{
  margin:0;
  font-size:var(--fs-peek); line-height:1.45;
  color:rgba(255,255,255,.86);
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
  overflow:hidden;
}

/* ==========================================================================
   read-more sheet
   ========================================================================== */

.sheet{
  position:fixed; inset:0; z-index:70;
  padding:60px 24px 48px;
  overflow-y:auto;
  background:rgba(8,7,7,.78);
  opacity:0;
  transition:opacity .28s var(--ease);
}
.sheet[hidden]{ display:none; }
.sheet.is-open{ opacity:1; }

.sheet__panel{
  width:100%; max-width:1040px;         /* design 1446 */
  margin-inline:auto;
  background:var(--panel-bg);
  color:var(--panel-ink);
  border-radius:10px;
  overflow:hidden;
  transform:translateY(20px);
  transition:transform .32s var(--ease);
}
.sheet.is-open .sheet__panel{ transform:none; }

.sheet__banner{
  position:relative;
  aspect-ratio:var(--reel-ratio);
  background:#BEBEBE;
  overflow:hidden;
}
.sheet__banner img,
.sheet__banner video{ width:100%; height:100%; object-fit:cover; display:block; }
.sheet__banner-img{ width:100%; height:100%; }

.sheet__close{
  position:absolute; top:16px; right:18px;
  width:36px; height:36px;
  display:grid; place-items:center;
  border-radius:50%;
  background:rgba(20,17,17,.82);
  transition:background .18s var(--ease);
}
.sheet__close:hover{ background:var(--ink-invert); }
.sheet__close svg{ width:15px; height:15px; stroke:var(--accent); stroke-width:2.5; }

/* ---- showreel sound ---- */
/* Same chip as the sheet's close button, bottom right of the reel. */
.sheet__banner-img{ position:relative; }
.reel-sound{
  position:absolute; right:16px; bottom:14px; z-index:3;
  display:flex; align-items:center; gap:8px;
}
.reel-sound__btn{
  position:relative;
  width:36px; height:36px;
  display:grid; place-items:center;
  border-radius:50%;
  background:rgba(20,17,17,.82);
  transition:background .18s var(--ease), transform .18s var(--ease);
}
.reel-sound__btn svg{
  width:17px; height:17px;
  fill:none; stroke:var(--accent); stroke-width:2; stroke-linecap:round; stroke-linejoin:round;
}
.reel-sound__btn:hover:not(:disabled){ background:var(--ink-invert); }
.reel-sound__btn:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
.reel-sound[data-silent] .reel-sound__btn{ cursor:default; }
.reel-sound[data-silent] .reel-sound__btn svg{ stroke:rgba(255,255,255,.55); }
.peek .reel-sound{ right:12px; bottom:10px; }
.reel-sound[data-lifted]{ bottom:58px; }
.peek .reel-sound__btn{ width:30px; height:30px; }
.peek .reel-sound__btn svg{ width:14px; height:14px; }

/* The hint: a quiet label and a slow ring, shown only while a reel that has
   a soundtrack is still muted, and gone for good once it is touched. */
.reel-sound__hint{
  font-size:12px; font-weight:600; letter-spacing:.01em;
  color:#fff;
  padding:6px 10px; border-radius:999px;
  background:rgba(20,17,17,.72);
  opacity:0; transform:translateX(6px);
  animation:reelHintIn .5s var(--ease) 1.2s forwards;
  pointer-events:none;
}
.reel-sound[data-on="false"]:not([data-silent]) .reel-sound__btn::after{
  content:''; position:absolute; inset:0; border-radius:50%;
  box-shadow:0 0 0 0 rgba(129,232,33,.55);
  animation:reelRing 2.4s ease-out 1.2s 3;
}
.reel-sound[data-touched] .reel-sound__hint,
.reel-sound[data-on="true"] .reel-sound__hint{ display:none; }
.peek .reel-sound__hint{ font-size:11px; padding:5px 9px; }
@keyframes reelHintIn{ to{ opacity:1; transform:none; } }
@keyframes reelRing{
  0%{ box-shadow:0 0 0 0 rgba(129,232,33,.55); }
  70%,100%{ box-shadow:0 0 0 12px rgba(129,232,33,0); }
}
@media (prefers-reduced-motion: reduce){
  .reel-sound__hint{ animation:none; opacity:1; transform:none; }
  .reel-sound__btn::after{ animation:none !important; }
}

/* One source of truth for the panel inset. The case study bleeds to the panel
   edge by negating these, so a breakpoint can never move one without the
   other, which is exactly how they drifted apart before. */
.sheet__panel{
  --sheet-px:40px;
  --sheet-pt:30px;
  --sheet-pb:44px;
  /* Most projects sit on a light panel; a dark-only brand overrides both. */
  --panel-bg:var(--placeholder);
  --panel-ink:var(--ink-invert);
}
/* Project themes colour the whole panel, so the lead and the case study below
   it share one ground. metro's is the lavender from its showcase deck. */
.sheet__panel[data-theme="metro"]{ background:#F4EBFF; }
.sheet__panel[data-theme="navicane"]{ background:#F7F6F2; }
.sheet__panel[data-theme="reap"]{ background:#F2F2F2; }
.sheet__panel[data-theme="metrobrand"]{ background:#FFFFFF; }
.sheet__panel[data-theme="markenbrand"]{ --panel-bg:#000000; --panel-ink:#FFFFFF; }
.sheet__panel[data-theme="wolbrand"]{ background:#D5C9BF; }
.sheet__panel[data-theme="bstender"]{ background:#FFFFFF; }
.sheet__panel[data-theme="kaws"]{ background:#EBE8E7; }
.sheet__panel[data-theme="hightides"]{ background:#6E9FC1; }
.sheet__panel[data-theme="gameboy"]{ background:#ADAEAE; }
.sheet__panel[data-theme="gameboiplus"]{ background:#EFE0C5; }
.sheet__panel[data-theme="reapdoc"]{ background:#F2F2F2; }
/* Dark-only brand: the whole panel inverts, not just the study below it. */
.sheet__panel[data-theme="markenos"]{ --panel-bg:#1A1B12; --panel-ink:#F4F5EA; }
.sheet__panel[data-theme="weboflies"]{ --panel-bg:#1E1E1E; --panel-ink:#EDE6DC; }
.sheet__body{ padding:var(--sheet-pt) var(--sheet-px) var(--sheet-pb); }
/* Several blocks in here set their own `display`, which outranks the UA rule
   for [hidden] and leaves them occupying space while "hidden". */
.sheet__body > [hidden]{ display:none; }

.sheet__lead{
  display:grid;
  grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
  gap:34px;
  margin-bottom:12px;   /* name sits close to the description under it */
}

.sheet__progress{ display:flex; align-items:center; gap:14px; margin-bottom:11px; }
.sheet__progress .dot{
  width:34px; height:34px; flex:none;
  border-radius:50%;
  background:var(--panel-ink);
  display:grid; place-items:center;
}
.sheet__progress .dot svg{ width:11px; height:13px; fill:var(--panel-bg); }
.sheet__progress .rail{ flex:1; height:2px; background:var(--accent); border-radius:2px; }
.sheet__request{ margin:0; font-size:18px; font-weight:600; }

.sheet__meta{ font-size:var(--fs-sheet); line-height:1.4; }
.sheet__meta dl{ display:grid; grid-template-columns:auto 1fr; gap:6px 12px; margin:0; }
.sheet__meta dt{ color:color-mix(in srgb, var(--panel-ink) 60%, transparent); text-align:right; white-space:nowrap; }
.sheet__meta dd{ margin:0; font-weight:600; }

/* The hover panel's one-liner, restated at full page width. */
.sheet__synopsis{
  margin:0;
  /* deliberately under the project name above it, not level with it */
  font-size:clamp(13.5px, .3vw + 11px, 15px);
  line-height:1.5;
  font-weight:500;
  color:color-mix(in srgb, var(--panel-ink) 78%, transparent);
  text-wrap:pretty;
}
.sheet__synopsis[hidden]{ display:none; }

/* Small print: a disclaimer or credit that belongs on the page but not in
   the description. Quieter than the synopsis, still above the 4.5:1 floor. */
.sheet__note{
  margin:8px 0 0;
  font-size:clamp(11.5px, .2vw + 10px, 12.5px);
  line-height:1.45;
  font-style:italic;
  /* 68% clears 4.5:1 on the lightest panel ground in the set (#ADAEAE). */
  color:color-mix(in srgb, var(--panel-ink) 68%, transparent);
  text-wrap:pretty;
}
.sheet__note[hidden]{ display:none; }
.sheet__link{
  white-space:nowrap;
  font-weight:700;
  color:inherit;
  border-bottom:1.5px solid currentColor;
  padding-bottom:1px;
  transition:opacity .18s var(--ease);
}
.sheet__link::after{ content:' \2197'; }   /* outbound */
.sheet__link:hover{ opacity:.6; }

.sheet__title{
  margin:0 0 10px;
  font-size:var(--fs-sheet-ttl); font-weight:700;
  letter-spacing:-.02em;
}
.sheet__title img{ height:38px; width:auto; }

.sheet__copy{ margin:0; max-width:64ch; font-size:var(--fs-sheet); line-height:1.5; }
.sheet__copy p{ margin:0 0 11px; }
.sheet__copy p:last-child{ margin-bottom:0; }

.sheet__gallery{
  margin-top:38px;
  display:grid;
  grid-template-columns:repeat(2, minmax(0,1fr));
  gap:24px 34px;
  align-items:start;
}
.sheet__shot{
  border-radius:4px; overflow:hidden;
  background:#C6C6C6;
  aspect-ratio:2 / 1;
}
.sheet__shot img{ width:100%; height:100%; object-fit:cover; }
.sheet__aside{ font-size:var(--fs-sheet); line-height:1.5; }

/* ==========================================================================
   project case study
   Palette and rhythm lifted from "metro project showcase.pdf": lavender
   ground, purple feature cards with a generous radius, square green callout
   blocks, justified body copy.
   ========================================================================== */

/* The study runs edge to edge by negating the panel's own inset, so the two
   are derived from one token rather than repeated as magic numbers. */
.sheet__detail{
  /* Side and bottom bleeds must negate the panel inset exactly; the top gap
     is free to be tighter, so the study follows straight on from the copy. */
  margin:14px calc(var(--sheet-px) * -1) calc(var(--sheet-pb) * -1);
  container-type:inline-size;
  container-name:study;
}
.sheet__detail[hidden]{ display:none; }

.detail{
  --m-bg:#F4EBFF;
  --m-feature:#721CB8;
  --m-accent:#69A11D;
  --m-ink:#111516;
  --m-on-accent:#FFFFFF;    /* text over --m-accent */
  --m-on-feature:#FFFFFF;   /* text over --m-feature */
  /* Emphasis is a *text/fill* colour, which is not always the same thing as
     the card fill: on a dark theme the card is near-black and would vanish
     as a numeral. Themes point it wherever it actually reads. */
  --m-emphasis:var(--m-feature);
  --m-on-emphasis:var(--m-on-feature);
  --m-line:rgba(0,0,0,.10);

  /* Sized against the container, not the viewport: this study sits in a panel
     capped at 1040px, so the panel's width is what actually governs it. Every
     step is continuous, so there is no single width where the layout lurches. */
  --d-pad:clamp(20px, 4.2cqi, 44px);
  --d-gap:clamp(18px, 2.4cqi, 28px);
  --d-fs:clamp(13.5px, 0.9cqi + 10.5px, 15px);
  --d-radius:clamp(16px, 2.4cqi, 26px);
  /* how tall a mockup is allowed to stand next to its callout */
  --d-media:clamp(320px, 50cqi, 520px);
  --d-split-gap:var(--d-gap);   /* feature rows read as separate sections */

  background:var(--m-bg);
  color:var(--m-ink);
  padding:calc(var(--d-pad) + 8px) var(--d-pad) calc(var(--d-pad) + 16px);
  font-size:var(--d-fs);
  line-height:1.55;
  text-wrap:pretty;
}

/* Spaced by adjacency rather than one blanket gap, a heading belongs close
   to the text it introduces and further from whatever preceded it. */
/* `.detail` is doubled to reach (0,2,0): at plain (0,1,0) this ties with every
   component's own `margin:0` and loses on source order. */
.detail.detail > * + *{ margin-top:var(--d-gap); }
.detail > .d-h,
.detail > .d-h-group{ margin-top:calc(var(--d-gap) + .4em); }
.detail > .d-h + *,
.detail > .d-h-group + *{ margin-top:.75em; }
.detail > .d-lead + .d-ol{ margin-top:1.1em; }
/* How close consecutive feature rows sit. Metro runs them almost flush as one
   continuous stretch; other studies keep each row a distinct section. */
.detail.detail > .d-split + .d-split{ margin-top:var(--d-split-gap); }

/* Metro's rows run almost flush into one continuous stretch. */
.detail[data-theme="metro"]{ --d-split-gap:clamp(4px, 0.7cqi, 9px); }

/* ---- a supplied document, reproduced ---- */
/* Brand kits ship as a finished deck, so the study gets out of the way: the
   plates run edge to edge and scale to the panel, and nothing is added around
   them. Zero gap between plates, they are contiguous cuts of one sheet and
   have to reassemble seamlessly. */
.d-plates{ display:flex; flex-direction:column; font-size:0; line-height:0; }
.d-plates img{ width:100%; height:auto; display:block; }

/* Each kit is presented as itself. The detail drops its padding so the sheet
   meets the panel edge, and the panel takes the deck's own ground so the two
   are indistinguishable, white for Namma Metro, black for Marken. */
.detail[data-theme="metrobrand"]{
  --m-bg:#FFFFFF;
  --m-ink:#212121;
  padding:0;
}
.detail[data-theme="markenbrand"]{
  --m-bg:#000000;
  --m-ink:#FFFFFF;
  padding:0;
}
.detail[data-theme="wolbrand"]{
  --m-bg:#D5C9BF;
  --m-ink:#1E1E1E;
  padding:0;
}
/* Two decks on one page, the kit, then the campaign. Both sit on white, so
   they run on without a seam and the gap between them stays invisible. */
.detail[data-theme="bstender"]{
  --m-bg:#FFFFFF;
  --m-ink:#1C1A1A;
  padding:0;
}
.detail[data-theme="bstender"].detail > .d-plates + .d-plates{ margin-top:0; }

/* ---- a supplied comp, rebuilt ---- */
/* A project page supplied as one tall PDF drawing (Kaws, Reap) is rebuilt
   rather than sliced. Items are placed in the drawing's own points and --u
   converts a point into rendered pixels, so the page scales as one piece and
   the overlaps it is built on (figures over panels, phones under bands)
   survive at every width. Each page brings its own type scale as --t-*
   variables in its own points, plus --t-min, the pixel floor body copy is
   never allowed under. Below the page's `flowAt` width the drawing stops
   being a drawing and stacks (.is-flow, set by app.js). */
.d-canvas{
  position:relative;
  container-type:inline-size;
  width:100%;
  aspect-ratio:var(--cw) / var(--ch);
  --u:calc(100cqw / var(--cw));
  font-size:max(calc(var(--t-min) * 1px), calc(var(--t-body) * var(--u)));
  line-height:var(--t-lh, 1.5);
}
.d-canvas__item{
  position:absolute;
  left:calc(var(--x) * var(--u));
  top:calc(var(--y) * var(--u));
  width:calc(var(--w) * var(--u));
  margin:0;
}
.d-canvas img.d-canvas__item{ height:auto; display:block; }
/* Panels are laid down first: renders, phones and figures sit over them. */
.d-canvas__item{ z-index:1; }
.d-canvas__copy[data-box]{ z-index:0; }
.d-canvas__copy p{
  margin:0;
  /* A wide panel still reads at a column's measure. */
  max-width:calc(var(--measure, 100000) * var(--u));
}

/* The statement line. */
.d-canvas__copy[data-size="display"]{
  font-size:max(calc(var(--t-min) * 1.55px), calc(var(--t-display) * var(--u)));
  line-height:var(--t-dlh, 1.27);
  font-weight:500;
  color:var(--c-dim);
  letter-spacing:-.018em;
}
/* A secondary statement, between body and display. */
.d-canvas__copy[data-size="lead"]{
  font-size:max(calc(var(--t-min) * 1.2px), calc(var(--t-lead) * var(--u)));
  line-height:1.375;
  font-weight:500;
  color:var(--c-dim);
  letter-spacing:-.012em;
}
.d-canvas__copy[data-size="list"]{ color:#363535; }
.d-canvas [data-tone="ink"]{ color:var(--c-ink); }
.d-canvas [data-tone="dim"]{ color:var(--c-dim); }
.d-canvas [data-tone="accent"]{ color:var(--c-accent); }

/* Panels share one inset on the module; a page or a single panel can move
   the sides (--pl / --pr) to clear whatever overlaps it. A panel is as tall
   as its copy. */
.d-canvas__copy[data-box]{
  box-sizing:border-box;
  min-height:calc(var(--h, 0) * var(--u));
  padding:calc(var(--pt, 24) * var(--u)) calc(var(--pr, 20) * var(--u))
          calc(var(--pb, 32) * var(--u)) calc(var(--pl, 20) * var(--u));
  color:#FFF;
}
/* Kaws: the PDF's 39% black wash puts white at 3.2:1; held at the value
   white actually clears, 4.5:1. */
.d-canvas__copy[data-box="wash"]{ background:rgba(0,0,0,.5); }
/* Reap: the brand green, under its own grain. White measures 7.2:1 or better
   on it, so the card body can keep the PDF's quieter 64% white. */
.d-canvas__copy[data-box="card"],
.d-canvas__copy[data-box="band"]{
  background:#1D5A26 url('../assets/projects/reap-p-green.jpg') center / cover no-repeat;
}
.d-canvas__copy[data-box="card"]{ color:rgba(255,255,255,.72); }
.d-canvas [data-tone="head"]{
  font-size:max(calc(var(--t-min) * 1.2px), calc(var(--t-head, var(--t-lead)) * var(--u)));
  line-height:1.3;
  font-weight:500;
  letter-spacing:-.012em;
  color:#FFF;
  margin-bottom:calc(20 * var(--u));
}

/* A row of panels that share one height. */
.d-canvas__row{
  display:grid;
  grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:calc(var(--gap, 24) * var(--u));
  align-items:stretch;
}

/* Section labels, in the margin. */
.d-canvas__rail{
  writing-mode:vertical-rl;
  font-size:max(10px, calc(var(--t-rail, 10) * var(--u)));
  line-height:1;
  letter-spacing:.06em;
  color:#625F5E;
}
.d-canvas__rail[data-up]{ transform:rotate(180deg); }

/* Stacked: --u drops to a plain pixel and the items run in source order on
   an 8px module, 24 between things in a section, more above a section
   label. Phones and other tall art keep a sensible width (--flowW) instead of
   filling a tablet column. */
.d-canvas.is-flow{
  --u:1px;
  aspect-ratio:auto;
  display:flex;
  flex-direction:column;
  gap:24px;
  padding:0 20px 16px;
  font-size:max(15px, calc(var(--t-min) * 1px));
  line-height:1.6;
}
.d-canvas.is-flow .d-canvas__item{ position:static; left:auto; top:auto; width:auto; }
.d-canvas.is-flow .d-canvas__item[data-flow="off"]{ display:none; }
.d-canvas .d-canvas__item[data-flow="only"]{ display:none; }
.d-canvas.is-flow .d-canvas__item[data-flow="only"]{ display:block; }
.d-canvas.is-flow img.d-canvas__item{ width:100%; }
.d-canvas.is-flow .d-canvas__item[data-flow-w]{
  align-self:center;
  width:100%;
  max-width:calc(var(--flowW) * 1px);
}
.d-canvas.is-flow .d-canvas__copy[data-box]{ min-height:0; padding:24px 22px 28px; }
.d-canvas.is-flow .d-canvas__row{ grid-template-columns:1fr; grid-auto-rows:1fr; gap:24px; }
.d-canvas.is-flow .d-canvas__copy[data-size="display"]{
  font-size:clamp(calc(var(--t-min) * 1.55px), 6.5cqi, calc(var(--t-display) * .8px));
  line-height:1.25;
}
.d-canvas.is-flow .d-canvas__copy[data-size="lead"]{ font-size:clamp(18px, 4.6cqi, 22px); }
.d-canvas.is-flow [data-tone="head"]{ font-size:clamp(18px, 4.6cqi, 22px); margin-bottom:12px; }
.d-canvas.is-flow > .d-canvas__rail{ margin-top:40px; }
/* The drawing's line breaks are its width, not its sentences, so the
   statement runs on as one sentence once the width is gone. */
.d-canvas.is-flow .d-canvas__copy[data-size="display"] p{ display:inline; }
.d-canvas.is-flow .d-canvas__copy[data-size="display"] p + p::before{ content:' '; }
/* A figure that hangs off the image above it keeps hanging: placed in the
   drawing's proportions of the full-bleed width (100% + the 40px of
   padding), pulled up from --hang, the host's bottom edge in points. */
.d-canvas.is-flow .d-canvas__item[data-flow="hang"]{
  align-self:flex-start;
  width:calc(var(--w) / var(--cw) * (100% + 40px));
  margin-left:calc(var(--x) / var(--cw) * (100% + 40px) - 20px);
  margin-top:calc((var(--y) - var(--hang)) / var(--cw) * (100% + 40px) - 24px);
  margin-bottom:-8px;
}
.d-canvas.is-flow .d-canvas__rail{
  writing-mode:horizontal-tb;
  transform:none;
  font-size:11px;
  margin-bottom:-12px;
}
/* Full-bleed in the drawing, full-bleed here. */
.d-canvas.is-flow .d-canvas__item[data-bleed]{
  width:calc(100% + 40px);
  max-width:none;
  margin-inline:-20px;
}


/* ==========================================================================
   Gameboi+ : the comp's warm ground (#EFE0C5) and near-black ink, 12.8:1.
   Latin in Manrope, Japanese in Noto Sans JP, both at the comp's 20/27 and
   its 16pt right-hand labels. Two pieces of the page are live: the wipe
   between the two photographs, and Tetris on the render's screen.
   ========================================================================== */
.d-canvas[data-canvas="gameboiplus"]{
  --c-ink:#141210;
  color:#141210;
  font-family:'Manrope', 'Noto Sans JP', sans-serif;
  font-weight:400;
  letter-spacing:-.004em;
}
.d-canvas[data-canvas="gameboiplus"] .d-canvas__copy p + p{ margin-top:calc(27 * var(--u)); }
/* The Japanese lines: the comp sets them larger than the copy and hangs them
   off the right margin. */
.d-canvas[data-canvas="gameboiplus"] [data-size="jp"]{
  font-family:'Noto Sans JP', 'Manrope', sans-serif;
  font-size:max(15px, calc(24 * var(--u)));
  line-height:1.2;
  font-weight:500;
}
.d-canvas[data-canvas="gameboiplus"] [data-size="jp"] p + p{ margin-top:calc(23 * var(--u)); }
.d-canvas[data-canvas="gameboiplus"] [data-size="label"]{
  font-size:max(11.5px, calc(16 * var(--u)));
  line-height:1.2;
  letter-spacing:.01em;
}
.d-canvas[data-canvas="gameboiplus"] [data-size="label"] p + p{ margin-top:calc(20 * var(--u)); }
.d-canvas[data-canvas="gameboiplus"] [data-align="right"]{ text-align:right; }
.d-canvas[data-canvas="gameboiplus"] [data-align="centre"]{ text-align:center; }
/* The caps sub-heads inside a block: set apart by space, not by weight, the
   way the comp does it. */
.d-canvas[data-canvas="gameboiplus"] [data-tone="cap"]{
  display:block;
  margin-top:calc(27 * var(--u));
  font-weight:600;
  letter-spacing:.015em;
}
.d-canvas[data-canvas="gameboiplus"] [data-tone="cap"]:first-child{ margin-top:0; }
.d-canvas.is-flow[data-canvas="gameboiplus"]{ gap:26px; padding-top:26px; }
.d-canvas.is-flow[data-canvas="gameboiplus"] .d-canvas__copy p + p{ margin-top:14px; }
.d-canvas.is-flow[data-canvas="gameboiplus"] [data-size="jp"]{ font-size:17px; }
.d-canvas.is-flow[data-canvas="gameboiplus"] [data-size="jp"] p + p{ margin-top:8px; }
.d-canvas.is-flow[data-canvas="gameboiplus"] [data-size="label"]{ font-size:12px; }
.d-canvas.is-flow[data-canvas="gameboiplus"] [data-size="label"] p + p{ margin-top:8px; }
.d-canvas.is-flow[data-canvas="gameboiplus"] [data-align="right"]{ text-align:left; }
.d-canvas.is-flow[data-canvas="gameboiplus"] [data-tone="cap"]{ margin-top:14px; }

/* ---- the wipe between two photographs ---- */
/* Both of these are placed by the canvas engine, so neither sets its own
   position in the drawing; stacked, they become the positioning context
   their own parts need. */
.d-wipe{ overflow:hidden; --p:50%; }
.d-canvas.is-flow .d-wipe{ position:relative; }
.d-wipe__img{ display:block; width:100%; height:auto; }
/* The top frame keeps the whole picture and is clipped to the wipe, so its
   photograph never squeezes into the sliver. */
.d-wipe__top{
  position:absolute; inset:0;
  clip-path:inset(0 calc(100% - var(--p)) 0 0);
}
.d-wipe__top .d-wipe__img{ width:100%; height:100%; object-fit:cover; }
.d-wipe__line{
  position:absolute; top:0; bottom:0; left:var(--p);
  width:2px; margin-left:-1px;
  background:rgba(255,255,255,.9);
  box-shadow:0 0 0 1px rgba(0,0,0,.25);
  pointer-events:none;
}
.d-wipe__grip{
  position:absolute; left:50%; top:calc(100% - 13%);
  transform:translate(-50%,-50%);
  width:clamp(34px, 3.4cqw, 52px); aspect-ratio:34 / 14;
  display:grid; place-items:center;
  border-radius:999px;
  background:#EFE0C5; color:#141210;
  box-shadow:0 2px 10px rgba(0,0,0,.35);
}
.d-wipe__grip svg{ width:72%; height:auto; }
.d-wipe__tag{
  position:absolute; bottom:3.4%; left:3%;
  font-size:max(10px, calc(13 * var(--u, 1px)));
  letter-spacing:.14em; text-transform:uppercase;
  color:#FFF; text-shadow:0 1px 6px rgba(0,0,0,.75);
  pointer-events:none;
}
.d-wipe__tag[data-side="right"]{ left:auto; right:3%; }
/* The range is the control: it covers the frame, so a drag anywhere moves the
   wipe, and it brings arrow keys and a name for assistive tech with it. */
.d-wipe__range{
  position:absolute; inset:0;
  width:100%; height:100%;
  margin:0; padding:0;
  -webkit-appearance:none; appearance:none;
  background:transparent; opacity:0; cursor:ew-resize;
}
.d-wipe__range::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  width:44px; height:100%;
}
.d-wipe__range::-moz-range-thumb{ width:44px; height:100%; border:0; }
.d-wipe:focus-within .d-wipe__line{ box-shadow:0 0 0 2px var(--accent, #C9F24D); }

/* ---- Tetris, on the render's own screen ---- */
.d-play:focus{ outline:none; }
/* The console, its screen and its controls have to be playable in one
   viewport, so the body is capped by the viewport's height rather than the
   drawing's width. The cap is on the stage, not the picture: the screen is
   placed in percentages of this box, so box and picture must stay the same
   rectangle. 1000 / 991 is the render's own ratio. */
.d-play__stage{
  position:relative;
  width:min(100%, calc(60vh * 1000 / 991));
  margin-inline:auto;
}
.d-play__body{ display:block; width:100%; height:auto; }
.d-play__screen{
  position:absolute;
  left:33.6%; top:18.57%; width:33%; height:21.7%;
  background:#93B58C;
  overflow:hidden;
}
.d-play__canvas{ width:100%; height:100%; display:block; }
/* The controls the render already draws, made live. They are invisible: the
   art is the button, and the focus ring is the only thing drawn. */
.d-play__hit{
  position:absolute;
  border:0; padding:0; background:transparent;
  border-radius:8px;
  cursor:pointer;
}
.d-play__hit:focus-visible{ outline:2px solid #141210; outline-offset:2px; }
.d-play:focus-visible .d-play__screen{ outline:3px solid #141210; outline-offset:3px; }
/* And the same moves under the picture, at a size a thumb can hit. */
.d-play__pad{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:center;
  gap:10px;
  margin-top:clamp(12px, calc(26 * var(--u, 1px)), 26px);
}

/* The rules, over the console, until the game starts. */
.d-play__intro{
  /* over the body of the console, not over its screen: the LCD is the thing
     being explained */
  position:absolute; left:50%; bottom:4%;
  transform:translateX(-50%);
  z-index:3;
  width:min(300px, 86%);
  padding:18px 20px 20px;
  display:flex; flex-direction:column; align-items:center; gap:10px;
  background:rgba(239,224,197,.96);
  border:1.5px solid #141210;
  border-radius:12px;
  box-shadow:0 18px 40px rgba(20,18,16,.28);
  text-align:center;
}
.d-play[data-started="true"] .d-play__intro{ display:none; }
.d-play__introttl{
  margin:0;
  font-size:13px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
}
.d-play__legend{
  margin:0;
  display:grid; grid-template-columns:auto 1fr; gap:4px 12px;
  align-items:baseline;
  font-size:13px; line-height:1.3;
  text-align:left; width:100%;
}
.d-play__legend dt{
  justify-self:end;
  font-family:'IBM Plex Mono', ui-monospace, monospace;
  font-size:12px;
  color:rgba(20,18,16,.62);
  white-space:nowrap;
}
.d-play__legend dd{ margin:0; }
.d-play__introfoot{
  margin:0;
  font-size:11.5px; line-height:1.35;
  color:rgba(20,18,16,.7);
}
.d-play__go{ margin-top:2px; }
@media (hover:none){
  /* A phone has no arrow keys; the buttons under the console are the game. */
  .d-play__legend{ display:none; }
}
/* Stacked, the screen is under the picture rather than inside it, so a card
   laid over the stage would cover the screen. The rules take their own place
   between the two instead, still ahead of the first piece. */
.d-canvas.is-flow .d-play__stage{ display:flex; flex-direction:column; }
.d-canvas.is-flow .d-play__body{ order:1; }
.d-canvas.is-flow .d-play__intro{
  order:2;
  position:static; transform:none;
  width:min(300px, 100%);
  margin:14px auto 0;
  padding:14px 16px 16px; gap:8px;
}
.d-canvas.is-flow .d-play__screen{ order:3; }
.d-canvas.is-flow .d-play__legend{ font-size:12px; }
.d-play__key,
.d-play__start{
  min-width:44px; height:44px;
  display:grid; place-items:center;
  border:1.5px solid rgba(20,18,16,.5);
  border-radius:10px;
  background:rgba(255,255,255,.35);
  color:#141210;
  transition:background .16s var(--ease), border-color .16s var(--ease);
}
.d-play__start{ padding:0 18px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; font-size:13px; }
.d-play__key svg{ width:20px; height:20px; }
.d-play__key:hover,
.d-play__start:hover{ background:#141210; border-color:#141210; color:#EFE0C5; }
.d-play[data-running="true"] .d-play__start{ background:#141210; border-color:#141210; color:#EFE0C5; }
/* Stacked, the render is a picture of a console and the screen steps out of
   it: a hundred pixels of LCD is not a game. The bezel comes with it, so it
   still reads as the same screen. */
/* Stacked, the screen is no longer laid over the art, so the picture can be
   capped by its own height and the screen can take the full column. */
.d-canvas.is-flow .d-play__stage{ width:100%; }
.d-canvas.is-flow .d-play__body{ max-height:34vh; width:auto; margin-inline:auto; }
.d-canvas.is-flow .d-play__screen{
  position:static;
  width:min(100%, calc(38vh * 330 / 215)); height:auto;
  aspect-ratio:330 / 215;
  margin:14px auto 0;
  border:5px solid #2C3A27;
  border-radius:5px;
}
.d-canvas.is-flow .d-play__hit{ display:none; }
.d-canvas.is-flow .d-play__pad{ margin-top:14px; }

.d-play__hint{
  flex:1 0 100%;
  margin:2px 0 0;
  text-align:center;
  font-size:max(11px, calc(14 * var(--u, 1px)));
  color:rgba(20,18,16,.62);
}
@media (hover:none){ .d-play__hint{ display:none; } }

/* Page palettes and scales. */
/* CMF / Nothing Gameboy, to the PDF: #ADAEAE ground, #1D1D1D ink (7.6:1),
   Doto for every dot-matrix line, round dots for the title and square dots
   for heads and labels, Inter for the copy. Labels sit right against the
   margin. The PDF set its copy at 20/24; it is opened to 20/29 here so the
   long paragraphs read, the only departure. */
@font-face{
  font-family:'Doto';
  src:url('../assets/fonts/Doto-Variable.ttf') format('truetype');
  font-weight:100 900;
  font-display:swap;
}
.d-canvas[data-canvas="gameboy"]{ --c-ink:#1D1D1D; color:#1D1D1D; font-family:'Inter', 'Manrope', sans-serif; font-weight:400; }
.d-canvas[data-canvas="gameboy"] [data-size="title"],
.d-canvas[data-canvas="gameboy"] [data-size="sub"],
.d-canvas[data-canvas="gameboy"] [data-size="label"]{ font-family:'Doto', ui-monospace, monospace; color:#1D1D1D; }
.d-canvas[data-canvas="gameboy"] [data-size="title"]{
  font-size:max(26px, calc(64 * var(--u)));
  line-height:1.05;
  font-weight:800;
  font-variation-settings:"ROND" 100;
}
.d-canvas[data-canvas="gameboy"] [data-size="sub"]{
  font-size:max(15px, calc(24 * var(--u)));
  line-height:1.2;
  font-weight:800;
  font-variation-settings:"ROND" 0;
}
.d-canvas[data-canvas="gameboy"] [data-size="label"]{
  font-size:max(11px, calc(16 * var(--u)));
  line-height:1.15;
  font-weight:800;
  font-variation-settings:"ROND" 0;
}
.d-canvas[data-canvas="gameboy"] [data-size="label"] p + p{ margin-top:calc(30 * var(--u)); }
.d-canvas[data-canvas="gameboy"] [data-align="right"]{ text-align:right; }
.d-canvas[data-canvas="gameboy"] .d-canvas__copy:not([data-size]) p[data-tone="break"]{ margin-top:1.45em; }
.d-canvas.is-flow[data-canvas="gameboy"]{ gap:22px; padding-top:28px; }
.d-canvas.is-flow[data-canvas="gameboy"] [data-size="title"]{ font-size:clamp(26px, 8.2cqi, 56px); }
.d-canvas.is-flow[data-canvas="gameboy"] [data-size="sub"]{ font-size:17px; margin-top:18px; }
/* The paired line under the hero reads as one row, not two sections. */
.d-canvas.is-flow[data-canvas="gameboy"] [data-size="sub"] + [data-size="sub"][data-align="right"]{ margin-top:-12px; }
.d-canvas.is-flow[data-canvas="gameboy"] [data-size="label"]{ font-size:12px; }
.d-canvas.is-flow[data-canvas="gameboy"] [data-size="label"] p + p{ margin-top:12px; }
/* High Tides: the PDF's steel blue and navies. The statement's serif is the
   PDF's; its small line above sits in white on #6E9FC1 in the PDF, which is
   2.8:1, so it is set in the wordmark's navy instead. */
.d-canvas[data-canvas="hightides"]{ --c-ink:#050E64; --c-dim:#222B79; --c-accent:#222B79; color:#050E64; }
.d-canvas[data-canvas="hightides"] .d-canvas__copy[data-size="display"]{
  font-family:'Playfair Display', Georgia, serif;
  font-weight:600;
  letter-spacing:0;
  color:#222B79;
}
.d-canvas[data-canvas="hightides"] .d-canvas__copy[data-size="kicker"]{
  font-size:max(12px, calc(17 * var(--u)));
  font-weight:700;
  letter-spacing:.04em;
  line-height:1.45;
  color:#050E64;
}
/* The story beside the claim: navy on the steel blue (6:1), paragraphs spaced
   as a column, and the two short lines set heavier so they land as beats. */
.d-canvas[data-canvas="hightides"] .d-canvas__copy:not([data-size]) p + p{ margin-top:calc(16 * var(--u)); }
.d-canvas[data-canvas="hightides"] .d-canvas__copy:not([data-size]) p{ color:#0B1660; font-weight:500; }
.d-canvas[data-canvas="hightides"] .d-canvas__copy:not([data-size]) p[data-tone="ink"]{ color:#050E64; font-weight:800; letter-spacing:-.01em; }
.d-canvas.is-flow[data-canvas="hightides"]{ gap:28px; }
.d-canvas.is-flow[data-canvas="hightides"] .d-canvas__copy[data-size="kicker"]{ margin-bottom:-18px; font-size:12px; }
.d-canvas.is-flow[data-canvas="hightides"] .d-canvas__copy[data-size="display"]{ font-size:clamp(30px, 9.5cqi, 48px); }
.d-canvas.is-flow[data-canvas="hightides"] .d-canvas__copy:not([data-size]) p + p{ margin-top:14px; }
.d-canvas[data-canvas="kaws"]{ --c-ink:#000; --c-dim:#595656; --c-accent:#000; }
.d-canvas[data-canvas="reap"]{
  --c-ink:#000; --c-dim:#525252; --c-accent:#50851F;
  --pt:36; --pb:40; --pl:32; --pr:32;
}
.d-canvas.is-flow[data-canvas="reap"]{ gap:32px; padding-top:32px; }
/* The phone keeps sitting over its band, as in the PDF: the band tucks up
   under the handset and clears it with its own top inset. */
.d-canvas.is-flow[data-canvas="reap"] img.d-canvas__item{ position:relative; z-index:1; }
.d-canvas.is-flow[data-canvas="reap"] .d-canvas__copy[data-box="band"]{ margin-top:-64px; padding-top:88px; }

/* The page is the comp's own ground, edge to edge, and the drawing carries
   its own margins. */
.detail[data-theme="gameboy"]{
  --m-bg:#ADAEAE;
  --m-ink:#1D1D1D;
  padding:0;
}
.detail[data-theme="gameboiplus"]{
  --m-bg:#EFE0C5;
  --m-ink:#141210;
  padding:0;
}
.detail[data-theme="hightides"]{
  --m-bg:#6E9FC1;
  --m-ink:#050E64;
  padding:0;
}
.detail[data-theme="reapdoc"]{
  --m-bg:#F2F2F2;
  --m-ink:#000;
  padding:0;
}
.detail[data-theme="kaws"]{
  --m-bg:#EBE8E7;
  --m-ink:#1A1817;
  padding:0;
}

/* Marken OS ships a single dark appearance on purpose: its #FBFF12 on #1A1B12
   measures 16:1, where the same yellow on white is 1.1:1. A light variant
   would be a different brand, not an inverted one, so the panel goes dark
   with it, and text over the accent flips to the near-black it was designed
   against. Values are its own token layer (showreel/src/marken/theme.ts). */
.detail[data-theme="markenos"]{
  --m-bg:#1A1B12;
  --m-feature:#2E3021;
  --m-accent:#FBFF12;
  --m-ink:#F4F5EA;
  --m-on-accent:#1A1B12;
  --m-on-feature:#F4F5EA;
  /* The card fill is near-black here, so emphasis rides the accent instead. */
  --m-emphasis:#FBFF12;
  --m-on-emphasis:#1A1B12;
  --m-line:rgba(255,255,255,.11);
  --d-gap:clamp(28px, 3.8cqi, 48px);
  --d-pad:clamp(22px, 4.6cqi, 52px);
}

/* This study runs long, six full-width screens in a row, so its rhythm is
   tuned rather than left to one blanket gap. Specificity is (0,3,0) to clear
   the doubled `.detail.detail` owl above. */
.detail[data-theme="markenos"].detail > .d-showcase + .d-showcase{ margin-top:calc(var(--d-gap) * 1.3); }
/* The band is full-bleed; room on both sides makes it punctuation rather
   than a stripe wedged between paragraphs. */
.detail[data-theme="markenos"].detail > .d-band{ margin-top:calc(var(--d-gap) * 1.25); }
.detail[data-theme="markenos"].detail > .d-band + *{ margin-top:calc(var(--d-gap) * 1.25); }
/* The links belong to the introduction they follow, so they sit closer to it. */
.detail[data-theme="markenos"].detail > .d-intro + .d-links{ margin-top:calc(var(--d-gap) * .7); }
/* Running text stops at a comfortable measure; the screens still go full width. */
.detail[data-theme="markenos"] > .d-h,
.detail[data-theme="markenos"] > .d-lead,
.detail[data-theme="markenos"] > .d-ol,
.detail[data-theme="markenos"] .d-intro__body{ max-width:72ch; }

/* Web of Lies, straight off its brand sheet: #715D4C, #1E1E1E, #3E3024 and
   white. The sheet's brown is a surface colour, on the near-black panel it
   measures 2.7:1 as text, so emphasis rides a lifted tint of the same brown
   and the brown itself carries bands and cards. */
.detail[data-theme="weboflies"]{
  --m-bg:#1E1E1E;
  --m-feature:#3E3024;
  --m-accent:#715D4C;
  --m-ink:#EDE6DC;
  --m-on-accent:#FFFFFF;
  --m-on-feature:#EDE6DC;
  --m-emphasis:#B7A08A;
  --m-on-emphasis:#1E1E1E;
  --m-line:rgba(237,230,220,.14);
  --d-gap:clamp(28px, 3.8cqi, 48px);
}
/* The sheet pairs a condensed display face with Manrope. Delirium NCU is not
   a webfont, so this uses Anton, the stand-in the game itself ships, and
   Courier Prime for the places the game reads as a typed document. */
.detail[data-theme="weboflies"] .d-h,
.detail[data-theme="weboflies"] .d-band[data-size="lg"] p{
  font-family:'Anton','Haettenschweiler','Arial Narrow',sans-serif;
  font-weight:400; letter-spacing:.01em; text-transform:uppercase;
}
.detail[data-theme="weboflies"] .d-h{ font-size:clamp(21px, 2.1cqi + 13px, 33px); }
.detail[data-theme="weboflies"] .d-stats__eyebrow,
.detail[data-theme="weboflies"] .d-creds__eyebrow,
.detail[data-theme="weboflies"] .d-link__body span,
.detail[data-theme="weboflies"] .d-ol b{
  font-family:'Courier Prime',ui-monospace,'Courier New',monospace;
}

/* The opener, set rather than supplied as a flat banner: the laptop holds the
   left column and the copy runs beside it, in the study's own faces. */
.detail[data-theme="weboflies"] .d-intro{
  padding-bottom:var(--d-pad);
  gap:clamp(20px, 3.2cqi, 42px);
}
.detail[data-theme="weboflies"] .d-intro__body p{ font-weight:500; }
.detail[data-theme="weboflies"] .d-intro__display{
  font-family:'Anton','Haettenschweiler','Arial Narrow',sans-serif;
  font-weight:400; text-transform:uppercase; letter-spacing:.01em;
  font-size:clamp(18px, 1.7cqi + 11px, 27px);
  line-height:1.2; margin-top:.9em; text-wrap:balance;
}
@container study (min-width:600px){
  .detail[data-theme="weboflies"] .d-intro{
    grid-template-columns:minmax(0, 46%) minmax(0, 1fr);
    align-items:center;
  }
  /* Art leads, as it does in the reference. */
  .detail[data-theme="weboflies"] .d-intro__art{ order:-1; }
}

/* Reap's world, read off its own feature boards and app screens. The board
   background is the panel, the wordmark green is the accent, and the deep
   green out of the screen gradient carries the filled cards, white on it
   measures 10:1, where white on the brand green alone would be 4.4:1. */
.detail[data-theme="reap"]{
  --m-bg:#F2F2F2;
  --m-feature:#234A32;
  --m-accent:#50861F;
  --m-ink:#17251C;
  --m-on-accent:#FFFFFF;
  --m-on-feature:#FFFFFF;
  --d-gap:clamp(30px, 4.2cqi, 52px);
}
/* Each feature is a titled block against its render, so consecutive rows stay
   distinct rather than running together. */
.detail[data-theme="reap"]{ --d-split-gap:clamp(30px, 4.2cqi, 52px); }

/* Each feature row stands on the brand's own ground, the sprayed cross
   texture off the cover plate, rather than floating on the panel. That is
   also how the product sets its own cards: near-white on textured green.
   The image is the plate itself, tinted deeper so white copy holds over it. */
.detail[data-theme="reap"] .d-split{
  position:relative;
  border-radius:var(--d-radius);
  overflow:hidden;
  background:
    linear-gradient(180deg, rgba(14,38,20,.30), rgba(14,38,20,.46)),
    url('../assets/projects/reap-texture.jpg') center / cover no-repeat,
    var(--m-feature);
  padding-inline:clamp(16px, 3cqi, 40px);
}
/* Alternating the plate keeps six rows from reading as one repeated tile. */
.detail[data-theme="reap"] .d-split[data-flip="true"]{ background-position:center, right center; }
.detail[data-theme="reap"] .d-split:nth-of-type(even){ background-position:center, left bottom; }

/* On the green ground the copy card flips to the app's own near-white,
   which is both the product's pairing and the legible one here. */
.detail[data-theme="reap"] .d-split .d-note{
  background:#F8FAF6; color:#17251C;
  border-radius:clamp(12px, 1.6cqi, 18px);
  box-shadow:0 20px 44px rgba(10,28,14,.34);
}
.detail[data-theme="reap"] .d-split .d-note b{ color:#234A32; }

/* Reap's feature rows sit as one object: the render tips off-axis and laps
   over its copy card rather than the two squaring up in separate columns.
   The tilt is applied to the image alone, so the card stays upright. */
/* The renders are cropped to their content box, but the drop shadow falls
   right and low, so the phone body sits 124px of 3712 above the bitmap's
   centre. Centring the box would leave the phone visibly high against its
   card, so the image is nudged down by that same 3.34% and the *phone*
   ends up on the row's centre line. */
.detail[data-theme="reap"] .d-split__media img{
  transform:translateY(3.34%) rotate(-3.2deg);
  position:relative; z-index:2;
}
.detail[data-theme="reap"] .d-split[data-flip="true"] .d-split__media img{
  transform:translateY(3.34%) rotate(3.2deg);
}
/* The tilt sweeps a taller box than the upright image, and the stage clips
   its overflow, so the row carries enough padding for the rotated corners. */
.detail[data-theme="reap"] .d-split{ padding-block:clamp(22px, 3cqi, 38px); }
/* Stacked: the render laps down onto the card. */
.detail[data-theme="reap"] .d-split{ gap:0; }
.detail[data-theme="reap"] .d-split__media{ margin-bottom:-22px; }
/* One feature runs into the next: the heading already separates them, so the
   full section gap on top of the rows' own padding opens too large a hole. */
.detail[data-theme="reap"].detail > .d-split + .d-h-group{
  margin-top:clamp(12px, 1.6cqi, 22px);
}

/* Navicane's world, read off navicanee.netlify.app. Its sections are spaced
   generously so each capability reads as its own beat. */
.detail[data-theme="navicane"]{
  --m-bg:#F7F6F2;
  --m-feature:#0F2A40;
  --m-accent:#2B5D8C;
  --m-ink:#1C1D1F;
  --d-gap:clamp(32px, 4.4cqi, 56px);
}

/* ---- go and see it: live link, source, demo sign-ins ---- */
.d-links{ display:flex; flex-direction:column; gap:clamp(12px, 1.6cqi, 18px); }
.d-links__row{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(238px, 1fr));
  gap:clamp(10px, 1.4cqi, 14px);
}
.d-link{
  display:flex; align-items:center; gap:clamp(10px, 1.4cqi, 14px);
  padding:clamp(13px, 1.8cqi, 18px) clamp(14px, 2cqi, 20px);
  border:1px solid var(--m-line);
  border-radius:clamp(10px, 1.2cqi, 14px);
  background:var(--m-feature); color:var(--m-on-feature);
  text-decoration:none;
  transition:border-color .18s ease, transform .18s ease, background-color .18s ease;
}
/* The live deployment is the primary invitation, so it carries the accent. */
.d-link[data-kind="live"]{
  background:var(--m-accent); color:var(--m-on-accent);
  border-color:transparent;
}
.d-link__icon{
  flex:0 0 auto; width:clamp(22px, 2.6cqi, 27px); height:clamp(22px, 2.6cqi, 27px);
  display:grid; place-items:center;
}
.d-link__icon svg{ width:100%; height:100%; }
.d-link__body{ display:flex; flex-direction:column; gap:.15em; min-width:0; }
.d-link__body b{
  font-size:clamp(13px, .8cqi + 10px, 15px);
  font-weight:700; letter-spacing:-.01em;
}
.d-link__body span{
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:clamp(10.5px, .6cqi + 8px, 12px);
  line-height:1.3;
  opacity:.72;
  overflow-wrap:anywhere;
}
.d-link__go{
  flex:0 0 auto; margin-left:auto;
  width:clamp(17px, 1.9cqi, 20px); height:clamp(17px, 1.9cqi, 20px);
  opacity:.6;
  transition:transform .18s ease, opacity .18s ease;
}
.d-link__go svg{ width:100%; height:100%; display:block; }
.d-link:hover{ border-color:var(--m-accent); transform:translateY(-1px); }
.d-link[data-kind="live"]:hover{ border-color:transparent; }
.d-link:hover .d-link__go{ opacity:1; transform:translate(2px, -2px); }
.d-link:focus-visible{ outline:2px solid var(--m-accent); outline-offset:3px; }

.d-creds{
  border:1px solid var(--m-line);
  border-radius:clamp(10px, 1.2cqi, 14px);
  padding:clamp(14px, 2cqi, 20px);
  display:flex; flex-direction:column; gap:clamp(9px, 1.2cqi, 13px);
}
.d-creds__eyebrow{
  margin:0;
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:clamp(10px, .9cqi + 7px, 11.5px);
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--m-accent);
}
.d-creds__list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:clamp(7px, .9cqi, 10px); }
.d-creds__list li{
  display:flex; flex-wrap:wrap; align-items:baseline;
  gap:clamp(6px, .9cqi, 10px);
}
.d-creds__role{
  flex:0 0 auto; min-width:clamp(80px, 11cqi, 104px);
  font-size:clamp(11.5px, .7cqi + 9px, 13px);
  font-weight:700;
  color:color-mix(in srgb, var(--m-ink) 64%, transparent);
}
.d-creds__names{ display:flex; flex-wrap:wrap; gap:6px; min-width:0; }
.d-creds code{
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:clamp(10.5px, .6cqi + 8.5px, 12px);
  line-height:1.5;
  padding:.24em .55em;
  border-radius:5px;
  background:var(--m-feature);
  color:var(--m-on-feature);
  white-space:nowrap;
}
.d-creds__pw{ margin:0; font-size:clamp(12px, .7cqi + 9.5px, 13.5px); }
.d-creds__pw code{ background:var(--m-accent); color:var(--m-on-accent); font-weight:600; }
.d-creds__note{
  margin:0;
  font-size:clamp(11px, .6cqi + 8.5px, 12.5px);
  line-height:1.5;
  color:color-mix(in srgb, var(--m-ink) 55%, transparent);
}

/* ---- research / impact figures ---- */
.d-stats{ display:flex; flex-direction:column; gap:clamp(12px, 1.8cqi, 20px); }
.d-stats__eyebrow{
  margin:0;
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:clamp(10px, .9cqi + 7px, 11.5px);
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--m-accent);
}
.d-stats__grid{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(148px, 1fr));
  gap:clamp(14px, 2.2cqi, 26px);
}
.d-stat{
  border-top:2px solid var(--m-accent);
  padding-top:clamp(9px, 1.2cqi, 14px);
}
.d-stat b{
  display:block;
  font-size:clamp(26px, 2.6cqi + 16px, 38px);
  font-weight:800; letter-spacing:-.03em; line-height:1;
  color:var(--m-emphasis);
  font-variant-numeric:tabular-nums;
}
.d-stat span{
  display:block; margin-top:.5em;
  font-size:clamp(12px, .8cqi + 9px, 13.5px);
  line-height:1.4;
  color:color-mix(in srgb, var(--m-ink) 68%, transparent);
}
.d-stats__note{
  margin:0;
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:clamp(10px, .8cqi + 7px, 11.5px);
  line-height:1.5;
  color:color-mix(in srgb, var(--m-ink) 52%, transparent);
}

/* ---- intro card ---- */
.d-intro{
  position:relative;
  background:var(--m-feature);
  color:var(--m-on-feature);
  border-radius:var(--d-radius);
  padding:var(--d-pad) var(--d-pad) calc(var(--d-pad) + 46px);
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:clamp(18px, 2.6cqi, 34px);
  align-items:center;
  overflow:hidden;
}
.d-intro__logo{
  width:clamp(130px, 17cqi, 184px);
  height:auto; margin:0 0 clamp(12px, 1.8cqi, 20px);
}
.d-intro__body p{ margin:0 0 .8em; font-weight:600; }
.d-intro__body p:last-child{ margin-bottom:0; }
.d-intro__phone{
  width:clamp(140px, 18cqi, 186px);
  height:auto; justify-self:center;
}
/* Landscape opener art. Its ground is painted the same value as the card it
   sits on, so it reads as part of the panel rather than a pasted rectangle. */
.d-intro__art{ width:100%; height:auto; border-radius:clamp(6px, .8cqi, 10px); }
.d-intro__train{
  position:absolute; left:-14px; bottom:-4px;
  width:clamp(110px, 16cqi, 176px);
  height:auto; pointer-events:none;
  transform:scaleX(-1);          /* train faces into the card */
}

/* ---- headings and copy ---- */
.d-h{
  margin:0;
  font-size:clamp(19px, 1.5cqi + 13px, 23px);
  font-weight:800; letter-spacing:-.02em;
  text-wrap:balance;
}
/* Eyebrow + heading as one block: the label sits tight above the title it
   introduces, and the group as a whole spaces like a heading. */
.d-h-group{ margin:0; }
.d-h-group__eyebrow{
  margin:0 0 .35em;
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:clamp(10px, .9cqi + 7px, 11.5px);
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--m-accent);
}
.d-h-group .d-h{ margin:0; }

.d-lead{ margin:0; }

.d-ol{
  margin:0; padding-left:1.7em;
  display:flex; flex-direction:column; gap:.72em;
}
.d-ol p{ margin:0; }
.d-ol b{ font-weight:700; }
.d-ol[data-size="sm"]{ font-size:.92em; }

/* ---- alternating media / callout rows ---- */
.d-split{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:clamp(18px, 2.6cqi, 36px);
  align-items:center;
}
.d-split__media{
  display:flex; align-items:center; justify-content:center;
  flex-wrap:wrap; gap:14px; min-width:0;
}
/* Height is definite, not a max: with `width:auto` in a flex row there is no
   basis to size from, so these lazy mockups measured zero until they loaded
   and then popped in. A set height lets the width resolve off the intrinsic
   ratio, reserving the space up front. */
.d-split__media img{
  height:var(--d-media);
  width:auto; max-width:100%;
  flex:0 0 auto;
  object-fit:contain;
}
/* Sits smaller beside its mockup rather than matching its height, the card
   is landscape, so equal heights would make it read as the larger object. */
.d-split__media img.is-sm{
  height:auto;
  width:clamp(104px, 23cqi, 240px);
}
/* Landscape art, a UI screenshot fills its column rather than being sized by
   height and letterboxed inside it. */
.d-split__media img.is-wide{
  height:auto;
  width:100%;
  border-radius:6px;
}

/* ---- full-width screen showcase ---- */
.d-showcase{ display:flex; flex-direction:column; gap:clamp(12px, 1.6cqi, 18px); }
.d-showcase__frame{
  border:1px solid var(--m-line);
  border-radius:clamp(8px, 1cqi, 12px);
  overflow:hidden;
  background:var(--m-feature);
  line-height:0;                     /* no descender gap under the image */
}
.d-showcase__frame img{ width:100%; height:auto; }
.d-showcase__cap{
  display:flex; flex-direction:column; gap:.35em;
  max-width:76ch;
}
.d-showcase__cap b{
  font-size:clamp(13px, .9cqi + 10px, 15.5px);
  font-weight:800; letter-spacing:.01em;
  color:var(--m-emphasis);
}
.d-showcase__cap p{ margin:0; }

/* ---- the stack ---- */
.d-stack{ display:flex; flex-direction:column; gap:clamp(12px, 1.8cqi, 18px); }
.d-stack__grid{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(166px, 1fr));
  gap:clamp(8px, 1.2cqi, 12px);
}
.d-stack__item{
  display:flex; align-items:flex-start; gap:clamp(9px, 1.2cqi, 13px);
  border:1px solid var(--m-line);
  border-radius:clamp(8px, 1cqi, 12px);
  padding:clamp(11px, 1.5cqi, 16px);
  background:var(--m-feature);
  color:var(--m-on-feature);
}
/* The marks carry their own brand colours; the two monochrome ones (Next,
   Vercel) take the card's ink instead, which is what they ship as. */
.d-stack__icon{
  flex:0 0 auto;
  width:clamp(22px, 2.4cqi, 27px); height:clamp(22px, 2.4cqi, 27px);
  margin-top:.05em;
}
.d-stack__icon svg{ width:100%; height:100%; display:block; }
.d-stack__text{ display:flex; flex-direction:column; min-width:0; }
.d-stack__item b{
  display:block;
  font-size:clamp(13px, .8cqi + 10px, 15px);
  font-weight:700; letter-spacing:-.01em;
}
.d-stack__text span{
  display:block; margin-top:.35em;
  font-size:clamp(11px, .6cqi + 8.5px, 12.5px);
  line-height:1.35;
  color:color-mix(in srgb, var(--m-on-feature) 62%, transparent);
}

/* ---- full-bleed colour band ---- */
.d-band{
  margin-inline:calc(var(--d-pad) * -1);
  padding:clamp(22px, 3cqi, 36px) var(--d-pad);
  color:var(--m-on-feature);
}
.d-band[data-tone="accent"]{ background:var(--m-accent); color:var(--m-on-accent); }
.d-band[data-tone="feature"]{ background:var(--m-feature); }
.d-band p{ margin:0; }
.d-band b{ font-weight:800; }
/* A band carrying a statement rather than a paragraph. Set large and bold,
   which is how a pull-quote should read anyway, and at this size a 4.4:1
   pairing clears WCAG's large-text threshold, where body copy would not. */
.d-band[data-size="lg"]{ padding-block:clamp(26px, 3.6cqi, 44px); }
.d-band[data-size="lg"] p{
  font-size:clamp(17px, 1.5cqi + 11px, 24px);
  font-weight:700; line-height:1.32; letter-spacing:-.015em;
  /* One line wherever it fits: `max-content` sets the unwrapped width, and
     the cap lets it fall back to wrapping on a container too narrow for it
     rather than running out of the panel. */
  width:max-content; max-width:100%;
  text-wrap:balance;
}

.d-note{
  padding:clamp(18px, 2.4cqi, 28px);
  color:var(--m-on-feature);
  align-self:center;
}
.d-note[data-tone="accent"]{ background:var(--m-accent); color:var(--m-on-accent); }
.d-note[data-tone="feature"]{ background:var(--m-feature); }
.d-note p{ margin:0; }
.d-note b{ font-weight:800; }
.d-note p + p{ margin-top:.85em; }
.d-note__more{ opacity:.86; }
/* Specifics under the prose: a label and its value per row, ruled rather than
   boxed so the card stays one object. */
.d-note__points{
  list-style:none; margin:1.1em 0 0; padding:.95em 0 0;
  border-top:1px solid color-mix(in srgb, currentColor 22%, transparent);
  display:flex; flex-direction:column; gap:.6em;
  font-size:.92em;
}
.d-note__points li{ display:flex; flex-direction:column; gap:.12em; }
.d-note__points b{
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:.82em; font-weight:600;
  letter-spacing:.1em; text-transform:uppercase;
  opacity:.7;
}
.d-note__points span{ line-height:1.4; }

/* ---- three pillars ---- */
.d-pillars{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(3, minmax(0,1fr));
  gap:clamp(10px, 2cqi, 22px);
}
.d-pillars li{
  display:flex; flex-direction:column; align-items:center; text-align:center;
}
/* Three across only while each column can still hold a line of prose. */
.d-pillars[data-detailed]{
  grid-template-columns:repeat(auto-fit, minmax(196px, 1fr));
  gap:clamp(22px, 3cqi, 34px);
}
.d-pillar__icon{
  width:clamp(52px, 8cqi, 76px);
  height:clamp(52px, 8cqi, 76px);
  margin-bottom:clamp(8px, 1.4cqi, 13px);
  border-radius:50%;
  background:var(--m-emphasis); color:var(--m-on-emphasis);
  display:grid; place-items:center;
}
.d-pillar__icon svg{ width:60%; height:60%; }
.d-pillar__label{
  color:var(--m-emphasis);
  font-size:clamp(12px, 1.1cqi + 9px, 15px);
  font-weight:600; line-height:1.25;
  text-wrap:balance;
}
/* A step number riding the icon, for pillars that read as a sequence rather
   than a set of equals. */
.d-pillars[data-numbered] li{ position:relative; }
.d-pillar__icon{ position:relative; }
.d-pillar__step{
  position:absolute; top:-2px; right:-2px;
  width:clamp(20px, 2.6cqi, 25px); height:clamp(20px, 2.6cqi, 25px);
  border-radius:50%;
  background:var(--m-bg); color:var(--m-emphasis);
  border:2px solid var(--m-emphasis);
  display:grid; place-items:center;
  font-size:clamp(10px, .8cqi + 7px, 12.5px);
  font-weight:800; line-height:1;
  font-variant-numeric:tabular-nums;
}
.d-pillar__text{
  margin:.55em 0 0;
  font-size:clamp(11.5px, .7cqi + 8.8px, 13px);
  line-height:1.5;
  color:color-mix(in srgb, var(--m-ink) 70%, transparent);
  text-wrap:pretty;
}

/* ---- a designed opener, in place of the built intro card ---- */
.d-hero{
  border-radius:var(--d-radius);
  overflow:hidden;
  line-height:0;                     /* no descender gap under the image */
}
.d-hero img{ width:100%; height:auto; display:block; }
/* A very wide opener collapses to an unreadable strip on a phone, this one
   is 3.4:1, which is 96px tall at 330. Below the two-column threshold it is
   art-directed to its subject instead; the pitch it carries in type is
   already above the study as the project's own synopsis. */
@container study (max-width:519px){
  .d-hero img{
    height:clamp(180px, 58cqi, 240px);
    /* Hard left: the crop lands on the laptop and stops before the set copy,
       rather than slicing sentences down the middle. */
    object-fit:cover; object-position:left center;
  }
}

/* ---- closing collage ---- */
.d-collage{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:clamp(10px, 1.4cqi, 16px);
}
.d-collage__item{
  margin:0; overflow:hidden;
  border-radius:clamp(14px, 1.8cqi, 22px);
  background:var(--m-feature);
  line-height:0;
}
.d-collage__item img{ width:100%; height:100%; object-fit:cover; display:block; }

/* ---- outcome card ---- */
.d-summary{
  background:var(--m-feature); color:var(--m-on-feature);
  border-radius:var(--d-radius);
  padding:var(--d-pad);
  display:flex; flex-direction:column;
  gap:clamp(12px, 1.6cqi, 17px);
}
.d-summary p{ margin:0; }
.d-summary b{ font-weight:800; }

/* ---- brand imagery ---- */
.d-gallery{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:clamp(10px, 1.4cqi, 14px);
}
.d-shot{
  margin:0; overflow:hidden;
  background:#DED2F0;
  aspect-ratio:4 / 3;
}
.d-shot img{ width:100%; height:100%; object-fit:cover; display:block; }

/* --------------------------------------------------------------------------
   Widening the container, in the order the content actually needs the room.
   -------------------------------------------------------------------------- */

@container study (min-width:520px){
  .d-gallery{
    grid-template-columns:repeat(2, minmax(0,1fr));
    grid-auto-rows:clamp(150px, 20cqi, 212px);
  }
  .d-shot{ aspect-ratio:auto; }
  .d-shot[data-span="2"]{ grid-row:span 2; }

  /* Lead image across the top, the pair beneath it. Each keeps its own
     proportion, so nothing is cropped to force a uniform cell. */
  .d-collage{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  .d-collage__item[data-lead]{ grid-column:1 / -1; }
}

@container study (min-width:600px){
  .d-intro{ grid-template-columns:minmax(0,1fr) clamp(150px, 18cqi, 186px); }
  .d-intro__phone{ justify-self:end; }
}

@container study (min-width:680px){
  .d-split{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); }
  .d-split[data-flip="true"] .d-split__media{ order:2; }
  /* only once each column can hold a mockup at a readable size */
  .d-split__media{ flex-wrap:nowrap; gap:0; }
  /* Overlap scales with the art: at the larger mockup size the pair would
     otherwise outgrow its column, since neither image is allowed to shrink. */
  .d-split__media img + img{ margin-left:-80px; margin-top:26px; align-self:flex-start; }

  /* Justified setting needs a wide measure or it opens rivers; the deck's
     look is not worth that, so it only applies where it reads well. */
  .d-intro__body p,
  .d-note p,
  .d-band p,
  .d-summary p{ text-align:justify; hyphens:auto; }
  /* …but never a display line. Justifying a short, large measure tears word
     spaces open, which is exactly what a pull-quote must not do. */
  /* `.d-intro__body p` above is (0,1,1), so the display line has to match it
     to win, a bare class would lose and stay justified. */
  .d-band[data-size="lg"] p,
  .d-intro__body .d-intro__display{ text-align:left; hyphens:manual; }

  /* Reap's rows are a centred pair rather than two equal columns: the two
     sit against each other with the render lapping over the card, and the
     group is centred in the study instead of stretched across it. */
  .detail[data-theme="reap"] .d-split{
    display:flex; justify-content:center; align-items:center;
    gap:0;
  }
  .detail[data-theme="reap"] .d-split__media{
    flex:0 0 auto; margin-bottom:0; margin-right:-30px;
  }
  /* `order:2` above puts the render on the right for a flipped row, so the
     overlap has to change sides with it. */
  .detail[data-theme="reap"] .d-split[data-flip="true"] .d-split__media{
    margin-right:0; margin-left:-30px;
  }
  .detail[data-theme="reap"] .d-note{
    flex:0 1 clamp(290px, 42cqi, 440px);
    align-self:center;
  }
  /* Justified text needs room; at this measure it opens rivers. */
  .detail[data-theme="reap"] .d-note p{ text-align:left; hyphens:manual; }
}

body.is-locked{ overflow:hidden; }

/* ==========================================================================
   contact
   "Get in touch page.pdf": the same ground and green as the about page, the
   banner as the one supplied composition, and the deck's own two columns -
   the character on the left, the form on the right, the two contacts along
   the bottom. Below 880px they stack in that order.
   ========================================================================== */

.ct{
  --ct-green:#60F421;
  background:#221F1E;
  color:#FFF;
  padding:clamp(22px, 2.6vw, 44px) 0 clamp(56px, 7vw, 104px);
}
.ct__shell{
  width:min(1253px, 100% - 2 * var(--gutter));
  margin-inline:auto;
  display:flex; flex-direction:column;
  gap:clamp(34px, 4.4vw, 74px);
}
.ct__hero{ display:block; width:100%; height:auto; }

/* The deck gives the character the left half and the form the right, with the
   form's column starting at 57% of the page. */
.ct__row{
  display:grid;
  grid-template-columns:minmax(0, 1fr) minmax(0, 1.12fr);
  gap:clamp(26px, 4vw, 64px);
  align-items:center;
}

/* ---- the character ---- */
.ct__pal{ display:flex; justify-content:center; }
.pal{
  position:relative;
  width:min(100%, 430px);
  aspect-ratio:1227 / 1282;
}
.pal__skin{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:contain;
  opacity:0;
  transition:opacity .34s var(--ease);
}
/* One face at a time, cross-faded: the eyes are drawn over the top of all
   three, so nothing about them moves when the expression changes. */
.pal[data-state="neutral"] .pal__skin[data-skin="neutral"],
.pal[data-state="shock"]   .pal__skin[data-skin="shock"],
.pal[data-state="excited"] .pal__skin[data-skin="excited"]{ opacity:1; }
.pal__eyes{
  position:absolute; inset:0;
  width:100%; height:100%;
  pointer-events:none;
}
.pal__ball{ fill:#FBFAFB; stroke:#141210; stroke-width:10; }
.pal__pupil{ transition:none; }
@media (prefers-reduced-motion:reduce){
  .pal__skin{ transition:none; }
}

/* ---- the form ---- */
.ct__form{ display:flex; flex-direction:column; gap:clamp(18px, 2vw, 28px); }
.fld{ display:flex; flex-direction:column; gap:10px; }
.fld__lb{
  font-size:clamp(14px, .62vw + 8px, 17px);
  line-height:1.3;
  color:#FFF;
}
.fld__in,
.fld__ta{
  width:100%;
  font:inherit;
  font-size:clamp(14px, .6vw + 8px, 17px);
  color:#FFF;
  background:transparent;
  border:0;
  border-radius:0;
  padding:8px 2px;
  transition:border-color .18s var(--ease), box-shadow .18s var(--ease);
}
/* The deck rules a hairline under the two single-line fields and boxes the
   message; both in its green. */
.fld__in{ border-bottom:1px solid var(--ct-green); }
.fld__ta{
  border:1px solid var(--ct-green);
  padding:12px 14px;
  min-height:clamp(104px, 10vw, 132px);
  resize:vertical;
  line-height:1.45;
}
.fld__in:focus,
.fld__ta:focus{
  outline:none;
  box-shadow:0 1px 0 0 var(--ct-green);
}
.fld__ta:focus{ box-shadow:0 0 0 1px var(--ct-green); }
.fld__in::placeholder,
.fld__ta::placeholder{ color:rgba(255,255,255,.4); }

/* The honeypot: off-screen rather than display:none, which some bots skip. */
.fld__trap{
  position:absolute; left:-9999px; top:auto;
  width:1px; height:1px; overflow:hidden;
}

.ct__acts{ display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
.ct__send[disabled]{ opacity:.6; }
.ct__send{
  min-height:44px;
  padding:0 clamp(18px, 1.6vw, 26px);
  font-size:clamp(14px, .62vw + 8px, 17px);
  color:#FFF;
  background:transparent;
  border:1px solid var(--ct-green);
  border-radius:0;
  transition:background .18s var(--ease), color .18s var(--ease);
}
.ct__send:hover{ background:var(--ct-green); color:#141210; }
.ct__err{
  margin:0;
  font-size:14px;
  color:#FF9A8A;
}
/* ---- the note has gone: said once, over the page ---- */
.pop{
  position:fixed; inset:0; z-index:900;
  display:grid; place-items:center;
  padding:clamp(16px, 4vw, 40px);
  opacity:0;
  transition:opacity .2s var(--ease);
}
.pop.is-open{ opacity:1; }
.pop[hidden]{ display:none; }
.pop__scrim{
  position:absolute; inset:0; z-index:0;
  border:0; padding:0;
  background:rgba(8,6,6,.86);
  backdrop-filter:blur(5px);
}
.pop__card{
  position:relative; z-index:1;
  width:min(420px, 100%);
  display:flex; flex-direction:column; align-items:flex-start;
  gap:10px;
  padding:clamp(22px, 3vw, 32px);
  background:#221F1E;
  border:1px solid var(--ct-green, #60F421);
  color:#FFF;
  box-shadow:0 26px 60px rgba(0,0,0,.6);
  animation:popIn .26s var(--ease);
}
@keyframes popIn{ from{ opacity:0; transform:translateY(10px) scale(.97) } to{ opacity:1; transform:none } }
.pop__ttl{
  margin:0;
  font-size:clamp(18px, 1vw + 12px, 23px);
  font-weight:600;
  color:var(--ct-green, #60F421);
}
.pop__sub{
  margin:0;
  font-size:14.5px; line-height:1.55;
  color:rgba(255,255,255,.78);
}
.pop__mail,
.pop__ok{
  margin-top:6px;
  min-height:42px;
  display:inline-flex; align-items:center;
  padding:0 18px;
  font-size:15px;
  text-decoration:none;
  border-radius:0;
}
.pop__mail{ color:#141210; background:var(--ct-green, #60F421); border:0; }
/* the author display would otherwise beat the browser's own [hidden] rule */
.pop__mail[hidden]{ display:none; }
.pop__mail:hover{ filter:brightness(1.08); }
.pop__ok{
  color:#FFF; background:transparent;
  border:1px solid rgba(255,255,255,.4);
  transition:border-color .18s var(--ease), color .18s var(--ease);
}
.pop__ok:hover{ border-color:var(--ct-green, #60F421); color:var(--ct-green, #60F421); }
@media (prefers-reduced-motion:reduce){
  .pop{ transition:none; }
  .pop__card{ animation:none; }
}

/* ---- the two contacts, along the bottom ---- */
.ct__ends{
  display:flex; justify-content:space-between; align-items:baseline;
  gap:clamp(18px, 4vw, 64px); flex-wrap:wrap;
}
.ct__end{
  font-size:clamp(18px, 2vw, 30px);
  font-weight:600;
  letter-spacing:-.015em;
  color:#FFF;
  text-decoration:none;
  border-bottom:1px solid transparent;
  transition:border-color .18s var(--ease), color .18s var(--ease);
}
.ct__end:hover{ color:var(--ct-green); border-bottom-color:var(--ct-green); }

@media (max-width:879px){
  .ct__row{ grid-template-columns:1fr; gap:clamp(22px, 5vw, 36px); }
  .pal{ width:min(100%, 240px); }
  .ct__ends{ flex-direction:column; align-items:flex-start; gap:14px; }
}
@media (max-width:639px){
  /* A small figure standing on top of the form rather than a portrait above
     it, sized so that the two of them share one screen: just enough room
     between its feet and the first label. */
  .ct__row{ gap:12px; }
  .ct__pal{ justify-content:flex-start; padding-left:4px; }
  .pal{ width:min(34%, 112px); }
  .ct__shell{ gap:clamp(20px, 5vw, 32px); }
  .ct__form{ gap:14px; }
  .fld{ gap:6px; }
  .fld__in{ padding:6px 2px; }
  /* height, not min-height: the rows attribute would otherwise keep it tall */
  .fld__ta{ height:78px; min-height:78px; padding:10px 12px; }
  .ct__acts{ gap:12px; }
}

/* ==========================================================================
   gallery
   The same card the projects use, laid out as one field of them: no shelves,
   no arrows, no reading order. The line on each card arrives on hover, and
   stays put where there is no pointer to hover with.
   ========================================================================== */

.gal{ padding:clamp(34px, 4.4vw, 68px) 0 clamp(56px, 7vw, 104px); }
.gal__head{ margin-bottom:clamp(20px, 2.6vw, 34px); }
.gal__title{
  margin:0;
  font-size:var(--fs-row); font-weight:600; letter-spacing:-.01em;
  text-transform:none;
}
.gal__sub{
  margin:6px 0 0;
  font-size:13px;
  color:var(--ink-faint);
}
.gal__grid{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(min(var(--card-w), 48%), 1fr));
  gap:calc(var(--card-gap) + 10px) var(--card-gap);
}

.gcard{
  position:relative;
  margin:0;
  border-radius:var(--card-r);
  overflow:hidden;
  background:var(--surface);
  transition:transform .22s var(--ease);
}
.gcard__open{
  display:block; width:100%;
  padding:0; border:0; background:none;
  cursor:zoom-in;
}
.gcard__open:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; }
.gcard img{
  display:block;
  width:100%; height:auto;
  aspect-ratio:1440 / 946;
  object-fit:cover;
}
.gcard__cap{
  position:absolute; left:0; right:0; bottom:0;
  padding:26px 12px 11px;
  font-size:12.5px; line-height:1.35; font-weight:500;
  color:#FFF;
  background:linear-gradient(to top, rgba(10,8,8,.88) 38%, rgba(10,8,8,0));
  text-wrap:pretty;
  opacity:0; transform:translateY(6px);
  transition:opacity .2s var(--ease), transform .2s var(--ease);
}
.gcard:hover,
.gcard:focus-within{ transform:translateY(-3px); }
.gcard:hover .gcard__cap,
.gcard:focus-within .gcard__cap{ opacity:1; transform:none; }

@media (hover:none){
  /* Nothing to hover with: the line just lives on the card. */
  .gcard__cap{ opacity:1; transform:none; }
  .gcard:hover{ transform:none; }
}
@media (max-width:640px){
  /* Two cards to a phone screen: the line steps down a size so it sits in
     the bottom of the picture rather than across the middle of it. */
  .gcard__cap{ font-size:11px; line-height:1.3; padding:20px 9px 8px; }
}
@media (prefers-reduced-motion:reduce){
  .gcard,
  .gcard__cap{ transition:none; }
  .gcard:hover,
  .gcard:focus-within{ transform:none; }
}

/* ---- the photograph, full frame ----
   No crop here: the picture keeps the shape it was taken in and takes as much
   of the window as it can without either edge running off. */
.lbox{
  position:fixed; inset:0; z-index:900;
  display:grid;
  grid-template-columns:auto minmax(0, 1fr) auto;
  align-items:center;
  gap:clamp(6px, 1.6vw, 22px);
  padding:clamp(14px, 3vw, 40px);
  opacity:0;
  transition:opacity .2s var(--ease);
}
.lbox.is-open{ opacity:1; }
.lbox[hidden]{ display:none; }
.lbox__scrim{
  position:absolute; inset:0; z-index:0;
  border:0; padding:0;
  background:rgba(8,6,6,.92);
  backdrop-filter:blur(6px);
  cursor:zoom-out;
}
.lbox__frame{
  position:relative; z-index:1;
  margin:0;
  display:flex; flex-direction:column; align-items:center; gap:12px;
  min-width:0;
}
.lbox__img{
  display:block;
  max-width:100%;
  max-height:calc(100dvh - clamp(96px, 16vh, 150px));
  width:auto; height:auto;
  aspect-ratio:var(--r, auto);
  object-fit:contain;
  border-radius:4px;
  background:#141111;
}
.lbox__cap{
  display:flex; align-items:baseline; gap:12px; flex-wrap:wrap;
  justify-content:center;
  font-size:13px; line-height:1.4;
  color:rgba(255,255,255,.82);
  text-align:center;
}
.lbox__count{ color:var(--ink-faint); font-variant-numeric:tabular-nums; }
.lbox__nav,
.lbox__close{
  position:relative; z-index:2;
  width:max(44px, var(--tap, 44px)); height:max(44px, var(--tap, 44px));
  display:grid; place-items:center;
  border:1px solid rgba(255,255,255,.22);
  border-radius:50%;
  background:rgba(20,17,17,.72);
  color:#FFF;
  transition:background .16s var(--ease), border-color .16s var(--ease);
}
.lbox__nav:hover,
.lbox__close:hover{ background:rgba(20,17,17,.95); border-color:var(--accent); }
.lbox__nav svg{ width:22px; height:22px; fill:none; stroke:currentColor; stroke-width:2; }
.lbox__close{ position:absolute; top:clamp(10px, 2vw, 22px); right:clamp(10px, 2vw, 22px); }
.lbox__close svg{ width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2.2; }
@media (max-width:640px){
  /* On a phone the arrows sit under the picture rather than squeezing it. */
  .lbox{
    grid-template-columns:1fr;
    grid-template-areas:'frame' 'nav';
    align-content:center;          /* the two rows stay together, not spread */
    row-gap:16px;
  }
  .lbox__frame{ grid-area:frame; }
  .lbox__nav{ grid-area:nav; }
  /* Both arrows share the row, so they are nudged either side of its centre:
     back on the left, forward on the right, the way they read. */
  .lbox__nav{ justify-self:center; }
  .lbox__nav[data-step="-1"]{ transform:translateX(-46px); }
  .lbox__nav[data-step="1"]{ transform:translateX(46px); }
  .lbox__img{ max-height:calc(100dvh - 210px); }
}
@media (prefers-reduced-motion:reduce){ .lbox{ transition:none; } }

/* ==========================================================================
   about me
   "About Me Page.pdf", rebuilt: its ground (#221F1E), its green (#60F421)
   and its two-column grid, with the banner as the one supplied composition.
   The deck sets 24/33 on a 1440 page with a 1253 column, which is what the
   type scale below rebases against; below 860px the rows fold to one column
   and the doodles follow the paragraph they belong to.
   ========================================================================== */

.ab{
  --ab-green:#60F421;
  background:#221F1E;
  color:#FFF;
  padding:clamp(22px, 2.6vw, 44px) 0 clamp(56px, 7vw, 110px);
  /* the masthead sticks, so the jump from the nav has to clear it */
  scroll-margin-top:76px;
}
.ab__shell{
  width:min(1253px, 100% - 2 * var(--gutter));
  margin-inline:auto;
  display:flex; flex-direction:column;
  /* the deck leaves roughly four lines between one section and the next */
  gap:clamp(40px, 5.2vw, 84px);
}
.ab__hero{ display:block; width:100%; height:auto; }

.ab p{
  margin:0;
  font-size:clamp(15px, 1.15vw + 4px, 21px);
  line-height:1.375;          /* the deck's 33 on 24 */
  color:rgba(255,255,255,.94);
  text-wrap:pretty;
}
.ab__block,
.ab__col{ display:flex; flex-direction:column; gap:1.375em; }
/* The one line the deck sets in green. */
.ab__lead{ color:var(--ab-green) !important; }

/* A paragraph and the drawing it belongs to, side by side. The deck gives the
   words a little more room than the drawing. */
.ab__row{
  display:grid;
  grid-template-columns:minmax(0, 1.07fr) minmax(0, 1fr);
  gap:clamp(28px, 6cqw, 92px);
  align-items:center;
}
/* The drawing changes sides; the words keep the wider of the two columns. */
.ab__row--flip{ grid-template-columns:minmax(0, 1fr) minmax(0, 1.07fr); }
.ab__row--flip .ab__col{ grid-column:2; }
.ab__row--flip .ab__doodle{ grid-column:1; grid-row:1; }
.ab__doodle{
  display:block;
  width:100%; height:auto;
  justify-self:center;
}

.ab__foot{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:clamp(24px, 4vw, 64px);
  margin-top:clamp(8px, 2vw, 34px);
}
.ab__cta{
  display:inline-flex; align-items:center; gap:.55em;
  color:var(--ab-green);
  font-size:clamp(15px, 1.15vw + 4px, 21px);
  line-height:1.2;
  text-decoration:none;
  border-bottom:1px solid transparent;
  transition:border-color .18s var(--ease), opacity .18s var(--ease);
}
.ab__cta svg{ width:.86em; height:.86em; fill:currentColor; flex:none; }
.ab__cta:hover{ border-bottom-color:var(--ab-green); }
.ab__sig{
  width:clamp(190px, 34%, 488px);
  height:auto;
  flex:none;
}

@media (max-width:859px){
  /* One column: the drawing follows the words it illustrates, which is the
     order the deck reads in anyway. */
  .ab__row{ grid-template-columns:1fr; gap:clamp(20px, 5vw, 34px); }
  .ab__row--flip .ab__col,
  .ab__row--flip .ab__doodle{ grid-column:1; grid-row:auto; }
  .ab__doodle{ width:min(100%, 460px); }
  .ab__foot{ flex-direction:column; align-items:flex-start; gap:26px; }
  .ab__sig{ width:min(100%, 330px); align-self:flex-end; }
}

/* ==========================================================================
   placeholder slots
   ========================================================================== */

.slot{
  display:inline-flex; align-items:center; justify-content:center;
  border:1.5px dashed rgba(255,255,255,.3);
  border-radius:6px;
  padding:8px 14px;
  font-size:11px; font-weight:600;
  letter-spacing:.09em; text-transform:uppercase;
  color:var(--ink-faint);
  background:rgba(255,255,255,.03);
  text-align:center;
}
.slot--title{ height:var(--hero-title); min-width:0; max-width:100%; font-size:12px; }
.slot--title-sm{ height:24px; min-width:110px; padding:0 10px; font-size:10px; }
.slot--title-md{ height:38px; min-width:170px; padding:0 13px; font-size:11px; }
.slot--on-light{
  border-color:rgba(0,0,0,.28);
  color:rgba(0,0,0,.42);
  background:rgba(0,0,0,.03);
}

/* ==========================================================================
   footer
   ========================================================================== */

.foot{
  --foot-green:#61F421;
  --foot-word:#3D5C31;
  position:relative;
  background:var(--foot-green);
  color:#000;
  overflow:hidden;                       /* the wordmark is cut by the edge */
  padding-top:clamp(10px, 1vw, 15px);
  line-height:1.35;
}
.foot a{ color:inherit; text-decoration:none; }
.foot a:hover{ text-decoration:underline; text-underline-offset:3px; }

.foot__in{
  position:relative;
  width:min(1348px, 100% - 2 * clamp(20px, 3.5vw, 52px));
  margin-inline:auto;
}

/* The line, set in the same olive as the wordmark. */
.foot__ask{
  margin:0;
  text-align:center;
  font-size:clamp(15px, .75vw + 10px, 24px);
  font-weight:500;
  color:var(--foot-word);
}

/* One band under it: the small print against one margin, the button in the
   middle, the links against the other. */
.foot__row{
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:center;
  gap:16px;
  margin-top:clamp(14px, 2.3vw, 33px);
}
.foot__legal{
  margin:0;
  font-size:clamp(11px, .35vw + 8px, 14px);
  line-height:1.45;
}

/* The button the drawing puts under the line: olive, with the ground's own
   green as its label. */
/* `.foot a` would otherwise win on specificity and leave the label black. */
.foot a.foot__cta{
  justify-self:center;
  display:inline-flex; align-items:center; justify-content:center;
  min-width:clamp(150px, 13.5vw, 195px);
  min-height:clamp(36px, 2.64vw, 38px);
  padding:0 clamp(16px, 3vw, 44px);
  border-radius:clamp(5px, .55vw, 8px);
  background:var(--foot-word);
  color:var(--foot-green);
  font-size:clamp(13px, .6vw + 8px, 20px);
  font-weight:500;
  text-decoration:none;
  /* the same gesture the links beside it use */
  transition:transform .18s var(--ease), opacity .18s var(--ease);
}
.foot a.foot__cta:hover,
.foot a.foot__cta:focus-visible{
  transform:translateY(-2px);
  opacity:.8;
  text-decoration:none;
}
.foot a.foot__cta:active{ transform:translateY(0); }
@media (prefers-reduced-motion:reduce){
  .foot a.foot__cta{ transition:none; }
  .foot a.foot__cta:hover,
  .foot a.foot__cta:focus-visible{ transform:none; }
}

.foot__soc{
  justify-self:end;
  margin:0; padding:0; list-style:none;
  display:flex; align-items:center;
  gap:clamp(14px, 2.8vw, 40px);
}
.foot__soc a{
  display:block;
  opacity:.5;                     /* the drawing has them at half strength */
  transition:transform .18s var(--ease), opacity .18s var(--ease);
}
.foot__soc img{
  display:block;
  width:clamp(22px, 1.95vw, 28px);
  height:auto;
}
.foot__soc a:hover{ transform:translateY(-2px); opacity:.8; }

/* The wordmark: as wide as the word, centred on the page, running off both
   margins with its baseline just past the bottom edge. */
.foot__word{
  position:relative; left:50%;
  transform:translateX(-50%);
  width:max-content;
  margin:clamp(6px, .9vw, 13px) 0 -.173em;  /* the deck cuts the baseline off */
  /* js/foot.js fits this exactly; the value here is what it lands on, so the
     word is the right size before the script runs and if it never does. */
  font-size:17.24vw;
  line-height:.78;
  font-weight:800;
  letter-spacing:-.022em;
  white-space:nowrap;
  color:var(--foot-word);
  user-select:none;
  /* Its line box is far taller than the element and reaches back up over the
     row, where it was swallowing the clicks meant for the button and the
     links. It is decoration; nothing needs to hit it. */
  pointer-events:none;
}

@media (max-width:599px){
  /* Stacked: the button takes its own line, the small print and the links
     share the one under it. */
  .foot__row{
    grid-template-columns:1fr auto;
    grid-template-areas:'cta cta' 'legal soc';
    row-gap:clamp(14px, 4vw, 20px);
  }
  .foot__cta{ grid-area:cta; width:100%; }
  .foot__legal{ grid-area:legal; line-height:1.5; }
  .foot__soc{ grid-area:soc; }
  .foot__word{ margin-bottom:-.16em; }
}

/* ==========================================================================
   small screens
   The design set is desktop-only, so everything here is a proposal.
   ========================================================================== */

@media (max-width:767px){
  .masthead{ padding-block:14px 0; }
  .nav{ flex-wrap:wrap; height:auto; gap:12px; }
  .nav__brand{ height:22px; }
  .nav__toggle{
    display:grid; place-items:center;
    width:38px; height:38px; margin-left:auto;
    border-radius:8px; background:rgba(255,255,255,.07);
  }
  .nav__toggle svg{ width:19px; height:19px; stroke:var(--white); stroke-width:2; fill:none; }
  /* A stacked list rather than links wrapping onto extra rows, the bar keeps
     one height and the menu opens beneath it. */
  .nav__links{
    order:3; flex:1 0 100%;
    display:none; flex-direction:column; align-items:stretch;
    gap:2px; margin-top:6px;
    border-top:1px solid rgba(255,255,255,.09);
    padding-top:8px;
  }
  .nav__links.is-open{ display:flex; }
  .nav__links li{ display:flex; }   /* let the anchor fill the row */
  .nav__link{
    justify-content:flex-start;
    flex:1;
    height:44px;                 /* comfortable tap target */
    padding-inline:14px;
    border-radius:10px;
    font-size:16px;
  }
  .search{ order:4; flex:1 0 100%; width:auto; height:36px; }

  .hero{ padding-top:20px; }
  /* Stacked rather than full-viewport: a 100dvh card on a phone leaves no
     room for the copy underneath, so height comes from the contents. */
  .hero__card{
    height:auto; min-height:0;
    flex-direction:column; align-items:stretch;
  }
  /* Media keeps its own shape here, so nothing crops on a phone. */
  /* Keep the whole hero on one screen. The media prefers its own ratio but is
     allowed to give height back when the card is capped, so a short phone
     trims the photo rather than pushing the button below the fold. */
  .hero__card{ max-height:calc(100dvh - 132px); }
  .hero__media{
    position:relative; z-index:auto;
    aspect-ratio:var(--hero-ratio);
    flex:1 1 auto; min-height:96px;
  }
  /* The copy sits below the photo here rather than over it, so the portrait
     is left alone: no side blur, and only enough darkening to match the shell. */
  .hero__media img{
    object-fit:cover; object-position:center 22%;
    filter:brightness(.92) contrast(1.04) saturate(.96);
  }
  /* The photograph goes out of focus on its way down to the copy: the blur
     is masked so it starts at nothing halfway and is full strength at the
     seam, which reads as depth rather than as a grey panel over the picture. */
  .hero__media::before{
    display:block;
    backdrop-filter:blur(18px) saturate(.7) contrast(.92);
    -webkit-backdrop-filter:blur(18px) saturate(.7) contrast(.92);
    /* Many stops rather than two: a straight ramp bands on a gradient this
       long, and the picture has to arrive at the copy with no edge in it. */
    -webkit-mask-image:linear-gradient(0deg, #000 0%, rgba(0,0,0,.99) 8%, rgba(0,0,0,.94) 16%, rgba(0,0,0,.82) 24%, rgba(0,0,0,.64) 33%, rgba(0,0,0,.44) 42%, rgba(0,0,0,.26) 52%, rgba(0,0,0,.12) 62%, rgba(0,0,0,.04) 70%, transparent 78%);
    mask-image:linear-gradient(0deg, #000 0%, rgba(0,0,0,.99) 8%, rgba(0,0,0,.94) 16%, rgba(0,0,0,.82) 24%, rgba(0,0,0,.64) 33%, rgba(0,0,0,.44) 42%, rgba(0,0,0,.26) 52%, rgba(0,0,0,.12) 62%, rgba(0,0,0,.04) 70%, transparent 78%);
  }
  .hero__body{ flex:0 0 auto; }
  /* The copy's own ground, coming up to meet it on the same long ramp, so the
     photograph dissolves into the panel rather than stopping at it. */
  .hero__media::after{
    background:linear-gradient(0deg,
      var(--surface) 0%,
      rgba(29,26,26,.97) 7%,
      rgba(29,26,26,.88) 14%,
      rgba(29,26,26,.72) 22%,
      rgba(29,26,26,.52) 31%,
      rgba(29,26,26,.34) 40%,
      rgba(29,26,26,.19) 50%,
      rgba(29,26,26,.08) 61%,
      rgba(29,26,26,.02) 70%,
      transparent 80%);
  }
  .hero__body{ padding:18px var(--hero-pad) 24px; background:var(--surface); max-width:none; }
  .btn-play{ height:40px; }

  /* The tap-to-open panel is styled by .is-centred, not by this breakpoint;
     see the .peek block above. A hover-less tablet is wider than 767px. */

  .sheet{ padding:12px 10px 32px; }
  .sheet__panel{ --sheet-px:18px; --sheet-pt:20px; --sheet-pb:28px; }
  /* Stacked, the lead's two cells would drop Genres/Cast between the project
     name and its description. Dissolve the grid so the body can order them:
     name, description, then the meta. */
  .sheet__body{ display:flex; flex-direction:column; }
  .sheet__lead{ display:contents; }
  .sheet__lead > :first-child{ order:1; }
  .sheet__synopsis{ order:2; margin-top:10px; }
  .sheet__meta{ order:3; margin-top:16px; }
  .sheet__detail{ order:4; }
  .sheet__gallery{ grid-template-columns:1fr; gap:18px; }
}

@media (max-width:1023px) and (min-width:768px){
  .sheet__lead{ grid-template-columns:1fr; gap:22px; }
  .sheet__panel{ --sheet-px:28px; --sheet-pt:26px; --sheet-pb:36px; }
}

@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
}
