/* ==========================================================================
   Drawn to Detail Studio — shared stylesheet
   One file, linked from every page. Change a value here, every page follows.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   Palette sampled directly out of "inspiration/Gallery page.psd" — the same
   render the hero plates are cut from, so type and chrome sit in the room
   rather than beside it. Every hex below is a real pixel from that file.
   Fonts are [PLACEHOLDER] stacks — the real families are not yet identified.
   -------------------------------------------------------------------------- */
:root {
  /* Room palette */
  /* --wall is the plate's row 0 exactly. The hero paints the band above the
     photograph with it, so the painted wall and the photographed wall meet
     with no seam. Change it only if the plate is recut. */
  --wall:         #dbdbdc;
  --wall-hi:      #e3e5e6;
  --wall-deep:    #d0cfce;  /* wall just above the floor join */
  --concrete:     #8a8e8f;  /* polished floor, mid */
  --floor-edge:   #808786;  /* the plate's very last row — the controls bar
                               starts here so the floor runs into it */
  --concrete-deep:#6e7375;  /* floor, foreground */
  --ink:          #1b2228;  /* the frame moulding's black */
  --ink-soft:     #2b3238;
  --slate:        #525c63;
  --brass:        #7a6431;  /* the rope, darkened to carry text weight */
  --brass-deep:   #6d5626;
  --brass-bright: #c9ad78;  /* the rope's lit face — for use on dark only */
  --silver:       #bdc4c7;  /* the frame's outer lip */
  --paper:        #ffffff;
  --paper-alt:    #eceeef;

  /* Semantic roles */
  --bg:           var(--paper);
  --bg-alt:       var(--paper-alt);
  --bg-invert:    var(--ink-soft);
  --text:         var(--ink);
  --text-muted:   var(--slate);
  --text-invert:  #e8eaeb;
  --accent:       var(--brass);
  --rule:         #d3d7d9;
  --focus:        var(--brass);

  /* Type */
  --font-body:    system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Display deliberately resolves to the body stack. It was ui-rounded, which
     gave macOS SF Pro Rounded and everyone else a flat grotesque — so the
     headings had one voice on Apple devices and another everywhere else. Using
     the body stack makes every visitor see the same thing, which is the whole
     reason for the change.

     The two tokens are identical on purpose; this is not duplication to tidy
     away. Keeping the seam means a real display face can be dropped in here in
     one line, and all five display usages follow it. */
  --font-display: var(--font-body);
  --font-serif:   "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-mono:    ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* The posters' own display face: a light geometric sans, near-Futura.
     Matched with what ships on the OS, since the site has no build step and
     no webfont files. Futura and Avenir cover macOS; Century Gothic and URW
     Gothic cover Windows and Linux. Everywhere else falls back to the body
     stack, which is grotesque rather than geometric — close, not identical.
     Self-hosting one woff2 (Jost is a free Futura-alike) is the only way to
     guarantee it; that is one @font-face block and no build step, so say the
     word. */
  --font-poster:  Futura, "Avenir Next", Avenir, "Century Gothic", "URW Gothic",
                  "Trebuchet MS", system-ui, sans-serif;

  --step--1: clamp(0.78rem, 0.75rem + 0.15vw, 0.86rem);
  --step-0:  clamp(1rem, 0.96rem + 0.2vw, 1.09rem);
  --step-1:  clamp(1.2rem, 1.1rem + 0.5vw, 1.5rem);
  --step-2:  clamp(1.5rem, 1.3rem + 1vw, 2.1rem);
  --step-3:  clamp(2rem, 1.6rem + 2vw, 3.2rem);
  --step-4:  clamp(2.4rem, 1.7rem + 3.4vw, 4.4rem);

  /* Space */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4.5rem;
  --sp-9: 6.5rem;

  --measure: 62ch;
  --shell:   88rem;
  --radius:  2px;

  /* ---- Gallery scene geometry -------------------------------------------
     The hero is one photographic room, cut out of the PSD into three plates
     that move at three different speeds. Every measurement below is the
     fraction that thing occupies in the original 1920 x 1080 render, so the
     whole scene scales from a single height and nothing drifts out of
     register. Do not hand-tune one of these without the others.

       room-slice.png      wall + floor      never moves
       (the frames)        HTML, not a plate  drifts at DRIFT_SPEED
       stanchion-rope.png  posts + rope      drifts ~2x faster
     ---------------------------------------------------------------------- */
  --scene-h:        clamp(430px, 48vw, 760px);

  --frame-w:        calc(var(--scene-h) * 0.4657);  /* 503 / 1080 */
  --frame-h:        calc(var(--scene-h) * 0.6778);  /* 732 / 1080 */
  --frame-top:      calc(var(--scene-h) * 0.1472);  /* 159 / 1080 */
  --frame-gap:      calc(var(--scene-h) * 0.1120);  /* 121 / 1080 */
  --frame-bezel:    calc(var(--frame-w)  * 0.0696); /*  35 / 503  */
  --frame-bezel-b:  calc(var(--frame-w)  * 0.1074); /*  54 / 503  */

  --strip-w:        calc(var(--scene-h) * 0.5);     /* 540 / 1080 */
  /* 234 / 1080. Was 0.1704 (184) until 2026-08-30, which cut the stanchions'
     reflections off mid-fade — the tile now runs to the bottom of the render,
     so strip-top + strip-h is exactly 1. If the tile is ever recut, this is
     the number that must move with it: main.js reads --strip-w for the
     parallax period, but nothing reads the height, so a mismatch here just
     squashes the plate silently. */
  --strip-h:        calc(var(--scene-h) * 0.2167);  /* 234 / 1080 */
  --strip-top:      calc(var(--scene-h) * 0.7833);  /* 846 / 1080 */

  /* The aspect every artwork is shown at. The photographed frame's own
     opening is 432:643, but every real piece the studio supplied is
     exactly 2:3 — so the frame stretches to the art rather than the art
     being cropped to the frame. The moulding is a nine-slice, so it
     absorbs the difference without distorting its corners. */
  --art-ratio: 2 / 3;
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  /* Global guard: nothing may push the page sideways. */
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 0 0 var(--sp-4);
  text-wrap: balance;
}

