/* ==========================================================================
   dept. of — homepage
   Location: ~/Documents/deptof-site/homepage.html

   TWO MECHANISMS THAT SOLVE THE WIX PROBLEMS

   A. FLUID TYPE. Every size is clamp(min, Xvw, max). The middle value is a
      percentage of window width, which is how galleries already behave, so
      text rescales with the window instead of clipping. Prose is separately
      capped in `ch` so LINE LENGTH stays readable as the type grows.

   B. THE FIRST SCREEN ALWAYS FITS. .hero-screen has a DEFINITE height of
      100dvh (not min-height — percentage heights inside only resolve
      against a definite parent) and is a flex column: header, then images
      with flex:1 1 0 and min-height:0 so they absorb the leftover, then
      the display line. The lockup is always on screen when you land.
   ========================================================================== */

/* ---------------------------------------------------------------------
   COLOUR TOKENS — light only, by choice.
   There is no dark mode and no theme switching: the work is colour-critical
   and the white ground is part of how it reads. Named tokens stay because
   they keep the palette in one place, not because a second theme is coming.
   --------------------------------------------------------------------- */
:root{
  color-scheme: light;   /* light only — without this a dark-mode OS still
                            darkens scrollbars and form controls */

  --ink:#141414;
  --muted:#6E6E6E;
  --rule:#E3E3E3;
  --paper:#FFFFFF;
  --accent:#EC0F9C;

  --frame:#F2F2F2;            /* placeholder behind a loading image */
  --deck-frame:#F4F3F0;
  --band:#EAEAE7;             /* the ground behind a banded section. The exact
                                midpoint of the warm #ECE8DF and the cool
                                #E9ECEE: neutral grey with three points of
                                warmth, so it reads as paper, not as a tint
                                in either direction. */
  --scrim:rgba(255,255,255,.96);   /* work-tile hover ground */
  --scrim-ink:#141414;
  --cta-ground:#141414;
  --cta-letters:#FFFFFF;
  --bar:rgba(255,255,255,.60);     /* glass header */
  --bar-solid:rgba(255,255,255,.94);
  --bar-edge:rgba(20,20,20,.06);
  --bar-gloss:rgba(255,255,255,.62);   /* the lit top edge */
  --bar-shadow:rgba(20,20,20,.10);
  --swatch-edge:rgba(0,0,0,.10);

  --gutter:clamp(20px, 3.4vw, 72px);   /* the one margin, everywhere */
  --gap:clamp(10px, 1.4vw, 24px);      /* gap between images */
  --header-h:clamp(88px, 7vw, 132px);
  /* ONE vertical rhythm for every homepage section, top and bottom, so the
     air between any two sections is always the same two pads stacked. */
  --sec-pad:clamp(30px, min(3.4vw, 4.2dvh), 72px);
  /* One screen of page, below the fixed bar. Sections marked .fit are
     exactly this tall, so a section is never half-visible.        */
  --screen:calc(100dvh - var(--header-h));
  --sans:Archivo,"Helvetica Neue",Helvetica,Arial,sans-serif;

  /* ------------------------------------------------------------------
     THE TYPE SCALE. Seven steps and a micro label, defined here and
     nowhere else. Before this the site carried 32 distinct sizes across
     six pages, most of them accidental: nine different sizes doing body
     work between 19.4 and 22.9px, four title sizes within 5px of each
     other, and a pager whose two halves were 35.6 and 36.1. Differences
     that small don't read as intent, they read as noise.

     Every step is fluid on width, mid-values shown at a 1508px window.
     A role picks a step. If something needs a size that isn't here, the
     answer is almost always that it should use the nearest step.       */
  --t-display:clamp(34px, 3.3vw + 15px, 92px);    /* 65 — page titles, the lockup */
  --t-head:clamp(26px, 1.7vw + 16px, 50px);       /* 42 — section openers */
  --t-sub:clamp(20px, 1.5vw + 12px, 38px);        /* 35 — tile and pager titles */
  --t-read:clamp(19px, 0.65vw + 15px, 29px);      /* 25 — sustained reading */
  --t-ui:clamp(16px, 0.72vw + 12px, 28px);        /* 23 — nav, buttons, product names */
  --t-body:clamp(16px, 0.5vw + 13.4px, 23px);     /* 21 — body, captions, facts, prices */
  --t-label:clamp(12px, 0.3vw + 11.5px, 18px);    /* 16 — uppercase tracked labels */
  --t-micro:clamp(11px, 0.2vw + 9.5px, 14px);     /* 12.5 — swatch and placeholder text */
}


*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--sans);
  font-size:var(--t-body);
  line-height:1.55; -webkit-font-smoothing:antialiased;
}
img{display:block; max-width:100%}
a{color:var(--ink)}
a:hover{color:var(--accent)}
/* No max-width: the page runs edge to edge at every window size, with only
   the gutter holding content off the sides. Line length is protected
   separately — every block of prose is capped in `ch`, not by the
   container — so widening the window grows the artwork, not the text. */
.wrap{padding-inline:var(--gutter)}
/* A BANDED SECTION. The tint goes on the .wrap, not the section, because the
   wrap already runs the full width of the page — so the band bleeds edge to
   edge while the content inside it stays on the same gutter as everywhere
   else. No negative margins, no stacking-context games.
   The product frames stay WHITE on purpose: white cards on a tinted ground
   is what separates the shop, and the shots' own white surrounds merge into
   the card instead of fighting it. */
.band{
  background:var(--band);
  /* Secondary text needs a touch more weight on a tinted ground: #6E6E6E on
     #ECE8DF measures 4.17:1, under the 4.5:1 floor for text this size.
     #5F5F5F takes it to 5.2:1 and still reads as the quiet line. */
  --muted:#5F5F5F;
}
/* a banded section carries a little more air, so the tint has room to read
   as a ground rather than a stripe. `.home-section.fit` sets the same
   property later in this file at equal specificity, so this rule names the
   class too — otherwise it silently loses the cascade. */
.band > .home-section,
.band > .home-section.fit,
.band > .cs-section{padding-block:calc(var(--sec-pad) * 1.3)}

/* ---------------- header ----------------
   Sticky, and it fades out as you scroll down, back in as you scroll up.
   Sits OUTSIDE .hero-screen so stickiness isn't trapped inside it; the
   hero then takes the remaining 100dvh minus the header.              */
/* Glass bar, always present. Content scrolls beneath it, so it needs
   enough tint to keep the nav legible over saturated artwork while still
   letting colour through. Same material language as the hero arrows.  */
.site-header-outer{
  position:sticky; top:0; z-index:20;
  background:var(--bar);
  -webkit-backdrop-filter:blur(16px) saturate(165%);
  backdrop-filter:blur(16px) saturate(165%);
  border-bottom:1px solid var(--bar-edge);
  /* the lit top edge plus a soft cast shadow is what reads as thickness —
     the same pair that gives the hero arrows their dimension */
  box-shadow:0 6px 26px var(--bar-shadow),
             inset 0 1px 0 var(--bar-gloss);
}
/* Tucks away as you scroll down, comes back the moment you scroll up, so
   the artwork gets the whole window while you're moving through it. It
   SLIDES rather than fades: a glass bar at half opacity reads as a smear
   over saturated work, while a slide leaves the material intact the whole
   way out. Hidden by transform only — the bar keeps its space in the
   layout, so --header-h and every section height stay put.            */
.site-header-outer{
  transition:transform .34s cubic-bezier(.2,.7,.2,1);
  will-change:transform;
}
.site-header-outer.is-tucked{
  transform:translateY(-101%);   /* 101% takes the hairline and shadow too */
}
@media (prefers-reduced-motion: reduce){
  .site-header-outer{transition:none}
}

/* where the blur isn't supported, go denser rather than see-through */
@supports not (backdrop-filter: blur(4px)){
  .site-header-outer{background:var(--bar-solid)}
}
.site-header{
  display:flex; align-items:center; justify-content:space-between; gap:20px;
  height:var(--header-h);
}
.logo{text-decoration:none; color:var(--ink)}
.logo img{width:clamp(52px,4.6vw,98px); height:auto}
.logo .wordmark{display:none}
/* nav and cart share one row and one type setting; the menu button only
   exists on phones */
.hdr-right,.nav{
  display:flex; align-items:center; gap:clamp(14px,2vw,34px);
  font-family:var(--sans); font-weight:600;
  font-size:var(--t-ui);
}
.hdr-right a{text-decoration:none}
.menu-btn{display:none}

/* ---- phones: logo (wordmark on the homepage) left, cart + menu right ----
   On the homepage the logo gives way to the words "Dept. of", set like the
   lockup and at the same size as "A creative playground" under the hero,
   so the two read as one statement split across the screen. The three links move
   into a panel that drops from the bar.                                */
