/* ============================================================
   Leyla Mozayen — Catalogue, v2 "the workshop catalogue"
   Palette: her materials — ochre of the yellow piece, fur browns,
   madder rust, bone — over the umber ground of her painting.
   Shell + Studio machinery: the proven v1 base, re-tokened.
   Public surface: the workshop language (threads, tags, register).
   ============================================================ */
/* ============================================================
   v2 — the workshop catalogue
   ------------------------------------------------------------
   Palette: her materials, not a designer's palette. The ochre of
   the yellow piece (#f0c018/#d89000), fur browns (#604830,
   #907860), madder rust (#783030), bone (#a89078) — over the
   umber ground of her painting. Accent = the yellow piece,
   because that object is the most "her" thing on the page.

   The language: everything hangs, tags swing, threads connect,
   the register numbers things deadpan. Assembled, not designed.
   ============================================================ */

:root {
  /* ground — umber black from her painting's darks */
  --ink:        #0e0a07;
  --ink-raised: #191009;
  --ink-line:   rgba(237, 226, 203, 0.13);

  /* type — bone, from the mannequin */
  --paper:      #ede2cb;
  --paper-dim:  #c7b699;
  --paper-faint:#97876c;

  /* accents — her materials.
     Her note of 22 Aug: "can the second main color be changed from this orange
     to a bright orangey pink? it will match the brighter background better".
     So the accent is no longer the yellow piece. It is sampled from the
     painting that IS the brighter background — mainphoto.jpeg's hottest tone
     runs #ed3046 at hue 353, and her sculpture carries #f71476 at hue 334.
     The accent sits between them, which is what "orangey pink" describes.
     The ochre survives as --ochre for the places that are literally her
     yellow object. */
  --ember:      #ff8a93;   /* bright orangey pink, pastel */
  --ember-lift: #ffb3b8;
  /* 22 Aug: "can the second main color be changed from this orange to a bright
     orangey pink?"  24 Aug: "can the secondary color be brighter? more pastel
     I mean not so dark."  So the hue stays where her materials put it (355, between
     the painting's #ed3046 and the sculpture's #f71476) and the tone lifts:
     lightness 63% -> 77%, which also takes contrast on the umber ground from
     5.7:1 to 8.7:1, so the small mono labels set in it get easier to read
     rather than harder. */
  --ochre:      #e6b41e;   /* the yellow piece */
  --copper:     #8a6f52;   /* fur brown */
  --madder:     #b0512e;   /* rust red — marks, live dots */

  --font-display: 'Instrument Serif', Georgia, serif;
  --font-body:    'Inter', system-ui, sans-serif;
  --font-script:  'La Belle Aurore', cursive;
  --font-mono:    'IBM Plex Mono', ui-monospace, 'SFMono-Regular', monospace;

  --bar-h: 62px;
  --pad: clamp(1.25rem, 5vw, 5rem);
  --measure: 62ch;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}


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

/* The `hidden` attribute is only enforced by the UA sheet's [hidden]{display:none},
   which ANY author rule setting display silently beats. That is not a theoretical
   risk here: `.gd__panel{display:flex}` left the guide panel permanently open over
   the page, and `.btn{display:inline-flex}` did the same to every button that is
   supposed to start hidden. This makes the attribute mean what it says. */
[hidden] { display: none !important; }

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

body {
  /* one number per surface, read by BOTH background layers */
  --bg-level: var(--bg-index, .42);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-body);
  font-weight: 300;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  min-height: 100svh;
}

:root[data-grow="1"] .home__stage { min-height: 0; flex: 0 0 auto; }
:root[data-grow="1"] .home__portal {
  /* height-driven sizing cannot work once .home is auto-height: the stage
     collapses and the portal with it. Driven by WIDTH here, so her oval can be
     as large as she asks and the page simply gets taller. */
  position: relative; inset: auto; margin: 0 auto;
  height: auto; max-height: none;
  width: min(var(--hl-oval-w, 53.5vw), 1600px); max-width: none;
}

body[data-view="home"]     { --bg-level: var(--bg-home, .44); }
body[data-view="category"] { --bg-level: var(--bg-category, .42); }
body[data-view="work"]     { --bg-level: var(--bg-work, .30); }
/* About follows her index setting but never brighter than the writing can take */
body[data-view="about"]    { --bg-level: min(var(--bg-index, .42), .46); }

body[data-booting] .stage { opacity: 0; }

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

::selection { background: var(--ember); color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 3px;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ember); color: var(--ink); padding: .75rem 1.25rem;
}
.skip-link:focus { left: 0; }

/* ---------- atmosphere: her artwork, heavily restrained ---------- */

.atmosphere {
  position: fixed; inset: 0; z-index: -2;
  overflow: hidden; pointer-events: none;
  background: var(--ink);
}
/* Her treatment, not ours.
 *
 * She has asked three times for the background to be "brighter and look more
 * like the index.html file". The file she sent carries the numbers:
 *
 *   @keyframes ambientScale
 *     0%   { transform: scale(1);    filter: brightness(.55) contrast(1.1) saturate(1.2) hue-rotate(-5deg) }
 *     100% { transform: scale(1.08); filter: brightness(.65) contrast(1.3) saturate(1.4) hue-rotate(-2deg) }
 *
 * We were running brightness(.34) saturate(.9) — barely half her brightness
 * and desaturated on top. So this is hers, values and all, including the slow
 * 30s breath between the two states.
 *
 * The scrim above it is much lighter than it was, because the old one was
 * doing a second round of darkening on an already-dark image. Text contrast
 * was measured against the brightest part of the result afterwards, since
 * "as legible as possible with the complexity of the images" is also hers.
 */
.atmosphere__img {
  position: absolute; inset: -4%;
  background: url('assets/mainphoto.jpeg') no-repeat;
  /* The crop every surface EXCEPT the home page uses. Untouched since the
     beginning; her sliders no longer reach it, because they were never meant
     to — they arrange the home page. */
  background-position: 50% 50%;
  background-size: cover;
  animation: ambient 30s ease-in-out infinite alternate;
  /* Per surface. --bg-level is set on the BODY, not here, because the home
     page paints its OWN fixed background layer (.home__bg) on top of this one
     and both must read the same number. That was the bug: the slider moved
     this layer while she was looking at .home__bg, which had brightness(.42)
     hard-coded, so nothing she did to the home page had any visible effect. */
  filter: brightness(var(--bg-level, .42)) saturate(.92);
}

/* The home page's OWN crop, back where it was.
 *
 * Until .home__bg was retired, the home page painted its own layer: fixed,
 * full-bleed, cropped at `center 22%`, and perfectly still. Hiding that layer
 * silently handed the home page the index's crop instead — centred, inset by
 * 4%, and slowly breathing. Nothing about her settings changed, which is why
 * no slider could put it back:
 *
 *   "the homepage now no longer shows the corner curve either which is
 *    supposed to mirror the oval"
 *   "it's too much corners now it's not showing the pink in the piece"
 *   "it looks perfect in the older tab I have but I don't know when it shifted"
 *
 * 22% is what her older tab renders. The corner curve and the pink both live in
 * the upper part of the photograph, and centring the crop cut them off.
 *
 * "can the background be slightly more zoomed I don't want to show the left of
 * the image in so much detail" — her three background sliders steer THIS rule,
 * so they move the page she is actually arranging and leave the rest alone.
 */
body[data-home] .atmosphere__img,
body[data-designing] .atmosphere__img {
  inset: 0;
  animation: none;
  background-position: var(--hl-bg-x, 50%) var(--hl-bg-y, 50%);
  /* The zoom pivots on the same point the crop is anchored to, exactly as
     .home__bg did. Scaling about the centre instead moves the picture even
     when her two position numbers have not changed. */
  transform: scale(var(--hl-bg-zoom, 1));
  transform-origin: var(--hl-bg-x, 50%) var(--hl-bg-y, 50%);
  scale: none;
}

.atmosphere::after {
  content: ''; position: absolute; inset: 0;
  /* the home page's scrim, so the two surfaces read as one picture */
  background: linear-gradient(to bottom,
    rgba(14,10,7,.32) 0%,
    rgba(14,10,7,.10) 34%,
    rgba(14,10,7,.38) 62%,
    rgba(14,10,7,.86) 84%,
    var(--ink) 100%);
}
/* The SAME recipe the home page uses, because they have to match.
   Her report: "why does the index background seem so much brighter than the
   home page ... the index is definitely more orange / less grey". She was
   right, and it was not the numbers: .home__bg ran brightness x saturate(.92)
   while this ran brightness x contrast(1.2) x saturate(1.3) x
   hue-rotate(-3.5deg). Equal settings could never look equal. The slow breath
   is now scale only, so the tone is identical on every page. */
@keyframes ambient {
  from { transform: scale(1); }
  to   { transform: scale(1.08); }
}

.grain {
  position: fixed; inset: 0; z-index: 60; pointer-events: none;
  opacity: .05; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- top bar ---------- */

.topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 70;
  height: var(--bar-h);
  /* the wordmark is gone at her request, so the nav holds the right-hand end
     on its own; space-between would have thrown it to the left */
  display: flex; align-items: center; justify-content: flex-end;
  padding: 0 var(--pad);
  /* NOTE: no backdrop-filter / transform / filter on this element.
     Any of them would make .topbar the containing block for its own
     position:fixed children, which collapses the mobile nav overlay to the
     height of the bar. The blur lives on ::before instead. */
}
.topbar::before {
  content: ''; position: absolute; inset: 0 0 auto 0; z-index: -1;
  /* a plain scrim, no backdrop-filter: the frosted strip read as a glass
     band framing the page, and it blurred the tops of the hanging pieces.
     bottom:auto so the height below actually applies — with inset:0 the box
     was pinned top AND bottom and any height was ignored. */
  /* Solid for the height of the bar, then a short fade. It was a gradient
     that started at .88 and reached ZERO by the bottom of the bar, so page
     text scrolling under it stayed readable and collided with the wordmark
     and the nav — measured at 2015px2 over the wordmark and 4528px2 over the
     nav on her index page at 1280x720. That is the "19 PIECES / 3 CATEGORIES"
     line sitting on top of "Leyla CATALOGUE" in her screenshot of 25 Aug. */
  background: linear-gradient(to bottom,
    rgba(14,10,7,1) 0%, rgba(14,10,7,1) 72%, rgba(14,10,7,.55) 88%, rgba(14,10,7,0) 100%);
  height: calc(100% + 22px);
  pointer-events: none;
}

.mark { display: flex; align-items: baseline; gap: .6rem; min-width: 0; }
.mark__script {
  font-family: var(--font-script);
  font-size: 1.75rem; line-height: 1; color: var(--paper);
  white-space: nowrap;
}
.mark__sub {
  font-size: .62rem; letter-spacing: .28em; text-transform: uppercase;
  color: var(--paper-faint); white-space: nowrap;
}

.nav { display: flex; align-items: center; gap: clamp(1rem, 2.5vw, 2.25rem); }
.nav a, .nav__studio {
  font-size: .68rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper-dim); padding: .5rem 0;
  border-bottom: 1px solid transparent;
  transition: color .3s, border-color .3s;
  white-space: nowrap;
}
.nav a:hover, .nav__studio:hover { color: var(--paper); }
.nav a[aria-current="page"] { color: var(--paper); border-bottom-color: var(--ember); }
.nav__studio { color: var(--paper-faint); }

.burger { display: none; width: 34px; height: 34px; position: relative; flex: 0 0 auto; }
.burger span {
  position: absolute; left: 6px; right: 6px; height: 1px; background: var(--paper);
  transition: transform .34s var(--ease), opacity .2s;
}
.burger span:first-child { top: 13px; }
.burger span:last-child  { top: 20px; }

/* ---------- stage / views ---------- */

.stage {
  position: relative; z-index: 1;
  padding: calc(var(--bar-h) + 2rem) var(--pad) 6rem;
  min-height: 100svh;
  transition: opacity .5s var(--ease);
  outline: none;
}

.view { animation: rise .7s var(--ease) both; }
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- section heading ---------- */

.sec-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1.5rem; flex-wrap: wrap;
  padding-bottom: 1.25rem; margin-bottom: 2.5rem;
  border-bottom: 1px solid var(--ink-line);
}
.sec-head h2 {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.6rem, 4vw, 2.6rem); letter-spacing: -.01em;
}
.sec-head__count {
  font-size: .68rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper-faint);
}

/* ---------- back link ---------- */

.back {
  display: inline-flex; align-items: center; gap: .6rem;
  font-size: .68rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper-faint); margin-bottom: 2.25rem;
  transition: color .3s, gap .3s var(--ease);
}
.back:hover { color: var(--ember); gap: 1rem; }

/* ---------- empty state ---------- */

.empty {
  border: 1px dashed var(--ink-line); padding: clamp(2rem, 6vw, 4rem);
  text-align: center; color: var(--paper-faint);
}
.empty h3 {
  font-family: var(--font-display); font-weight: 300; font-size: 1.35rem;
  color: var(--paper-dim); margin-bottom: .75rem;
}
.empty p { font-size: .9rem; max-width: 42ch; margin: 0 auto; }

/* ---------- prose page (about / contact) ---------- */

.page { max-width: var(--measure); }
.page h1 {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(2rem, 6vw, 3.4rem); line-height: 1.05;
  margin-bottom: 2rem; letter-spacing: -.015em;
}
.page a[href^="mailto"], .page a[href^="http"] {
  color: var(--ember); border-bottom: 1px solid rgba(216,120,74,.4);
}

.contact-list { display: grid; gap: 1rem; margin-top: 2.5rem; }
.contact-list a {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  padding: 1.1rem 0; border-bottom: 1px solid var(--ink-line);
  font-size: .72rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--paper-dim); transition: color .3s;
}
.contact-list a:hover { color: var(--ember); }
.contact-list span:last-child { color: var(--paper-faint); text-transform: none; letter-spacing: normal; }

/* ============================================================
   STUDIO (admin)
   ============================================================ */

.studio { max-width: 900px; }
.studio__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 1.25rem; margin-bottom: 2rem;
  border-bottom: 1px solid var(--ink-line);
}
.studio__bar h1 {
  font-family: var(--font-display); font-weight: 300; font-size: 1.75rem;
}
.studio__tag {
  font-size: .6rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink); background: var(--ember); padding: .3rem .6rem;
}

.panel {
  background: rgba(20,16,16,.72); border: 1px solid var(--ink-line);
  padding: clamp(1.25rem, 3vw, 2rem); margin-bottom: 1.5rem;
}
.panel > h2 {
  font-family: var(--font-display); font-weight: 300; font-size: 1.2rem;
  margin-bottom: 1.25rem;
}

.field { display: grid; gap: .45rem; margin-bottom: 1.1rem; }
.field > label {
  font-size: .64rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--paper-faint);
}
/* Type-agnostic on purpose. Enumerating input types meant adding type="email"
   for the sign-in form silently fell through to the browser default, painting a
   white box on a black page. Anything that is not a checkbox or radio is a text
   field here. */
