/* arden.css — Arthur Arden layout, Cyril's content.
   Values taken from the reference's own computed styles:
     names  Switzer 700 / 56px / uppercase / letter-spacing -.06em / lh 1.1em
     scale  56 · 40 · 32 · 20
     ink    #1a1a1a on #fff, greys #575757 #a1a1a1 #ccc
     ease   cubic-bezier(.16,1,.3,1) over .25s
   The landing page is one vertically looping, drag-scrollable column of
   centred project names. Nothing else. */

:root{
  --paper:#ffffff;
  --ink:#1a1a1a;
  --grey:#575757;
  --mute:#a1a1a1;
  --rule:#0000001a;

  --display:'Switzer', 'Overused Grotesk', -apple-system, sans-serif;
  --text:'Switzer', 'Overused Grotesk', -apple-system, sans-serif;

  --edge:clamp(16px, 2.6vw, 34px);
  --bar:58px;
  --ease:cubic-bezier(.16,1,.3,1);
}

*{margin:0;padding:0;box-sizing:border-box}

body{
  background:var(--paper);
  color:var(--ink);
  font-family:var(--text);
  font-size:15px;
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
  cursor:none;
}


/* ---------- bars ---------- */
/* name top-left, MENU top-right, email bottom-left, © bottom-right —
   the four corners of the reference, with the loop running between them */

/* The name and MENU ride the viewport; the email and the year do not - they
   sit at the very end of the page, after the client strip, with screen one
   keeping its own copy of them in its bottom corners. */
.bar{
  height:var(--bar);
  display:flex; align-items:center; justify-content:space-between;
  padding:0 var(--edge);
  z-index:40;
  pointer-events:none;        /* names underneath stay clickable */
}
/* fixed: the name and MENU have to stay reachable all the way down the page,
   and the band of air at the top of the title list is exactly where they sit
   once screen one has gone */
.bar--top{position:fixed; top:0; left:0; right:0}
/* Once screen one has gone it needs its own ground: it is pinned over the
   title list now, and a transparent band would have names running through
   the name and MENU. Over the photograph it stays clear, where the
   difference blend is what keeps it legible. */
body:not(.at-hero) .bar--top{background:var(--paper)}
/* screen one's own foot line. It lives inside the pinned photograph, so it
   holds the two bottom corners there and is simply covered as the paper
   rides up over it - no scroll handling of its own. */
.bar--hero{position:absolute; bottom:0; left:0; right:0; z-index:5}

/* in flow at the foot of the page. It needs its own ground and layer: the
   pinned photograph is still painted behind everything at this depth. */
.bar--bottom{
  position:relative;
  z-index:2;
  background:var(--paper);
}
.bar > *{pointer-events:auto}

.bar button, .bar a{
  font-family:var(--display);
  font-weight:500;
  font-size:clamp(12px, 1.15vw, 16px);
  letter-spacing:0;
  text-transform:uppercase;
  color:var(--ink);
  background:none; border:0;
  text-decoration:none;
  transition:opacity .25s var(--ease);
  /* the text is small; the target should not be. The padding grows the hit
     area and the matching negative margin pulls the box back so the letters
     stay exactly where they sat in the corners. Vertically it fills the bar;
     sideways it reaches into the edge margin, which is empty anyway. */
  padding:20px 28px;
  margin:-20px -28px;
}
.bar button:hover, .bar a:hover{opacity:.5; cursor:pointer !important}

/* Over the hero photo the corner text would vanish into whatever is behind
   it. It used to be difference-blended, but at this size that produces the
   same muddy mid-tone the project name avoids - so these are sampled by
   pickInk() in arden.js, exactly as the name and the chevron are, and each
   commits to flat black or flat white against its own corner of the photo. */