@media (max-width:640px){
  :root{--header-h:64px; --lockup-m:calc((100vw - 2 * var(--gutter)) / 13.4)}
  /* only the homepage swaps the logo for the wordmark; elsewhere the page
     title is the headline, and a second set of big words above it clashed */
  .logo img{width:44px}
  .is-home .logo img{display:none}
  .is-home .logo .wordmark{
    display:block; font-weight:800; text-transform:uppercase;
    letter-spacing:-0.03em; line-height:1; font-size:var(--lockup-m);
  }
  .hdr-right{gap:14px}
  .menu-btn{
    display:grid; place-content:center; gap:6px;
    width:44px; height:44px; margin-right:-10px;   /* full tap target, lines on the gutter */
    padding:0; border:0; background:none; color:var(--ink); cursor:pointer;
  }
  .menu-btn span{
    display:block; width:22px; height:2px; background:currentColor; border-radius:1px;
    transition:transform .28s cubic-bezier(.2,.7,.2,1);
  }
  .menu-btn[aria-expanded="true"] span:first-child{transform:translateY(4px) rotate(45deg)}
  .menu-btn[aria-expanded="true"] span:last-child{transform:translateY(-4px) rotate(-45deg)}
  .nav{
    position:absolute; left:0; right:0; top:100%;
    flex-direction:column; align-items:stretch; gap:0;
    padding:6px var(--gutter) 18px;
    background:var(--paper);           /* solid: artwork behind it made the links hard to read */
    border-bottom:1px solid var(--bar-edge);
    box-shadow:0 14px 26px var(--bar-shadow);
    font-size:22px;
    opacity:0; visibility:hidden; transform:translateY(-8px);
    transition:opacity .22s ease, transform .28s cubic-bezier(.2,.7,.2,1), visibility 0s .28s;
  }
  .nav.is-open{opacity:1; visibility:visible; transform:none; transition-delay:0s}
  .nav a{padding:12px 0; border-top:1px solid var(--bar-edge)}
  .nav a:first-child{border-top:0}
}
@media (max-width:640px) and (prefers-reduced-motion:reduce){
  .nav,.menu-btn span{transition:none}
}
.nav a.active{color:var(--accent)}
.cart{display:inline-flex; align-items:center; gap:4px}
.cart svg{width:1.2em; height:1.2em}
.cart b{font-size:.62em}

/* ---------------- first screen ---------------- */
.hero-screen{
  height:calc(100dvh - var(--header-h)); min-height:440px;
  display:flex; flex-direction:column;
  padding-bottom:clamp(18px,2.4vw,40px);
}
.hero-media{
  position:relative;        /* the arrows anchor to this band */
  flex:1 1 0; min-height:0;
  overflow:hidden;          /* the track is wider than the page on purpose */
  touch-action:pan-y;       /* sideways swipes drive the band (site.js) */
  overscroll-behavior-x:contain;
}

/* Each arrow lives in a hover zone down one side of the band. The arrow
   is invisible until the cursor enters that side — or the button takes
   keyboard focus — so nothing sits on top of the artwork at rest.
   Holding an arrow accelerates the marquee; releasing lets it coast back. */
.hero-zone{
  position:absolute; top:0; bottom:0; z-index:2;
  width:clamp(88px, 20%, 240px);
  display:flex; align-items:center;
}
.hero-zone.left { left:0;  justify-content:flex-start; padding-left:clamp(10px,1.2vw,20px) }
.hero-zone.right{ right:0; justify-content:flex-end;   padding-right:clamp(10px,1.2vw,20px) }