.field input:not([type="checkbox"]):not([type="radio"]),
.field textarea,
.field select {
  width: 100%; background: #0d0a09; border: 1px solid var(--ink-line);
  color: var(--paper); padding: .8rem .9rem; font-family: var(--font-body);
  font-size: .95rem; border-radius: 0;
}
.field textarea { min-height: 150px; resize: vertical; line-height: 1.7; }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--ember);
}
.field__hint { font-size: .72rem; color: var(--paper-faint); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .85rem 1.5rem; background: var(--ember); color: var(--ink);
  font-size: .68rem; letter-spacing: .18em; text-transform: uppercase;
  font-weight: 500; transition: background .3s, opacity .3s;
}
.btn:hover { background: var(--ember-lift); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn--ghost {
  background: none; border: 1px solid var(--ink-line); color: var(--paper-dim);
}
.btn--ghost:hover { background: none; border-color: var(--ember); color: var(--ember); }
.btn--danger { background: none; border: 1px solid #6b2a22; color: #c9705c; }
.btn--danger:hover { background: #6b2a22; color: var(--paper); }

.row { display: flex; gap: .75rem; flex-wrap: wrap; align-items: center; }

/* drop zone */
.drop {
  border: 1px dashed var(--ink-line); padding: 2rem 1rem; text-align: center;
  color: var(--paper-faint); transition: border-color .3s, background .3s;
  cursor: pointer;
}
.drop[data-over] { border-color: var(--ember); background: rgba(216,120,74,.06); }
.drop strong { display: block; color: var(--paper-dim); font-weight: 400; margin-bottom: .35rem; }
.drop small { font-size: .72rem; }

.thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: .6rem; margin-top: 1rem; }
.thumb { position: relative; aspect-ratio: 1; background: var(--ink-raised); }
.thumb img { width: 100%; height: 100%; object-fit: cover; }
.thumb button {
  position: absolute; top: 3px; right: 3px; width: 22px; height: 22px;
  background: rgba(10,8,7,.85); color: var(--paper); font-size: .8rem; line-height: 1;
}
.thumb button:hover { background: #6b2a22; }

/* admin list rows */
.admin-row {
  display: flex; align-items: center; gap: 1rem;
  padding: .85rem 0; border-bottom: 1px solid var(--ink-line);
}
.admin-row__thumb { width: 46px; height: 46px; object-fit: cover; background: var(--ink-raised); flex: 0 0 auto; }
.admin-row__main { flex: 1; min-width: 0; }
.admin-row__main strong { display: block; font-weight: 400; font-size: .95rem; }
.admin-row__main small { color: var(--paper-faint); font-size: .72rem; }
.admin-row__acts { display: flex; gap: .4rem; flex: 0 0 auto; }
.admin-row__acts button {
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--paper-faint); padding: .4rem .6rem; border: 1px solid var(--ink-line);
}
.admin-row__acts button:hover { color: var(--ember); border-color: var(--ember); }

.storage-note {
  font-size: .72rem; color: var(--paper-faint); line-height: 1.7;
  border-left: 2px solid var(--copper); padding-left: .9rem; margin-top: 1rem;
}

/* toast */
.toast {
  position: fixed; left: 50%; bottom: 1.5rem; transform: translate(-50%, 250%);
  background: var(--ink-raised); border: 1px solid var(--ink-line);
  border-left: 2px solid var(--ember);
  padding: .85rem 1.25rem; font-size: .82rem; z-index: 120;
  transition: transform .4s var(--ease); max-width: calc(100vw - 2rem);
}
.toast[data-on] { transform: translate(-50%, 0); }
.toast[data-kind="error"] { border-left-color: #c9705c; }

/* ============================================================
   MOBILE — the thing that was broken
   ============================================================ */

@media (max-width: 820px) {
  .burger { display: block; z-index: 80; }

  .nav {
    position: fixed; inset: 0;
    flex-direction: column; align-items: flex-start; justify-content: center;
    gap: 1.75rem;
    padding: 0 var(--pad);
    background: rgba(10,8,7,.97);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    transform: translateY(-100%);
    transition: transform .5s var(--ease);
    z-index: 75;
  }
  .nav[data-open] { transform: none; }
  .nav a, .nav__studio {
    font-family: var(--font-display); font-size: 2rem; letter-spacing: 0;
    text-transform: none; color: var(--paper-dim); border: none; padding: 0;
  }
  .nav a[aria-current="page"] { color: var(--ember); }

  .burger[aria-expanded="true"] span:first-child { transform: translateY(3.5px) rotate(45deg); }
  .burger[aria-expanded="true"] span:last-child  { transform: translateY(-3.5px) rotate(-45deg); }

  /* the mark must never collide with the nav again */
  .mark__sub { display: none; }

  .index-row {
    grid-template-columns: 2.25rem 1fr;
    gap: .9rem;
  }
  .index-row__n { grid-column: 2; margin-top: .1rem; }
  .index-row:hover, .index-row:focus-visible { padding-left: .25rem; }
}

@media (max-width: 420px) {
  .mark__script { font-size: 1.45rem; }
}

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

/* example-content notice — removed with the demo data */

/* ============================================================
   Archive-scale Studio + lightbox (wave 2)
   ============================================================ */

.statgrid {
  display: flex; flex-wrap: wrap; gap: .75rem 2rem;
  font-size: .78rem; color: var(--paper-faint);
}
.statgrid b { color: var(--paper); font-weight: 400; }
.statgrid .stat--flag b { color: var(--ember); }

.panel--intake { border-color: rgba(216,120,74,.32); }

.drop--lg { padding: 3rem 1rem; }

.intake-progress {
  margin-top: 1rem; font-size: .78rem; color: var(--ember);
  font-variant-numeric: tabular-nums;
}

/* toolbar: search + filter tabs */
.toolbar {
  display: flex; flex-wrap: wrap; gap: .75rem; align-items: center;
  justify-content: space-between; margin-bottom: 1.25rem;
}
.toolbar input[type="search"] {
  flex: 1 1 220px; min-width: 0;
  background: #0d0a09; border: 1px solid var(--ink-line); color: var(--paper);
  padding: .7rem .85rem; font-family: var(--font-body); font-size: .9rem;
}
.toolbar input[type="search"]:focus { outline: none; border-color: var(--ember); }

.tabs { display: flex; flex-wrap: wrap; gap: .35rem; }
.tabs button {
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--paper-faint); padding: .5rem .7rem;
  border: 1px solid var(--ink-line); white-space: nowrap;
  transition: color .25s, border-color .25s;
}
.tabs button:hover { color: var(--paper-dim); }
.tabs button[aria-selected="true"] {
  color: var(--ink); background: var(--ember); border-color: var(--ember);
}

.pill {
  display: inline-block; margin-left: .45rem; padding: .1rem .4rem;
  font-size: .58rem; letter-spacing: .12em; text-transform: uppercase;
  border: 1px solid var(--ink-line); color: var(--paper-faint);
}

/* inline series editor */
.inline-edit {
  border-left: 2px solid var(--ember);
  padding: 1.25rem 0 1.25rem 1.25rem;
  margin: 0 0 1rem;
  background: rgba(216,120,74,.04);
}

.newseries { margin-top: 1.5rem; border-top: 1px solid var(--ink-line); padding-top: 1.25rem; }
.newseries > summary {
  cursor: pointer; list-style: none;
  font-size: .66rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ember); padding: .35rem 0;
}
.newseries > summary::-webkit-details-marker { display: none; }
.newseries > summary::before { content: '+ '; }
.newseries[open] > summary::before { content: '– '; }

/* thumbs gain a caption field */
.thumb { display: flex; flex-direction: column; aspect-ratio: auto; }
.thumb img { aspect-ratio: 1; object-fit: cover; }
.thumb__cap {
  width: 100%; background: #0d0a09; border: 1px solid var(--ink-line);
  border-top: none; color: var(--paper-dim);
  padding: .4rem .5rem; font-family: var(--font-body); font-size: .68rem;
}
.thumb__cap:focus { outline: none; border-color: var(--ember); }


/* zoomable plate */

/* ---------------- lightbox ---------------- */

.lb {
  position: fixed; inset: 0; z-index: 200;
  display: none; align-items: center; justify-content: center;
  background: rgba(6,5,4,.97);
  padding: clamp(1rem, 4vw, 3rem);
}
.lb[data-on] { display: flex; }

.lb__stage {
  max-width: 100%; max-height: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 1rem;
}
.lb__img {
  max-width: 100%; max-height: calc(100svh - 8rem);
  width: auto; height: auto; object-fit: contain;
}
.lb__cap {
  font-size: .78rem; color: var(--paper-dim); text-align: center;
  max-width: 60ch;
}

.lb__close, .lb__prev, .lb__next {
  position: absolute; color: var(--paper-dim);
  width: 56px; height: 56px; display: grid; place-items: center;
  font-size: 2rem; line-height: 1; transition: color .25s;
}
.lb__close:hover, .lb__prev:hover, .lb__next:hover { color: var(--ember); }
.lb__close { top: .5rem; right: .75rem; font-size: 2.4rem; }
.lb__prev { left: .25rem; top: 50%; transform: translateY(-50%); font-size: 3rem; }
.lb__next { right: .25rem; top: 50%; transform: translateY(-50%); font-size: 3rem; }
.lb__count {
  position: absolute; bottom: 1rem; left: 50%; transform: translateX(-50%);
  font-size: .68rem; letter-spacing: .18em; color: var(--paper-faint);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 820px) {
  .toolbar { flex-direction: column; align-items: stretch; }
  /* flex-basis becomes the HEIGHT once the toolbar stacks — without this the
     search field renders 220px tall. */
  .toolbar input[type="search"] { flex: 0 0 auto; }
  .tabs { justify-content: flex-start; }
  /* Admin rows: thumb + title on one line, actions on the next. Four buttons
     beside the text at 390px squeezed titles into four-line wraps. */
  .admin-row { flex-wrap: wrap; }
  .admin-row__main { flex: 1 1 auto; min-width: 0; }
  .admin-row__acts {
    flex: 1 1 100%; justify-content: flex-start; margin-top: .6rem;
  }
  .admin-row__acts button { padding: .5rem .7rem; }
  .lb__prev, .lb__next { width: 44px; font-size: 2.2rem; }
}

/* ============================================================
   Server backend: sign-in, notes inbox, feedback widget
   ============================================================ */

.btn--sm { padding: .55rem .9rem; font-size: .62rem; }

.studio__bar { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.studio__bar h1 { margin-right: auto; }

/* ---------------- sign-in ---------------- */

.panel--signin { max-width: 30rem; }
.signin__msg {
  border-left: 2px solid var(--ember);
  background: rgba(216,120,74,.06);
  padding: .75rem 1rem; margin-bottom: 1.25rem;
  font-size: .82rem; color: var(--paper-dim);
}

/* ---------------- notes inbox ---------------- */

.note {
  display: flex; gap: 1rem; align-items: flex-start;
  padding: 1rem 0; border-bottom: 1px solid var(--ink-line);
}
.note__body { flex: 1; min-width: 0; }
.note__body p { font-size: .88rem; color: var(--paper); white-space: pre-wrap; }
.note__body small {
  display: block; margin-top: .4rem;
  font-size: .68rem; color: var(--paper-faint);
}
.note__body small a { color: var(--ember); }
.note__acts { display: flex; gap: .35rem; flex: 0 0 auto; }
.note__acts button {
  font-size: .6rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--paper-faint); border: 1px solid var(--ink-line);
  padding: .4rem .6rem; transition: color .25s, border-color .25s;
}
.note__acts button:hover { color: var(--ember); border-color: var(--ember); }
.note--done { opacity: .45; }
.note--done .note__body p { text-decoration: line-through; }

/* ---------------- feedback widget ---------------- */

.fb {
  position: fixed; right: clamp(1rem, 3vw, 2rem); bottom: clamp(1rem, 3vw, 2rem);
  z-index: 120; display: flex; flex-direction: column; align-items: flex-end; gap: .75rem;
}

.fb__btn {
  display: inline-flex; align-items: center; gap: .5rem;
  background: var(--ink-raised); border: 1px solid var(--ink-line);
  color: var(--paper-dim);
  padding: .7rem 1.05rem;
  font-size: .64rem; letter-spacing: .18em; text-transform: uppercase;
  transition: color .3s, border-color .3s, background-color .3s;
  box-shadow: 0 10px 30px rgba(0,0,0,.4);
}
.fb__btn:hover { color: var(--paper); border-color: var(--ember); }
.fb__dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--ember);
  flex: 0 0 auto;
}
.fb--hint .fb__dot { animation: fbpulse 2.4s ease-in-out infinite; }
@keyframes fbpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(216,120,74,.55); }
  50%      { box-shadow: 0 0 0 7px rgba(216,120,74,0); }
}

.fb__panel {
  width: min(24rem, calc(100vw - 2rem));
  background: var(--ink-raised);
  border: 1px solid var(--ink-line);
  padding: 1.1rem;
  box-shadow: 0 20px 60px rgba(0,0,0,.55);
  animation: rise .3s var(--ease) both;
}
.fb__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: .5rem;
}
.fb__head strong {
  font-size: .68rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--paper);
}
.fb__x { font-size: 1.5rem; line-height: 1; color: var(--paper-faint); padding: 0 .25rem; }
.fb__x:hover { color: var(--ember); }

.fb__ctx { font-size: .68rem; color: var(--paper-faint); margin-bottom: .75rem; }
.fb__ctx span { color: var(--ember); }

.fb__panel textarea {
  width: 100%; background: #0d0a09; border: 1px solid var(--ink-line);
  color: var(--paper); padding: .7rem .8rem; resize: vertical;
  font-family: var(--font-body); font-size: .85rem; line-height: 1.55;
}
.fb__panel textarea:focus { outline: none; border-color: var(--ember); }

.fb__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem; margin-top: .75rem;
}
.fb__count { font-size: .66rem; color: var(--paper-faint); }
.fb__note { margin-top: .6rem; font-size: .64rem; color: var(--paper-faint); line-height: 1.5; }
.fb__err {
  margin-top: .6rem; font-size: .7rem; color: var(--ember-lift);
}
.fb__done { text-align: center; padding: 1rem .5rem; }
.fb__done strong {
  display: block; font-size: .7rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--ember); margin-bottom: .5rem;
}
.fb__done p { font-size: .8rem; color: var(--paper-dim); margin-bottom: 1rem; }

@media (max-width: 620px) {
  .fb { right: 1rem; bottom: 1rem; left: 1rem; align-items: stretch; }
  .fb__btn { align-self: flex-end; }
  .fb__panel { width: 100%; }
  .note { flex-wrap: wrap; }
  .note__acts { flex: 1 1 100%; margin-top: .5rem; }
}

.hb { display: block; margin-top: .5rem; color: var(--paper-faint); font-weight: 400; }
.hb--stale { color: var(--ember); }

/* Chrome paints autofilled inputs white, which broke the sign-in form against
   the dark ground. There is no way to set the background directly — the inset
   shadow trick is the only reliable override. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--paper);
  -webkit-box-shadow: 0 0 0 1000px #0d0a09 inset;
  box-shadow: 0 0 0 1000px #0d0a09 inset;
  caret-color: var(--paper);
  transition: background-color 9999s ease-in-out 0s;
}

/* ============================================================
   Wave 4 — built from Leyla's own notes (20 Aug)
   ============================================================ */

/* ---------- work page: horizontal gallery, writing beneath ---------- */

.wk__head { margin-bottom: 2rem; }
.wk__title {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.9rem, 4.2vw, 3.2rem); line-height: 1.05;
  letter-spacing: -.015em; color: var(--paper);
}

.reel { position: relative; margin: 0 calc(var(--pad) * -1); }

.reel__track {
  display: flex; gap: 1.25rem;
  overflow-x: auto; overflow-y: hidden;
  /* NO scroll-behavior:smooth here. Any animated scroll (CSS smooth, native
     scrollBy smooth, or a rAF tween) is suspended while the tab is not
     visible, which left the arrows dead. Instant paging always lands. */
  scroll-behavior: auto;
  /* No scroll-snap. With it enabled, EVERY programmatic scroll was re-snapped
     back to 64px (the track's left padding) regardless of method: raw
     assignment, scrollBy instant, scrollBy smooth, and an rAF tween all lost.
     Free scrolling plus arrow paging behaves correctly. */
  padding: 0 var(--pad) 1rem;
  scrollbar-width: thin;
  outline: none;
  /* the pointer-drag handler owns horizontal panning */
  touch-action: pan-x pan-y;
}
.reel__track::-webkit-scrollbar { height: 3px; }
.reel__track::-webkit-scrollbar-thumb { background: var(--ink-line); }

.reel__item {
  flex: 0 0 auto;
  max-width: min(86vw, 1100px);
  display: flex; flex-direction: column;
}
/* A lazy image has NO intrinsic size until it loads, so `width:auto` collapses
   the item to zero and every un-loaded plate stacks at the same x. The stored
   dimensions are emitted as an inline aspect-ratio, which lets the browser
   reserve the correct width before a single byte arrives. */
.reel__item img, .reel__item video {
  height: min(66vh, 620px); width: auto; max-width: 86vw;
  object-fit: contain; background: #070505;
  user-select: none; -webkit-user-drag: none;
}
.reel__item img:not([style*="aspect-ratio"]),
.reel__item video:not([style*="aspect-ratio"]) { aspect-ratio: 4 / 3; }
.reel__item figcaption {
  margin-top: .7rem; font-size: .72rem; color: var(--paper-faint);
  max-width: 60ch;
}

.reel__nav {
  position: absolute; top: calc(min(66vh, 620px) / 2); transform: translateY(-50%);
  z-index: 3; width: 46px; height: 46px;
  display: grid; place-items: center;
  font-size: 2rem; line-height: 1;
  color: var(--paper-dim); background: rgba(10,8,7,.78);
  border: 1px solid var(--ink-line);
  transition: color .25s, border-color .25s, opacity .25s;
}
.reel__nav:hover { color: var(--ember); border-color: var(--ember); }
.reel__nav:disabled { opacity: .2; pointer-events: none; }
.reel__nav--prev { left: calc(var(--pad) - 23px); }
.reel__nav--next { right: calc(var(--pad) - 23px); }

.reel__count {
  margin: .9rem var(--pad) 0;
  font-size: .68rem; letter-spacing: .2em; color: var(--paper-faint);
  font-variant-numeric: tabular-nums;
}
.reel__count b { color: var(--ember); font-weight: 400; }