h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-1); }

p { margin: 0 0 var(--sp-4); }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-underline-offset: 0.18em; }
a:hover { color: var(--brass-deep); }

img, svg { max-width: 100%; height: auto; display: block; }

ul { margin: 0; padding: 0; list-style: none; }

button { font: inherit; color: inherit; }

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

/* --------------------------------------------------------------------------
   3. Utilities
   -------------------------------------------------------------------------- */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.shell {
  width: min(100% - 2.5rem, var(--shell));
  margin-inline: auto;
}

.eyebrow {
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-3);
}

.lede {
  font-size: var(--step-1);
  max-width: var(--measure);
  color: var(--text-muted);
}

.placeholder-tag {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  color: var(--brass-deep);
  background: var(--paper-alt);
  border: 1px dashed var(--brass);
  border-radius: var(--radius);
  padding: 0.1em 0.45em;
  white-space: nowrap;
}

.section { padding-block: var(--sp-8); }
.section--tint { background: var(--bg-alt); }
.section--invert { background: var(--bg-invert); color: var(--text-invert); }
.section--invert h1,
.section--invert h2 { color: var(--paper); }
.section--invert .eyebrow { color: rgba(232, 234, 235, 0.72); }
.section--invert .lede { color: rgba(232, 234, 235, 0.86); }
/* Brass is only legible on the dark ground in its lit form. */
.section--invert a { color: var(--brass-bright); }
.section--invert { --focus: var(--brass-bright); }

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: 0.75rem 1.6rem;
  border: 2px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}

.btn--primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.btn--primary:hover { background: var(--brass-deep); border-color: var(--brass-deep); color: var(--paper); }
/* On the dark ground the solid-ink button disappears, so it inverts to brass. */
.section--invert .btn--primary {
  background: var(--brass-bright);
  border-color: var(--brass-bright);
  color: var(--ink);
}
.section--invert .btn--primary:hover { background: var(--paper); border-color: var(--paper); color: var(--ink); }

.btn--ghost {
  background: transparent;
  border-color: currentColor;
  color: inherit;
}
.btn--ghost:hover { background: rgba(122, 100, 49, 0.12); color: inherit; }

.btn--block { width: 100%; }

/* --------------------------------------------------------------------------
   5. Canonical header
   The markup for this block is byte-identical in every page. Active state is
   driven by [data-specialty] on <body>, so no per-page markup change is needed.
   -------------------------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100%;
  z-index: 100;
  background: var(--ink);
  color: var(--paper);
  padding: var(--sp-3) var(--sp-4);
  text-decoration: none;
  border-radius: var(--radius);
}
.skip-link:focus { top: var(--sp-3); }

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}

.site-header__inner {
  width: min(100% - 2.5rem, var(--shell));
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: 4rem;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 0.4em;
  text-decoration: none;
  color: var(--ink);
}
.brand__name {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--step-1);
  letter-spacing: 0.01em;
}
.brand__sub {
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.nav-toggle {
  display: none;
  flex-shrink: 0;   /* never let a long specialty name squeeze it off-screen */
  width: 44px; height: 44px;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
}
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  display: block;
  width: 20px; height: 2px;
  background: var(--ink);
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-toggle__bars { position: relative; }
.nav-toggle__bars::before,
.nav-toggle__bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle__bars::before { top: -6px; }
.nav-toggle__bars::after  { top: 6px; }
[aria-expanded="true"] .nav-toggle__bars { background: transparent; }
[aria-expanded="true"] .nav-toggle__bars::before { transform: translateY(6px) rotate(45deg); }
[aria-expanded="true"] .nav-toggle__bars::after  { transform: translateY(-6px) rotate(-45deg); }