body.at-hero .bar--top button,
body.at-hero .bar--hero a,
body.at-hero .bar--hero span{
  color:#1a1a1a;
  transition:color .3s var(--ease);
}
body.at-hero .bar--top button.on-dark,
body.at-hero .bar--hero a.on-dark,
body.at-hero .bar--hero span.on-dark{color:#ffffff}

/* ---------- hero ---------- */
/* screen one: one portfolio photo at a time, cross-fading. Full bleed, and
   it scrolls away normally — the titles arrive underneath it. */

/* Screen one does not scroll away - it is pinned to the viewport and the
   paper below rides up over it, so the photo stays whole behind the list
   instead of sliding off the top. The spacer after it (.hero__hold) carries
   the scroll distance it used to take up itself. */
.hero{
  position:fixed;
  top:0; left:0; right:0;
  height:100svh;
  overflow:hidden;
  background:#111;
  z-index:0;
}
.hero__hold{height:100svh}


.hero__slides{position:absolute; inset:0}
/* Half the photo each, meeting in the middle, so there is no dead strip
   anywhere on screen one. The statement sits above them (z-index 3) but only
   its letters take a click, so prev and next own everything right up to the
   words and hand over exactly where they begin. */
.hero__zone{
  position:absolute;
  top:0; bottom:0;
  width:50%;
  z-index:1;
  background:none; border:0; padding:0;
}
.hero__zone--prev{left:0}
.hero__zone--next{right:0}
.hero__slide{
  position:absolute; inset:0;
  opacity:0;
  transition:opacity 1.6s ease;
}
.hero__slide.on{opacity:1}
.hero__slide img{
  width:100%; height:100%;
  object-fit:cover; display:block;
  -webkit-user-drag:none; user-select:none;
  transform-origin:center;
  will-change:transform;
}

/* A photo held perfectly still reads as a screenshot. A slow push over the
   whole hold makes the screen breathe without anyone noticing why. The
   direction alternates per slide so consecutive photos do not pulse in the
   same direction, and the animation runs 7.2s - longer than hold + fade -
   so it never finishes and snaps mid-crossfade. */
@keyframes heroDriftIn  { from{transform:scale(1)}     to{transform:scale(1.075)} }
@keyframes heroDriftOut { from{transform:scale(1.075)} to{transform:scale(1)} }
.hero__slide img.drift-in { animation:heroDriftIn  7.2s linear forwards }
.hero__slide img.drift-out{ animation:heroDriftOut 7.2s linear forwards }

/* which project the photo belongs to. Without it the photo is a door with
   no sign on it - you would be clicking into an unknown. It rides in the top
   bar itself, centred between the name and MENU, so screen one reads as one
   line of three across the top - and it is clear of the statement below it.
   The bar's own height is what centres it: same box, same middle. */
.hero__now{
  position:absolute;
  background:none; border:0;
  left:50%; top:0;
  height:var(--bar);
  display:flex; align-items:center; justify-content:center;
  transform:translateX(-50%);
  z-index:4;
  /* narrow enough that a long title never reaches the name or MENU either
     side of it; the ellipsis below takes whatever will not fit */
  max-width:min(52vw, 540px);
  font-family:var(--display);
  font-weight:700;
  font-size:12px;
  letter-spacing:.14em;
  text-transform:uppercase;
  text-align:center;
  white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
  /* Not difference-blended like the statement above it: at this size
     difference produces a muddy mid-tone against a busy photo. Instead the
     photo is sampled behind the label and it commits to flat black or flat
     white - see pickInk() in arden.js. */
  color:#1a1a1a;
  opacity:0;
  pointer-events:none;
  transition:opacity .5s var(--ease), color .3s var(--ease);
  /* generous padding so the small name is still an easy target. Only the
     vertical offset needs cancelling - the box is centred by translateX,
     so extra width on both sides keeps it centred on its own. */
  padding:0 20px;
}
/* only the name opens the project now, not the whole photo */
.hero__now.on{opacity:1; pointer-events:auto}
.hero__now.on:hover{opacity:.6}
.hero__now.on-dark{color:#ffffff}
body:not(.at-hero) .hero__now{opacity:0; pointer-events:none}

/* the scroll mark, in the spot the project name used to hold. The titles
   begin below the fold and nothing else on screen one says so. Sampled
   rather than difference-blended for the same reason the name is: a 1.6px
   stroke over a busy photograph goes muddy under difference. */
.hero__down{
  position:absolute;
  /* the foot bar's own height centres it, exactly as the top bar centres the
     project title - so the chevron sits on the line the email and the year
     already hold, rather than floating above it. A wrapper, because the
     glyph inside has to stay 24x14 while this box is bar-height. */
  left:50%; bottom:0;
  height:var(--bar);
  display:flex; align-items:center; justify-content:center;
  transform:translateX(-50%);
  z-index:4;
  pointer-events:none;
  color:#1a1a1a;
  opacity:.72;
  transition:opacity .5s var(--ease), color .3s var(--ease);
}
.hero__down.on-dark{color:#ffffff}
body:not(.at-hero) .hero__down{opacity:0}

@media (prefers-reduced-motion: reduce){
  .hero__slide img{animation:none !important}
}

/* the standing invitation, centred over the photo. Difference blending is
   the same trick the corner text uses — it stays readable whether the photo
   behind it is a dark curtain or a white wall. */
.hero__say{
  /* the box is the full width of the screen and two lines tall; only the
     letters inside it should answer a click, or it would swallow the whole
     middle of the photo - see .hero__say-text */
  pointer-events:none;
  position:absolute;
  left:50%; top:50%;
  transform:translate(-50%, -50%);
  z-index:3;
  /* a fixed width rather than max-content: the statement rolls through
     phrases of very different lengths, and a box that resized on every turn
     would jitter the whole screen */
  width:min(92vw, 1180px);
  padding:0;
  background:none; border:0;
  font-family:var(--display);
  font-weight:700;
  font-size:clamp(38px, 9vw, 116px);
  line-height:1.1em;
  letter-spacing:-.06em;
  text-transform:uppercase;
  text-align:center;
  color:#fff;
  mix-blend-mode:difference;
  transition:opacity .25s var(--ease);
}
.hero__say:hover{opacity:.6}

/* The statement dissolves the way the photograph behind it does, on the same
   1.6s - the two lines sit on top of each other and cross, so nothing moves
   and nothing is clipped. Two lines tall, so the long phrases can break
   without the short ones changing the box. */
.hero__say-window{
  display:block;
  position:relative;
  width:100%;
  height:2.2em;
}
.hero__say-line{
  position:absolute;
  inset:0;
  display:grid;
  place-items:center;
  text-align:center;
  opacity:0;
  transform-origin:center;
  will-change:transform, opacity;
  /* leaving: eases IN, so it lets go gently and then clears quickly */
  transition:opacity .34s cubic-bezier(.4, 0, 1, 1);
}
/* arriving: eases OUT, and waits exactly as long as the outgoing phrase
   takes to clear. A straight crossfade like the photograph's would put two
   phrases of different lengths on top of each other - photographs dissolve
   because they fill the frame; words over words just tangle. Ease-in out,
   ease-out in is the pair that reads as one smooth handover rather than two
   separate fades. The whole exchange is 0.86s, well inside the
   photograph's 1.6s, so the words settle first and the picture follows. */
.hero__say-line.on{opacity:1; transition:opacity .52s cubic-bezier(0, 0, .2, 1) .34s}

/* the letters themselves: the only part of the statement that takes a click,
   so the bands either side of it run right up to the words */
.hero__say-text{
  display:inline-block;
  max-width:100%;
  text-wrap:balance;
  pointer-events:auto;
}

/* and the same slow push, at a fraction of the photograph's - type reads a
   scale change far more readily than a photograph does. Alternating, and
   running longer than hold plus fade, so it never finishes and snaps. */
@keyframes sayDriftIn  { from{transform:scale(1)}    to{transform:scale(1.022)} }
@keyframes sayDriftOut { from{transform:scale(1.022)} to{transform:scale(1)} }
.hero__say-line.drift-in { animation:sayDriftIn  7.2s linear forwards }
.hero__say-line.drift-out{ animation:sayDriftOut 7.2s linear forwards }

@media (prefers-reduced-motion: reduce){
  .hero__say-line{animation:none !important}
}

/* ---------- the title list ---------- */
/* below the fold, on paper. Scrolling past the last name wraps silently back
   a full set, so the column never ends. */

.loop{
  position:relative;
  z-index:1;                  /* rides over the pinned photo */
  /* The top air was sized for the one-screen step that used to land here:
     the page arrived at the list already settled, and the gap read as the
     list's own margin. Now the paper slides up continuously, so that gap is
     crossed in full view and a deep one reads as the sheet arriving empty.
     Halved.
     Equal at both ends again, and 40px deeper than it was at every window
     size - the services band used to hold some of this air and has moved to
     its own page. */
  padding-block:clamp(64px, calc(6vh + 40px), 112px);
  background:var(--paper);
  /* a hard, square edge and no shadow: the paper arrives over the photo as
     a flat plane. Nothing lifts it off the picture - it is the page itself
     moving up, not a card sliding over one. */
}
.loop__track{position:relative}

.loop__item{
  /* name on the left, what it is and when on the right - the row runs the
     full width and the two ends hold it apart */
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:clamp(16px, 4vw, 80px);
  position:relative;
  width:100%;
  padding:0 var(--edge);
  /* only the letters answer the pointer - the empty run between the name and
     the meta is dead. :hover still reaches the row, because hovering a child
     puts every ancestor in :hover whatever their pointer-events say, so the
     band, the dimming and the thumbnail all keep working off the row. */
  pointer-events:none;
  font-family:var(--display);
  font-weight:700;
  font-size:clamp(30px, 5.6vw, 56px);
  line-height:1.1em;
  letter-spacing:-.06em;
  text-transform:uppercase;
  text-align:center;
  /* the long names still wrap rather than overflow a narrow window */
  white-space:normal;
  text-align:left;
  color:var(--ink);
  text-decoration:none;
  -webkit-user-drag:none;
  transition:opacity .25s var(--ease), color .25s var(--ease),
             background-color .25s var(--ease),
             transform .25s var(--ease), filter .25s var(--ease);
}

/* the column only recedes once a row is actually held - being anywhere in
   the region is not enough, or crossing the empty run beside the titles
   would grey the whole list with nothing lit in return. "Held" is set by
   arden.js, not by :hover - see the note on .loop__name::before. */
.loop:has(.loop__item.is-held) .loop__item{opacity:.22}
/* the name under the cursor fills with its own colour from the palette the
   project panels used to roll - assigned per project in arden.js. This is
   also the fallback for the four projects that have no photograph yet. */
.loop__item.is-held{
  opacity:1 !important; cursor:pointer !important;
  background:var(--hov, transparent);
  color:var(--hov-fg, var(--ink));
}

/* what the project is and when, at the two ends of the hover band. Hidden
   until the row is pointed at, so the column reads as bare as before. */
/* the name carries the row; the meta sits opposite it, quiet until the row
   is pointed at. Category first so the years form a clean column down the
   right edge - a ragged right on a list this size reads as an accident. */
.loop__name{flex:0 1 auto; min-width:0; pointer-events:auto}

/* What ARMS a row: an invisible panel over the whole of it. It hangs off
   the name rather than the row itself so the row can stay
   pointer-events:none, which keeps the meta spans inert and lets the panel
   be the single hit target. The lit state is still a class from arden.js
   rather than :hover, because scrolling must not arm anything. */
.loop__name::before{
  content:'';
  position:absolute;
  inset:0;
  pointer-events:auto;
}

.loop__meta{
  flex:none;
  display:flex;
  align-items:baseline;
  gap:clamp(12px, 2vw, 34px);
  font-family:var(--display);
  font-weight:500;
  font-size:clamp(10px, .95vw, 13px);
  letter-spacing:.14em;
  line-height:1;
  text-transform:uppercase;
  white-space:nowrap;
  /* Black on the paper - NOT var(--hov-fg). That is the ink paired with the
     row's swatch and it is set on every row whether lit or not, so the rows
     holding the one dark swatch were drawing their meta in its near-white
     partner and vanishing against the page until you hovered them. The
     swatch's ink is only right once the band is actually behind the text. */
  color:var(--ink);
  pointer-events:none;
}
.loop__item.is-held .loop__meta{color:var(--hov-fg, var(--ink))}
.loop__meta-year{min-width:4ch; text-align:right}

/* On a phone the name takes the full width and what it is and when sits
   underneath it as a caption - the way a printed index sets an entry. One
   row beside a 30px name only fits by dropping the category and shrinking
   the two longest names, and this keeps both values at full size however
   long the name runs. */
@media (max-width:760px){
  .loop__item{
    flex-direction:column;
    align-items:flex-start;
    gap:3px;
    /* asymmetric on purpose: the caption belongs to the name above it, so
       the gap under the caption has to be clearly larger than the one above
       the name, or each caption reads as the label of the next project */
    padding-block:.1em .48em;
  }
  .loop__name{flex:0 0 auto}
  .loop__meta{
    gap:0;                  /* the separator does the spacing */
    font-size:10px;
    letter-spacing:.14em;
  }
  /* a middot between the two, rather than a gap that would read as a column */
  .loop__meta-cat:not(:last-child)::after{
    content:'\00B7';
    margin:0 .7em;
    opacity:.55;
  }
  .loop__meta-year{min-width:0; text-align:left}
}

/* ---------- the "soon" note ---------- */
/* The four projects with nothing to show yet. No chip and no container -
   just the word, set small and letterspaced on the name's own baseline, the
   way the rest of the page handles a label. It reads as a note about the
   project rather than an object stuck on top of it. The word comes from
   data-soon, set in arden.js, so it follows the language. */
.loop__item.is-soon .loop__name::after{
  content:attr(data-soon);
  display:inline;
  /* measured on the note's own size, not the title's, so the gap holds its
     proportion as the headline scales with the viewport */
  margin-left:1.6em;
  font-size:clamp(10px, .2em, 14px);
  font-weight:500;
  /* the row sets line-height in em, which inherits as a computed LENGTH.
     At the note's much smaller font-size that leaves a taller half-leading
     than the title's, so the line box grew and the four rows carrying a
     note stood 17px taller than every other row. */
  line-height:1;
  letter-spacing:.16em;
  text-transform:uppercase;
  vertical-align:baseline;
  color:var(--mute);
  white-space:nowrap;
}
/* on a lit row it takes the band's own ink, like everything else there */
.loop__item.is-held.is-soon .loop__name::after{color:var(--hov-fg, var(--ink))}

/* ---------- the hovered project's photograph ---------- */
/* Rides with the cursor, in the same tracked-element family as the dot and
   its Previous/Next label. The four projects with no photograph yet show
   nothing here and keep the flat colour above on its own. */
.shot{
  position:fixed;
  left:0; top:0;
  z-index:998;
  width:clamp(240px, 26vw, 420px);
  /* a circle, so the box has to be square - 4/3 with a 50% radius would
     round it into an ellipse. object-fit:cover on the image below crops to
     the new frame rather than squashing the photograph into it. */
  aspect-ratio:1 / 1;
  border-radius:50%;
  overflow:hidden;
  pointer-events:none;
  opacity:0;
  /* clear of the row being hovered - a preview that covers the name you are
     pointing at is no preview. Both offsets are set by arden.js, which holds
     the box inside the window on either axis. */
  transform:translate(var(--shot-x, 44px), var(--shot-y, -108%)) scale(.94);
  transition:opacity .28s var(--ease);
}
.shot img{width:100%; height:100%; object-fit:cover; display:block}
.shot.on{opacity:1; transform:translate(var(--shot-x, 44px), var(--shot-y, -108%)) scale(1)}

@media (hover:none){
  .shot{display:none !important}
}

/* ---------- the closing client strip ---------- */
/* The names the work was made with and for, running past on a loop - the
   same list the earlier version carried, and the last thing on the page now
   that the title list has an end. Rides over the pinned photo like the list
   above it, so it needs its own ground and its own layer. */
.clients{
  position:relative;
  z-index:1;
  background:var(--paper);
  /* clear space under the strip, then the footer bar itself, which now
     follows in flow rather than riding the viewport */
  padding:0 0 72px;
}
.clients__strip{
  overflow:hidden;
  border-top:1px solid var(--rule);
  border-bottom:1px solid var(--rule);
  padding:8px 0;
}
.clients__track{
  /* flex, NOT inline-flex: an inline-level box sits on the strip's text
     baseline, and the descender space under that baseline put ~6px more air
     below the logos than above them - the strip's own padding is symmetric,
     so the lopsidedness was never the padding. width:max-content keeps what
     inline-flex was there for: the track has to be as wide as its contents,
     or the -50% below would be half the window instead of half the track and
     the loop would no longer close on the identical copy. */
  display:flex;
  width:max-content;
  /* one axis for everything in the row - the logos are a fixed 38px and the
     wordmarks are type, so without this they hang from different edges */
  align-items:center;
  white-space:nowrap;
  will-change:transform;
  /* the track holds the list twice; travelling exactly half its width lands
     on the identical copy, so the loop closes with nothing to see */
  animation:clientsRun 52s linear infinite;
}
.clients__half{display:inline-flex}
.clients__logo{
  /* the files are trimmed to the artwork itself, so the box IS the logo and
     the strip's 8px is the only air around it. Height sets the common size;
     max-width stops the long wordmarks from running away with the row, which
     is also how they read correctly against the compact marks. */
  height:38px;
  max-width:136px;
  width:auto;
  margin:0 clamp(20px, 3vw, 56px);
  object-fit:contain;
  flex:none;
  /* white artwork on transparent, drawn for a dark ground - inverting turns
     it black for the paper and leaves the alpha alone */
  filter:invert(1);
}
/* the clients with no logotype on hand, set in the display face and sized to
   sit with the real marks rather than beside them */
.clients__word{
  flex:none;
  margin:0 clamp(10px, 1.6vw, 28px);
  font-family:var(--display);
  font-weight:700;
  font-size:clamp(17px, 1.9vw, 27px);
  letter-spacing:-.01em;
  line-height:1;
  white-space:nowrap;
  color:var(--ink);
}

@keyframes clientsRun{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}
@media (prefers-reduced-motion: reduce){
  .clients__track{animation:none}
}

/* ---------- menu overlay ---------- */

.menu{
  position:fixed; inset:0;
  z-index:60;
  background:var(--paper);
  display:flex; flex-direction:column;
  align-items:center; justify-content:center;
  gap:clamp(4px, 1vh, 14px);
  opacity:0; visibility:hidden;
  transition:opacity .3s var(--ease), visibility .3s var(--ease);
}
.menu.on{opacity:1; visibility:visible}
.menu button{
  font-family:var(--display);
  font-weight:700;
  font-size:clamp(30px, 5.6vw, 56px);
  line-height:1.1em;
  letter-spacing:-.06em;
  text-transform:uppercase;
  color:var(--ink);
  background:none; border:0; padding:4px 20px;
  transition:opacity .25s var(--ease);
}
.menu:hover button{opacity:.22}
.menu button:hover{opacity:1 !important; cursor:pointer !important}

/* ---------- project view ---------- */
/* the reference gives each project its own page; this is the same content on
   a full-bleed panel over the loop, so the whole site stays one document */

.proj{
  position:fixed; inset:0;
  z-index:70;
  /* the project takes over the whole screen in the colour its name carried
     in the list. Every piece of type on it stays black: the tokens the
     detail is already built from are redefined here, so specs, rules and
     body copy all follow without restyling any of them individually. */
  --panel-fg:#111111;
  --ink:#111111;
  --fg:#111111;
  --paper:var(--panel, #ffffff);
  --bg:var(--panel, #ffffff);
  --grey:rgba(17,17,17,.78);
  --mute:rgba(17,17,17,.55);
  --muted:rgba(17,17,17,.55);
  --rule:rgba(17,17,17,.18);
  --rule-row:rgba(17,17,17,.18);
  background:var(--panel, var(--paper));
  color:#111111;
  overflow-y:auto;
  overflow-x:hidden;
  opacity:0; visibility:hidden;
  transition:background-color .35s var(--ease),
             opacity .35s var(--ease), visibility .35s var(--ease);
}
.proj.on{opacity:1; visibility:visible}
.proj__inner{padding:calc(var(--bar) + 4vh) var(--edge) 12vh; max-width:1500px; margin:0 auto}
.proj__title{
  font-family:var(--display);
  font-weight:700;
  font-size:clamp(30px, 5.6vw, 56px);
  line-height:1.1em;
  letter-spacing:-.06em;
  text-transform:uppercase;
  margin-bottom:clamp(20px, 4vh, 48px);
}

/* the detail body, moved in from the store and moved back out on close */
.proj .detail{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.25fr); gap:clamp(24px,4vw,72px); align-items:start}
.proj .detail__text p{max-width:60ch; margin-bottom:1em; color:var(--grey)}
/* The label is 11px and the value 15px, so their boxes are different heights:
   top-aligning them leaves the two sitting a couple of pixels apart, which is
   exactly what you see on a list this short. Baseline is the line the eye
   actually reads them both on. The row gap is what makes each label+value
   read as one line - it has to be clearly larger than the air inside a row. */
.proj .spec{display:grid; grid-template-columns:auto 1fr; align-items:baseline; gap:14px 18px; margin-bottom:26px; font-size:13px}
.proj .spec dt{font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--mute)}
.proj .spec dd{color:var(--ink)}
/* detail.css lays the project copy out as a label column beside a text
   column. That holds on a wide panel and collapses to three words a line the
   moment it narrows, so here the label sits above its text and the copy gets
   the full measure. */
.proj .detail__text{display:block}
.proj .detail__text p{
  grid-column:auto;
  max-width:62ch;
  margin:0 0 1em;
  font-size:15px;
  line-height:1.6;
  color:var(--grey);
}
.proj .detail__text p.about-label{
  grid-column:auto;
  margin:28px 0 8px;
  font-family:var(--display);
  font-size:11px; font-weight:500;
  letter-spacing:.11em; text-transform:uppercase;
  color:var(--mute);
}
.proj .detail__text p.about-label + p{margin-top:0}

/* the menu closes the same way a project does, from the same corner */
/* The panels paint over the fixed top bar, so its two corners have to be
   rebuilt on top of them - otherwise a project reads as having a MENU and
   no name at all. Same geometry, same type, one on each side. */
.proj__close, .menu .menu__close, .proj__home{
  position:fixed; top:0;
  height:var(--bar);
  display:flex; align-items:center;
  padding:0 var(--edge);
  z-index:71;
  font-family:var(--display); font-weight:500;
  font-size:clamp(12px, 1.15vw, 16px);
  text-transform:uppercase;
  color:var(--ink); background:none; border:0;
}
.proj__close, .menu .menu__close{right:0}
.proj__home{left:0}
.proj__close:hover, .menu .menu__close:hover, .proj__home:hover{opacity:.5 !important; cursor:pointer !important}

/* ---------- about / contact panels ---------- */

.panel{
  position:fixed; inset:0;
  z-index:65;
  background:var(--paper);
  overflow-y:auto;
  opacity:0; visibility:hidden;
  transition:opacity .35s var(--ease), visibility .35s var(--ease);
}
.panel.on{opacity:1; visibility:visible}
/* About, Contact and Services stand on their own rather than over the list,
   so they take a wider margin than the page edge the project panel uses -
   the copy and the forms read better pulled in from the window. */
.panel__inner{padding:calc(var(--bar) + 6vh) clamp(24px, 5vw, 72px) 12vh; max-width:1500px; margin:0 auto}
.panel h2, .panel .section__title{
  font-size:11px; font-weight:500; letter-spacing:.1em; text-transform:uppercase;
  color:var(--mute); margin-bottom:clamp(14px,2.5vh,28px); display:block;
}

/* The panel's own title is set exactly like a project title, so About and
   Contact arrive at the same weight as a project does. Only the heading that
   names the panel - "Awards" stays a small label over its column, or the
   two columns would read as two panels. */
.panel .section__title,
.panel .about > h2{
  font-family:var(--display);
  font-weight:700;
  font-size:clamp(30px, 5.6vw, 56px);
  line-height:1.1em;
  letter-spacing:-.06em;
  text-transform:uppercase;
  color:var(--ink);
  /* more air than a project title gets: there the title leads straight into
     a spec list, here it stands over the whole panel on its own */
  margin-bottom:clamp(36px, 7vh, 84px);
}
.panel .about p{
  font-size:clamp(1.05rem, 1.7vw, 1.5rem); line-height:1.45;
  letter-spacing:-.02em; max-width:62ch; margin-bottom:.8em;
}
.pairgrid{display:grid; grid-template-columns:minmax(0,1.5fr) minmax(0,1fr); gap:clamp(28px,5vw,80px); align-items:start}
/* Bio and awards are stacked in one grid cell rather than being two cells,
   so the awards list starts directly under the copy however tall the photo
   next to them happens to be. width/height are on the tag, so the space is
   held before it loads. */
.about-col{min-width:0}
.about-col .awards{margin-top:clamp(40px,7vh,84px)}
.about__portrait{
  display:block; align-self:start;
  width:100%; max-width:420px; height:auto;
}
.awards__list{list-style:none}
/* name and year on the first line, what it was for underneath. The three
   spans are siblings rather than nested, because 04-lang.js swaps the text
   of every `.awards__list li span` - a nested one would be flattened the
   first time the language changed. */
.awards__list li{
  display:flex; flex-wrap:wrap; justify-content:space-between;
  align-items:baseline; gap:4px 20px;
  padding:11px 0; border-bottom:1px solid var(--rule); font-size:14px;
}
.awards__year{color:var(--mute)}
.awards__for{
  flex-basis:100%;
  order:3;                      /* its own line, under both of the above */
  font-size:11px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--mute);
}
.panel .clients{display:none}

/* ---------- services page ---------- */
/* Each block is the same split as the reference and as the home-page band:
   what it is on the left, what it covers on the right. No rule between the
   sections - the only hairlines on the page are the ones bracketing the list
   items themselves, so the space does the separating. */
.svc{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.25fr);
  /* the number gets a row of its own above both columns, so the heading and
     the first list item share a row and can be set on the same baseline -
     with the number inside the left column the list started level with it
     instead, half a line above the title */
  grid-template-areas:
    "num  ."
    "head list";
  column-gap:clamp(20px,4vw,72px);
  align-items:baseline;
  padding-block:clamp(28px,5vh,56px);
}
.svc__head{grid-area:head; display:grid; gap:10px}
.svc__list{grid-area:list}
/* the number is a marker, not a heading - same small caps as the column
   labels on the project rows */
.svc__num{
  grid-area:num; margin-bottom:12px;
  font-family:var(--display); font-weight:500;
  font-size:11px; letter-spacing:.14em; color:var(--mute);
}
.svc h3{
  margin:0;
  font-family:var(--display); font-weight:700;
  font-size:clamp(20px,2.6vw,32px); line-height:1.15;
  letter-spacing:-.04em; text-transform:uppercase; color:var(--ink);
}
.svc__lede{
  margin:0; max-width:46ch;
  font-size:15px; line-height:1.55; color:var(--grey);
}
.svc__list{list-style:none; margin:0; padding:0}
/* the first row has no top rule: the line sat above the heading it belongs to */
.svc__list li:first-child{padding-top:0}
.svc__list li{
  padding:11px 0; border-bottom:1px solid var(--rule);
  font-size:15px; line-height:1.45; color:var(--ink);
}


/* the form is back, so the panel is the same two columns as About/Awards:
   the form takes the measure, the direct details sit beside it. The narrow
   block at the foot of this file collapses it to one column. */
.contact{
  display:grid; grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);
  gap:clamp(28px,5vw,80px); align-items:start;
}
/* on its own the form would stretch to the full 1.5fr track, which on a wide
   panel leaves three inputs the width of a paragraph of body copy */