/* writing sits under the gallery, quieter than the work itself */
.wk__writing {
  margin-top: 3.5rem; padding-top: 2rem;
  border-top: 1px solid var(--ink-line);
  max-width: var(--measure);
}
.wk__writing .prose { font-size: .95rem; color: var(--paper-dim); }

@media (max-width: 820px) {
  .reel__item img, .reel__item video { height: min(52vh, 440px); max-width: 88vw; }
  .reel__nav { display: none; }   /* swipe is the gesture on touch */
  .reel__item { max-width: 88vw; }
}

/* ---------- categories: nesting + search ---------- */

.cat-search {
  flex: 0 1 22rem; min-width: 0;
  background: #0d0a09; border: 1px solid var(--ink-line); color: var(--paper);
  padding: .6rem .8rem; font-family: var(--font-body); font-size: .82rem;
}
.cat-search:focus { outline: none; border-color: var(--ember); }

.index-row--sub { padding-left: 2.5rem; }
.index-row--sub .index-row__title { font-size: .92em; color: var(--paper-dim); }
.index-row--sub::before {
  content: '↳'; position: absolute; left: 1rem;
  color: var(--paper-faint); font-size: .8rem;
}
.index-row { position: relative; }

.srch__lead {
  font-size: .64rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ember); margin: 1.75rem 0 .75rem;
}

.nest { color: var(--ember); }

/* ---------- Studio: draggable plates + video thumbs ---------- */

.thumb { position: relative; cursor: default; }
.thumb__grip {
  position: absolute; top: 4px; left: 4px; z-index: 2;
  font-size: .8rem; line-height: 1; color: var(--paper-dim);
  background: rgba(10,8,7,.7); padding: 3px 5px;
  cursor: grab; user-select: none;
}
.thumb:active .thumb__grip { cursor: grabbing; }
.thumb--dragging { opacity: .35; }

.thumb__vid { position: relative; }
.thumb__vid img { aspect-ratio: 1; object-fit: cover; width: 100%; }
.thumb__novid { aspect-ratio: 1; background: #131010; }
.thumb__vid span {
  position: absolute; bottom: 4px; right: 4px;
  font-size: .55rem; letter-spacing: .14em; text-transform: uppercase;
  background: rgba(216,120,74,.9); color: var(--ink); padding: 2px 5px;
}

@media (max-width: 620px) {
  .sec-head { flex-wrap: wrap; gap: .75rem; }
  .cat-search { flex: 1 1 100%; }
}

/* ---------- studio: paste-a-link row ---------- */

.linkrow { display: flex; gap: .6rem; margin-top: .75rem; flex-wrap: wrap; }
.linkrow input[type="url"] {
  flex: 1 1 18rem; min-width: 0;
  background: #0d0a09; border: 1px solid var(--ink-line); color: var(--paper);
  padding: .65rem .8rem; font-family: var(--font-body); font-size: .82rem;
}
.linkrow input[type="url"]:focus { outline: none; border-color: var(--ember); }

/* ============================================================
   Wave 6 — intake that matches how she actually works
   ------------------------------------------------------------
   Her pieces carry 20-35 photographs each. The old intake silently made one
   piece per photo, so documenting a single installation produced 29 unusable
   drafts with no way to merge them. Now the grouping is asked before anything
   is written, and either answer is undoable.
   ============================================================ */

.stage-box {
  margin-top: 1.25rem; padding: 1.25rem;
  border: 1px solid var(--ember); background: rgba(216,120,74,.05);
}

.stage-count { font-size: .82rem; color: var(--paper); margin-bottom: .9rem; }
.stage-count b { color: var(--ember); }
.stage-warn {
  display: inline-block; margin-left: .6rem;
  font-size: .7rem; color: var(--ember-lift);
}
.stage-msg { font-size: .82rem; color: var(--paper-dim); line-height: 1.65; }
.stage-msg strong { color: var(--ember); }

.stage-thumbs {
  display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: 1.1rem;
}
.stage-thumb {
  position: relative; width: 54px; height: 54px;
  border: 1px solid var(--ink-line); overflow: hidden; flex: 0 0 auto;
}
.stage-thumb img, .stage-thumb video { width: 100%; height: 100%; object-fit: cover; }
.stage-thumb span {
  position: absolute; bottom: 0; right: 0;
  font-size: .5rem; letter-spacing: .08em; text-transform: uppercase;
  background: rgba(216,120,74,.92); color: var(--ink); padding: 1px 3px;
}
.stage-more {
  width: 54px; height: 54px; display: grid; place-items: center;
  border: 1px dashed var(--ink-line); color: var(--paper-faint); font-size: .7rem;
}

.stage-choice { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: 1rem; }
.choice {
  flex: 1 1 15rem; display: flex; gap: .6rem; align-items: flex-start;
  padding: .8rem .9rem; border: 1px solid var(--ink-line); cursor: pointer;
  transition: border-color .2s, background-color .2s;
}
.choice:hover { border-color: var(--paper-faint); }
.choice:has(input:checked) { border-color: var(--ember); background: rgba(216,120,74,.07); }
.choice input { margin-top: .25rem; accent-color: var(--ember); flex: 0 0 auto; }
.choice span { font-size: .82rem; color: var(--paper-dim); line-height: 1.45; }
.choice b { color: var(--paper); font-weight: 400; }
.choice small { color: var(--paper-faint); font-size: .7rem; }

.intake-result {
  margin-top: 1.25rem; padding: 1rem 1.25rem;
  border-left: 2px solid var(--ember); background: rgba(216,120,74,.04);
}
.intake-result .btn { margin-top: .75rem; margin-right: .5rem; }

.failed-list {
  list-style: none; margin: .5rem 0 0; padding: 0;
  font-size: .76rem; color: var(--paper-dim);
}
.failed-list li { padding: .3rem 0; border-bottom: 1px solid var(--ink-line); }
.failed-list small { color: var(--paper-faint); margin-left: .5rem; }

@media (max-width: 620px) {
  .stage-choice { flex-direction: column; }
  .choice { flex: 1 1 auto; }
}

/* ---------- works list: find it, judge it, act on it ---------- */

.w-cat {
  flex: 0 1 12rem; min-width: 0;
  background: #0d0a09; border: 1px solid var(--ink-line); color: var(--paper-dim);
  padding: .65rem .7rem; font-family: var(--font-body); font-size: .78rem;
}
.w-cat:focus { outline: none; border-color: var(--ember); }

.bulkbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  padding: .6rem 0 1rem; margin-bottom: .5rem;
  border-bottom: 1px solid var(--ink-line);
}
.bulkbar[data-on] { border-bottom-color: var(--ember); }
.bulk-all {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .68rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--paper-dim); cursor: pointer;
}
.bulk-all input { accent-color: var(--ember); }
.bulk-hint { font-size: .68rem; color: var(--paper-faint); margin-left: auto; }
.bulkbar select {
  background: #0d0a09; border: 1px solid var(--ink-line); color: var(--paper-dim);
  padding: .5rem .6rem; font-family: var(--font-body); font-size: .76rem;
}
.bulkbar select:focus { outline: none; border-color: var(--ember); }

.admin-row--work { align-items: center; }
.row-tick { accent-color: var(--ember); flex: 0 0 auto; margin-right: .2rem; }

.admin-row__thumb--empty {
  display: grid; place-items: center;
  font-size: .55rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--paper-faint); background: #131010;
}

/* draft / live toggle, readable at a glance */
.admin-row__acts button[data-act="pub"] { min-width: 3.6rem; }
.admin-row__acts button.is-live { color: var(--ember); border-color: rgba(216,120,74,.45); }

/* film tiles on public cards */
.card__frame { position: relative; }
.card__empty--film { color: var(--ember); }
.card__film {
  position: absolute; bottom: .5rem; right: .5rem;
  font-size: .55rem; letter-spacing: .14em; text-transform: uppercase;
  background: rgba(10,8,7,.82); color: var(--paper-dim);
  border: 1px solid var(--ink-line); padding: .15rem .35rem;
}

@media (max-width: 620px) {
  .w-cat { flex: 1 1 100%; }
  .bulk-hint { margin-left: 0; }
}


/* ---------- reordering photos: drag anywhere, or nudge with the arrows ---------- */

.thumb { touch-action: pan-y; }        /* let a vertical page scroll through */
.thumb img, .thumb video, .thumb__vid { pointer-events: none; }  /* the tile drags, not the media */
.thumb__x { position: absolute; top: 4px; right: 4px; z-index: 3; }

.thumb__move {
  position: absolute; left: 4px; bottom: 30px; z-index: 3;
  display: flex; gap: 2px; opacity: 0; transition: opacity .2s;
}
.thumb:hover .thumb__move,
.thumb:focus-within .thumb__move { opacity: 1; }
.thumb__move button {
  width: 20px; height: 20px; display: grid; place-items: center;
  font-size: .85rem; line-height: 1;
  background: rgba(10,8,7,.85); border: 1px solid var(--ink-line);
  color: var(--paper-dim);
}
.thumb__move button:hover { color: var(--ember); border-color: var(--ember); }

/* On touch there is no hover, so the arrows are always visible. */
@media (hover: none) {
  .thumb__move { opacity: 1; }
  .thumb__move button { width: 26px; height: 26px; }
}

/* ============================================================
   The guide — opt-in, never in the way
   ------------------------------------------------------------
   Sits beside the Feedback button and shares its switch (REVIEW_MODE in
   js/config.js). It is a slide-in panel rather than an overlay tour: nothing
   is blocked, nothing auto-opens, and it can be closed at any point.
   ============================================================ */

.gd {
  position: fixed; right: clamp(1rem, 3vw, 2rem);
  bottom: calc(clamp(1rem, 3vw, 2rem) + 3.1rem);
  z-index: 121;
}

.gd__btn {
  display: inline-flex; align-items: center; gap: .5rem;
  background: var(--ink-raised); border: 1px solid var(--ink-line);
  color: var(--paper-dim);
  padding: .7rem 1.05rem;
  font-size: .64rem; letter-spacing: .18em; text-transform: uppercase;
  box-shadow: 0 10px 30px rgba(0,0,0,.4);
  transition: color .3s, border-color .3s;
}
.gd__btn:hover { color: var(--paper); border-color: var(--ember); }
.gd__mark {
  width: 15px; height: 15px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid currentColor; font-size: .58rem; letter-spacing: 0;
  flex: 0 0 auto;
}
.gd--new .gd__mark {
  background: var(--ember); border-color: var(--ember); color: var(--ink);
  animation: gdpulse 2.6s ease-in-out infinite;
}
@keyframes gdpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(216,120,74,.5); }
  50%      { box-shadow: 0 0 0 6px rgba(216,120,74,0); }
}

/* the panel */
.gd__panel {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: min(25rem, 100vw);
  background: var(--ink-raised);
  border-left: 1px solid var(--ink-line);
  box-shadow: -24px 0 60px rgba(0,0,0,.5);
  display: flex; flex-direction: column;
  animation: gdslide .32s var(--ease) both;
}
@keyframes gdslide { from { transform: translateX(14px); opacity: 0; } to { transform: none; opacity: 1; } }

.gd__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; padding: 1.15rem 1.25rem 1rem;
  border-bottom: 1px solid var(--ink-line);
}
.gd__head strong {
  display: block;
  font-size: .68rem; letter-spacing: .18em; text-transform: uppercase; color: var(--paper);
}
.gd__sub {
  display: block; margin-top: .3rem;
  font-family: var(--font-spec, monospace);
  font-size: .62rem; letter-spacing: .12em; color: var(--ember);
}
.gd__x { font-size: 1.6rem; line-height: 1; color: var(--paper-faint); padding: 0 .25rem; }
.gd__x:hover { color: var(--ember); }

.gd__body { flex: 1; overflow-y: auto; padding: .5rem 1.25rem 1.25rem; }

.gd__sec { margin-top: 1.35rem; }
.gd__sec h3 {
  font-family: var(--font-spec, monospace);
  font-size: .6rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper-faint); font-weight: 400;
  padding-bottom: .5rem; border-bottom: 1px solid var(--ink-line);
}

.gd__item { border-bottom: 1px solid rgba(42,34,32,.6); }
.gd__item > summary {
  cursor: pointer; list-style: none; padding: .7rem 0;
  font-size: .84rem; color: var(--paper-dim); line-height: 1.45;
  display: flex; gap: .55rem;
  transition: color .2s;
}
.gd__item > summary::-webkit-details-marker { display: none; }
.gd__item > summary::before { content: '+'; color: var(--ember); flex: 0 0 auto; }
.gd__item[open] > summary { color: var(--paper); }
.gd__item[open] > summary::before { content: '–'; }
.gd__item:hover > summary { color: var(--paper); }
.gd__item p {
  font-size: .8rem; line-height: 1.65; color: var(--paper-faint);
  padding: 0 0 .75rem 1.1rem;
}

.gd__show {
  margin: 0 0 .85rem 1.1rem; padding: .35rem .7rem;
  font-size: .6rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ember); border: 1px solid rgba(216,120,74,.4);
  transition: background-color .2s;
}
.gd__show:hover { background: rgba(216,120,74,.12); }
.gd__away {
  display: block; margin: 0 0 .85rem 1.1rem;
  font-size: .64rem; letter-spacing: .1em; color: var(--paper-faint);
  font-style: italic;
}

.gd__foot {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .85rem 1.25rem; border-top: 1px solid var(--ink-line);
  font-size: .64rem; color: var(--paper-faint);
}
.gd__dismiss {
  font-size: .6rem; letter-spacing: .1em; color: var(--paper-faint);
  text-decoration: underline; text-underline-offset: 3px;
}
.gd__dismiss:hover { color: var(--ember); }

/* "Show me" outlines the real control rather than describing it */
.gd-spot {
  outline: 2px solid var(--ember) !important;
  outline-offset: 4px;
  animation: gdspot 2.6s ease-out both;
  border-radius: 2px;
}
@keyframes gdspot {
  0%, 70% { box-shadow: 0 0 0 6px rgba(216,120,74,.16); }
  100%    { box-shadow: 0 0 0 0 rgba(216,120,74,0); }
}

@media (max-width: 620px) {
  .gd { right: 1rem; bottom: calc(1rem + 3.1rem); }
  .gd__label { display: none; }          /* just the ? on a phone */
  .gd__btn { padding: .7rem .8rem; }
  .gd__panel { width: 100vw; }
}

@media (prefers-reduced-motion: reduce) {
  .gd__panel { animation: none; }
  .gd--new .gd__mark, .gd-spot { animation: none; }
}

/* ============================================================
   HOMEPAGE — built to her sketch and design document (21 Aug)
   ------------------------------------------------------------
   "use image and crop fading to black ... Add the moving stars / glimmers only
   from that design. Scrap the rest. ... Upper left corner should be my name
   large. ... one of my sculptures 'cut out' against the background. It will be
   'behind' the large oval. ... The large oval will be a default image unless the
   categories beneath are being toggled over."
   ============================================================ */

/* One composition, one screen.
   The oval only means anything while the categories are visible with it: the
   aperture previews whichever one you are on. With the categories below the
   fold that interaction cannot happen, so the whole block is sized to the
   viewport and the portal takes whatever height is left over. */
.home {
  position: relative;
  /* A DEFINITE height, not a minimum. With min-height the stage could only
     grow, so shrinking the portal never pulled the category row back up and it
     stayed 30px below the fold. Given a real height, flex hands the stage
     exactly what is left and the portal sizes itself to that.
     The subtractions are the page shell's own padding: bar-height + 2rem above,
     1.25rem below. */
  height: calc(100svh - var(--bar-h) - 2rem - 1.25rem);
  min-height: 30rem;             /* below this it may scroll, which is fine */
  display: flex; flex-direction: column;
  padding-bottom: 0;
}

/* the homepage is a fixed composition, so it does not want the tall footer gap */
body[data-home] .stage { padding-bottom: 1.25rem; }

/* her artwork, cropped at the top, faded to black downward */
.home__bg {
  position: absolute; top: calc(var(--bar-h) * -1); left: calc(var(--pad) * -1);
  right: calc(var(--pad) * -1); height: min(72svh, 620px);
  background: url('assets/mainphoto.jpeg') center 22% / cover no-repeat;
  z-index: 0; pointer-events: none;
}
.home__bg::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    rgba(10,8,7,.15) 0%,
    rgba(10,8,7,.45) 45%,
    rgba(10,8,7,.92) 82%,
    var(--ink) 100%);
}

.home__stars {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: 1; pointer-events: none;
}

/* name, upper left, large */
.home__top {
  position: relative; z-index: 3; flex: 0 0 auto;
  padding-top: var(--hl-top-pad, clamp(.5rem, 2.5vh, 2rem));
}