.site-nav__list {
  display: flex;
  gap: var(--sp-5);
}
.site-nav__list a {
  display: block;
  padding: var(--sp-3) 0;
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--slate);
  border-bottom: 2px solid transparent;
}
.site-nav__list a:hover,
.site-nav__list a:focus-visible { color: var(--ink); border-bottom-color: var(--brass); }

/* --------------------------------------------------------------------------
   6. "You are here" — the specialty label in the header
   --------------------------------------------------------------------------
   Chris did not want the three specialties presented as an obvious switcher,
   so the header no longer offers them as links. It names the one you are in
   and nothing else; the footer is where every specialty stays reachable.

   This is a label, not a link — deliberately. Making it a link to the
   specialty gallery would rebuild the switcher one page at a time, and the
   nav beside it already carries Gallery.

   The auto end-margin is what pushes the nav to the far right, so the brand
   and the label read together as one unit.
   -------------------------------------------------------------------------- */
.site-header__here {
  margin: 0 auto 0 0;
  padding-inline-start: var(--sp-4);
  border-inline-start: 1px solid var(--rule);
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   7. Hero — the photographic gallery room

   Three stacked layers inside .gallery__stage, back to front:

     the stage's own background   room-slice.png, repeated across  static
     .gallery__viewport           the framed artwork               drifts
     .gallery__rope               stanchion-rope.png, repeated     drifts ~2x

   Only the two front layers are transformed, and only ever on the x axis, so
   the whole hero is one composited layer per frame with no layout work.
   -------------------------------------------------------------------------- */
.hero {
  /* The intro sits on the same wall the plate starts with, so the painted
     color and the photograph meet with no visible join. */
  background: var(--wall);
  color: var(--ink);
  padding-top: var(--sp-7);
  overflow: hidden;
}

/* Only the headline hangs above the artwork. The location line and the
   subtitle moved below the room — see .gallery-note — because between them
   they pushed the first frame about 125px further down the page. */
.hero__intro {
  width: min(100% - 2.5rem, 56rem);
  margin-inline: auto;
  text-align: center;
  margin-bottom: var(--sp-6);
}
.hero__intro h1 { color: var(--ink); margin-bottom: 0; }

/* --- The label under the room -------------------------------------------
   Reads as a museum didactic panel: where the studio is, what the work is,
   then how it is made. Three paragraphs at descending weight, so they group
   as one block rather than stacking as three equal ones. */
.gallery-note { text-align: center; }
.gallery-note__lede {
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.25;
  color: var(--text);
  text-wrap: balance;
  max-width: 34ch;
  margin: 0 auto var(--sp-5);
}
.gallery-note .lede { margin-inline: auto; }

/* --- The stage ----------------------------------------------------------- */
.gallery { position: relative; }

.gallery__stage {
  position: relative;
  height: var(--scene-h);
  /* An 8px-wide vertical slice of the room, repeated. The room has no
     horizontal detail, so this covers any width for half a kilobyte. */
  background-image: url("/assets/img/scene/room-slice.png");
  background-image: image-set(
    url("/assets/img/scene/room-slice.webp") type("image/webp"),
    url("/assets/img/scene/room-slice.png")  type("image/png")
  );
  /* Stretched, not tiled. The slice is horizontally uniform by construction,
     so stretching is exact — and tiling a 5-6px fractional width across the
     viewport puts visible vertical stripes into the wall. */
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

/* The horizontal falloff — corners a touch darker than center — is added
   here rather than baked into the plate, so it answers to the viewport
   instead of being frozen at one width. */
.gallery__stage::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(135% 100% at 50% 40%,
              rgba(0, 0, 0, 0) 58%, rgba(23, 28, 32, 0.13) 100%);
  pointer-events: none;
}

.gallery__viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* Back layer: the artwork. padding-top lands the frames on the same wall
   line they occupy in the render. */
.gallery__track {
  display: flex;
  gap: var(--frame-gap);
  padding: var(--frame-top) var(--frame-gap) 0;
  width: max-content;
  will-change: transform;
}

/* Front layer: stanchions and rope, one repeating photographic tile.
   The tile is cut at the bottom of the rope's sag, so it holds one whole
   post in its center and half a span at each edge and repeats without a
   seam or a kink. */
.gallery__rope {
  position: absolute;
  left: 0;
  top: var(--strip-top);
  /* One tile wider than the stage: the parallax translate is taken modulo
     the tile width, so the layer never has to be wider than this. */
  width: calc(100% + var(--strip-w));
  height: var(--strip-h);
  background-image: url("/assets/img/scene/stanchion-rope.png");
  background-image: image-set(
    url("/assets/img/scene/stanchion-rope.webp") type("image/webp"),
    url("/assets/img/scene/stanchion-rope.png")  type("image/png")
  );
  background-repeat: repeat-x;
  /* main.js reads this width back to get the parallax period. It is the one
     number the script needs from the CSS, so the CSS stays authoritative. */
  background-size: var(--strip-w) 100%;
  pointer-events: none;
  will-change: transform;
}

/* --- A framed piece ------------------------------------------------------ */
.frame {
  flex: 0 0 var(--frame-w);
  width: var(--frame-w);
  position: relative;
}

.frame__link {
  display: block;
  text-decoration: none;
  color: inherit;
}

/* The moulding is the photographed frame, used as a nine-slice border so it
   fits whatever the artwork's proportions turn out to be. The corners come
   from the render; only the flat runs stretch. */
/* The moulding itself, shared by the gallery wall and the artwork detail
   page. Only the border-width differs between the two, because they size
   themselves differently: the gallery scales with the room, the detail page
   with its column. Everything else — the image, the slices, the shadow —
   is one definition. */
.moulding {
  display: block;
  border-style: solid;
  border-image-source: url("/assets/img/scene/frame.png");
  border-image-source: image-set(
    url("/assets/img/scene/frame.webp") type("image/webp"),
    url("/assets/img/scene/frame.png")  type("image/png")
  );
  border-image-slice: 35 35 54 35;
  border-image-width: 1;
  border-image-repeat: stretch;
}

.frame__mat {
  height: var(--frame-h);
  border-width: var(--frame-bezel) var(--frame-bezel) var(--frame-bezel-b);
  /* The render carries no shadow — frames move independently of the wall, so
     their shadow has to be drawn live or it would slide across the room. */
  filter: drop-shadow(calc(var(--scene-h) * 0.004) calc(var(--scene-h) * 0.008)
                      calc(var(--scene-h) * 0.012) rgba(24, 30, 35, 0.34));
  transition: transform 0.3s ease;
}
.frame__link:hover .frame__mat { transform: translateY(-6px); }

.frame__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--concrete);
}