.hero-arrow{
  /* Glass: a low-opacity tint plus a backdrop blur, a bright top edge and
     a soft drop shadow — so it reads as a lens sitting over the artwork
     rather than a solid chip punched out of it. The colour underneath
     shows through, which is the point on work this saturated.          */
  display:flex; align-items:center; justify-content:center;
  width:clamp(48px,4.2vw,66px); aspect-ratio:1;
  padding:0; border-radius:50%;
  border:1px solid rgba(255,255,255,.55);
  background:rgba(255,255,255,.21);
  -webkit-backdrop-filter:blur(14px) saturate(160%);
  backdrop-filter:blur(14px) saturate(160%);
  box-shadow:0 4px 18px rgba(20,20,20,.16),
             inset 0 1px 0 rgba(255,255,255,.55);
  color:#141414;   /* the backdrop is artwork, not the page */
  cursor:pointer;
  transition:background .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.hero-arrow:hover{
  background:rgba(255,255,255,.30);
  border-color:rgba(255,255,255,.75);
  box-shadow:0 6px 22px rgba(20,20,20,.22),
             inset 0 1px 0 rgba(255,255,255,.7);
}
.hero-arrow:active{
  background:rgba(255,255,255,.42);
  box-shadow:0 2px 10px rgba(20,20,20,.18),
             inset 0 1px 0 rgba(255,255,255,.5);
}
.hero-arrow:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
.hero-arrow svg{
  width:52%; height:52%; display:block;
  /* the chevron needs its own lift to stay legible over light artwork */
  filter:drop-shadow(0 1px 1px rgba(255,255,255,.6));
}

/* where the backdrop filter isn't supported, fall back to a denser tint
   so the button never becomes an unreadable transparent ring */
@supports not (backdrop-filter: blur(4px)){
  .hero-arrow{background:rgba(255,255,255,.82)}
}

@media (prefers-reduced-motion:reduce){ .hero-arrow{transition:none} }
/* The track holds the image set TWICE. Sliding it left by exactly one set
   (plus the gap that follows it) lands on an identical frame, so the loop
   is seamless with no jump.                                              */
.hero-track{
  display:flex; gap:var(--gap); width:max-content; height:100%;
  will-change:transform;
}
.hero-set{display:flex; gap:var(--gap); height:100%}
.hero-set img,
.hero-set video{
  /* TRUE ASPECT RATIO: height fills the band, width is derived from the
     image's own proportions. Nothing is cropped or squashed — each piece
     simply takes the width it deserves.                                  */
  height:100%; width:auto; max-width:none; display:block;
  background:var(--frame);
}

/* the lockup — 2x2 grid so left stays left and right stays right */
.display{
  margin:clamp(16px,2vw,34px) 0 0;
  display:grid; grid-template-columns:auto auto;
  justify-content:space-between; align-items:baseline;
  column-gap:clamp(40px,7vw,180px);
  row-gap:clamp(6px,1.1vw,18px);
}
.display .hl{
  font-weight:800; text-transform:uppercase; letter-spacing:-0.03em;
  line-height:1.02; font-size:var(--t-display);
}
/* ---- the lockup lifts, one half at a time ----
   Each half moves as a single line: "Dept. of" sets itself, then most of a
   second later the other half answers it. Word-by-word was livelier but it
   made the lockup read as five things rather than two statements.

   Not a blur here. At 65px a blur reads as a rendering fault before it reads
   as a focus pull.

   Transform and opacity only, no layout properties: the two halves are
   baseline-aligned across the page and anything that changed their metrics
   would drag that line around while they settle. */
.reveal-on .display .reveal-now{
  opacity:0; transform:translate3d(0, 0.34em, 0);
  transition:opacity 1.05s cubic-bezier(.22,.61,.36,1),
             transform 1.35s cubic-bezier(.16, 1, .3, 1);
  transition-delay:calc(var(--s, 0) * 700ms);
}
.reveal-on .display .reveal-now.is-in{opacity:1; transform:none}
/* the answering half travels in from the right, toward the side of the page it
   sits on, instead of lifting. Same easing and timing as the lift; the hero
   clips horizontal overflow at the screen edge, so the offset never opens a
   scrollbar and the line enters from off-screen, not from behind the gutter. */
.reveal-on .display .r.reveal-now{transform:translate3d(1.2em, 0, 0)}
.reveal-on .display .r.reveal-now.is-in{transform:none}
.reveal-on .hero-screen{overflow-x:clip}
.display .r{text-align:right}
/* Phones: "Dept. of" moves up into the bar (the wordmark), and "A creative
   playground" runs as one line under the hero, sized to fill the width.
   The first half stays in the h1 for screen readers, just not on screen. */
@media (max-width:640px){
  .display{display:block; margin-top:32px}
  .display .hl:not(.r){position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap}
  .display .r{display:block; text-align:left; white-space:nowrap; font-size:var(--lockup-m)}
}

/* ---------------- section openers ----------------
   One treatment everywhere: a headline, an optional note on the right,
   and a rule beneath. Used by the homepage's work and about sections,
   the shop, and every case-study section, so a reader learns the shape
   once.                                                              */
.sec-head,
.shop-head{
  display:flex; align-items:baseline; justify-content:space-between;
  gap:20px; flex-wrap:wrap;
  padding-bottom:clamp(12px,1.4vw,18px);
  border-bottom:1px solid var(--ink);
  margin-bottom:clamp(18px, min(2.8vw, 2.8dvh), 44px);
}
.sec-head h2,
.shop-head h2,
.cs-section > h2{
  margin:0; font-weight:700; letter-spacing:-0.025em; line-height:1.1;
  font-size:var(--t-head); text-transform:none;
}
.cs-section > h2{
  padding-bottom:clamp(12px,1.4vw,18px);
  border-bottom:1px solid var(--ink);
}
.sec-head .meta,
.shop-head .count{
  margin:0; color:var(--muted);
  font-size:var(--t-label);
  letter-spacing:.12em; text-transform:uppercase;
}

/* homepage sections share one vertical rhythm */
.home-section{padding-block:var(--sec-pad)}
/* the work grid has no headline of its own — it reads as a continuation of
   the lockup, so it sits closer than a titled section would */
#work{padding-top:clamp(24px, min(2.6vw, 3.2dvh), 56px)}

/* ---------------- sections that fit one screen ----------------
   Same mechanism as the hero: a DEFINITE height (percentage heights inside
   only resolve against a definite parent), a flex column, and one band in
   the middle that carries flex-shrink so it absorbs whatever is left after
   the fixed parts. Scroll to a .fit section at any window shape and the
   whole thing is on screen — headline, media, writing, call to action.

   Type inside these sections is sized off min(vw, dvh) rather than vw
   alone: a short wide window has to shrink the writing, not clip it.   */
.home-section.fit{
  /* max-height, NOT height: a section shorter than the screen keeps its own
     natural height and the page's spacing rhythm holds. Only a section that
     would run past the screen gets clamped, and then the flex line below
     shrinks to fit. A fixed height left a hole under the shop CTA.      */
  max-height:var(--screen);
  display:flex; flex-direction:column;
  padding-block:var(--sec-pad);
  /* land the section's top edge just under the glass bar, so "jump to shop"
     puts the whole screen-height section in view */
  scroll-margin-top:var(--header-h);
}
.home-section.fit > .sec-head{flex:none}
/* the band: shrinks, never grows past its natural size.
   grid-template-rows:minmax(0,1fr) is the part that makes shrinking REAL —
   an implicit grid row sizes to its content and refuses to give way, so the
   pictures spilled out of the section instead of getting smaller.        */
.home-section.fit > .shop-teaser,
.home-section.fit > .studio{flex:0 1 auto; min-height:0; grid-template-rows:minmax(0,1fr)}
.home-section.fit .product{min-height:0}
/* aspect-ratio gives the frame its natural square height; flex-shrink lets
   that height compress in a short window while the width stays put, and the
   contained image simply gets smaller inside it. */
.home-section.fit .product .frame{flex:0 1 auto; min-height:0}

/* Phones stack the studio and put the shop teaser in one column, so a screen
   can no longer hold a whole section — there, sections go back to flowing at
   their natural height rather than squeezing pictures to nothing. */
@media (max-width:820px){
  .home-section.fit{max-height:none}
}
/* Phones: the hero band no longer fills the screen. A full-height band on a
   tall phone turned into a portrait crop, so it takes a fixed landscape
   height (about 4:3 of the width) and landscape work reads at its size. */
@media (max-width:640px){
  .hero-screen{height:auto; min-height:0; padding-bottom:4px}
  .hero-media{flex:none; height:68vw}
}

/* ---------------- work grid ---------------- */
.grid{
  /* ONE gap, both directions — a row gap wider than the column gap made the
     vertical space between projects read as inconsistent. */
  display:grid; gap:var(--gap);
  grid-template-columns:repeat(3, minmax(0,1fr));
  padding-block:0;
}
.tile{
  position:relative; display:block; overflow:hidden; background:var(--frame);
  aspect-ratio:var(--tile-ratio, 1/1);
}
/* name appears on hover, before anyone has to click */
.tile .cap{
  position:absolute; inset:0;
  display:flex; flex-direction:column;
  justify-content:center; align-items:center; text-align:center;
  gap:clamp(3px,0.4vw,8px);
  padding:clamp(14px,1.6vw,26px);
  background:var(--scrim);
  opacity:0; transition:opacity .22s ease;
}
.tile:hover .cap,
.tile:focus-visible .cap{opacity:1}
.tile:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
.tile .cap h3{
  margin:0; font-weight:600; line-height:1.2; letter-spacing:-0.015em;
  font-size:var(--t-sub);
  color:var(--scrim-ink);        /* stays black — the tile is a link, and the
                              link hover colour would otherwise tint it */
}
.tile .cap span{
  color:var(--muted); line-height:1.3;
  font-size:var(--t-body);
}
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
.tile img,.tile video{display:block; width:100%; height:100%; object-fit:cover;
  transition:transform .5s cubic-bezier(.2,.7,.2,1)}
.tile:hover img,.tile:hover video{transform:scale(1.04)}

/* Touch screens can't hover, so the name would never show. There the
   caption leaves the image and sits underneath it, always visible.     */
@media (hover:none){
  .grid{row-gap:calc(var(--gap) * 1.6)}
  .tile{aspect-ratio:auto; overflow:visible; background:none; text-decoration:none}
  .tile img,.tile video{aspect-ratio:var(--tile-ratio, 1/1); height:auto; background:var(--frame)}
  .tile:hover img,.tile:hover video{transform:none}
  .tile .cap{
    position:static; opacity:1; background:none;
    align-items:flex-start; justify-content:flex-start; text-align:left;
    gap:1px; padding:8px 0 0;
  }
  .tile .cap h3{font-size:15px; color:var(--ink)}
  .tile .cap span{font-size:13.5px}
}
/* phones: the grid stays 3 across, so the captions step down to fit */
@media (hover:none) and (max-width:560px){
  .tile .cap h3{font-size:12.5px; line-height:1.25}
  .tile .cap span{font-size:11.5px}
}

/* ---------------- about band ----------------
   Left is the STATEMENT, right is REFERENCE. Sizing them the same flattened
   the difference, so the lead now runs about 1.6x the facts, with tighter
   leading — large type needs less.                                         */
/* ---------------- the studio (about) ----------------
   Portrait on the left, everything written on the right. The facts had to
   be renamed off `.facts`: the project pages use that class for their
   Client/Discipline/Tools block, and its top rule was leaking in here.  */
.studio{
  display:grid;
  grid-template-columns:1fr 1fr;      /* equal halves */
  column-gap:clamp(32px,4vw,84px);
  row-gap:clamp(26px,3vw,44px);
  align-items:stretch;
  scroll-margin-top:clamp(16px,3vw,48px);
}
/* The portrait fills its half exactly. Its image is taken out of flow, so
   the row's height is set by the writing on the right and the photo crops
   to match — no dead space under it, and both columns end level.       */
.studio-portrait{margin:0; position:relative; overflow:hidden; min-height:100%}
.studio-portrait img{
  position:absolute; inset:0;
  width:100%; height:100%; object-fit:cover; object-position:center 28%;
}

.studio-copy{
  display:flex; flex-direction:column; justify-content:center;
  gap:clamp(14px, min(2.6vw, 2.6dvh), 44px);
  min-height:0;
}
/* min(vw, dvh): the lead tracks window WIDTH as before, but a short window
   caps it by HEIGHT so the section keeps fitting instead of running off the
   bottom. The two are near-identical at a normal 16:10 desktop window, so
   nothing shrank on the screens he already approved.                    */
.studio-copy .lead{
  margin:0; max-width:30ch;
  /* The height cap is affine, not a straight percentage: it is set to meet
     the width term exactly at a normal desktop window (so the approved
     42-43px lead is untouched there) and then fall away faster than the
     window does, which buys clearance in a short one. */
  /* EXCEPTION, documented: the only two sizes on the site that answer to
     window HEIGHT as well as width, because this section has to fit one
     screen. Each is its scale step capped by a dvh term, never a size
     invented on the spot. */
  font-size:min(var(--t-sub), 4.45dvh - 6.9px);
  line-height:1.22; letter-spacing:-0.022em; text-wrap:pretty;
}
.studio-facts{
  margin:0; display:flex; flex-direction:column;
  gap:min(1.05em, 1.5dvh);
  font-size:min(var(--t-body), 1.94dvh - 1.85px); line-height:1.45;
}
.studio-facts p{margin:0; max-width:56ch; text-wrap:pretty}
.studio-facts b{font-weight:700}
.studio-facts a{text-decoration:underline; text-underline-offset:2px}

@media (max-width:820px){
  .studio{grid-template-columns:1fr}
  .studio-portrait{position:static; min-height:0; max-width:460px}
  .studio-portrait img{position:static; height:auto; object-fit:contain}
}

/* ---------------- footer ---------------- */
.foot{
  display:flex; justify-content:space-between; align-items:center;
  gap:20px; flex-wrap:wrap;
  padding-block:clamp(34px,4vw,80px) clamp(28px,3.4vw,60px);
  font-size:var(--t-body); color:var(--muted);
}
.social{display:flex; gap:clamp(14px,1.4vw,22px); align-items:center; color:var(--ink)}
.social svg{width:clamp(24px,1.9vw,38px); height:auto}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .tile img,.tile video{transition:none}
  .site-header-outer{transition:none}
}