/* Her name is set in the hand from the site she liked: La Belle Aurore, the
   same face, spacing and weight as `.signature-text` there, so the mark reads
   the same on the homepage as it does in the bar. */
.home__name {
  font-family: var(--font-script);
  font-weight: 400; text-transform: none;
  font-size: clamp(2.6rem, min(9vw, 12vh), 6.5rem);
  line-height: 1.05; letter-spacing: 2px;
  color: var(--paper);
  text-shadow: 0 2px 30px rgba(10,8,7,.75);
}

.home__intro {
  margin-top: clamp(.85rem, 2.4vh, 1.75rem);
  max-width: 52ch;
  font-size: clamp(.88rem, 1.1vw, 1rem); line-height: 1.7;
  color: var(--paper);
  text-shadow: 0 1px 16px rgba(10,8,7,.85);
}

/* the stage: sculpture behind, oval in front */
.home__stage {
  position: relative; z-index: 2;
  margin-top: clamp(.5rem, 1.6vh, 1.25rem);
  display: grid; place-items: center;
  flex: 1 1 auto; min-height: 0;      /* takes the leftover height */
}


/* the portal */
.home__oval img {
  width: 100%; height: 100%; object-fit: cover;
  transition: opacity .34s var(--ease);
}

/* categories, a horizontal row under the portal */
.home__cats {
  position: relative; z-index: 3; flex: 0 0 auto;
  margin-top: clamp(.9rem, 2.4vh, 1.75rem);
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: clamp(1rem, 3vw, 2.75rem);
}
.home__cat {
  font-size: clamp(.66rem, 1vw, .78rem);
  letter-spacing: .22em; text-transform: uppercase;
  color: var(--paper-dim);
  padding: .5rem 0; border-bottom: 1px solid transparent;
  transition: color .28s, border-color .28s;
  text-shadow: 0 1px 12px rgba(10,8,7,.8);
}
.home__cat:hover, .home__cat:focus-visible { color: var(--paper); border-bottom-color: var(--ember); }
.home__cat[data-peeked="1"] { color: var(--ember); border-bottom-color: var(--ember); }

.home__line {
  position: relative; z-index: 3; flex: 0 0 auto;
  margin: clamp(.8rem, 2vh, 1.5rem) auto 0;
  max-width: 52ch; text-align: center;
  font-size: .84rem; line-height: 1.7; color: var(--paper-faint);
}

/* the homepage carries its own name, so the bar wordmark stands down */
body[data-home] .mark { opacity: 0; pointer-events: none; }

/* The suspension block further down owns the sculpture and the aperture at
   these widths; these only handle the surrounding type. */
@media (max-width: 860px) {
  .home__intro { max-width: 100%; }
}

@media (max-width: 620px) {
  .home__cats { gap: .9rem 1.4rem; }
}

/* ---------- category page ---------- */

.cat__head { margin-bottom: 1.5rem; }
.cat__title {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.7rem, 3.6vw, 2.8rem); line-height: 1.05;
  letter-spacing: -.01em; color: var(--paper);
}
.cat__blurb {
  margin-top: .9rem; max-width: var(--measure);
  font-size: .9rem; line-height: 1.7; color: var(--paper-dim);
}
.cat__blurb--sub { margin: .25rem 0 1.25rem; font-size: .82rem; }

.cat__tools { margin-bottom: 2rem; }

.subgroup { margin-top: 3.25rem; }
.subgroup .sec-head h3 {
  font-size: .66rem; letter-spacing: .24em; text-transform: uppercase;
  color: var(--ember); font-weight: 400;
}


/* ============================================================
   SUSPENSION — the homepage as hung work
   ------------------------------------------------------------
   Her studio photograph is a room of things hanging from the ceiling; the
   sculpture in her design document stands on a rod; and her own sketch has a
   small object dangling on a line. So the portal and the sculpture both hang
   from threads and sway, nudged by the pointer. Only transform:rotate animates,
   with the origin at the top of the thread, so it costs the compositor and
   nothing else.
   ============================================================ */

.hang {
  position: absolute; top: 0;
  transform-origin: 50% 0;          /* pivots at the ceiling, not its middle */
  will-change: transform;
  display: flex; flex-direction: column; align-items: center;
}

.hang__thread {
  display: block; width: 1px; flex: 0 0 auto;
  background: linear-gradient(to bottom,
    rgba(237,231,225,0) 0%,
    rgba(237,231,225,.28) 35%,
    rgba(237,231,225,.42) 100%);
}

.hang--sculpture {
  right: clamp(-2rem, 3vw, 5rem);
  z-index: 1;                        /* BEHIND the portal, as she drew it */
}
.hang--sculpture .hang__thread { height: clamp(10px, 4vh, 46px); }
.hang--sculpture .hang__obj {
  height: clamp(230px, 46svh, 470px);
  width: auto; max-width: none;
  filter: drop-shadow(0 18px 44px rgba(0,0,0,.7));
  user-select: none; -webkit-user-drag: none;
}


/* the aperture: a hole cut through the dark, not a photo with rounded corners.
   It sits still on purpose while the sculpture swings across it. */
.home__oval {
  position: relative; z-index: 2;
  --oval-x: 50%;
  --oval-y: 36%;
  /* Sized from her own design document, measured: the oval is 86% of the page
     width there, at roughly a 1.42 landscape aspect, and it owns the lower half
     of the page. The first build had it at 48% of width, which is why she said
     it was "way way too small" and why the shape change was invisible. */
  /* Width-driven, not height-driven. The old rule capped it at 640px and
     squeezed it to whatever was left after the rest of the page, which is why
     she said it was "way way too small". In her document the oval is 86% of the
     page width, so that is what it is here, and the page scrolls like her
     document does rather than cramming everything into one screen. */
  width: min(86vw, 1180px);
  height: auto;
  aspect-ratio: 1.42;
  background: #000;
  clip-path: ellipse(var(--oval-x) var(--oval-y) at 50% 50%);
  transition: clip-path .5s var(--ease);
}
.home__oval img {
  width: 100%; height: 100%; object-fit: cover;
  transition: opacity .34s var(--ease);
}
/* inner shadow so it reads as depth through an opening */
.home__oval-rim {
  position: absolute; inset: 0; pointer-events: none;
  box-shadow:
    inset 0 0 60px 12px rgba(10,8,7,.85),
    inset 0 0 0 1px rgba(237,231,225,.16);
  border-radius: 50%;
}

@media (max-width: 860px) {
  .hang--sculpture .hang__obj { height: clamp(180px, 34svh, 320px); opacity: .9; }
  .hang--sculpture { right: -2.5rem; }
}
@media (max-width: 620px) {
  .hang--sculpture .hang__obj { height: 210px; opacity: .75; }
  .hang--sculpture { right: -3.5rem; }
}

.cover-preview { margin-top: .6rem; }
.cover-preview img {
  width: 132px; height: 92px; object-fit: cover;
  border: 1px solid var(--ink-line);
}


/* ---------- portal spacing, measured off her design document ----------
   Her PDF: background image over the top ~51% of the page, intro text at 42%
   down at 60% width, sculpture 16% wide crossing the boundary, oval 86% wide
   from the 51% line. She asked for the document's spacing, not the sketch's. */

.home { min-height: auto; padding-bottom: clamp(3rem, 8vh, 6rem); }
.home__bg { height: min(56svh, 560px); }
.home__stars { height: min(64svh, 640px); }
.home__top { padding-top: var(--hl-top-pad, clamp(1.5rem, 6vh, 4rem)); }

/* name on two lines, then air, then the sentences */
.home__name .home__surname { display: block; }
.home__intro {
  margin-top: clamp(2rem, 7vh, 4.5rem);
  max-width: 60%;
  font-size: clamp(.8rem, .95vw, .92rem);
  color: var(--paper-dim);
}

.home__stage { margin-top: clamp(.5rem, 2vh, 1.5rem); min-height: auto; }

/* "categories need to be way bigger than the sentences explaining about the
   site. they are the most important part." */
.home__cats { margin-top: clamp(2rem, 6vh, 4rem); }
.home__cat {
  font-family: var(--font-display);
  font-size: clamp(1rem, 2.1vw, 1.75rem);
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--paper);
}
.home__cat:hover, .home__cat:focus-visible { color: var(--ember); }

@media (max-width: 860px) {
  .home__intro { max-width: 100%; font-size: .82rem; }
  .home__cat { font-size: clamp(.95rem, 4.4vw, 1.3rem); }
}


/* ============================================================
   Portal, second pass — her notes of 22 Aug
   ============================================================ */

/* "Could we make the background and sparkles more like the crop I showed you in
   the index file?" The old build cropped it full-bleed and centred, then pulled
   it to black with a RADIAL vignette from the middle out. This build was fading
   it downward in a straight line, which is a different picture. Back to hers. */
.home__bg {
  /* fixed and full-viewport: the painting runs edge to edge with no inner
     frame. The CONTENT keeps its padding — the background simply does not
     know the padding exists. */
  position: fixed;
  inset: 0;
  height: 100%;
  /* NOT PAINTED. .atmosphere already covers every page with this same
     photograph; the home page was drawing a SECOND copy on top of it, each
     darkened to the same percentage, so the home page came out visibly darker
     than the index at identical settings. Her report: "it's very dark when I
     make the homepage background half brightness like the index number - I
     don't think both recipes have stopped." One layer now, so the two pages
     are literally the same pixels. */
  display: none;
}
.home__bg::after {
  display: none;
  background: linear-gradient(to bottom,
    rgba(14,10,7,.32) 0%,
    rgba(14,10,7,.10) 34%,
    rgba(14,10,7,.38) 62%,
    rgba(14,10,7,.86) 84%,
    var(--ink) 100%);
}

/* the glimmers travel the whole field, as they did in the old build */
.home__stars { height: 100%; }

/* "I want this oval to mirror the curve of background image" — the dome in her
   painting arcs across the upper half, so the aperture is lifted to sit inside
   that arc rather than below it. */
.home__stage { margin-top: clamp(-2rem, -3vh, 0rem); }

.home__oval-rim {
  box-shadow:
    inset 0 0 90px 18px rgba(10,8,7,.8),
    inset 0 0 0 1px rgba(237,231,225,.12);
}
/* ============================================================
   Home fits ONE screen
   ------------------------------------------------------------
   Measured before this: 496px of overflow at 1440x900, with the oval alone
   831px tall in an 838px space, so the name, the sentences and the categories
   were all pushed off the bottom and the page had to be scrolled to see its own
   navigation. The oval was also sized from the viewport WIDTH, which ignores
   how much vertical room is actually left.

   Now the page is a column exactly one screen tall and the oval takes whatever
   height remains, so it is always as large as it can be without pushing
   anything off. On a tall narrow phone it goes back to being width-bound.
   ============================================================ */

.home {
  /* One screen by default. When she asks for an oval bigger than fits, these
     two give way instead of the oval silently refusing to grow — her report:
     "The oval is not letting me make it bigger or taller". */
  height: var(--hl-home-h, calc(100svh - var(--bar-h)));
  min-height: 0;
  overflow: var(--hl-home-of, hidden);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-bottom: clamp(.75rem, 2.5vh, 1.75rem);
}

.home__top { flex: 0 0 auto; padding-top: var(--hl-top-pad, clamp(.5rem, 2vh, 1.5rem)); }

/* the script face carries tall ascenders, so its box ran far above the glyphs */
.home__name {
  font-size: clamp(2.2rem, 5.4vw, 4.6rem);
  line-height: 1.24;
}

.home__intro {
  margin-top: clamp(.75rem, 2.5vh, 1.75rem);
  max-width: min(56ch, 60%);
  font-size: clamp(.76rem, .88vw, .88rem);
  line-height: 1.6;
}

/* the aperture takes the space that is left, and no more */
.home__stage {
  flex: 1 1 auto;
  min-height: 0;
  margin-top: clamp(.5rem, 2vh, 1.25rem);
  display: grid;
  place-items: center;
}

.home__oval {
  height: 100%;
  max-height: 100%;
  width: auto;
  max-width: min(92vw, 1180px);
  aspect-ratio: var(--oval-ratio, 1.6);
}

/* the sculpture scales with the space too, rather than a fixed clamp */
.hang--sculpture .hang__obj { height: min(46vh, 420px); }

/* "make the categories under the oval a little smaller" */
.home__cats {
  flex: 0 0 auto;
  margin-top: clamp(.75rem, 2.5vh, 1.75rem);
  gap: clamp(.75rem, 2.4vw, 2rem);
}
.home__cat { font-size: clamp(.8rem, 1.35vw, 1.15rem); }

/* a short landscape window (a laptop in a browser with tabs) is the tight case */
@media (max-height: 720px) {
  .home__name { font-size: clamp(1.9rem, 4.4vw, 3.2rem); }
  .home__intro { margin-top: .6rem; font-size: .76rem; }
  .home__cat { font-size: clamp(.74rem, 1.1vw, .95rem); }
}

/* on a phone the page is narrow and tall: let it breathe and scroll if needed */
@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  .home { height: auto; min-height: calc(100svh - var(--bar-h)); overflow: visible; }
  .home__oval { height: auto; width: 88vw; max-width: 88vw; }
  .home__intro { max-width: 100%; }
  .hang--sculpture .hang__obj { height: min(34vh, 300px); }
}

/* The oval must be bound by the HEIGHT that is left, not by the viewport width.
   `height:100%` on a centred grid item does not resolve (the row is sized by the
   item, which is circular), so it kept taking its size from max-width and stayed
   831px tall in an 838px space. Positioning it against the stage makes the
   percentage definite. */
.home__stage { position: relative; display: block; }

.home__oval {
  position: absolute;
  inset: 0;
  margin: auto;
  height: 100%;
  width: auto;
  max-width: min(92vw, 1180px);
  max-height: 100%;
  aspect-ratio: var(--oval-ratio, 1.6);
}

@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  /* back to normal flow on a phone, where width is the binding constraint */
  .home__stage { position: relative; min-height: 46svh; }
  .home__oval { position: relative; inset: auto; height: auto; width: 88vw; max-width: 88vw; }
}

/* `.stage` pads every view by the bar height plus 2rem top and 6rem bottom.
   The home page sizes itself to the viewport, so that padding was pushing ~190px
   of it below the fold no matter how small its contents got. On this one page
   the padding is the bar height and nothing else, so the column really is one
   screen tall. */
body[data-home] .stage {
  padding-top: var(--bar-h);
  padding-bottom: 0;
  min-height: 0;
}

/* ============================================================
   Portal + sculpture, third pass — her note of 22 Aug
   ------------------------------------------------------------
   "On mobile it overlaps the sculpture and on desktop the spacing between
   the two is a bit strange— but I think it's most important they don't
   overlap this way."

   Why it happened, measured: the oval is centred and sized by leftover
   HEIGHT, but the sculpture was pinned to the STAGE's right edge — the full
   page width. Their gap therefore changed with every window shape, and at
   many widths the figure landed in the corners the ellipse clips away,
   showing flush against the aperture. On a phone the 88vw oval leaves ~6vw
   per side while the figure hung past the right edge straight across the
   portal's third.

   The fix is structural: one wrapper (.home__portal) carries the oval's box,
   and BOTH the aperture and the figure live inside it. The sculpture is now
   anchored to the RIM, not to the page, so their relation is deliberate and
   identical at every window shape:
   - desktop: she drew the figure behind the portal crossing its right edge;
     it hangs half behind the rim, by a bounded amount that can never run
     away on a narrow window.
   - phone: her rule is no overlap, so the portal gives up ~26vw of its own
     width and the figure hangs in that reserved column beside it, touching
     nothing.
   Her drawing is still coming; this holds the composition until it does.
   ============================================================ */

/* the wrapper owns the oval's sizing (moved from .home__oval unchanged) */
.home__portal {
  position: absolute;
  inset: 0;
  margin: auto;
  height: 100%;
  width: auto;
  max-width: min(92vw, 1180px);
  max-height: 100%;
  aspect-ratio: var(--oval-ratio, 1.6);
}

/* the aperture fills its wrapper; the clip-path still lives here, and the
   --oval-x/--oval-y vars are still set inline on #oval by wirePortal */
.home__oval {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
}

/* anchored to the rim: right edge at the wrapper's boundary, then eased out
   so part of the figure sits behind the portal and part shows past it.
   `translate` (not transform) because hang.js writes transform: rotate()
   every frame — the two properties compose instead of clobbering.
   The protrusion is bounded by viewport width so it can never push the
   figure off-screen on a narrow window; .home{overflow:hidden} clips the
   rest. */
.hang--sculpture {
  right: 0;
  left: auto;
  translate: min(55%, clamp(18px, 4.5vw, 88px)) 0;
}