/* The wall label, engraved above each piece. Kept to one line — a long name
   is ellipsised rather than allowed to wrap and shove the frames out of
   line with the stanchions. */
.frame__title {
  /* Block matters even though it is positioned: in the mobile stack the
     label goes back to static flow, and text-align has no effect on an
     inline box. */
  display: block;
  position: absolute;
  bottom: 100%;
  left: 0;
  right: 0;
  margin-bottom: calc(var(--scene-h) * 0.028);
  text-align: center;
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--slate);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.frame__link:hover .frame__title { color: var(--ink); }

/* --- Controls ------------------------------------------------------------ */
.gallery__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding-block: var(--sp-4) var(--sp-7);
  /* Picks up exactly where the plate's last row leaves off, so the floor
     runs off the bottom of the room rather than stopping at a hard edge. */
  background: linear-gradient(var(--floor-edge), var(--concrete-deep));
}
/* Tertiary: icon only, no outline. The 44px box is the hit area, not a shape —
   nothing is painted until hover. */
.gallery__btn {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: rgba(255, 255, 255, 0.78);
  cursor: pointer;
  transition: color 0.18s ease, background-color 0.18s ease;
}
.gallery__btn:hover {
  color: var(--paper);
  background: rgba(255, 255, 255, 0.14);
}
.gallery__controls { --focus: var(--brass-bright); }

.gallery__icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Playing → offer Pause; paused (aria-pressed="true") → offer Play. */
.gallery__icon-play { display: none; }
[aria-pressed="true"] .gallery__icon-play  { display: block; }
[aria-pressed="true"] .gallery__icon-pause { display: none; }

/* --------------------------------------------------------------------------
   8. Work grid (used on /all and specialty index sections)
   -------------------------------------------------------------------------- */
.work-grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}

.work-card {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.work-card a { text-decoration: none; color: inherit; display: block; }
.work-card img {
  width: 100%;
  aspect-ratio: var(--art-ratio);
  object-fit: cover;
}
.work-card__body { padding: var(--sp-4); }
.work-card__place {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brass-deep);
}
.work-card__title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin: 0.2em 0 0.35em;
}
.work-card__note { font-size: var(--step--1); color: var(--text-muted); margin: 0; }

/* --------------------------------------------------------------------------
   9. Specialty chooser (site root)
   Three pieces hung on the gallery wall, one per collection. It reuses
   .moulding, so the frame here is the same photographed frame as everywhere
   else and the front door reads as the same room.
   -------------------------------------------------------------------------- */
.chooser {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* The same wall the galleries open with, so clicking through is continuous. */
  background: linear-gradient(var(--wall-hi) 55%, var(--wall-deep));
  color: var(--ink);
  padding-block: var(--sp-7);
  text-align: center;
}
.chooser h1 { color: var(--ink); font-size: var(--step-2); margin-bottom: var(--sp-3); }
.chooser__lede {
  color: var(--slate);
  font-size: var(--step-0);
  max-width: 48ch;
  margin-inline: auto;
}