/* ==========================================================================
   PROJECT PAGE
   The container widens with the window; the TEXT MEASURE does not. Extra
   width goes to the artwork, never to the prose.
   ========================================================================== */

.project-top{padding-block:clamp(40px,5vw,84px) clamp(36px,4.5vw,76px)}

.project-head{margin-bottom:clamp(22px,2.6vw,38px)}
.project-eyebrow{
  margin:0; color:var(--muted);
  font-size:var(--t-label);
  letter-spacing:.1em; text-transform:uppercase;
}
.project-title{
  margin:clamp(8px,1vw,14px) 0 0; max-width:22ch;
  font-weight:700; line-height:1.08; letter-spacing:-0.03em;
  font-size:var(--t-display);
}
.project-desc{
  margin:0 0 clamp(30px,3.4vw,54px); max-width:60ch;
  font-size:var(--t-read); line-height:1.5;
  text-wrap:pretty;
}
.project-desc p{margin:0}
.project-desc p + p{margin-top:1em}

/* facts sit BELOW the description, labelled and quiet */
.facts{
  margin:0; padding-top:clamp(18px,2vw,28px); border-top:1px solid var(--rule);
  display:grid; grid-template-columns:repeat(auto-fit, minmax(min(100%,190px), 1fr));
  gap:clamp(16px,2vw,32px);
}
.facts div{display:flex; flex-direction:column; gap:5px}
.facts dt{
  font-size:var(--t-label); font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; color:var(--muted);
}
.facts dd{margin:0; font-size:var(--t-body)}

/* the artwork — this is where the width goes */
.project-media{display:flex; flex-direction:column; gap:var(--gap);
  padding-bottom:clamp(50px,6vw,100px)}
.project-media figure{margin:0}
/* JUSTIFIED ROWS. Each row is a flex line where every image's flex-grow
   is its own aspect ratio, so widths come out proportional to shape and
   the heights land identical. True proportions, nothing cropped, and the
   row has a flat top and bottom edge that meets both margins.          */
.project-media .row{display:flex; gap:var(--gap)}
.project-media .row img,
.project-media .row video{
  /* flex-grow must be >= 1 or a lone item can't fill its row: a single
     portrait with --r:0.99 grew to only 99% and stopped short of the
     margin. Scaling by 1000 keeps the ratios proportional between
     siblings while guaranteeing every grow factor exceeds 1.          */
  flex:calc(var(--r) * 1000) 1 0; min-width:0;
  display:block; width:auto; height:auto; max-width:none;
}
/* A pair of full-bleed grids (icons running to the image edge) melts into
   one grid at the standard gap: the page gap is narrower than the spacing
   inside each image. .row.apart opens it to about twice that spacing so the
   two sets read as two. Desktop only; stacked on a phone they're already apart. */
.project-media .row.apart{
  --apart:clamp(40px, 7vw, 128px);
  gap:var(--apart);
  /* the outer margins match the gap, so the pair sits centred and smaller,
     with even space on all three sides of each set */
  padding-inline:var(--apart);
}
/* An image that links out (the published article): the link takes the
   image's place in the row, so it carries --r and the flex sizing, and the
   image simply fills it. */
.project-media .row > .link-img{flex:calc(var(--r) * 1000) 1 0; min-width:0; display:block}
.project-media .row > .link-img img{width:100%; height:auto; display:block; transition:opacity .2s ease}
.project-media .row > .link-img:hover img{opacity:.88}
.project-media .row > .link-img:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
@media (max-width:760px){
  .project-media .row{flex-direction:column}
  .project-media .row > .link-img{flex:none; width:100%}
  .project-media .row.apart{gap:var(--gap); padding-inline:0}
  .project-media .row img,
  .project-media .row video{flex:none; width:100%}
}

/* Prev / next pager. Two halves of one band so a reader can go either
   way; the previous side is right-aligned so each label sits against the
   edge it points toward.                                                */
.pager{
  display:grid; grid-template-columns:1fr 1fr; gap:clamp(20px,3vw,64px);
  border-top:1px solid var(--ink);
  padding-block:clamp(28px,3.4vw,52px);
}
/* after a banded section the navigation needs real air, or its rule reads as
   the band's own edge rather than the start of something else */
.band + .wrap .pager{margin-top:clamp(48px,5.5vw,96px)}
.pager a{
  display:flex; flex-direction:column; gap:6px; text-decoration:none;
  min-width:0;
}
.pager .prev{align-items:flex-start; text-align:left}
.pager .next{align-items:flex-end; text-align:right}
.pager .eyebrow{
  margin:0; color:var(--muted);
  font-size:var(--t-label);
  letter-spacing:.12em; text-transform:uppercase;
}
.pager .title{
  font-weight:600; letter-spacing:-0.02em; line-height:1.15;
  font-size:var(--t-sub); text-wrap:balance;
}
/* phones keep the two halves side by side, like desktop, at a smaller size;
   long titles wrap within their own half */
@media (max-width:620px){
  .pager{gap:20px}
  .pager a{gap:4px}
  .pager .eyebrow{font-size:11px; letter-spacing:.1em}
  .pager .title{font-size:15px; line-height:1.25}
}

/* kept for the case study's single "back to the project" link */
/* No rule above this one. It is an invitation to keep reading THIS project,
   not a new section, and a rule here plus the pager's rule right below made
   the page look like it ended twice. The pager keeps its rule, so the only
   line down here is the one that genuinely separates project from navigation. */
.next-project{
  display:flex; flex-direction:column; gap:6px; text-decoration:none;
  /* Pulls back most of .project-media's bottom padding so this sits close
     under the last image. It is an invitation to keep reading THIS project,
     and a full section's worth of air above it read as a new section. The
     negative margin is expressed as the difference between the gap wanted
     and the padding above, so both stay fluid and it never has to be
     re-tuned when the media padding changes. */
  margin-top:calc(clamp(24px,2.6vw,40px) - clamp(50px,6vw,100px));
  /* and a full section's worth of air BELOW it: close to the project it
     belongs to, clearly apart from the navigation that follows. */
  padding-block:0 clamp(64px,7vw,120px);
}
.next-project .eyebrow{
  margin:0; color:var(--muted);
  font-size:var(--t-label);
  letter-spacing:.12em; text-transform:uppercase;
}
.next-project .next-title{
  font-weight:600; letter-spacing:-0.02em; line-height:1.15;
  font-size:var(--t-sub);
}

/* ==========================================================================
   PROJECT TOP — layout variants (for comparison on project-options.html)
   ========================================================================== */

/* B — split: title and facts on the left, the reading on the right */
.top-b{
  display:grid; grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
  column-gap:clamp(32px,5vw,110px); align-items:start;
}
.top-b .project-head{margin-bottom:clamp(22px,2.6vw,36px)}
.top-b .project-title{font-size:var(--t-head); max-width:16ch}
.top-b .facts{
  grid-template-columns:1fr; gap:clamp(14px,1.6vw,20px);
  border-top:1px solid var(--rule); padding-top:clamp(18px,2vw,26px);
}
.top-b .project-desc{margin:0; max-width:58ch}
@media (max-width:860px){ .top-b{grid-template-columns:1fr; row-gap:clamp(26px,4vw,40px)} }

/* C — the title runs full width, then reading and facts share a band */
.top-c .project-title{
  max-width:none; font-size:var(--t-display); line-height:1.05;
}
.top-c .c-band{
  display:grid; grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
  gap:clamp(30px,5vw,96px); align-items:start;
  padding-top:clamp(26px,3vw,44px); border-top:1px solid var(--ink);
}
.top-c .project-desc{margin:0; max-width:58ch}