@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  /* her rule for phones: they do not overlap. The stage reserves a column on
     the right; the portal takes what remains and the figure hangs fully
     inside the reserved space. The figure is small here, as she drew it in
     her own sketch: a small object dangling on a line at the top right. */
  .home__stage {
    padding-right: 21vw;
    min-height: auto;
  }
  .home__portal {
    position: relative;
    inset: auto;
    height: auto;
    width: 100%;
    max-width: none;
  }
  .hang--sculpture {
    right: auto;
    left: 100%;                    /* the portal's right rim ... */
    translate: 8% 0;               /* ... nudged into the reserved column */
    top: 0;
    opacity: .9;
  }
  /* sized by WIDTH so the whole figure fits inside the 21vw column at any
     phone width: 17vw wide + a 1vw inset leaves ~3vw clear before the page
     edge */
  .hang--sculpture .hang__obj {
    height: auto;
    width: clamp(56px, 17vw, 96px);
    opacity: .9;
  }
}

/* ============================================================
   Portal + figures, fourth pass — her annotated PDF (23 Aug)
   ------------------------------------------------------------
   She pasted her edits onto a screenshot and sent it as
   "website design with edits.pdf". The PDF's own image placements
   give exact coordinates (percentages of the browser viewport):

     green oval   x 21.0→74.5%   y 29.6→89.9%   aspect ~1.6
     name         x  4.6→22.6%   y  9.4→29.8%
     yellow piece x 17.6→34.3%   y  4.7→16.5%
     categories   x 14.3→85.6%   y 89.9→98.5%
     sculpture    x 68.5→95.8%   y 14.6→69.4%

   So: the aperture dominates the screen, wider than before (1.6),
   its centre pulled slightly left; both hanging pieces sit CLEAR of
   it; the categories close right under the rim. Implemented with
   viewport units straight from those numbers.
   ============================================================ */

@media ((min-width: 721px) and (min-aspect-ratio: 4/5)) {
  /* ------------------------------------------------------------------
     Every number below is a variable with today's value as its fallback,
     so the page is unchanged for anyone who never opens the designer, and
     whatever she saves in it IS the site — there is no second step where
     someone re-types her decisions into a stylesheet.
     Written by js/ui/homelayout.js from `home.layout`.
     ------------------------------------------------------------------ */
  .home__top { padding-top: var(--hl-top-pad, clamp(.35rem, 1vh, 1rem)); }
  .home__name { font-size: clamp(1.5rem, var(--hl-name, 5vw), 9rem); }
  .home__intro {
    margin-top: clamp(.5rem, 1.6vh, 1.25rem);
    font-size: clamp(.6rem, var(--hl-intro, .84vw), 1.6rem);
    max-width: var(--hl-intro-w, 46ch);
  }

  .home__stage { margin-top: var(--hl-stage-top, clamp(-3.5rem, -5vh, -1.5rem)); }

  /* the portal: width-driven now, to her 53.5vw, centre at ~48% of the screen */
  .home__portal {
    position: absolute;
    inset: 0;
    margin: auto;
    height: 100%;
    width: auto;
    max-width: min(var(--hl-oval-w, 53.5vw), 1280px);
    max-height: var(--hl-oval-maxh, 100%);
    aspect-ratio: var(--oval-ratio, 1.6);
    /* The opening is centred on the screen. Always, on every width, whatever
       is hanging beside it. It used to carry a horizontal offset as well, and
       that offset is what put it 134px left of centre on a 1920px window: the
       stage underneath measured 0px off centre the whole time. Only the
       vertical nudge survives. */
    translate: 0 var(--hl-oval-y, 0px);
    z-index: 2;
  }

  /* sculpture: her left edge sits just inside the rim and the body extends
     out past it; offsets are percent-of-wrapper so the relation holds at
     every window shape */
  .hang--sculpture {
    width: max-content;
    /* Measured from the page's right edge, full stop.
       It used to be measured from the opening, with a second term clamping it
       back on screen, which meant a narrower window MOVED it: it slid inward as
       the opening changed size. It holds its place on the page now and passes
       behind the opening when the two meet, which is the whole point of the
       opening being a hole in a wall. */
    left: auto;
    right: calc(2vw - var(--hl-sculpt-x));
    /* Hung from its BOTTOM, not its top. Positioning the top meant the piece
       ended in a different place on every screen, because the offset is a
       percentage of the portal while the figure's height is in svh: measured,
       its bottom sat 14px BELOW the categories line on her 1190x634 monitor and
       125px, 170px and 217px ABOVE it at 1440x900, 1512x982 and 1728x1117.
       Hers is the one she looks at, so hers is the one that looked right.
       "I want the sculpture to begin/end at the bottom around the line of the
        categories. so in most of these it's just very high"
       The categories sit a steady 8-10% of the portal's height below it at
       every size, so her number is now how far past the portal's bottom edge
       the figure ends, and it lands on that line everywhere. 13% is where her
       own screen already had it, to the pixel. */
    top: auto;
    bottom: calc(var(--hl-sculpt-y, 13%) * -1);
    translate: none;
    z-index: 1;
  }
  .hang--sculpture .hang__thread { height: 6svh; }
  .hang--sculpture .hang__obj {
    height: min(var(--hl-sculpt-h, 55svh), 760px);
    max-width: 34vw;               /* narrow desktops: shrink, never sprawl */
    opacity: var(--hl-sculpt-o, .96);
  }

  /* her yellow piece: above the portal's left shoulder, thread from the
     ceiling. Her gap between its top and the oval's top is ~41% of the
     portal's height */
  .hang--yellow {
    /* Sized by its contents, not by whatever room is left.
       "on the desktop with the archway it doesn't let me move the yellow
        sculpture much. can it go more right than the slider currently allows?"
       The slider was never the limit. These pieces are absolutely positioned
       with only `left` set, so the browser shrinks them to fit the space
       remaining inside the portal, and the arch makes that portal narrow:
       measured on her 1190x634 screen, moving the piece right took it from
       228px wide to 7px. It was being crushed, not stopped. */
    width: max-content;
    /* From the page's left edge, the mirror of the sculpture. No floor: it is
       not pushed back on screen any more, it simply keeps its place. */
    left: calc(2vw + var(--hl-yellow-x));
    right: auto;
    /* no max() floor: she reported "I had to remove the yellow piece bc it
       could not be raised high enough", and the floor was ours, not hers */
    top: var(--hl-yellow-y, max(-45%, calc(-25svh + var(--bar-h))));
    z-index: 1;
    display: var(--hl-yellow-show, block);
  }
  .hang--yellow .hang__thread { height: 4svh; }
  .hang--yellow .hang__obj {
    width: clamp(60px, var(--hl-yellow-w, 16.5vw), 460px);
    height: auto;
    opacity: .92;
    filter: drop-shadow(0 14px 30px rgba(0,0,0,.65));
    user-select: none; -webkit-user-drag: none;
  }

  /* categories close under the rim, as she placed them */
  .home__cats {
    margin-top: var(--hl-cats-top, clamp(.25rem, .6vh, .5rem));
    gap: clamp(.4rem, var(--hl-cat-gap, 2vw), 5rem);
  }
  .cat-tag__name { font-size: clamp(.8rem, var(--hl-cat, 1.7vw), 3rem); }
  .home__line { font-size: clamp(.8rem, var(--hl-line, 1.5vw), 3rem); }
}

/* phones keep last week's no-overlap arrangement; the yellow piece is a
   desktop composition from her drawing.

   NOTE: a stray `}` used to sit directly under this block, and Chrome dropped
   the rule while parsing because of it. No visible effect, as it happens —
   the later mobile pass deliberately sets this piece back to display:block so
   it hangs dimmed behind the text — but a rule silently missing from the
   CSSOM is exactly the kind of thing that has no error and no test, so it is
   fixed rather than left. */
@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  .hang--yellow { display: none; }
}
/* ------------------------------------------------------------
   cursor — a needle's eye: a thread-coloured ring that tightens
   onto its dot over anything interactive
   ------------------------------------------------------------ */
.cursor { display: none; }
@media (hover: hover) and (pointer: fine) {
  .cursor {
    display: block; position: fixed; top: 0; left: 0;
    width: 30px; height: 30px; z-index: 90; pointer-events: none;
    transform: translate3d(-100px, -100px, 0);
    will-change: transform;
  }
  .cursor::before {
    content: ''; position: absolute; inset: 0; margin: auto;
    width: 5px; height: 5px; border-radius: 50%;
    background: var(--ember);
  }
  .cursor::after {
    content: ''; position: absolute; inset: 0;
    border: 1px solid rgba(237, 226, 203, .38);
    border-radius: 50%;
    transition: inset .28s var(--ease), border-color .28s, transform .28s var(--ease);
  }
  .cursor[data-hot]::after {
    inset: 7px;
    border-color: var(--ember);
    border-radius: 3px;
    transform: rotate(45deg);
  }
}

/* focus is a stitch, not a box */
:focus-visible {
  outline: 1px dashed var(--ember);
  outline-offset: 4px;
}

/* ------------------------------------------------------------
   kinetic display — the outline treatment from the build she
   liked, kept alive without GSAP
   ------------------------------------------------------------ */
.kinetic {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: .96;
  letter-spacing: .01em;
  display: block;
}
.kinetic--outline {
  color: transparent;
  -webkit-text-stroke: 1px var(--paper-dim);
}

/* ============================================================
   HOME — the workshop wall
   ============================================================ */

/* her name: the hand from the site she liked, with a struck
   underline like a signature finished in one move */
.home__name { position: relative; }
.home__name::after {
  content: '';
  position: absolute; left: .2em; bottom: -.18em;
  width: 5.5em; height: 1px;
  background: linear-gradient(to right, var(--ember), transparent 78%);
  opacity: .55;
  transform: scaleX(0); transform-origin: left;
  animation: sign .9s .5s var(--ease) forwards;
}
@keyframes sign { to { transform: scaleX(1); } }

/* her line beneath the categories — it is HER writing, so it is
   set in her hand, in ochre */
.home__line {
  margin-top: clamp(.4rem, 1vh, .8rem);
  font-family: var(--font-script);
  font-size: clamp(1.25rem, 2.1vw, 1.7rem);
  color: var(--ember);
  opacity: .82;
  transform: rotate(-.6deg);
  text-align: center;
}

/* ---------- the categories: tags hung from a rail ---------- */

.home__cats {
  position: relative;
  display: flex; flex-wrap: wrap; justify-content: center;
  align-items: flex-start;
  gap: clamp(.75rem, 2.6vw, 2.4rem);
  padding-top: 4px;
  /* the rail every tag hangs from */
}
.home__cats::before {
  content: '';
  position: absolute; top: 0; left: 2%; right: 2%; height: 1px;
  background: linear-gradient(to right, transparent, rgba(237,226,203,.34) 12%, rgba(237,226,203,.34) 88%, transparent);
}

.cats__strings {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  overflow: visible;
}
.cats__string {
  fill: none;
  stroke: rgba(237, 226, 203, .22);
  stroke-width: 1;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: stringdraw 1.4s .45s var(--ease) forwards;
}
@keyframes stringdraw { to { stroke-dashoffset: 0; } }

/* one tag: a small plate hanging on its own thread off the rail */
.cat-tag {
  position: relative;
  display: block;
  transform: rotate(var(--tilt, 0deg));
  transform-origin: 50% 0;
  transition: transform .45s var(--ease);
}
.cat-tag__thread {
  display: block; width: 1px;
  height: var(--drop, 18px);
  margin: 0 auto;
  background: linear-gradient(to bottom,
    rgba(237,226,203,0), rgba(237,226,203,.4) 40%, rgba(237,226,203,.5));
}
.cat-tag__plate {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: .15rem;
  padding: .55rem 1.05rem .45rem;
  background: linear-gradient(168deg, rgba(25,16,9,.92), rgba(14,10,7,.94));
  border: 1px solid var(--ink-line);
  box-shadow: 0 10px 26px rgba(0,0,0,.45);
}
/* the punched hole the thread runs through */
.cat-tag__hole {
  position: absolute; top: 7px; left: 50%;
  width: 7px; height: 7px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: var(--ink);
  box-shadow: inset 0 1px 2px rgba(0,0,0,.9), 0 0 0 1.5px rgba(237,226,203,.28);
}
.cat-tag__name {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.7vw, 1.5rem);
  letter-spacing: .045em;
  text-transform: uppercase;
  color: var(--paper);
  padding-top: .45rem;
  transition: color .3s;
}
.cat-tag__count {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .14em;
  color: var(--paper-faint);
  transition: color .3s;
}
/* the peeked tag is the one the aperture is showing */
.cat-tag[data-peeked] .cat-tag__name,
.cat-tag:hover .cat-tag__name { color: var(--ember); }
.cat-tag[data-peeked] .cat-tag__count,
.cat-tag:hover .cat-tag__count { color: var(--ember); }
.cat-tag:hover, .cat-tag[data-peeked] {
  transform: rotate(0deg) translateY(2px);
}

/* ============================================================
   THE REGISTER — the full catalogue as a numbered list
   ============================================================ */

.reg-hero { padding: clamp(2rem, 6vh, 4.5rem) 0 clamp(1.5rem, 4vh, 3rem); }
.reg-hero__kicker {
  font-family: var(--font-script);
  font-size: clamp(1.15rem, 1.8vw, 1.5rem);
  color: var(--ember);
  opacity: .85;
  transform: rotate(-.8deg);
  margin-bottom: .9rem;
}
.reg-hero__title {
  font-size: clamp(2.6rem, 7.5vw, 5.6rem);
  margin-bottom: 1.4rem;
}
.reg-hero__title .kinetic { display: block; }
.reg-hero__blurb {
  max-width: 52ch;
  color: var(--paper-dim);
  font-size: .95rem; line-height: 1.75;
}
.reg-hero__meta {
  display: flex; align-items: center; gap: .9rem;
  margin-top: 1.4rem;
  font-family: var(--font-mono);
  font-size: .68rem; letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--paper-faint);
}
.reg-hero__meta b { color: var(--ember); font-weight: 500; }
.reg-hero__dot {
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--madder);
}

/* the register rows: numbered, threaded, deadpan */
.register { border-top: 1px solid var(--ink-line); }
.reg-row {
  position: relative;
  display: grid;
  /* Two columns, not three. The first used to hold the 01/02 ordinal; when the
     numbers came off the index the column stayed, so the title was squeezed
     into 3.2rem and "#59: hickey machine" wrapped down a 51px gutter while the
     rest of a 1900px row sat empty. .reg-row__thread is absolutely positioned
     and takes no cell, so the children are main + year. */
  grid-template-columns: 1fr auto;
  align-items: baseline; gap: 1.1rem;
  padding: 1.15rem .35rem 1.15rem .1rem;
  border-bottom: 1px solid var(--ink-line);
  transition: background-color .3s, padding-left .35s var(--ease);
}
.reg-row__thread {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 1px;
  background: linear-gradient(to bottom, transparent, var(--ember) 50%, transparent);
  opacity: 0;
  transition: opacity .3s;
}
.reg-row:hover { background: rgba(230, 180, 30, .045); padding-left: .8rem; }
.reg-row:hover .reg-row__thread { opacity: .8; }
.reg-row__num {
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--ember);
  letter-spacing: .1em;
}
.reg-row--sub { padding-left: 2.4rem; }
.reg-row--sub .reg-row__num { color: var(--paper-faint); }
.reg-row__title {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.4vw, 1.8rem);
  line-height: 1.15;
  color: var(--paper);
  transition: color .3s;
}
.reg-row:hover .reg-row__title { color: var(--ember); }
.reg-row__sub {
  display: block; margin-top: .15rem;
  font-size: .72rem; letter-spacing: .05em;
  color: var(--paper-faint);
}
.reg-row__n {
  font-family: var(--font-mono);
  font-size: .7rem;
  color: var(--paper-faint);
}

/* the preview that trails the pointer down the register */
.peek {
  position: fixed; z-index: 80; pointer-events: none;
  width: 220px; aspect-ratio: 4/3;
  opacity: 0; transform: translate3d(-50%, -50%, 0) scale(.96) rotate(1.2deg);
  transition: opacity .3s var(--ease), transform .3s var(--ease);
  box-shadow: 0 18px 50px rgba(0,0,0,.6);
  border: 1px solid var(--ink-line);
  background: var(--ink-raised);
}
.peek img { width: 100%; height: 100%; object-fit: cover; }
.peek[data-on] { opacity: 1; transform: translate3d(-50%,-50%,0) scale(1) rotate(0deg); }
@media (hover: none), (pointer: coarse) { .peek { display: none; } }

.srch__lead {
  font-family: var(--font-mono);
  font-size: .66rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper-faint);
  margin: 2rem 0 1rem;
}

/* ============================================================
   TAGS — work cards as hung tags
   ============================================================ */

.taggrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
  gap: clamp(1.5rem, 2.6vw, 2.4rem);
  padding-top: 2.4rem;              /* room for the threads above */
}