.chooser__wall {
  /* Narrower than the shell on purpose. At full width the three pieces are
     tall enough to push the page to twice the viewport, and a chooser that
     cannot show its choices at once is not doing its job. */
  width: min(100%, 30rem);
  margin: var(--sp-7) auto 0;
  display: grid;
  gap: var(--sp-7);
  grid-template-columns: 1fr;
}
.chooser__piece { container-type: inline-size; }

.chooser__link {
  display: grid;
  gap: var(--sp-3);
  text-decoration: none;
  color: inherit;
}
.chooser__mat {
  display: block;
  /* Same 35/503 and 54/503 as the galleries, measured against this column.
     The px line is the fallback for browsers without container query units —
     without it border-width falls back to `medium` and shreds the nine-slice. */
  border-width: 22px 22px 34px;
  border-width: 6.96cqw 6.96cqw 10.74cqw;
  filter: drop-shadow(0.4cqw 0.9cqw 1.8cqw rgba(24, 30, 35, 0.32));
  transition: transform 0.35s ease;
}
.chooser__mat img {
  width: 100%;
  aspect-ratio: var(--art-ratio);
  object-fit: cover;
  display: block;
}
.chooser__link:hover .chooser__mat { transform: translateY(-8px); }

.chooser__name {
  font-family: var(--font-display);
  /* One line each. At step-2 "Spaces We Shape" wraps in a 200px column and
     the three labels come out ragged against each other. */
  font-size: var(--step-1);
  line-height: 1.2;
  color: var(--ink);
  text-wrap: balance;
}
.chooser__link:hover .chooser__name { color: var(--brass-deep); }
.chooser__note {
  font-size: var(--step--1);
  color: var(--text-muted);
  max-width: 34ch;
  margin-inline: auto;
  text-wrap: balance;
}

.chooser__all {
  margin-top: var(--sp-6);
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   10. Artwork detail page
   -------------------------------------------------------------------------- */
.work {
  display: grid;
  gap: var(--sp-7);
  padding-block: var(--sp-7);
  grid-template-columns: 1fr;
}
/* The piece hangs on a panel of the same wall it hangs on in the gallery,
   in the same photographed frame, so arriving here from the carousel reads
   as walking up to the work rather than opening a product page. */
.work__media {
  background: var(--wall);
  padding: clamp(1.5rem, 5%, 3.5rem);
  /* Establishes the container the moulding measures its border against. */
  container-type: inline-size;
}
.work__moulding {
  /* Fixed fallback first: border-width takes no percentage, so a browser
     without container query units would drop the line below and fall back to
     `medium` — a 3px hairline that shreds the nine-slice. These are the right
     numbers for a column around 490px. */
  border-width: 34px 34px 52px;
  /* The same 35/503 and 54/503 the gallery uses, but resolved against this
     column instead of the room's height — cqw is 1% of .work__media's width,
     which is the only way to make a border-width track an element that has
     no fixed size. */
  border-width: 6.96cqw 6.96cqw 10.74cqw;
  filter: drop-shadow(0.4cqw 0.8cqw 1.6cqw rgba(24, 30, 35, 0.3));
}
.work__moulding img {
  width: 100%;
  aspect-ratio: var(--art-ratio);
  object-fit: cover;
  display: block;
}
.work__title { margin-bottom: var(--sp-3); }
.work__tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-5); }
.work__tag {
  font-size: var(--step--1);
  padding: 0.25em 0.7em;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bg-alt);
}

/* The three facts the studio quotes on every piece: what it is printed on,
   how long it takes, what comes with it. Confirmed, not [PLACEHOLDER]. */
.work__facts {
  display: grid;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--text-muted);
}
.work__facts span {
  color: var(--text);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.work__facts span::after { content: " —"; }

.panel {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--sp-5);
  margin-bottom: var(--sp-5);
  background: var(--paper);
}
.panel h2 { font-size: var(--step-1); margin-bottom: var(--sp-2); }
.panel__note { font-size: var(--step--1); color: var(--text-muted); }

.size-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-block: var(--sp-4);
}
.size-option { position: relative; }
.size-option input {
  position: absolute;
  opacity: 0;
  inset: 0;
  cursor: pointer;
}
.size-option span {
  min-height: 44px;
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  cursor: pointer;
}
/* The price is the second line of information in the same control, so it is
   set apart by weight and colour rather than by being a separate element the
   eye has to pair back up with its size. */