.contact form{max-width:460px}
/* The form and the details carry the whole panel - there is nothing else on
   it - so they are set to be read, not to sit quietly in a corner: the
   labels keep their small-caps role but gain weight, and what you actually
   type or click is a good step larger than body copy. */
.field{margin-bottom:26px}
.field label{display:block; font-family:var(--display); font-size:12px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--mute); margin-bottom:9px}
.field input, .field textarea{width:100%; font:inherit; font-size:clamp(17px,1.6vw,20px); font-weight:500; color:var(--ink); background:none; border:0; border-bottom:1px solid var(--rule); padding:11px 0; border-radius:0}
.field input:focus, .field textarea:focus{outline:0; border-bottom-color:var(--ink)}
.field textarea{min-height:130px; resize:vertical}
.send{font-family:var(--display); font-size:14px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--paper); background:var(--ink); border:0; padding:17px 32px}
.send:hover{opacity:.78; cursor:pointer !important}
.direct{font-size:clamp(17px,1.6vw,20px)}
.direct dt{font-family:var(--display); font-size:12px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--mute); margin-top:24px}
.direct dd{font-weight:500}
.direct dd a{color:var(--ink); text-decoration:none; border-bottom:1px solid var(--rule)}
.direct dd a:hover{border-bottom-color:var(--ink)}