.tag {
  position: relative;
  display: flex; flex-direction: column;
  transform: rotate(var(--tilt, 0deg));
  transform-origin: 50% 0;
  transition: transform .5s var(--ease);
}
/* thread + punched hole above every tag */
.tag__pin {
  display: block; height: 20px;
  position: relative;
}
.tag__string {
  position: absolute; left: 50%; top: 0;
  width: 1px; height: 100%;
  background: linear-gradient(to bottom, rgba(237,226,203,.05), rgba(237,226,203,.42));
}
.tag__string::after {
  content: '';
  position: absolute; left: 50%; bottom: -4px;
  width: 7px; height: 7px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: var(--ink);
  box-shadow: inset 0 1px 2px rgba(0,0,0,.9), 0 0 0 1.5px rgba(237,226,203,.3);
}
.tag__frame {
  position: relative; overflow: hidden;
  aspect-ratio: 4/5;
  background: var(--ink-raised);
  border: 1px solid var(--ink-line);
  /* a neutral mat, because her installation shots are very dark */
  padding: 7px;
}
.tag__frame::before {
  content: '';
  position: absolute; inset: 7px;
  border: 1px solid rgba(237,226,203,.07);
  pointer-events: none;
  z-index: 2;
}
.tag__frame img {
  width: 100%; height: 100%; object-fit: cover;
  filter: brightness(.94);
  transition: transform .8s var(--ease), filter .6s;
}
.tag:hover .tag__frame img { transform: scale(1.04); filter: brightness(1.05); }
.tag:hover { transform: rotate(0deg) translateY(-3px); }
.tag__film, .tag__empty--film {
  position: absolute; z-index: 3; left: 12px; bottom: 12px;
  font-family: var(--font-mono);
  font-size: .56rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--ember);
  padding: .2rem .5rem;
  border: 1px solid rgba(230, 180, 30, .4);
  background: rgba(14, 10, 7, .8);
}
.tag__empty {
  display: grid; place-items: center; height: 100%;
  color: var(--paper-faint);
  font-family: var(--font-mono);
  font-size: .62rem; letter-spacing: .18em; text-transform: uppercase;
}
.tag__num {
  margin-top: .8rem;
  font-family: var(--font-mono);
  font-size: .62rem; letter-spacing: .18em;
  color: var(--ember);
  opacity: .75;
}
.tag__title {
  margin-top: .3rem;
  font-family: var(--font-display);
  font-size: 1.12rem; line-height: 1.3;
  color: var(--paper);
}
.tag__meta {
  margin-top: .25rem;
  font-size: .64rem; letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--paper-faint);
}

/* ============================================================
   CATEGORY PAGE
   ============================================================ */

.cat__kicker {
  font-family: var(--font-script);
  font-size: 1.2rem;
  color: var(--ember);
  opacity: .8;
  transform: rotate(-.8deg);
  margin-bottom: .2rem;
}
.cat__title {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(2rem, 4.6vw, 3.4rem); line-height: 1.05;
  letter-spacing: .005em; color: var(--paper);
}
.cat__blurb {
  margin-top: .9rem; max-width: var(--measure);
  font-size: .92rem; line-height: 1.75; color: var(--paper-dim);
}
.cat__blurb--sub { margin: .25rem 0 1.25rem; font-size: .82rem; }
.cat__tools { margin: 2rem 0 0; }

.subgroup { margin-top: 3.25rem; }
.subgroup .sec-head h3 {
  font-family: var(--font-mono);
  font-size: .72rem; letter-spacing: .24em; text-transform: uppercase;
  color: var(--ember); font-weight: 500;
}
.sec-head h2 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.7rem, 3.6vw, 2.6rem); letter-spacing: .005em;
}

/* ============================================================
   THE STRIP — sideways navigation, stitched to a rail
   ============================================================ */

.catstrip {
  display: flex; flex-wrap: wrap; gap: .4rem 1.7rem; align-items: baseline;
  padding-bottom: 1.1rem; margin-bottom: 1.6rem;
  border-bottom: 1px dashed var(--ink-line);
  position: relative;
}
.catstrip__item {
  font-family: var(--font-mono);
  font-size: .62rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper-faint);
  padding: .3rem 0;
  border-bottom: 1px solid transparent;
  transition: color .25s, border-color .25s;
}
.catstrip__item:hover { color: var(--paper); }
.catstrip__item.is-on { color: var(--ember); border-bottom-color: var(--ember); }
.catstrip__item--all { margin-left: auto; color: var(--paper-dim); }
@media (max-width: 620px) {
  .catstrip { gap: .35rem 1rem; }
  .catstrip__item--all { margin-left: 0; }
}

/* ============================================================
   WORK PAGE — the strip, then the entry
   ============================================================ */

.wk__head { margin-bottom: 2rem; }
.wk__title {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(2rem, 4.8vw, 3.4rem); line-height: 1.06;
  letter-spacing: .005em;
}

.wk__foot {
  display: grid; gap: clamp(2rem, 5vw, 4rem);
  margin-top: clamp(2.5rem, 6vh, 4rem);
}
@media (min-width: 900px) {
  .wk__foot { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); align-items: start; }
}

/* the specification: a note taped into the workshop */
.spec {
  position: relative;
  padding: 1.5rem 1.4rem;
  background: rgba(25, 16, 9, .55);
  border: 1px solid var(--ink-line);
}
/* tape at two corners — translucent, slightly wrong, like it was placed by hand */
.spec::before, .spec::after {
  content: '';
  position: absolute;
  width: 74px; height: 20px;
  background: rgba(237, 226, 203, .09);
  border-left: 1px solid rgba(237,226,203,.1);
  border-right: 1px solid rgba(237,226,203,.1);
  backdrop-filter: blur(1px);
}
.spec::before { top: -9px; left: 18px; transform: rotate(-4deg); }
.spec::after { bottom: -9px; right: 22px; transform: rotate(3deg); }

.spec__lead {
  font-family: var(--font-mono);
  font-size: .62rem; letter-spacing: .26em; text-transform: uppercase;
  color: var(--ember);
  margin-bottom: 1rem;
}
.spec__list { display: grid; gap: .55rem; }
.spec__list div {
  display: grid; grid-template-columns: 6.5rem 1fr; gap: 1rem;
  padding-bottom: .55rem;
  border-bottom: 1px dashed var(--ink-line);
}
.spec__list div:last-child { border-bottom: none; padding-bottom: 0; }
.spec__list dt {
  font-family: var(--font-mono);
  font-size: .6rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--paper-faint);
  padding-top: .2rem;
}
.spec__list dd { font-size: .88rem; color: var(--paper); }

.wk__writing .prose { margin-top: 0; }
.prose {
  margin-top: 2rem; max-width: var(--measure);
  font-size: .98rem; line-height: 1.85;
  color: var(--paper-dim);
}
.prose p + p { margin-top: 1.15em; }

/* about */
.page__title {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(2.2rem, 6vw, 3.6rem); line-height: 1.05;
  margin-bottom: 2rem;
}
.page .prose a, .page a[href^="mailto"], .page a[href^="http"] {
  color: var(--ember);
  border-bottom: 1px dashed rgba(230, 180, 30, .4);
}
/* Contact reads as its own thing.
   It was a dashed rule and then more of the same quiet type, so it dissolved
   into the writing above it. It is a panel now, in the same faint-hairline
   idiom as the category plates, with the heading carrying the accent and the
   rows given a proper two-column rhythm. */
.about__contact {
  margin-top: clamp(2.25rem, 5vh, 3.5rem);
  padding: clamp(1.15rem, 2.6vw, 1.8rem) clamp(1.25rem, 2.8vw, 2rem);
  border: 1px solid rgba(237, 226, 203, .13);
  background:
    linear-gradient(to bottom, rgba(237, 226, 203, .035), rgba(237, 226, 203, 0) 42%),
    rgba(10, 8, 7, .42);
}
.about__contact .spec__lead {
  display: flex; align-items: center; gap: .7rem;
  margin-bottom: .85rem;
}
/* the hairline that runs out from the word, the way the categories hang from one */
.about__contact .spec__lead::after {
  content: ''; flex: 1 1 auto; height: 1px;
  background: linear-gradient(to right, rgba(237, 226, 203, .22), rgba(237, 226, 203, 0));
}
.about__contact .prose--tight p { color: var(--paper-dim); margin: 0; }
.about__contact .contact-list { margin-top: 1.25rem; gap: 0; }
.about__contact .contact-list a {
  padding: .85rem 0;
  border-bottom: 1px solid rgba(237, 226, 203, .09);
}
.about__contact .contact-list a:last-child { border-bottom: 0; padding-bottom: .15rem; }
.about__contact .contact-list span:last-child {
  color: var(--paper); font-size: .82rem;
}
.prose--tight p { font-size: .86rem; }
.contact-list { display: grid; gap: 1rem; margin-top: 2.5rem; }
.contact-list a {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  padding: 1.1rem 0; border-bottom: 1px solid var(--ink-line);
  font-family: var(--font-mono);
  font-size: .7rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--paper-dim); transition: color .3s;
}
.contact-list a:hover { color: var(--ember); }
.contact-list span:last-child { color: var(--paper-faint); text-transform: none; letter-spacing: normal; font-family: var(--font-body); }

/* buttons: primary reads as a label; hover flips it like a tag */
.btn {
  transition: transform .3s var(--ease), background-color .3s, color .3s, border-color .3s;
}
.btn:not(.btn--ghost):not(.btn--danger):hover { transform: rotate(-.6deg) translateY(-1px); }

/* ============================================================
   motion off
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .cats__string { animation: none; stroke-dashoffset: 0; }
  .home__name::after { animation: none; transform: scaleX(1); }
  .view { animation-duration: .01s; }
  .tag, .cat-tag, .btn { transition: none; }
}


/* ============================================================
   Mobile — the portal centred, the pieces behind everything
   ------------------------------------------------------------
   The reserved column left the phone half-empty. Her drawing puts
   the figure BEHIND the portal; on a phone that is the whole
   arrangement: the aperture centred, the pieces hanging behind it
   and behind the text, dimmed to stay out of the writing's way.
   ============================================================ */
@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  .home__stage { padding-right: 0; }
  .home__portal { width: 88vw; margin: 0 auto; }
  /* the pieces bleed past the edges on purpose; clip, never scroll */
  .home { overflow-x: clip; }

  .hang--sculpture {
    left: auto;
    right: -7vw;
    top: -7svh;
    translate: none;
    z-index: 0;
    opacity: .55;
  }
  .hang--sculpture .hang__thread { height: 5svh; }
  .hang--sculpture .hang__obj {
    height: auto;
    width: clamp(120px, 34vw, 220px);
    opacity: .9;
  }

  .hang--yellow {
    display: block;
    left: -4vw;
    right: auto;
    top: -10svh;
    z-index: 0;
    opacity: .5;
  }
  .hang--yellow .hang__thread { height: 4svh; }
  .hang--yellow .hang__obj {
    height: auto;
    width: clamp(100px, 30vw, 190px);
  }
}

/* ============================================================
   HER NOTES OF 22 AUGUST
   ------------------------------------------------------------
   Six comments left in the site's own feedback panel, applied
   here in one block so each one can be read against the rule
   that answers it. Appended last, deliberately: these override
   the earlier passes rather than being threaded through them.
   ============================================================ */

/* ---- "the main oval should be way bigger" (four separate times,
   ending "at least 10 times bigger... more like the pdf I sent")

   The aperture was not capped by width — it was capped by the HEIGHT left
   over after the name block, the tag rail and her handwritten line had taken
   theirs. Measured at 1440x900: 431px of stage, which at ratio 1.6 is 689px
   wide, 47.9% of the screen against the 53.5% her document draws.

   So the height is bought back from the two blocks under the portal, and
   NOT from the category tags, because her other instruction is that the
   categories are the most important thing on the page. The tag keeps its
   type size; only its padding and its thread give ground. ------------- */

@media ((min-width: 721px) and (min-aspect-ratio: 4/5)) {
  /* her document's own number, so the portal can never exceed what she drew */
  /* her document's own number, and the designer's control over it — this rule
     sits after the one in the fourth-pass block, so it is the one that wins */
  .home__portal { max-width: min(var(--hl-oval-w, 53.5vw), 1180px); }

  /* the rail sits closer under the rim */
  /* the WINNING rules for these four. The versions parameterised in the
      fourth-pass block sit earlier in the file and lose to these, which is why
      four of her designer controls did nothing at all. */
  .home__cats {
    margin-top: var(--hl-cats-top, clamp(.1rem, .3vh, .3rem));
    gap: clamp(.4rem, var(--hl-cat-gap, 2.6vw), 6rem);
  }

  /* the tag plate: same type, less air (was ~84px tall, now ~64px) */
  .cat-tag__thread { height: var(--drop, 11px); }
  .cat-tag__plate { padding: .34rem 1rem .3rem; }
  .cat-tag__name { padding-top: .3rem; font-size: clamp(.7rem, var(--hl-cat, 1.7vw), 4rem); }
  .cat-tag__count { font-size: .55rem; }

  /* her line: still her hand, one notch quieter so the portal can grow */
  .home__line {
    margin-top: clamp(.2rem, .5vh, .45rem);
    font-size: clamp(.7rem, var(--hl-line, 1.5vw), 4rem);
    line-height: 1.25;
  }
}

/* ---- "add some type of shadow to the oval so it looks more like a portal
   and there is more depth"

   The rim was a flat inset vignette plus a hairline. A portal needs the wall
   to fall away INTO it, so: a deeper inner shade that darkens toward the rim,
   a bright thin lip catching light at the top edge, and a cast shadow OUTSIDE
   the ellipse so the aperture reads as cut through something rather than
   printed on it. drop-shadow follows the clip-path; box-shadow would draw a
   rectangle. --------------------------------------------------------- */

.home__oval {
  filter:
    drop-shadow(0 24px 48px rgba(0, 0, 0, .72))
    drop-shadow(0 4px 12px rgba(0, 0, 0, .55));
}
.home__oval-rim {
  border-radius: 50%;
  box-shadow:
    inset 0 0 26px 6px rgba(8, 6, 5, .58),
    inset 0 0 78px 26px rgba(8, 6, 5, .72),
    inset 0 3px 2px -1px rgba(255, 246, 232, .30),
    inset 0 0 0 1px rgba(237, 231, 225, .20);
}
/* a second ring just outside the aperture: the thickness of the wall */
.home__oval-rim::after {
  content: '';
  position: absolute; inset: -1.5%;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(237, 231, 225, .07);
  pointer-events: none;
}

/* ---- "and make background like in index file not solid black" (her note on
   a piece page)

   Each plate letterboxed against #070505, so a portrait photograph sat in a
   black slab in the middle of a page whose ground is her painting. The
   letterbox is transparent now and the plate carries a soft edge instead. */

.reel__item img, .reel__item video { background: transparent; }
.reel__item img, .reel__item video, .reel__item .plate {
  box-shadow: 0 18px 40px rgba(0, 0, 0, .5);
}

/* ---- "can you add thumbnails of the works under the slider? I wouldn't know
   on the bigger galleries to keep going"

   Her installation pieces run to 29 and 35 photographs. A counter tells you
   there are 35; it does not tell you what is still ahead. --------------- */

.reel__thumbs {
  display: flex; gap: .5rem;
  overflow-x: auto; overflow-y: hidden;
  margin: 1rem 0 0;
  padding: 0 var(--pad) .5rem;
  scrollbar-width: thin;
  scroll-behavior: auto;
  touch-action: pan-x pan-y;
}
.reel__thumbs::-webkit-scrollbar { height: 3px; }
.reel__thumbs::-webkit-scrollbar-thumb { background: var(--ink-line); }

.reel__thumb {
  position: relative;
  flex: 0 0 auto;
  width: 62px; height: 46px;
  padding: 0;
  border: 1px solid var(--ink-line);
  background: #0d0a09;
  opacity: .42;
  transition: opacity .25s var(--ease), border-color .25s var(--ease);
}
.reel__thumb img { width: 100%; height: 100%; object-fit: cover; }
.reel__thumb:hover { opacity: .8; }
.reel__thumb[aria-current="true"] {
  opacity: 1;
  border-color: var(--ember);
}
/* film and embeds have no still to show unless a poster was saved */
.reel__thumb--film::after {
  content: '▶';
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-size: .6rem; color: var(--paper);
  text-shadow: 0 1px 3px rgba(0,0,0,.9);
}

@media (max-width: 820px) {
  .reel__thumb { width: 52px; height: 39px; }
}

/* ---- "Will this index page be removed? Maybe needs an index page listing
   works but this is too similar to homepage"

   The Register listed CATEGORIES, which is what the homepage already does
   with the portal. It lists PIECES now, so the two pages answer different
   questions: home is the way in by category, the index is everything there
   is. Category rows survive as quiet group headings. ------------------- */