.size-option__price {
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.size-option input:checked + span .size-option__price { color: var(--brass-deep); }
.size-option input:checked + span {
  border-color: var(--ink);
  background: var(--bg-alt);
  box-shadow: inset 0 0 0 1px var(--ink);
}
.size-option input:focus-visible + span {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.commission-options { display: grid; gap: var(--sp-3); margin-top: var(--sp-4); }

/* Walking the pieces in carousel order, wrapping at both ends. Static links,
   so they survive JavaScript being off and the link checker can see them. */
.work-nav {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: 1fr;
  padding-block: var(--sp-6);
  border-top: 1px solid var(--rule);
}
.work-nav__link {
  display: flex;
  flex-direction: column;
  gap: 0.2em;
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
  background: var(--paper);
  transition: border-color 0.18s ease, background-color 0.18s ease;
}
.work-nav__link:hover { border-color: var(--brass); background: var(--bg-alt); color: var(--text); }
.work-nav__dir {
  font-family: var(--font-serif);
  font-size: var(--step--1);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.work-nav__dir::before { content: "← "; }
.work-nav__link--next { text-align: right; }
.work-nav__link--next .work-nav__dir::before { content: none; }
.work-nav__link--next .work-nav__dir::after { content: " →"; }
.work-nav__title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.2;
}

/* --------------------------------------------------------------------------
   10c. About pages
   Each collection carries its own color, sampled from its own artwork:
   the teal of Marina City, the navy of the Ford F100, the red of the BelAir.
   Product started as the BelAir's sage, which sat too close to the
   architecture teal to read as a different collection; it moved to the
   deep red from the same poster. All three clear 6.7:1 against white. Without it the three About pages are the same page of
   grey text three times over.
   -------------------------------------------------------------------------- */
/* --collection       the banded background; only ever carries large text
   --collection-ink   the same idea as type on white, which needs 4.5:1
   --collection-warm  the hairline under the band; decorative, no text on it
   --collection-lit   the pool where the light falls, top left
   --collection-sheen the long sweep across the field

   The two washes LIGHTEN on the dark grounds and DARKEN on the light one.
   That is deliberate — see the product block.

   White is the least forgiving choice for the two dark grounds: it costs
   roughly 6:1 down to ~3.2:1 behind the headline. Still above the 3:1 bar
   large text needs, but it means NO TEXT UNDER 24px may sit on those bands.
   Swapping white for cream #F4E3C8 at the same alphas buys ~0.5 back — see
   TODO-next.md, kept as an option on purpose. */
[data-specialty="architecture"] {
  --collection: #1a5552; --collection-ink: #1a5552; --collection-warm: #e1694e;
  --collection-lit:   rgba(255, 255, 255, 0.39);
  --collection-sheen: rgba(255, 255, 255, 0.15);
}
[data-specialty="automotive"] {
  /* The 1985 Ford EXP's own ground — 77% of that poster. It was the 1956
     F100's navy, which fought the red poster beside it. The EXP is the
     confirmed first car and is not changing, so the page moved to it. */
  --collection: #951625; --collection-ink: #951625; --collection-warm: #E3A394;
  --collection-lit:   rgba(255, 255, 255, 0.43);
  --collection-sheen: rgba(255, 255, 255, 0.16);
}
[data-specialty="product"] {
  /* The BelAir poster's pink reads beautifully as a field but only manages
     3.18:1 with white, so type takes the deeper red sampled from the same
     poster: 4.51:1 on white. */
  --collection: #E46A71; --collection-ink: #DB3748; --collection-warm: #427968;
  /* Product's two washes DARKEN where the other collections lighten, and the
     reason is the field, not taste: #E46A71 is already light, so a white
     highlight has no headroom — at the shared 0.35 it drops white text to
     2.10:1, and even 0.06 would be the ceiling. On a light ground you model
     form with shadow instead of light, which is what the BelAir poster does
     with its sage band. Same geometry and same opacities as the other two;
     only the direction of the light is inverted. */
  --collection-lit:   rgba(168, 47, 56, 0.35);
  --collection-sheen: rgba(219, 55, 72, 0.23);
}

.about-hero {
  /* Not a flat field. The posters model a body panel with two moves: one
     broad warm pool where the light falls, and one long soft sweep across
     it. Geometry set by hand in Figma and read back off the gradient
     transforms — the light comes from the top left. Both are pulled from the collection's own artwork, and both are
     gradients — no image, no extra request. Kept low enough that it reads
     as depth rather than as decoration. */
  background-color: var(--collection, var(--ink));
  background-image:
    /* Geometry revised 2026-08-15: was 57% 121% at -4% -6%. The pool is now
       narrower and much taller, and its center sits far above the band
       (-59.44%), so only its lower falloff crosses the visible area. That is
       what makes it read as subtler — less of the bright core lands on the
       hero at all. The stops are unchanged. */
    radial-gradient(47.65% 161.83% at 11.58% -59.44%,
                    var(--collection-lit, transparent) 0%,
                    transparent 58%),
    linear-gradient(103deg, transparent 28%, var(--collection-sheen, transparent) 66%,
                    transparent 99%);
  position: relative;
  overflow: hidden;
  color: var(--paper);
  padding-block: var(--sp-8);
  /* A hairline of the collection's second color, the way the posters band
     one flat field against another. */
  border-bottom: 6px solid var(--collection-warm, var(--brass));
}
.about-hero::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent 4%,
              rgba(255, 255, 255, 0.62) 38%, transparent 88%);
  pointer-events: none;
}