/* THE BAND IS A FIXED SIZE ON EVERY PROJECT: about six lines of reading, the
   same height as the Client/Discipline/Tools list beside it. That is a COPY
   rule, not a CSS one — no layout trick can make a 2-line and a 25-line
   description end level while the type size stays put, and the type size
   staying put is the thing that makes fifteen pages feel like one site.
   Target 65-75 words, one paragraph. Anything further to say goes in
   .project-note, below the first row of images.
   min-height holds the floor so a slightly short one still can't collapse
   the band under the facts list. */
.top-c .project-desc{min-height:calc(6 * 1.5em)}

/* The rest of the reading, once the band has had its six lines.

   IT NEEDS AN ANCHOR. Left as a bare paragraph between two full-bleed rows
   it floated: three lines alone in a 1390px field with equal air above and
   below, belonging to neither the picture before it nor the one after. A
   rule across the full width gives it a top edge to hang from, the same move
   the band and the section openers make, so it reads as a deliberate break
   in the sequence rather than stray text. No label: a word like "Note" over
   three lines of writing announces something the writing does not need.

   The rule is 1px ink to match the band's rule on these pages, not the 2px
   the homepage section openers use. */
.project-note{
  border-top:1px solid var(--ink);
  padding-top:clamp(18px, 1.9vw, 30px);
  margin-block:clamp(40px,4.6vw,84px);
  font-size:var(--t-read); line-height:1.5;
  text-wrap:pretty;
}
/* ONE COLUMN, RUNNING THE FULL WIDTH — and therefore bigger.
   The two are a package, not a preference. Line length is measured in
   CHARACTERS, not pixels: this block spans 1390px, and at the reading size
   (25px) that is about 110 characters a line, roughly twice a comfortable
   measure, and the eye loses its place coming back to the left edge. Moving
   up to the SECTION-HEAD step buys that width back by making each character
   wider. Measured across the whole page: at the reading size this span runs
   131 characters, at the subhead step 93, at the head step 77 (82 at 1821px,
   72 at 1280). So the writing can cross the page precisely because it got
   larger, and 77 is only defensible because this block is two or three lines
   — a long note at this width would be unreadable, which is why the
   multi-paragraph ones stay in two columns at reading size.
   Larger type needs tighter leading, hence 1.25 against the body's 1.5. */
.note-body{max-width:none; font-size:var(--t-head); line-height:1.25; letter-spacing:-0.015em}
.note-body > *{max-width:none}
/* scoped to the body, not the container: a rule written for .project-note
   catches whatever child gets added to it later */
.note-body p{margin:0}
.note-body a{text-decoration-thickness:.06em; text-underline-offset:.14em}
.note-body p + p{margin-top:1em}

/* TWO OR MORE PARAGRAPHS RUN IN TWO COLUMNS. A single measure of text under
   a full-bleed row of images leaves the right half of the page empty and the
   writing reads as an afterthought; two columns give the block the same
   width as the pictures above it. Real multi-column, not a two-cell grid:
   the browser balances the height for us, and `break-inside:avoid` keeps
   each paragraph whole rather than splitting one across the gutter.
   Each column lands around 45 characters, which is still a good measure. */
/* A long note still splits into two columns, and at the subhead size each
   column would be barely 40 characters, so those stay at reading size. */
.project-note.split .note-body{
  display:block; font-size:var(--t-read); line-height:1.5; letter-spacing:0;
  columns:2; column-gap:clamp(30px,5vw,96px);
}
.project-note.split .note-body > p{max-width:none; break-inside:avoid}

@media (max-width:860px){
  /* a phone is narrow enough that the reading size already gives a good
     measure, and the subhead step would be shouting */
  .note-body{font-size:var(--t-read); line-height:1.5; letter-spacing:0}
  .project-note.split .note-body{columns:1}
}
.top-c .facts{border-top:0; padding-top:0; grid-template-columns:1fr; gap:clamp(14px,1.6vw,20px)}
@media (max-width:860px){ .top-c .c-band{grid-template-columns:1fr; gap:clamp(26px,4vw,38px)} }