.reg-group {
  display: flex; align-items: baseline; gap: .8rem;
  margin: 2.4rem 0 .6rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid var(--ink-line);
}
.reg-group:first-child { margin-top: .6rem; }
.reg-group__name {
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.5vw, 1.25rem);
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--paper);
}
.reg-group__sub {
  font-family: var(--font-mono); font-size: .6rem;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--paper-faint);
}
.reg-group__n {
  margin-left: auto;
  font-family: var(--font-mono); font-size: .62rem;
  color: var(--paper-faint); font-variant-numeric: tabular-nums;
}
.reg-row__where {
  font-family: var(--font-mono); font-size: .58rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--paper-faint);
}

/* visually hidden, still announced — used by the film thumbnails that have
   no poster to show */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* the register heading carries its full path, so a subcategory never appears
   on the page without the category it belongs to */
.reg-group__parent { color: var(--paper-faint); }
.reg-group__slash { color: var(--paper-faint); margin: 0 .45em; }

/* ============================================================
   RULES THE v2 STYLESHEET NEVER CARRIED OVER
   ------------------------------------------------------------
   v2 reskinned the public views and rewrote this stylesheet, but the Studio
   and the drag-organiser are v1's code UNCHANGED — so every class they emit
   that v1 styled and v2 never redefined has been rendering raw since the v2
   frontend shipped. Thirteen of them.

   Her report, 24 Aug 23:05: "it is glitching so I can't change cover photos
   it's showing every photo in a caegory full size ... small thumbnails like
   before was better." That is `.coverpick` and `.cover-opt` missing: the
   picker fell back to bare <button><img>, so every candidate rendered at its
   natural thumbnail size in a stack instead of a 58px grid.

   Found by extracting every class token the v2 modules can emit and diffing
   it against what this file defines. The audit is worth re-running after any
   reskin: `.plate--embed` and the whole drop-target layer were silently
   unstyled too, and nothing errors when a rule simply is not there.
   ============================================================ */

/* ---- the cover picker (her blocker) ---------------------------------- */
.coverpick { display: flex; flex-wrap: wrap; gap: .4rem; }
.cover-opt {
  position: relative; width: 58px; height: 58px; flex: 0 0 auto;
  border: 1px solid var(--ink-line); overflow: hidden; padding: 0;
  font-size: .55rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--paper-faint); background: #0d0a09;
  transition: border-color .2s, transform .2s var(--ease);
}
.cover-opt img { width: 100%; height: 100%; object-fit: cover; }
.cover-opt:hover { transform: translateY(-2px); border-color: var(--paper-faint); }
.cover-opt.is-on { border-color: var(--ember); outline: 1px solid var(--ember); }
.cover-opt span {
  position: absolute; bottom: 0; right: 0;
  background: rgba(10,8,7,.8); color: var(--paper-dim);
  font-size: .5rem; padding: 1px 3px;
}

/* ---- the drag organiser: it worked, you just could not see it -------- */
.dragdot {
  flex: 0 0 auto; cursor: grab; user-select: none;
  color: var(--paper-faint); font-size: .8rem; line-height: 1;
  padding: .35rem .15rem; opacity: .5;
  transition: opacity .2s, color .2s;
}
.admin-row:hover .dragdot { opacity: 1; }
.is-organising .dragdot { cursor: grabbing; }
.admin-row.is-dragging { opacity: .35; }

.admin-row.is-droptarget,
.toplevel.is-droptarget {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
  background: rgba(255, 138, 147, .12);
}
.admin-row.is-dropinto {
  outline: 2px dashed var(--ember);
  outline-offset: 2px;
  background: rgba(255, 138, 147, .09);
}
.admin-row.is-dropbefore { box-shadow: 0 -2px 0 0 var(--ember); }

/* drop here to bring a category back to the top level */
.toplevel {
  display: block; padding: .55rem .9rem; margin-bottom: .9rem;
  border: 1px dashed var(--ink-line);
  font-size: .6rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper-faint);
  transition: color .2s, border-color .2s, background-color .2s;
}
.is-organising .toplevel { border-color: var(--paper-faint); color: var(--paper-dim); }

/* what you are carrying */
.dnd-ghost {
  position: fixed; top: 0; left: 0; z-index: 300;
  pointer-events: none;
  padding: .45rem .8rem;
  background: var(--ember); color: var(--ink);
  font-size: .68rem; letter-spacing: .1em;
  box-shadow: 0 12px 30px rgba(0,0,0,.5);
  will-change: transform;
}
.dnd-ghost b { font-weight: 600; }
.is-organising { user-select: none; }
.is-organising .admin-row__acts { opacity: .25; pointer-events: none; }

/* ---- film and embeds on a piece page --------------------------------- */
.plate--embed { position: relative; width: min(86vw, 1000px); height: auto; }
.plate--embed iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
  background: #000;
}
.plate--loop { pointer-events: none; }          /* a moving image, not a player */
.plate--broken {
  display: grid; place-items: center;
  width: min(70vw, 480px); aspect-ratio: 16/9;
  background: #17120f; border: 1px dashed var(--ink-line);
  color: var(--ember); font-size: .72rem; letter-spacing: .14em;
  text-transform: uppercase;
}
@media (prefers-reduced-motion: reduce) {
  .plate--loop { pointer-events: auto; }
}

/* ---- register row middle column ------------------------------------- */
.reg-row__main { display: block; min-width: 0; }
/* the row hover was still tinted with the old ochre */
.reg-row:hover { background: rgba(255, 138, 147, .06); }

/* ---- the picker on a big category ------------------------------------
   PATAPHORICAL WEAPONRY carries 109 candidate photographs across its pieces
   and subcategories, which is an 638px wall of thumbnails with the Save
   button pushed off the bottom. Four rows and then it scrolls itself. */
.coverpick {
  max-height: 268px;
  overflow-y: auto;
  align-content: flex-start;
  padding-right: .25rem;
  scrollbar-width: thin;
}
.coverpick::-webkit-scrollbar { width: 3px; }
.coverpick::-webkit-scrollbar-thumb { background: var(--ink-line); }

/* ---- the publish toggle said what a piece IS, and did the opposite ----
   The first button on every row read "Live", which looks like a status badge,
   but clicking it takes the piece off the public site. Reaching for Edit and
   landing one button left silently unpublishes her work, with only a toast
   that fades. (Found the hard way: a test script clicked the first button in
   a row and took YOUR TURN, DOCTOR off the site.)

   The state still shows at rest. On hover or keyboard focus the label swaps to
   what the click will DO, so the action is never a guess. Same width either
   way, so the row does not jump. */
.pubtog { position: relative; display: inline-grid; }
.pubtog__is, .pubtog__do { grid-area: 1 / 1; text-align: center; transition: opacity .14s; }
.pubtog__do { opacity: 0; }
.pubtog:hover .pubtog__is,
.pubtog:focus-visible .pubtog__is { opacity: 0; }
.pubtog:hover .pubtog__do,
.pubtog:focus-visible .pubtog__do { opacity: 1; }

/* ---- images must not be drag SOURCES anywhere we run our own drag --------
   `pointer-events: none` stops an image being an event TARGET; it does not
   stop the browser using it as the source of a native HTML5 drag. That native
   drag fires pointercancel and killed both reorder gestures. Belt and braces
   with the dragstart guards in studio.js and dnd.js. */
.thumb img, .thumb video,
.admin-row__thumb,
.cover-opt img {
  -webkit-user-drag: none;
  user-select: none;
}

/* the piece-preview that follows the pointer on the index takes the shape of
   the photograph it is showing: "can the picture that pops up when you scan
   over the name be tall or vertical for some and horizontal for others?"

   Driven from HEIGHT, not width. A fixed width with a free aspect-ratio makes
   a 1:3 photograph 660px tall and it runs off the screen; a fixed height keeps
   every preview the same visual weight and lets the WIDTH carry the shape, so
   a portrait piece reads narrow and a landscape one reads wide. */
.peek {
  width: auto;
  height: clamp(180px, 28vh, 260px);
  /* Was min(320px, 26vw), which clamped EVERY landscape cover to exactly the
     same 320px and made her wide pieces indistinguishable from each other.
     Her covers run 0.66 to 1.78, so the box now spans roughly 172px wide for
     the tallest to 400px for the widest — a difference you can see. Past the
     cap the height gives way instead, which keeps the shape honest. */
  max-width: min(400px, 30vw);
  aspect-ratio: var(--peek-ratio, 4 / 3);
}
.peek img { object-fit: contain; background: var(--ink); }

/* ---- the organiser now names its own action -------------------------------
   Her 25 Aug message: "trying to change categories and now theyre
   subcategories of each other?" Two thirds of every row used to nest, and
   reordering meant hitting a ~26px band. The zones are quarter / half /
   quarter now, and the thing you are carrying says what letting go will do. */
.dnd-ghost { display: flex; flex-direction: column; gap: 2px; max-width: 15rem; }
.dnd-ghost__what { font-weight: 500; line-height: 1.15; }
.dnd-ghost__do {
  font-family: var(--font-mono);
  font-size: .55rem; letter-spacing: .1em; text-transform: uppercase;
  opacity: .82;
}
.dnd-ghost:not(.has-action) .dnd-ghost__do { display: none; }

/* move BELOW this one: the mirror of is-dropbefore */
.admin-row.is-dropafter { box-shadow: 0 2px 0 0 var(--ember); }

/* the nest zone is the middle of the row, so say so while organising */
.is-organising .admin-row.is-dropinto { background: rgba(255, 138, 147, .12); }

/* ---- the glimmers are hers to turn down --------------------------------
   "too large here and extremely distracting on mobile". Size and count were
   already reduced; this is the switch, so she can decide rather than describe. */
:root[data-glimmer="subtle"] .home__stars { opacity: .45; }
:root[data-glimmer="off"] .home__stars { display: none; }

/* ---- the Look panel ---------------------------------------------------- */
.look__row { display: flex; align-items: center; gap: .9rem; margin: .55rem 0; }
.look__row label { flex: 0 0 12rem; font-size: .72rem; color: var(--paper-dim); }
.look__row input[type="range"] { flex: 1 1 auto; accent-color: var(--ember); min-width: 8rem; }
.look__val {
  flex: 0 0 3rem; text-align: right;
  font-family: var(--font-mono); font-size: .65rem; color: var(--paper-faint);
  font-variant-numeric: tabular-nums;
}
.look__swatch {
  width: 34px; height: 34px; padding: 0; flex: 0 0 auto;
  border: 1px solid var(--ink-line); background: none; cursor: pointer;
}
.look__hint { font-size: .68rem; color: var(--paper-faint); margin-top: .5rem; }

/* ---- the visitor panel -------------------------------------------------- */
.stat-lead { font-size: .82rem; color: var(--paper-dim); margin-bottom: 1.1rem; }
.stat-lead b { color: var(--ember); font-weight: 500; }
.stat-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1.4rem 2rem; }
.stat-cols h3 {
  font-family: var(--font-mono); font-size: .6rem; font-weight: 400;
  letter-spacing: .18em; text-transform: uppercase; color: var(--paper-faint);
  margin-bottom: .5rem; padding-bottom: .35rem; border-bottom: 1px solid var(--ink-line);
}
.stat-list { list-style: none; margin: 0; padding: 0; }
.stat-list li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .3rem 0; font-size: .78rem; color: var(--paper-dim);
  border-bottom: 1px dotted var(--ink-line);
}
.stat-list li span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat-list li b { color: var(--paper); font-weight: 400; font-family: var(--font-mono); font-size: .72rem; }

/* ---- how the two hanging pieces behave on a phone -----------------------
   Her complaint, twice: "layered in.a bad way on mobile". Rather than guess a
   fourth arrangement, the three that exist are hers to choose between in the
   designer. "behind" is what the page does today, so nothing moves by default. */
@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  /* Centre the painting on a phone. Her focal point is set on a wide screen —
     hers is 100% 100% — and on a narrow one that shows nothing but the
     bottom-right corner: "it is just showing the right side of the background
     imahe now ... you cant see just that dark corner/curve". The zoom she
     chose still applies. */
  .atmosphere__img,
  body[data-home] .atmosphere__img,
  body[data-designing] .atmosphere__img {
    /* the same specificity as the home rule above, or this never wins */
    background-position: 50% 50%;
    transform-origin: 50% 50%;
  }

  :root[data-phone-pieces="hidden"] .hang { display: none; }

  /* The reserved column is 21vw wide and starts at the portal's right edge.
     The piece is CENTRED in it, with an explicit width, because the old
     `translate: 8%` was a guess against an auto-width wrapper: measured, it put
     the object at 368..435 on a 390px phone — 45px past the edge, clipped by
     .home { overflow: hidden }, so only a third of it was ever on screen. Her
     report: "I'm not seeing the scupltuures on the mobile version". */
  /* The opening sits in the middle of the screen, always.
     It used to be pushed aside to reserve a 21vw column for the sculpture,
     which put it 41px left of centre on a 390px phone and 34px on an SE,
     whether the pieces were shown or not. The room a piece might want is not a
     reason to move the thing the page is about. */
  :root[data-phone-pieces="beside"] .home__stage { padding-right: 0; }
  :root[data-phone-pieces="beside"] .home__portal { width: 100%; margin: 0 auto; }
  .home__oval { margin-inline: auto; }
  /* "the sculpture is too small on mobile" — it was, and her size dial could
     not reach it: the width was a flat 17vw no matter what she set. It is a
     multiple of her setting now (--hl-sculpt-k), with a ceiling that keeps the
     figure inside the ~102px the portal leaves free beside it, so turning her
     dial up cannot push it off the screen. */
  :root[data-phone-pieces="beside"] .hang--sculpture {
    /* Measured from the SCREEN's right edge, not from a column carved out of
       the page, so moving or resizing the piece cannot shift the opening out of
       the middle. A centred opening leaves only about 61px beside it on a 390px
       phone and the figure is 84px, so it hangs above the opening's shoulder
       rather than next to it, which is where she put it herself. */
    left: auto;
    right: calc(2vw - var(--hl-sculpt-x));
    translate: none;
    top: var(--hl-sculpt-y);
    width: min(calc(22vw * var(--hl-sculpt-k, 1)), 34vw);
    display: flex; flex-direction: column; align-items: center;
    opacity: .95; z-index: 1;
  }
  :root[data-phone-pieces="beside"] .hang--sculpture .hang__obj {
    height: auto;
    /* The ceiling was 23vw, which the dial reached at a size of 63 out of 120:
       everything above that did nothing while 40px of screen sat unused, and
       she had it set to 120. The multiplier is unchanged, so an untouched phone
       renders exactly as before; only the ceiling moved. */
    width: min(calc(20vw * var(--hl-sculpt-k, 1)), 32vw, 100%);
    max-width: 100%;
  }
  /* The column only fits one piece properly. She tried both and said so:
     "On mobile the yellow sculpture pushes the other piece badly and it's more
     important - we can just lose the yellow sculpture if they both can't fit on
     mobile. But I liked it better before." So: the sculpture, as it was. */
  :root[data-phone-pieces="beside"] .hang--yellow { display: none; }
}

/* ---- the home page designer -------------------------------------------- */
.dsg {
  position: fixed; top: var(--bar-h); right: 0; bottom: 0; z-index: 85;
  width: min(340px, 92vw);
  display: flex; flex-direction: column;
  background: rgba(10, 8, 7, .96);
  border-left: 1px solid var(--ink-line);
  box-shadow: -18px 0 50px rgba(0,0,0,.5);
  transition: transform .3s var(--ease);
}
.dsg[data-collapsed] { transform: translateX(calc(100% - 3.6rem)); }
.dsg__head {
  display: flex; align-items: center; gap: .6rem;
  padding: .8rem 1rem; border-bottom: 1px solid var(--ink-line); flex: 0 0 auto;
}
/* The toggle sits FIRST, at the left edge, because when the panel slides away
   the only part still on screen is its left 2.6rem. It used to be on the right,
   which put it at x=1697 on a 1440px screen once collapsed: her report, "every
   time I try to click the design panel aside it no longer lets me recover it". */