/* ---------- offstage store ---------- */
/* the 24 detail blocks live here so 07-projects.js can build their carousels
   at load; opening a project moves the node onto the panel and closing moves
   it back. Kept laid out (not display:none) so image sizing is measurable. */
.store{position:fixed; left:-99999px; top:0; width:1200px; visibility:hidden; pointer-events:none}

/* ---------- narrow ---------- */

@media (max-width:760px){
  .proj .detail, .pairgrid, .contact{grid-template-columns:1fr}
  /* the wrapper stops being a box so its two children become grid items in
     their own right, which lets the portrait sit between them rather than
     being stranded under the awards list */
  .svc{
    grid-template-columns:1fr;
    grid-template-areas:"num" "head" "list";
    row-gap:32px; align-items:start;
  }
  /* the description belongs to the heading above it, so these two sit
     tight - the air goes below them, before the list */
  .svc__head{gap:8px}
  /* stacked, the list has no column of its own to separate it from the
     heading, so it takes more air than the row gap gives. On its own margin
     rather than a bigger row-gap, which would push the number off the title
     it labels by the same amount. */
  .svc__list{margin-top:16px}
  .about-col{display:contents}
  .about{order:1}
  .about__portrait{order:2; max-width:none}
  .about-col .awards{order:3; margin-top:clamp(32px,5vh,56px)}
  /* padding-INLINE, not the shorthand: this rule sits after the list's own
     narrow-window block, and `padding:0 14px` there silently zeroed the
     vertical padding that separates a caption from the next project */
  /* the strip's lower gap, set to land level with the upper one once the
     footer bar's own vertical centring is accounted for */
  .clients{padding-bottom:50px}
  /* On a phone the name and the chevron are centred on the screen but sit
     between a wide left label and a narrow right one - 10px of air on the
     chevron's left against 128px on its right - so neither reads as centred.
     Each gets a line of its own instead, clear of both labels: the name
     drops below the top bar, the chevron rises above the foot bar. */
  .hero__now{
    top:calc(var(--bar) - 18px);
    max-width:calc(100vw - 2 * var(--edge));
  }
  .hero__down{bottom:calc(var(--bar) - 18px)}
  /* a smaller mark on a small screen. The stroke is widened to match,
     because scaling a 1.6 hairline down by a quarter leaves it wispy -
     2 x .75 lands back on the weight it has at full size. */
  .hero__down svg{width:18px; height:10.5px}
  .hero__down svg path{stroke-width:2}

  .loop__item{padding-inline:14px}
  /* This is what actually governs the list's air on a phone; the .loop rule
     further up only ever applied to wider windows. The two numbers differ
     on purpose: they are padding, but what has to match is the gap you see
     either side of the logo strip, and the footer bar below it centres its
     text inside 58px of its own height while the list's last caption ends
     almost on its box. Measured, not derived - see the pair below. */
  .loop{padding-block:clamp(20px, 5vh, 56px) 59px}

  /* On a phone the gallery is scrolled, not swiped: the stage stops being a
     fixed-ratio window with one slide cross-faded inside it and becomes a
     plain column of images at their own proportions. Overriding the layout
     here rather than in 07-projects.js keeps one carousel implementation -
     the JS still tracks a "current" slide and still answers taps, it just
     has nothing left to hide. */
  .detail .carousel__stage{
    aspect-ratio:auto; overflow:visible;
    display:flex; flex-direction:column; gap:10px;
    cursor:default;
    touch-action:auto;          /* nothing to swipe, so let the page scroll */
    transition:none;
  }
  /* set from the first image's natural size once it loads - meaningless now
     that each image carries its own height */
  .detail .carousel__stage.is-portrait,
  .detail .carousel__stage.is-square{aspect-ratio:auto}

  .detail .carousel__slide{position:static; opacity:1; transition:none}
  /* the drag handler writes a translateX straight onto the current slide's
     style attribute; a stray horizontal drag during a scroll would otherwise
     shunt one image sideways, and inline styles only yield to !important */
  .detail .carousel__slide{transform:none !important}
  .detail .carousel__slide img,
  .detail .carousel__stage.is-portrait img,
  .detail .carousel__stage.is-square img{
    height:auto; object-fit:contain; object-position:center;
  }

  /* both are affordances for a one-at-a-time gallery: there is no current
     image to count or to open on its own any more */
  .detail .filmstrip__bar,
  .detail .carousel__fullscreen{display:none}
}
@media (hover:none){
  .loop:has(.loop__item.is-held) .loop__item{opacity:1}
  .menu:hover button{opacity:1}
}

/* ---------- pointer ---------- */
/* the signature orange dot from the earlier version stands in for the native
   pointer here too (markup in index.html, behaviour in js/02-cursor.js,
   looks in css/cursor.css). Last in the file so it beats the cursor:pointer
   hover rules above; touch devices keep their normal behaviour. */
*, *::before, *::after{cursor:none !important}
@media (hover:none){
  *, *::before, *::after{cursor:auto !important}
}