/* comparison page chrome */
.marker{
  background:var(--ink); color:#fff; padding:22px var(--gutter);
  display:flex; gap:18px; align-items:baseline; flex-wrap:wrap;
}
.marker b{font-size:var(--t-body); font-weight:600}
.marker span{font-size:var(--t-label); color:#B9B9B9; max-width:60ch}
.demo{padding-bottom:clamp(40px,5vw,80px)}
.demo .project-top{padding-block:clamp(32px,4vw,64px) clamp(28px,3.4vw,52px)}

/* ---------- contact sheet (retouching) ----------
   A services page with 25 stills is a sample book, not a case study.
   Square crops in a tight grid, click to open the full frame.          */
.project-media.contact-sheet{
  display:grid; gap:var(--gap);
  grid-template-columns:repeat(auto-fill, minmax(min(100%, 190px), 1fr));
}
.contact-sheet .shot{display:block; overflow:hidden; aspect-ratio:1/1; background:var(--frame)}
.contact-sheet .shot img{
  width:100%; height:100%; object-fit:cover; max-width:none;
  transition:transform .5s cubic-bezier(.2,.7,.2,1);
}
.contact-sheet .shot:hover img{transform:scale(1.04)}
.contact-sheet .shot:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

/* ==========================================================================
   SHOP
   Products are a grid of equal cards: image on its own ground, then name,
   description, price. Sold-out pieces stay on the page — they're part of
   the body of work — but read as unavailable rather than for sale.
   ========================================================================== */

.shop-intro{padding-block:clamp(36px,4.5vw,76px) clamp(24px,3vw,44px)}
.shop-intro h1{
  margin:0; max-width:18ch; font-weight:700; line-height:1.08;
  letter-spacing:-0.03em; font-size:var(--t-display);
}
.shop-intro p{
  margin:clamp(14px,1.8vw,24px) 0 0; max-width:54ch;
  font-size:var(--t-read); line-height:1.5; text-wrap:pretty;
}

.shop-section{padding-bottom:clamp(44px,5vw,88px)}


.shop-grid{
  /* Three across, fixed, so paintings and wearables share one even grid at
     every window width. A phone gets one column. */
  display:grid; gap:clamp(26px,3vw,52px) var(--gap);
  grid-template-columns:repeat(3, minmax(0, 1fr));
}
@media (max-width:760px){
  /* two across on a phone: halves the scroll, and the paintings are bold
     enough to read at half width */
  .shop-grid{grid-template-columns:repeat(2, minmax(0, 1fr)); gap:28px 14px}
  .shop-grid .product .name{font-size:15px}
  .shop-grid .product .spec,.shop-grid .product .price{font-size:13.5px}
  /* size on its own line, never split mid-measurement */
  .shop-grid .spec .sep{display:none}
  .shop-grid .spec .dim{display:block}
}
.product{display:flex; flex-direction:column;
  gap:clamp(10px, 0.5vw + 6px, 18px);        /* the writing sits ON the picture,
                                                not adrift under it */
  text-decoration:none}
.product .frame{
  /* The product shots already carry their own white surround, so a tinted
     card ground read as a frame inside a frame. White lets the photo and
     the card become one field; the grid gap does the separating.

     SQUARE, NOT 4/5. The shots run 0.74 to 1.33, most of them square, so a
     4/5 box added a band of empty white above and below every landscape
     piece — which is what pushed the name away from the artwork.
     align-items:flex-end then rests every image on ONE baseline directly
     above its caption; whatever slack a shape leaves goes up, into the
     row, where its neighbours fill it.                                  */
  aspect-ratio:1/1; overflow:hidden; background:#FFFFFF;
  display:flex; align-items:flex-end; justify-content:center;
}
.product .frame img{
  max-width:100%; max-height:100%; width:auto; height:auto;
  object-fit:contain;
  transition:transform .5s cubic-bezier(.2,.7,.2,1);
}
.product:hover .frame img{transform:scale(1.04)}
.product:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
.product .meta{display:flex; flex-direction:column; gap:2px}
.product .name{font-weight:600; font-size:var(--t-ui); color:var(--ink)}
.product .spec{color:var(--muted); font-size:var(--t-body); line-height:1.35}
.product .spec .dim{white-space:nowrap}
.product .price{margin-top:.45em; font-weight:500; font-size:var(--t-body); color:var(--ink)}
.product.sold .frame img{opacity:.55}
.product.sold .price{color:var(--muted)}

.shop-note{
  border-top:1px solid var(--rule);
  padding-block:clamp(26px,3vw,48px) clamp(34px,4vw,64px);
  display:grid; gap:clamp(20px,3vw,64px);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  font-size:var(--t-body); color:var(--muted);
}
.shop-note p{margin:0; max-width:46ch; text-wrap:pretty}
.shop-note b{color:var(--ink); font-weight:600}
.shop-note a{color:var(--accent)}

/* ==========================================================================
   CASE STUDY (deep page — unlisted, reached by link, not in the nav)
   Deliberately typeset differently from a project page: it argues rather
   than presents, so it carries section rules, captions and discard rows.
   ========================================================================== */

.cs-intro{padding-block:clamp(36px,4.5vw,76px) clamp(22px,2.6vw,40px)}
.cs-intro h1{
  margin:0; font-weight:700; line-height:1.06; letter-spacing:-0.03em;
  font-size:var(--t-display);
}
.cs-intro p{
  margin:clamp(12px,1.6vw,22px) 0 0; max-width:56ch;
  font-size:var(--t-read); line-height:1.5; text-wrap:pretty;
}
.cs-hero{padding-bottom:clamp(44px,5vw,88px)}
.cs-hero img{display:block; width:100%; height:auto}

/* a section opens with a ruled heading and one line of argument */
.cs-section{padding-bottom:clamp(44px,5.5vw,96px)}
/* Same treatment as a project page's .note-body: full width at the head
   step rather than a narrow column of reading size. These are two-line
   statements introducing what follows, not sustained reading, and at this
   width they only work because they are short. */
.cs-intro p,
.cs-section > .lede{
  max-width:none; font-size:var(--t-head); line-height:1.25;
  letter-spacing:-0.015em; text-wrap:pretty;
}
.cs-section > .lede{margin:clamp(14px,1.6vw,22px) 0 clamp(30px,3.4vw,54px)}

/* the hero specimen of a section — the thing that won */
.cs-final{display:flex; flex-direction:column; align-items:center; gap:clamp(16px,2vw,28px)}
/* Both finals are sized by ONE rule, not one in CSS and one inline. They are
   different shapes — a wide lockup and a round mark — so matching their
   WIDTHS would leave the mark towering over the wordmark. A shared max-height
   is what actually makes them read as a pair: the wordmark is limited by its
   width, the mark by its height, and neither dominates. */
.cs-final img{
  display:block; width:auto; height:auto;
  max-width:min(100%, 620px);
  max-height:clamp(240px, 26vw, 380px);
}
.cs-final figcaption{
  max-width:52ch; text-align:center; color:var(--ink);
  font-size:var(--t-body); line-height:1.5; text-wrap:pretty;
}

/* discards — what lost, and why */
.cs-discards{
  display:grid; gap:clamp(24px,3vw,44px) var(--gap);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  margin-top:clamp(40px,5vw,84px);
}
.cs-discards figure{margin:0; display:flex; flex-direction:column; gap:clamp(12px,1.4vw,20px)}
.cs-discards .slot{
  /* A fixed slot height with the art sitting on the bottom edge gives the
     three wordmarks a shared baseline, so their captions start level
     instead of stepping down with each image's own height.             */
  display:flex; align-items:flex-end; justify-content:flex-start;
  height:clamp(84px, 9vw, 130px);
}
.cs-discards .slot img{max-height:100%; width:auto; max-width:100%; object-fit:contain; object-position:left bottom}
.cs-discards img{display:block; width:100%; height:auto}
.cs-discards figcaption{
  font-size:var(--t-body); line-height:1.45;
  color:var(--muted); text-wrap:pretty;
}
.cs-discards figcaption b{color:var(--ink); font-weight:600}

/* a discard row where the artwork is small and sits beside its caption */
.cs-discards.beside{grid-template-columns:1fr}
.cs-discards.beside figure{
  flex-direction:row; align-items:center; gap:clamp(18px,2.4vw,40px);
}
.cs-discards.beside .slot{flex:0 0 clamp(74px,7vw,104px); height:auto; align-items:flex-start}
.cs-discards.beside figcaption{max-width:52ch}

/* Phones: the winner has to read as the winner. At full width it leads, and
   the three discards step down into the small beside-the-caption rows, so
   they read as the directions that lost rather than as three more finals. */
@media (max-width:640px){
  .cs-final img{width:100%; max-height:240px; object-fit:contain}
  .cs-discards:not(.beside){grid-template-columns:1fr; gap:22px}
  .cs-discards:not(.beside) figure{flex-direction:row; align-items:center; gap:16px}
  .cs-discards:not(.beside) .slot{flex:0 0 108px; height:auto; align-items:center}
  .cs-discards:not(.beside) figcaption{font-size:14px}
}

/* the missing-asset placeholder, so a gap is obvious rather than silent */
.cs-missing{
  width:100%; height:100%; border:1px dashed var(--rule);
  display:flex; align-items:center; justify-content:center; text-align:center;
  padding:12px; color:var(--muted); font-size:var(--t-micro); line-height:1.4;
}

/* colour + type brief, side by side */
.cs-brief{
  display:grid; gap:clamp(30px,5vw,96px);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  padding-bottom:clamp(44px,5.5vw,96px);
}
/* These two sub-heads had plain 1px borders while every other rule on the
   site is an animated pseudo-element. Nominally the same weight, but a
   composited line renders a touch heavier, so the section rules looked
   thicker and these looked thin and detached. Same construction now, so
   they match and they draw with their block. */
.cs-brief h2{
  position:relative;
  margin:0 0 clamp(20px,2.4vw,34px); padding-bottom:10px;
  border-bottom:1px solid transparent;
  font-size:var(--t-ui); font-weight:700; letter-spacing:-0.02em;
}
.cs-brief h2::after{
  content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:var(--ink); transform-origin:left center; transform:scaleX(1);
  will-change:transform; backface-visibility:hidden;
  transition:transform 1.25s cubic-bezier(.65, 0, .35, 1) 140ms;
}
.reveal-on .cs-brief.reveal-block:not(.is-in) h2::after{transform:scaleX(0)}
/* the five swatches share the column edge to edge, out to the end of the
   rule above them, instead of sitting at a fixed size with a gap on the right */
.swatches{display:grid; grid-template-columns:repeat(5, minmax(0,1fr)); gap:clamp(10px,1.4vw,20px); margin:0; padding:0; list-style:none}
.swatches li{display:flex; flex-direction:column; align-items:center; gap:8px}
.swatches i{
  display:block; width:100%; aspect-ratio:1; border-radius:50%;
  border:1px solid var(--swatch-edge);
}
.swatches span{font-size:var(--t-micro); letter-spacing:.06em; text-transform:uppercase; color:var(--muted)}
/* centre, not baseline: the specimen is an IMAGE beside a label, and aligning
   its box to the label's baseline pushed it visibly high. */
.cs-type-row{display:flex; align-items:center; gap:14px; flex-wrap:wrap}
.cs-type-row .label{color:var(--muted); font-size:var(--t-label)}
.cs-type-row img{display:block; width:auto; max-width:min(100%,280px); height:auto}
.cs-brief p{
  margin:clamp(14px,1.6vw,22px) 0 0; max-width:44ch; color:var(--muted);
  font-size:var(--t-body); line-height:1.5; text-wrap:pretty;
}

/* the guidelines deck — the real differentiator, shown as spreads */
.cs-deck{
  /* Ten spreads, always two rows of five, whatever the window shape, so the
     section keeps one fixed silhouette. The spreads read as thumbnails here;
     clicking opens the readable version in the lightbox.                 */
  display:grid; gap:var(--gap);
  grid-template-columns:repeat(5, minmax(0, 1fr));
}
/* On a phone five across would be ~60px each. Keep the two rows but let the
   set scroll sideways, with snap, so each spread stays large enough to read
   as a spread. */
@media (max-width:760px){
  .cs-deck{
    grid-template-columns:none; grid-template-rows:repeat(2, auto);
    grid-auto-flow:column; grid-auto-columns:62vw;
    overflow-x:auto; scroll-snap-type:x mandatory; scrollbar-width:none;
    margin-inline:calc(var(--gutter, 16px) * -1); padding-inline:var(--gutter, 16px);
    scroll-padding-inline:var(--gutter, 16px);
  }
  .cs-deck::-webkit-scrollbar{display:none}
  .cs-deck a{scroll-snap-align:start}
}
.cs-deck a{display:block; overflow:hidden; background:var(--deck-frame); aspect-ratio:1500/810}
.cs-deck img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .5s cubic-bezier(.2,.7,.2,1);
}
.cs-deck a:hover img{transform:scale(1.03)}
/* the ring sits INSIDE the thumbnail: on phones the deck is a scroller,
   which clips anything drawn outside its items */
.cs-deck a:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
.cs-deck a.no-ring:focus-visible{outline:none}

/* the work grid is a landing target from every project page's pager, so
   it needs clearance from the sticky header when jumped to */