.dsg__toggle { order: -1; }
.dsg[data-collapsed] .dsg__head { padding-left: .55rem; }
.dsg__head h2 { font-family: var(--font-display); font-size: 1rem; font-weight: 400; flex: 1 1 auto; }
.dsg[data-collapsed] .dsg__head { border-bottom-color: transparent; }
.dsg[data-collapsed] .dsg__head h2 { opacity: 0; }
.dsg__toggle { font-size: 1.1rem; line-height: 1; color: var(--paper-dim); padding: .2rem .4rem; }
.dsg__body { flex: 1 1 auto; overflow-y: auto; padding: .9rem 1rem 1.4rem; scrollbar-width: thin; }
.dsg__body::-webkit-scrollbar { width: 3px; }
.dsg__body::-webkit-scrollbar-thumb { background: var(--ink-line); }
.dsg__group {
  font-family: var(--font-mono); font-size: .58rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--paper-faint);
  margin: 1.1rem 0 .5rem; padding-bottom: .3rem; border-bottom: 1px solid var(--ink-line);
}
.dsg__group:first-child { margin-top: 0; }
.dsg__row { display: grid; grid-template-columns: 1fr 3.2rem; gap: .2rem .6rem; margin: .45rem 0; }
.dsg__row label { grid-column: 1 / -1; font-size: .68rem; color: var(--paper-dim); }
.dsg__row input[type="range"] { accent-color: var(--ember); width: 100%; }
.dsg__row .dsg__v {
  font-family: var(--font-mono); font-size: .62rem; color: var(--paper-faint);
  text-align: right; font-variant-numeric: tabular-nums; align-self: center;
}
.dsg__presets { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .3rem; }
.dsg__preset {
  font-size: .66rem; padding: .35rem .6rem;
  border: 1px solid var(--ink-line); color: var(--paper-dim);
  transition: border-color .2s, color .2s;
}
.dsg__preset:hover, .dsg__preset[aria-pressed="true"] { border-color: var(--ember); color: var(--ember); }
.dsg__note { font-size: .64rem; color: var(--paper-faint); margin: .35rem 0 .2rem; min-height: 2.2em; }
.dsg__foot {
  flex: 0 0 auto; padding: .8rem 1rem; border-top: 1px solid var(--ink-line);
  display: flex; flex-wrap: wrap; gap: .5rem;
}
.dsg__saved { font-size: .64rem; color: var(--paper-faint); flex: 1 1 100%; }
.dsg select, .dsg input[type="checkbox"] { accent-color: var(--ember); }
.dsg select {
  width: 100%; background: #0d0a09; color: var(--paper);
  border: 1px solid var(--ink-line); padding: .4rem .5rem; font-size: .72rem;
}
/* the page keeps its whole width; the rail floats over it */
body[data-designing] .home { padding-right: 0; }
@media (max-width: 820px) {
  .dsg { width: 100vw; top: auto; height: 62svh; bottom: 0; border-left: none; border-top: 1px solid var(--ink-line); }
  .dsg[data-collapsed] { transform: translateY(calc(100% - 3.2rem)); }
}

/* a strip of her painting at the brightness a slider is set to, because the
   page behind the panel can only ever show ONE of the four surfaces */
.look__prev {
  flex: 0 0 auto; width: 52px; height: 26px;
  border: 1px solid var(--ink-line);
  background: url('assets/mainphoto.jpeg') center/cover no-repeat;
}

/* the settings she sends over, since the designer deliberately does not publish */
.dsg__out {
  flex: 1 1 100%; margin-top: .5rem;
  background: #0d0a09; color: var(--paper-dim);
  border: 1px solid var(--ink-line);
  font-family: var(--font-mono); font-size: .6rem; line-height: 1.5;
  padding: .5rem; resize: vertical; white-space: pre;
}

/* ---- finding your way around Studio -------------------------------------
   Nine panels, and Site text is the last: the Contact note field sits 6,490px
   down. She has twice said she cannot find something that is there. */
.jump {
  position: sticky; top: var(--bar-h); z-index: 40;
  display: flex; flex-wrap: wrap; gap: .3rem;
  margin: 0 0 1.6rem; padding: .6rem 0;
  background: linear-gradient(to bottom, rgba(14,10,7,.97) 70%, rgba(14,10,7,0));
}
.jump__to {
  font-family: var(--font-mono); font-size: .58rem;
  letter-spacing: .12em; text-transform: uppercase;
  padding: .34rem .6rem;
  border: 1px solid var(--ink-line); color: var(--paper-faint);
  transition: color .2s, border-color .2s;
}
.jump__to:hover { color: var(--paper); border-color: var(--paper-faint); }
.jump__to[data-here] { color: var(--ember); border-color: var(--ember); }

/* ---- a stale tab says so ------------------------------------------------
   Hash routing never refetches the code, so a tab open since this morning is
   still running this morning's site. It offers a refresh; it never takes one,
   because losing half-written text to a surprise reload is the worse bug. */
.fresh {
  position: fixed; left: 50%; bottom: 1.2rem; transform: translateX(-50%);
  z-index: 95; display: flex; align-items: center; gap: .7rem;
  padding: .6rem .7rem .6rem 1rem;
  background: var(--ink-raised); border: 1px solid var(--ember);
  box-shadow: 0 14px 40px rgba(0,0,0,.55);
  font-size: .74rem; color: var(--paper);
  max-width: min(92vw, 30rem);
}
.fresh__go {
  font-size: .7rem; letter-spacing: .08em; text-transform: uppercase;
  padding: .35rem .7rem; background: var(--ember); color: var(--ink);
}
.fresh__no { font-size: 1rem; line-height: 1; color: var(--paper-faint); padding: 0 .2rem; }
.fresh__no:hover { color: var(--paper); }


/* ============================================================
   Two arrangements: the desktop one and the phone one
   ============================================================

   Every control used to write --hl-* directly, from an inline style, which
   meant one set of numbers for every screen and no way for a phone to differ.
   Measured on the live page, twenty of her twenty-two controls did nothing at
   all on a phone, which is what she kept walking into:

     "the sculpture is too small on mobile and the background is zoomed weird"
     "it doesn't seem anything but the height of the sculptue i can change"
     "nothing changed with background placement for mobile"

   So the designer now writes two sets, --hld-* for a desktop and --hlm-* for a
   phone, and this is where one of them is chosen. Everything downstream still
   reads --hl-* and needs no changes. An inline style would beat a media query,
   which is exactly why nothing writes --hl-* any more.

   The phone defaults below are what the phone ALREADY renders, measured at
   390x664, so a phone she has not touched looks the way it looks today. */

:root {
  --hl-oval-w:    var(--hld-oval-w, 53.5vw);
  --hl-oval-y:    var(--hld-oval-y, 0px);
  --hl-sculpt-h:  var(--hld-sculpt-h, 55svh);
  --hl-sculpt-x:  var(--hld-sculpt-x, 6vw);
  --hl-sculpt-y:  var(--hld-sculpt-y, 13%);
  --hl-sculpt-o:  var(--hld-sculpt-o, .96);
  --hl-sculpt-k:  var(--hld-sculpt-k, 1);
  --hl-yellow-w:  var(--hld-yellow-w, 16.5vw);
  --hl-yellow-x:  var(--hld-yellow-x, -3.4vw);
  --hl-yellow-y:  var(--hld-yellow-y, -45%);
  --hl-name:      var(--hld-name, 5vw);
  --hl-intro:     var(--hld-intro, .84vw);
  --hl-intro-w:   var(--hld-intro-w, 46ch);
  --hl-cat:       var(--hld-cat, 1.7vw);
  --hl-line:      var(--hld-line, 1.5vw);
  --hl-cat-gap:   var(--hld-cat-gap, 2vw);
  --hl-cats-top:  var(--hld-cats-top, .6vh);
  --hl-stage-top: var(--hld-stage-top, -3vh);
  --hl-top-pad:   var(--hld-top-pad, 2.5vh);
  --hl-bg-zoom:   var(--hld-bg-zoom, 1);
  --hl-bg-x:      var(--hld-bg-x, 50%);
  --hl-bg-y:      var(--hld-bg-y, 50%);
}

@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  :root {
    --hl-oval-w:    var(--hlm-oval-w, 68.75vw);
    --hl-oval-y:    var(--hlm-oval-y, 0px);
    --hl-sculpt-x:  var(--hlm-sculpt-x, 0vw);
    --hl-sculpt-y:  var(--hlm-sculpt-y, 0%);
    --hl-sculpt-o:  var(--hlm-sculpt-o, .95);
    --hl-sculpt-k:  var(--hlm-sculpt-k, 1.075);
    --hl-yellow-w:  var(--hlm-yellow-w, 30vw);
    --hl-yellow-x:  var(--hlm-yellow-x, -4vw);
    --hl-yellow-y:  var(--hlm-yellow-y, -10%);
    --hl-name:      var(--hlm-name, 7.79vw);
    --hl-intro:     var(--hlm-intro, 3.12vw);
    --hl-intro-w:   var(--hlm-intro-w, 64ch);
    --hl-cat:       var(--hlm-cat, 4.31vw);
    --hl-line:      var(--hlm-line, 5.13vw);
    --hl-cat-gap:   var(--hlm-cat-gap, 3.08vw);
    --hl-cats-top:  var(--hlm-cats-top, 2.5vh);
    --hl-stage-top: var(--hlm-stage-top, 2vh);
    --hl-top-pad:   var(--hlm-top-pad, 3.2vh);
    --hl-bg-zoom:   var(--hlm-bg-zoom, 1.06);
    --hl-bg-x:      var(--hlm-bg-x, 50%);
    --hl-bg-y:      var(--hlm-bg-y, 50%);
  }

  /* and the phone rules read them, which they never did before */
  .home__oval  { width: var(--hl-oval-w); max-width: min(var(--hl-oval-w), 100%); }
  .home__portal { translate: 0 var(--hl-oval-y); }   /* centred, always */
  .home__name  { font-size: var(--hl-name); }
  .home__intro { font-size: var(--hl-intro); max-width: min(var(--hl-intro-w), 100%); }
  .cat-tag__name { font-size: var(--hl-cat); }
  .home__line  { font-size: var(--hl-line); }
  .home__cats  { gap: var(--hl-cat-gap); margin-top: var(--hl-cats-top); }
  .home__stage { margin-top: var(--hl-stage-top); }
  .home__top   { padding-top: var(--hl-top-pad); }

  /* her crop, on the phone's own numbers. It stays centred by default, which is
     hers: "the background image is not centered on mobile ... it is just
     showing the right side of the background imahe now". She can move it now. */
  .atmosphere__img,
  body[data-home] .atmosphere__img,
  body[data-designing] .atmosphere__img {
    background-position: var(--hl-bg-x) var(--hl-bg-y);
    transform-origin: var(--hl-bg-x) var(--hl-bg-y);
  }

  /* the hanging pieces, as offsets from where the phone already puts them */
  :root[data-phone-pieces="beside"] .hang--sculpture {
    /* from the SCREEN's edge, so the piece can never shove the opening off
       centre. This rule is the one that wins; the earlier block sets the same
       three properties and is overridden here. */
    left: auto;
    right: calc(2vw - var(--hl-sculpt-x));
    top: var(--hl-sculpt-y);
    opacity: var(--hl-sculpt-o);
  }
  :root[data-phone-pieces="behind"] .hang--yellow {
    left: var(--hl-yellow-x);
    top: var(--hl-yellow-y);
  }
  :root[data-phone-pieces="behind"] .hang--yellow .hang__obj { width: var(--hl-yellow-w); }
}

/* the Desktop / Mobile switch, and a real phone to arrange against */
.dsg__which { display: flex; gap: 0; margin-left: auto; }
.dsg__seg {
  font: inherit; font-size: .72rem; letter-spacing: .04em;
  padding: .3rem .7rem; cursor: pointer;
  color: var(--paper-dim); background: transparent;
  border: 1px solid var(--ink-line);
}
.dsg__seg:first-child { border-right: 0; }
.dsg__seg[aria-pressed="true"] { color: var(--ink); background: var(--paper); }

/* 390x844 is a real phone, not a scaled-down desktop, so the media queries it
   answers to are the ones her phone answers to. Scaled only to fit the screen. */
.dsg__phone {
  position: fixed; left: clamp(.5rem, 3vw, 3rem); top: 50%;
  /* A true 390px wide, because that is what the media queries answer to.
     Height just gives way on a short screen; she scrolls inside it.
     It is NOT scaled: scale() takes a number, and calc(length / number) is a
     length, so the whole transform was invalid and the centring went with it. */
  width: 390px; height: min(844px, calc(100vh - 4rem));
  transform: translateY(-50%);
  border: 10px solid #0d0b0a; border-radius: 34px;
  box-shadow: 0 30px 90px rgba(0,0,0,.7);
  background: var(--ink);
  z-index: 60; display: none;
}
body[data-designing-phone] .dsg__phone { display: block; }

/* ============================================================
   The shape of the opening, and whether the pieces hang at all
   ============================================================

   "i think the homepage still a bit goofy and i suspect youre right it's oval
    related ... maybe a circle instead of an oval to start"
   "do you think the sculptures add or detract from homepage?"

   Both are hers to try rather than ours to argue about, so both are in the
   designer. An oval has to choose a horizontal or a vertical and reads
   differently on every screen shape; a circle does not, and an arch is a
   doorway, which is what a portal is. */

/* the box that decides the proportions is the PORTAL, not the oval inside it.
   Clipping a 50%/50% ellipse inside a 1.6 box gives an ellipse, not a circle. */
:root[data-shape="circle"] .home__portal { aspect-ratio: 1; }
:root[data-shape="arch"]   .home__portal { aspect-ratio: .82; }

:root[data-shape="circle"] .home__oval {
  aspect-ratio: 1;
  /* fixed, not adaptive: the picture behind it changes proportion on every
     category, and a circle that follows it is just an oval again */
  clip-path: ellipse(50% 50% at 50% 50%);
}
:root[data-shape="circle"] .home__oval-rim { border-radius: 50%; }

:root[data-shape="arch"] .home__oval {
  aspect-ratio: .82;
  clip-path: none;
  overflow: hidden;
  border-radius: 50% 50% 3% 3% / 42% 42% 2% 2%;
}
:root[data-shape="arch"] .home__oval-rim {
  border-radius: 50% 50% 3% 3% / 42% 42% 2% 2%;
}

/* a circle and an arch are taller than they are wide, so they need the height
   the oval never asked for */
:root[data-shape="circle"] .home__oval,
:root[data-shape="arch"] .home__oval { max-height: var(--hl-oval-maxh, 100%); }

/* whether the hanging pieces appear at all, and on which screen */
:root[data-pieces="none"] .hang { display: none; }
@media ((min-width: 721px) and (min-aspect-ratio: 4/5)) {
  :root[data-pieces="phone"] .hang { display: none; }
}
@media (max-width: 720px), (max-aspect-ratio: 4/5) {
  :root[data-pieces="desktop"] .hang { display: none; }
}

/* The opening is a hole in a wall, so anything hanging passes BEHIND it when
   the window narrows enough for the two to meet. Nothing gets shoved aside. */
.home__portal { z-index: 3; }
.hang { z-index: 1; }

/* A picture on the About page.
   "is it possible to add one picture on the empty side of the about page? I
    want to put a studio pic there ... or below however, it will just be
    landscape". Beside the writing where there is room for it, underneath when
    there is not, which on a phone is always. */
/* The writing keeps its reading measure. The PAGE widens to make room for the
   picture beside it, rather than the two of them sharing one narrow column and
   leaving the side she pointed at still empty. */
.page--wide { max-width: min(1180px, 100%); }
.about--withphoto {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3.25rem);
  align-items: start;
}
.about--withphoto .about__col { max-width: var(--measure); }
@media (min-width: 900px) {
  .about--withphoto { grid-template-columns: minmax(0, 1fr) minmax(0, .92fr); }
}
/* A quiet ground under the writing, feathered out rather than boxed, so it
   reads over her painting at whatever brightness she has set. */
.about__col {
  /* generous padding so the gradient has room to reach nothing before the box
     ends. At 118%/88% it was still a third opaque at the edges, which drew a
     rectangle across her painting instead of a shadow. */
  padding: clamp(1.6rem, 3.4vw, 3rem) clamp(1.6rem, 3.6vw, 3.2rem);
  margin: calc(-1 * clamp(1.6rem, 3.4vw, 3rem)) calc(-1 * clamp(1.6rem, 3.6vw, 3.2rem));
  background: radial-gradient(78% 58% at 34% 42%,
    rgba(12, 9, 7, .80) 0%,
    rgba(12, 9, 7, .62) 38%,
    rgba(12, 9, 7, .28) 64%,
    rgba(12, 9, 7, .07) 80%,
    rgba(12, 9, 7, 0) 92%);
}

/* Her photographs are presented the same way everywhere on this site: no
   border, no slab, a soft shadow and the faintest inner hairline. Her own note
   on a piece page was "make background like in index file not solid black", and
   a 1px box around a studio shot is the same mistake in miniature. */
.about__photo { margin: 0; }
.about__photo img {
  display: block; width: 100%; height: auto;
  background: transparent;
  box-shadow:
    0 18px 40px rgba(0, 0, 0, .5),
    inset 0 0 0 1px rgba(237, 231, 225, .07);
}