.about-hero h1 {
  color: var(--paper);
  font-family: var(--font-poster);
  /* The posters set this face light and wide, never bold. */
  font-weight: 300;
  letter-spacing: 0.01em;
  font-size: var(--step-4);
  margin-bottom: var(--sp-4);
}
.about-hero .eyebrow { color: rgba(255, 255, 255, 0.72); }
.about-hero__lede {
  font-family: var(--font-poster);
  font-weight: 300;
  /* Never below 24px on purpose — see the product tokens above. */
  font-size: var(--step-2);
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.9);
  max-width: 46ch;
  margin: 0;
}

.about-intro { display: grid; gap: var(--sp-7); grid-template-columns: 1fr; }
.about-intro__text > .lede { margin-bottom: var(--sp-6); }
.about-intro h2 {
  font-family: var(--font-poster);
  font-weight: 400;
  font-size: var(--step-2);
  color: var(--collection-ink, var(--ink));
}

/* The featured piece, in the same moulding as everywhere else. */
.about-piece {
  margin: 0;
  /* Size containment: this element's width has to come from the grid, never
     from its own contents. Do NOT add max-width + auto margins here — that
     makes it shrink-to-fit, which under containment resolves to zero and the
     poster disappears entirely. Cap the grid column instead, below. */
  container-type: inline-size;
}
.about-piece__mat {
  display: block;
  border-width: 24px 24px 37px;               /* fallback: no container units */
  border-width: 6.96cqw 6.96cqw 10.74cqw;
  filter: drop-shadow(0.4cqw 0.9cqw 1.8cqw rgba(24, 30, 35, 0.3));
}
.about-piece__mat img {
  width: 100%;
  aspect-ratio: var(--art-ratio);
  object-fit: cover;
  display: block;
}
.about-piece figcaption {
  margin-top: var(--sp-3);
  font-size: var(--step--1);
  color: var(--text-muted);
  text-align: center;
}

.about-cols { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; }
.about-cols h2 {
  font-family: var(--font-poster);
  font-weight: 500;
  font-size: var(--step-1);
  padding-top: var(--sp-3);
  border-top: 2px solid var(--collection-ink, var(--rule));
}
.about-cols p { color: var(--text-muted); margin: 0; }

/* --------------------------------------------------------------------------
   11. Canonical footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--ink);
  color: var(--text-invert);
  padding-top: var(--sp-8);
  margin-top: var(--sp-9);
  --focus: var(--brass-bright);
}
.site-footer__inner {
  width: min(100% - 2.5rem, var(--shell));
  margin-inline: auto;
  display: grid;
  gap: var(--sp-6);
  grid-template-columns: 1fr;
}
.site-footer__brand .brand__name {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--step-1);
  margin-bottom: var(--sp-2);
  color: var(--paper);
}
.site-footer__note,
.site-footer__meta { font-size: var(--step--1); color: rgba(232, 234, 235, 0.7); }

.site-footer__nav {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
.site-footer__heading {
  font-family: var(--font-serif);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--brass-bright);
  margin: 0 0 var(--sp-3);
}
.site-footer__col a {
  display: block;
  padding: 0.3rem 0;
  font-size: var(--step--1);
  color: rgba(232, 234, 235, 0.86);
  text-decoration: none;
}
.site-footer__col a:hover { color: var(--paper); text-decoration: underline; }

.site-footer__base {
  width: min(100% - 2.5rem, var(--shell));
  margin: var(--sp-7) auto 0;
  padding-block: var(--sp-4);
  border-top: 1px solid rgba(232, 234, 235, 0.2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: space-between;
  font-size: var(--step--1);
  color: rgba(232, 234, 235, 0.7);
}
.site-footer__base a { color: var(--brass-bright); }

/* --------------------------------------------------------------------------
   12. Breakpoints
   480 / 768 / 1024 / 1280 per the brief.
   768 is also the carousel vertical-switch breakpoint (CONFIRMED).
   -------------------------------------------------------------------------- */

/* --- Below 768: the room flattens to a wall ------------------------------
   A 1080px-tall room needs width to read. Below the breakpoint the floor,
   the rope and the stanchions are dropped entirely and the frames stack
   vertically on a plain painted wall — the same wall color the plate opens
   with, so nothing about the palette changes, only the depth.
   ------------------------------------------------------------------------ */