#work{scroll-margin-top:clamp(70px, 6vw, 110px)}

/* ---------- the Gozos page crops ----------
   No CSS crop here any more. The live site applies a DIFFERENT crop rectangle
   to every image on this page — three pair ratios (0.873, 0.763) and three
   full-width ones (1.515, 1.153, 1.591), several with an off-centre offset,
   e.g. the hero band starts 322px down its source. A uniform CSS crop could
   not reproduce that and was slicing through the menu and the products.
   The crops now live in the exported FILES, read straight off the live page's
   Wix crop parameters, so this page uses the same justified-rows model as
   every other project page and `--r` carries each image's true shape.     */


/* ==========================================================================
   SCROLL REVEAL
   Elements lift a little and fade in as they enter the viewport. Nothing is
   hidden unless the page has confirmed it can reveal it again: the
   `reveal-on` class is set by an inline script in the head, and only when
   the viewer hasn't asked for reduced motion. Without JS, everything
   renders at rest — no blank page waiting on an observer.
   ========================================================================== */
.reveal-on .reveal{
  opacity:0; transform:translateY(28px);
  transition:opacity 1.8s cubic-bezier(.22,.61,.36,1),
             transform 1.8s cubic-bezier(.22,.61,.36,1);
  transition-delay:calc(var(--d, 0) * 200ms);
}

/* ---- a SECTION arriving, as against elements arriving ----
   The work grid reveals tile by tile, which is right for a gallery: it reads
   as a list being dealt out. The shop and the studio are not lists, they are
   places, so they arrive as a CHOREOGRAPHED SEQUENCE instead — one trigger
   for the whole section, then its parts land in a designed order.

   `.reveal-block` on the section is now only the trigger; it does not move.
   The parts carry `.st` and a stage number in `--s`:
     shop   0 headline+rule · 1 all three products together · 2 the button
     studio 0 headline+rule · 1 the portrait · 2 everything written

   Ordering by delay rather than by one observer per element is deliberate:
   these sections are about one screen tall, so per-element triggers would
   all fire within a few pixels of each other and the order would collapse
   into a trickle. A single trigger plus delays is what actually reads as
   first this, then that. */
.reveal-on .reveal-block .st{
  opacity:0; transform:translate3d(0, 34px, 0);
  transition:opacity 1.15s cubic-bezier(.22,.61,.36,1) calc(var(--s, 0) * 420ms),
             transform 1.55s cubic-bezier(.16,1,.3,1) calc(var(--s, 0) * 420ms);
  will-change:transform;
}
.reveal-on .reveal-block.is-in .st{opacity:1; transform:none}

/* ---- the footer resolves out of a blur ----
   It is the last thing on every page and it was the only thing that never
   moved. A blur rather than a rise because the footer is quiet type, not a
   picture: it comes into focus as you arrive instead of sliding into place,
   which suits the end of a page better than another lift.
   The rise is kept small, 12px, so the blur is what reads. */
.reveal-on .reveal-blur{
  opacity:0; filter:blur(9px); transform:translate3d(0,12px,0);
  transition:opacity 1.1s cubic-bezier(.22,.61,.36,1),
             filter 1.25s cubic-bezier(.22,.61,.36,1),
             transform 1.25s cubic-bezier(.22,.61,.36,1);
  /* same stage variable the sections use, so a blur can be ordered too.
     Fractions are fine: the lockup's second half runs at 0.7 of a step. */
  transition-delay:calc(var(--s, 0) * 420ms);
}
.reveal-on .reveal-blur.is-in{opacity:1; filter:blur(0); transform:none}

/* The rule under a section headline draws itself in, left to right, just
   after the headline starts to arrive. It has to be a pseudo-element: a
   border cannot be wiped. Absolutely positioned, so it never becomes a flex
   item of .sec-head, and the real border is kept for its 1px of layout with
   its colour taken away.

   Its resting state is DRAWN. Only `reveal-on` and a section that hasn't
   arrived yet collapse it — so if the failsafe drops the whole mechanism,
   or someone asked for less motion, the rule is simply there. */
/* 2px, and the same 2px at every moment. At 1px the line changed weight as it
   drew: a hairline sitting on a fractional y (the head's padding is a clamp,
   so its bottom edge rarely lands on a whole pixel) gets spread across two
   device rows while the layer is being composited for the transform, then
   snaps to one crisp row once the transform settles — thicker, then thinner.
   Two things hold it steady: a 2px line covers two device rows either way,
   and `will-change` keeps the pseudo-element on its own layer before, during
   AND after the wipe, so the rasterisation path never changes underneath it.
   The border keeps its 2px of layout with its colour taken away. */
.reveal-block .sec-head{position:relative; border-bottom-color:transparent; border-bottom-width:2px}
.reveal-block .sec-head::after{
  content:""; position:absolute; left:0; right:0; bottom:-2px; height:2px;
  background:var(--ink); transform-origin:left center; transform:scaleX(1);
  will-change:transform; backface-visibility:hidden;
  transition:transform 1.25s cubic-bezier(.65, 0, .35, 1) 140ms;  /* smooth in-out */
}
.reveal-on .reveal-block:not(.is-in) .sec-head::after{transform:scaleX(0)}

/* ---- the same two moves on a project page ----
   The band's rule and the note's rule draw themselves left to right when
   their block arrives, then the writing comes up under them in order:
   title and eyebrow, the reading, then Client / Discipline / Tools. Below
   the band every image row and the pager carry their own `.reveal`, so they
   arrive one at a time on the way down rather than all at once.

   Both rules are pseudo-elements for the same reason as the homepage: a
   border cannot be wiped. The real border keeps its 1px of layout with its
   colour taken away, and the line sits on top of it. */
.project-top .c-band,
.cs-section.reveal-block > h2,
.pager.reveal-block,
.project-note.reveal-block{position:relative}
.project-top .c-band{border-top-color:transparent}
.project-note.reveal-block{border-top-color:transparent}
/* the case-study section heading rules itself along its BOTTOM edge, so its
   line hangs off ::after at the foot rather than ::before at the head */
.cs-section.reveal-block > h2{border-bottom-color:transparent}
.cs-section.reveal-block > h2::after{
  content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:var(--ink); transform-origin:left center; transform:scaleX(1);
  will-change:transform; backface-visibility:hidden;
  transition:transform 1.25s cubic-bezier(.65, 0, .35, 1) 140ms;
}
.pager.reveal-block{border-top-color:transparent}
.project-top .c-band::before,
.pager.reveal-block::before,
.project-note.reveal-block::before{
  content:""; position:absolute; left:0; right:0; top:-1px; height:1px;
  background:var(--ink); transform-origin:left center; transform:scaleX(1);
  will-change:transform; backface-visibility:hidden;
  transition:transform 1.25s cubic-bezier(.65, 0, .35, 1) 140ms;
}
/* drawn at rest — only an unarrived block collapses it, so the failsafe and
   reduced motion both leave every rule simply there */
.reveal-on .reveal-block:not(.is-in) .c-band::before,
.reveal-on .cs-section.reveal-block:not(.is-in) > h2::after,
.reveal-on .project-note.reveal-block:not(.is-in)::before,
.reveal-on .pager.reveal-block:not(.is-in)::before{transform:scaleX(0)}
.reveal-on .reveal.is-in{opacity:1; transform:none}
@media (prefers-reduced-motion:reduce){
  .reveal-on .reveal,
  .reveal-on .reveal-block,
  .reveal-on .reveal-blur,
  .reveal-on .display .reveal-now,
  .reveal-on .reveal-block .st{opacity:1; filter:none; transform:none; transition:none}
  .reveal-on .display .r.reveal-now{transform:none}
  .reveal-block .sec-head::after,
  .project-top .c-band::before,
  .cs-section.reveal-block > h2::after,
  .cs-brief h2::after,
  .pager.reveal-block::before,
  .project-note.reveal-block::before{transform:scaleX(1); transition:none}
}

/* the call to action button */
.btn{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:clamp(52px,3.6vw,74px);
  padding:0 clamp(28px,2.2vw,46px);
  border-radius:999px; text-decoration:none;
  background:var(--cta-ground); color:var(--cta-letters); font-weight:600;
  font-size:var(--t-body);
  transition:background .18s ease;
}
.btn:hover{background:var(--accent); color:#FFFFFF}
.btn:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

/* ---------------- shop teaser on the homepage ---------------- */
.shop-teaser{
  display:grid; gap:clamp(22px,2.6vw,44px) var(--gap);
  grid-template-columns:repeat(3, minmax(0,1fr));
}
/* 3 across at every width, same as the work grid above it */
@media (max-width:560px){
  /* sized to match the work-grid captions above */
  .shop-teaser .product{gap:8px}
  .shop-teaser .product .name{font-size:12.5px}
  .shop-teaser .product .spec,.shop-teaser .product .price{font-size:11.5px}
  .shop-teaser .spec .sep{display:none}
  .shop-teaser .spec .dim{display:block}
}
.shop-cta{
  margin:clamp(18px, min(3.4vw, 3.2dvh), 56px) 0 0;
  display:flex; justify-content:flex-start;
}

/* ==========================================================================
   HUE / WEB3 IS COMING. The one page that breaks the grid, on purpose:
   the lettering is the hero, and the spreads (white-background mocks) and
   3D spots all sit on pure white, so they hang in the page with no frame.
   Only the full illustration keeps an edge, and it stays on the grid.
   Positions are % of fixed-ratio stages so the composition holds at every
   desktop width; under 760px it all drops to one stacked column.
   ========================================================================== */
/* headline stage. --sp is the breathing room web3 travels. Coming stays
   anchored and the words above it rise, so only the TOP needs reserving:
   no dead band between the headline and the first spread. */
.hue-type{--sp:clamp(40px,11vw,190px); padding-top:var(--sp)}
.ht-stage{position:relative; aspect-ratio:16/13}
.hue-type .ht{position:absolute; mix-blend-mode:multiply}
.hue-type img, .hue-collage img, .hue-collage video{display:block; width:100%; height:auto}
/* the closed state is the spread's own stacking: web3 on top, "is" tucked
   under its tail, Coming large across the bottom. % of the stage. */
.ht-web3  {left:10%; top:10.5%; width:38%}
.ht-is    {left:41%; top:30%; width:14%}
.ht-coming{left:18%; top:44%; width:70%}
.hue-type img{will-change:transform}

/* collage: % of a fixed-ratio stage, measured off his reference */
.hue-collage{position:relative; aspect-ratio:1820/1650; margin:clamp(20px,3vw,50px) 0}
.hue-collage .hc{position:absolute; mix-blend-mode:multiply}
.hc-s3    {left:0;   top:0;   width:64%}
/* his zigzag reference: blockchain right of the Blockchain/Crypto spread,
   crypto left of the Metaverse spread */
.hc-block {left:65%; top:9%;  width:30%}
.hc-crypto{left:4%;  top:57%; width:29%}
.hc-s2    {left:36%; top:50%; width:64%}
/* the Metaverse spot, big and centred under the headline. 1080px source,
   so it caps at 56% of the column to stay sharp on retina. */
.hue-hero-spot{width:56%; margin:0 auto; mix-blend-mode:multiply}
.hue-hero-spot video{display:block; width:100%; height:auto;
  /* feather the edges so the squares dissolve instead of being sliced off
     as they drift out of frame: 14% top and sides, only 5% at the bottom,
     where the legs reach the edge and a long fade would ghost them. */
  -webkit-mask-image:linear-gradient(to right, transparent, #000 14%, #000 86%, transparent),
                     linear-gradient(to bottom, transparent, #000 14%, #000 95%, transparent);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(to right, transparent, #000 14%, #000 86%, transparent),
             linear-gradient(to bottom, transparent, #000 14%, #000 95%, transparent);
  mask-composite:intersect}
/* the crypto spot slides coins in from the top and bottom, where the frame
   sliced them off; the same feather as the hero spot, even on all sides */
.hc-crypto video{
  -webkit-mask-image:linear-gradient(to right, transparent, #000 12%, #000 88%, transparent),
                     linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(to right, transparent, #000 12%, #000 88%, transparent),
             linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent);
  mask-composite:intersect}
.bob{animation:hue-bob var(--bob,7s) ease-in-out var(--bd,0s) infinite alternate}
@keyframes hue-bob{from{transform:translate3d(0,-1.5%,0) rotate(-.6deg)} to{transform:translate3d(0,1.5%,0) rotate(.6deg)}}
@media (prefers-reduced-motion:reduce){ .bob{animation:none} }
@media (max-width:760px){
  .hue-type{padding:0}
  .ht-stage{aspect-ratio:auto; display:flex; flex-direction:column; gap:var(--gap)}
  .hue-type .ht{position:static}
  .ht-web3{width:62%; align-self:flex-start}
  .ht-is{width:30%; align-self:center}
  .ht-coming{width:90%; align-self:flex-end}
  .hue-type img{transform:none !important}
  .hue-hero-spot{width:86%}
  .hue-collage{aspect-ratio:auto; display:flex; flex-direction:column; gap:var(--gap)}
  .hue-collage .hc{position:static; width:100%}
  .hc-block, .hc-crypto{width:62%}
  .hc-block{align-self:flex-end}
}

/* SLIDESHOW (Facebook Stickers & Backgrounds). The slides stack in one frame
   and crossfade; site.js advances them every 4s, and an arrow press jumps
   one and restarts the clock. The arrows are the homepage hero's glass
   buttons, reused as-is. --r is the slides' shared aspect ratio. */
.project-media .slides{position:relative; margin:0; aspect-ratio:var(--r); overflow:hidden}
.slides img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block;
  opacity:0; transition:opacity .8s ease;
}
.slides img.is-on{opacity:1}
@media (prefers-reduced-motion:reduce){ .slides img{transition:none} }

/* RETOUCHING GALLERY: the homepage hero marquee, dropped into a project page.
   Same track, same arrows, same JS. On the homepage the band's height comes
   from the first screen; here there is no screen to fill, so it gets a fixed
   height instead (flex:none, or the column flex would collapse it to 0). */
.project-media.gallery .hero-media{flex:none; height:clamp(320px, 62vh, 800px)}
@media (max-width:760px){ .project-media.gallery .hero-media{height:clamp(280px, 56vh, 480px)} }

/* Links inside a project description point to the published piece (the
   article, the feature, the issue). Underlined so they read as links
   without breaking the paragraph's color. */
.project-desc a{color:inherit; text-decoration:underline; text-decoration-thickness:.06em; text-underline-offset:.16em}
.project-desc a:hover{text-decoration-thickness:.12em}

/* Extra air above and below one row, added evenly so the row stays balanced
   between what comes before and after it (FB Plus icon-picker mockup). */
.project-media .row.breathe{margin-block:clamp(20px, 3.2vw, 60px)}

/* LIGHTBOX (Gozos guidelines deck). A spread opens large over the page, the
   page fades almost to white behind it, and the homepage's glass arrows step
   through the set. On white the glass tint would vanish, so the buttons
   here take a faint ink tint and edge instead. */
.lb{
  position:fixed; inset:0; z-index:100;
  display:grid; grid-template-rows:1fr auto; place-items:center;
  padding:clamp(16px,4vw,64px) clamp(16px,7vw,120px) clamp(12px,2vw,24px);
  background:rgba(255,255,255,.94);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  opacity:0; visibility:hidden; transition:opacity .25s ease, visibility 0s .25s;
}
.lb.is-open{opacity:1; visibility:visible; transition:opacity .25s ease}
.lb img{
  max-width:100%; max-height:100%; min-height:0; object-fit:contain; display:block;
  box-shadow:0 10px 40px rgba(20,20,20,.14);
}
.lb-count{margin:0; padding-top:clamp(10px,1.4vw,18px); font-size:var(--t-label); letter-spacing:.12em; text-transform:uppercase; color:var(--muted)}
.lb .hero-arrow{
  position:absolute; top:50%; transform:translateY(-50%);
  background:rgba(20,20,20,.06); border-color:rgba(20,20,20,.14);
  box-shadow:0 4px 18px rgba(20,20,20,.10), inset 0 1px 0 rgba(255,255,255,.6);
}
.lb .hero-arrow:hover{background:rgba(20,20,20,.11); border-color:rgba(20,20,20,.22)}
.lb .hero-arrow.prev{left:clamp(8px,1.6vw,28px)}
.lb .hero-arrow.next{right:clamp(8px,1.6vw,28px)}
.lb .hero-arrow.close{top:clamp(8px,1.6vw,28px); right:clamp(8px,1.6vw,28px); transform:none; width:clamp(40px,3.4vw,54px)}
html.lb-lock{overflow:hidden}
@media (prefers-reduced-motion:reduce){ .lb, .lb.is-open{transition:none} }

/* Text for search engines and screen readers only (the shop's H1: the page
   opens visually on "Paintings", but it still needs one main heading). */
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0}
/* with the visible "Shop" title gone, the first section takes over the
   top spacing the intro used to give the page */
.sr-only + .wrap > .shop-section{padding-top:clamp(40px,5vw,84px)}   /* = .project-top */