@media (max-width: 767.98px) {
  .nav-toggle { display: flex; }

  /* "Drawn to Detail Studio" + "ARCHITECTURE" + the toggle do not fit on one
     390px row, so the row wraps on its own. Left alone it wraps badly: the
     brand takes line 1, and line 2 opens with the label's orphaned left
     divider followed by the toggle. Giving the label its own full-width line
     makes that wrap deliberate — the divider becomes a rule above it, the
     toggle stays beside the brand, and the label reads as a heading for the
     nav that opens beneath it. Verified at 320px and 390px. */
  .site-header__here {
    order: 3;
    flex-basis: 100%;
    margin: 0;
    padding: var(--sp-2) 0;
    padding-inline-start: 0;
    border-inline-start: 0;
    border-top: 1px solid var(--rule);
  }
  .site-nav { order: 4; }

  .site-nav {
    flex-basis: 100%;
    display: none;
  }
  .site-nav.is-open { display: block; }
  .site-nav__list {
    flex-direction: column;
    gap: 0;
    padding-bottom: var(--sp-3);
  }
  .site-nav__list a {
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--rule);
  }

  .hero { padding-bottom: var(--sp-8); }

  .gallery__stage {
    height: auto;
    background-image: none;
    background: linear-gradient(var(--wall), var(--wall-deep));
  }
  .gallery__stage::before { display: none; }

  .gallery__viewport { position: static; overflow: visible; }
  .gallery__track {
    flex-direction: column;
    align-items: center;
    gap: var(--sp-8);
    width: 100%;
    padding: var(--sp-6) var(--sp-4) 0;
    transform: none !important;
  }
  .frame {
    flex: 0 0 auto;
    width: min(100%, 22rem);
  }
  .frame__mat { height: auto; }
  .frame__img { aspect-ratio: var(--art-ratio); height: auto; }
  /* The label has no wall above it in a stack — put it under the piece. */
  .frame__title {
    position: static;
    margin: var(--sp-3) 0 0;
  }
  /* Loop clones exist only to make horizontal scrolling seamless. In a
     vertical stack they would read as duplicates. */
  .frame[data-clone] { display: none; }
  .gallery__rope { display: none; }
  .gallery__controls { display: none; }
}

/* --- 768 and up: horizontal carousel ------------------------------------- */
@media (min-width: 768px) {
  /* The mission collapsed to one paragraph, so the text column is short.
     A narrower poster column brings the two to roughly the same height. */
  /* A fixed poster column rather than a fraction: the mission is two short
     paragraphs, and at a full fraction the poster ran nearly twice the height
     of the text beside it. Centred so the remainder reads as composition.
     Widened again once the caption came off — the piece is the anchor here
     and the alt text still names it. */
  .about-intro { grid-template-columns: minmax(0, 1fr) 17rem; align-items: center; }
  .about-cols { grid-template-columns: repeat(3, 1fr); gap: var(--sp-5); }
  .chooser__wall { width: min(100%, 40rem); grid-template-columns: repeat(3, 1fr); gap: var(--sp-5); }
  .work {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    align-items: start;
  }
  .work-nav { grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
  .site-footer__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
}

/* At the low end of the horizontal range the room has to shrink faster than
   the viewport, or a single frame fills the screen and the drift reads as a
   wall sliding past rather than a gallery. */
@media (min-width: 768px) and (max-width: 1023.98px) {
  :root { --scene-h: clamp(400px, 52vw, 520px); }
}

@media (min-width: 1280px) {
  :root { --scene-h: clamp(560px, 44vw, 780px); }
}

/* --------------------------------------------------------------------------
   13. Reduced motion
   Autoscroll and parallax are disabled in JS; here the gallery degrades to a
   plain, natively scrollable, snapping strip that is still fully navigable.
   The room stays — it is a photograph, not an animation.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .gallery__viewport {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
  }
  .gallery__track { transform: none !important; }
  .frame { scroll-snap-align: center; }
  .frame[data-clone] { display: none; }
  .gallery__rope { transform: none !important; }
}

/* --------------------------------------------------------------------------
   14. Print
   -------------------------------------------------------------------------- */
@media print {
  .site-header, .site-footer, .gallery__controls { display: none; }
  body { color: #000; background: #fff; }
  .gallery__stage { background: none; height: auto; }
  .gallery__rope { display: none; }
}

/* --- Instagram icon, and the "other ways to reach" list ------------------
   The handle used to be written out as a link in the footer and again on
   every connect page. An icon carries it in less space and stops the raw
   handle competing with the email address, which is the route the studio
   actually wants people to take. */
.icon-ig {
  width: 1.35rem;
  height: 1.35rem;
  display: inline-block;
  vertical-align: -0.28em;
}

.reach-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.6rem;
  align-items: center;
}

.reach-list a {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 44px;   /* the same touch target the form controls use */
}

.site-footer__social .icon-ig { width: 1.5rem; height: 1.5rem; }
