/* ============================================================
   Clara Delattre Ceramics

   The page is her backdrop. Every colour below was sampled off the
   margins of her own studio plates: a cool blue-grey ramp, hue ~220,
   near-zero saturation, near-black at the top and near-white at the
   bottom. So the site opens dark, the way every one of her photographs
   opens, and ramps to the light ground the pots stand on.

   The only warm colour on the site is what the fire put there.
   See DIRECTION.md before changing any of this.
   ============================================================ */

:root {
  /* her seamless, top to bottom */
  --ink-0: #1b1c1d;
  --ink-1: #2d2f33;
  --ink-2: #4f5259;
  --ink-3: #9fa0a3;
  --paper: #cfd1d2;

  /* What the fire leaves: ash/honey hue ~30, ember hue ~11. The exact
     values are set by contrast, not by eye — each one is the closest
     colour to hers that clears WCAG AA against the ground it sits on.
     `--honey` is the warm tone for the DARK hero, `--ember` the warm tone
     for the light page; no single warm tone clears 4.5:1 against both. */
  --ash: #94693b;
  --honey: #aa7c40;   /* 4.6:1 on the hero ground */
  --ember: #9d351e;   /* 4.6:1 on paper */
  --focus: #896138;   /* 3.6:1 on paper, 3.1:1 on the hero ground */

  --edge: color-mix(in srgb, var(--ink-0) 16%, transparent);
  --quiet: #57585c;   /* 4.6:1 on paper — small type lives in this */

  --display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --gutter: clamp(1.25rem, 5vw, 5rem);
  --gap: clamp(1.5rem, 4vw, 4.5rem);   /* between pots on a shelf */
  --unit-max: clamp(240px, 52vh, 560px);
  --unit: var(--unit-max);             /* the shelf: one pot-height unit */
  --scrim: rgba(27, 28, 29, 0.72);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-0);
  font-family: var(--display);
  font-optical-sizing: auto;
  font-variation-settings: "SOFT" 30, "WONK" 0;
  font-size: clamp(1rem, 0.95rem + 0.25vw, 1.15rem);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, video { display: block; max-width: 100%; }
a { color: inherit; }

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

:where(a, button, [tabindex]):focus-visible {
  /* Not --ember: it is only 2.6:1 against the dark hero, so a keyboard
     user tabbing the nav on the first screen could not see the ring. */
  outline: 2px solid var(--focus);
  outline-offset: 4px;
  border-radius: 1px;
}

.skip {
  position: fixed; top: 0; left: 0; z-index: 100;
  padding: 0.75rem 1rem; background: var(--ink-0); color: var(--paper);
  font-family: var(--mono); font-size: 0.75rem;
  transform: translateY(-120%);
}
.skip:focus { transform: none; }

/* Hit areas.
   This type is 11-14px, so several links were boxes of 17-22px — under
   the 24px minimum for a pointer target and far under what touch wants.
   Grown with an absolutely positioned pseudo-element rather than padding:
   padding moved where these sit on their baseline, and the ledger's name
   and its nav stopped lining up with each other. This adds nothing to
   layout. */
:where(.ledger, .services__reach, .foot) a { position: relative; }
:where(.ledger, .services__reach, .foot) a::after {
  content: ""; position: absolute; inset: -0.7rem -0.35rem;
}
@media (pointer: coarse) {
  :where(.ledger, .services__reach, .foot) a::after { inset: -1rem -0.5rem; }
}

/* A label in the register of a museum wall card or a kiln log. */
.tag {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  font-weight: 500;
}

/* ---------------------------------------------------------- the ledger

   Fixed, and the photographs pass behind it — the type never moves out
   of the way. It inverts once the firing has handed over to the page. */

.ledger {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem;
  padding: clamp(0.9rem, 2vw, 1.6rem) var(--gutter);
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--paper);
  transition: color 0.4s var(--ease);
  pointer-events: none;
}
/* Photographs pass behind the ledger, but body text scrolling under it
   would collide with it. A scrim keeps both readable without giving the
   header a hard edge. */
.ledger::before {
  content: "";
  position: absolute; inset: 0 0 -120% 0; z-index: -1;
  background: linear-gradient(to bottom, var(--scrim), transparent);
  pointer-events: none;
}
body.is-light, body.page--commissions { --scrim: rgba(207, 209, 210, 0.88); }
body.is-light .ledger, body.page--commissions .ledger { color: var(--ink-0); }
.ledger a { pointer-events: auto; text-decoration: none; }
.ledger__name { font-weight: 500; letter-spacing: 0.14em; white-space: nowrap; }
.ledger__nav { display: flex; gap: clamp(0.8rem, 2.4vw, 2.2rem); }
.ledger__nav a { opacity: 0.75; transition: opacity 0.25s var(--ease); }
.ledger__nav a:hover, .ledger__nav a[aria-current] { opacity: 1; }

@media (max-width: 640px) {
  /* No hamburger. The sections move to a thumb-reachable bar instead. */
  .ledger__nav {
    position: fixed; inset: auto 0 0 0;
    justify-content: space-around; align-items: stretch;
    padding: 0 max(0.5rem, calc(var(--gutter) - 0.75rem))
             env(safe-area-inset-bottom);
    background: color-mix(in srgb, var(--ink-0) 88%, transparent);
    backdrop-filter: blur(12px);
    color: var(--paper);
    font-size: 0.66rem;
  }
  /* The bar was 44px but each link inside it was 16px, so most of the bar
     was not actually tappable. The link is the target, not the bar. */
  /* The bar sets its own target size; the pseudo-element would only
     reach outside it and overlap its neighbours. */
  .ledger__nav a {
    display: flex; align-items: center;
    min-height: 46px;
    padding: 0 0.5rem;
  }
  .ledger__nav a::after { content: none; }
  /* Her name is the only thing left at the top, and section labels scroll
     under it. A gradient scrim is not enough at this size — two lines of
     small mono type simply collide — so the top bar is opaque here. */
  .ledger { padding-block: 0.75rem; }
  .ledger::before {
    inset: 0;
    background: var(--scrim);
    backdrop-filter: blur(10px);
  }
  :root { --scrim: rgba(27, 28, 29, 0.9); }
  body.is-light, body.page--commissions { --scrim: rgba(207, 209, 210, 0.94); }
  body.is-light .ledger__nav,
  body.page--commissions .ledger__nav { color: var(--paper); }
}

/* ------------------------------------------------------------ the firing

   Clara asked for this to be the first thing on the site. It plays once
   and holds, because a firing happens once. As it leaves, the panel
   closes into the silhouette of the very jar it is making — traced off
   `horsehair-honey-1`, which is the first pot on the first shelf. */

/* The travel the resolve gets. Generous on purpose, and more so on a
   phone: a flick covers a thousand pixels or more in one gesture, and at
   the old 165svh the pot had formed and gone again inside a single
   swipe. */
.firing-wrap { height: 210svh; position: relative; }
@media (max-width: 860px) { .firing-wrap { height: 300svh; } }

.firing {
  position: sticky; top: 0;
  height: 100svh;
  display: grid;
  place-items: center;
  padding: max(5rem, 10vh) var(--gutter) clamp(2rem, 6vh, 5rem);
  overflow: clip;

  /* morph runs 0 -> 1 across the back half of the hero's scroll */
  --morph: clamp(0, calc((var(--p, 0) - 0.28) * 1.39), 1);
  /* The pot closes EARLY, while the ground is still dark and the
     silhouette has something to be legible against. Left until the end it
     shuts on a light grey field and reads as the video shrinking into a
     blob rather than as a shape. */
  --close: clamp(0, calc(var(--morph) / 0.55), 1);
  /* and once it is a pot, it stays one for a while before it goes */
  --leave: clamp(0, calc((var(--morph) - 0.78) / 0.22), 1);
}

/* Two grounds, cross-faded: her seamless opening dark, then the light
   ground the work stands on. */
.firing::before, .firing::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
}
.firing::before {
  background: linear-gradient(180deg,
    var(--ink-0) 0%, var(--ink-0) 34%, var(--ink-1) 66%, var(--ink-2) 100%);
  opacity: calc(1 - var(--morph));
}
.firing::after {
  background: linear-gradient(180deg,
    var(--ink-2) 0%, var(--ink-3) 42%, var(--paper) 88%);
  opacity: var(--morph);
}

/* Held to a column so the composition stays tight on a wide monitor
   instead of drifting to opposite edges of the screen. */
.firing__inner {
  width: min(1480px, 100%);
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: clamp(2rem, 5vw, 7rem);
}

.firing__type {
  grid-column: 1;
  max-width: 9ch;
  color: var(--paper);
  opacity: calc(1 - var(--leave));
}
.firing__name {
  margin: 0;
  font-size: clamp(2.9rem, 8vw, 8.5rem);
  font-weight: 300;
  font-variation-settings: "SOFT" 60, "WONK" 1, "opsz" 144;
  line-height: 0.92;
  letter-spacing: -0.02em;
}
.firing__role {
  margin: 1.4rem 0 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0.72;
}
.firing__role i { font-style: normal; margin: 0 0.6em; }

.firing__stage {
  grid-column: 2;
  margin: 0;
  opacity: calc(1 - var(--leave) * 0.92);
}

/* The firing, seen last through the shape of the pot it is making.
   The panel keeps the video's own 9:16 throughout — squashing it toward
   the jar's proportion was tried and reads as a glitch, not a form. What
   changes is the mask: it starts far larger than the panel, so no edge of
   it is on screen, and closes to the jar's silhouette traced from
   `horsehair-honey-1`. Height is `auto` so the shape stays pot-shaped
   while it scales. */
.firing__vessel {
  position: relative;
  height: min(76svh, 42vw);
  aspect-ratio: 0.5625;
  mask-image: url("media/vessel.svg");
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: calc(100% + 260% * (1 - var(--close))) auto;
}
.firing__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  background: var(--ink-1);
}

/* Anchored to the corner of the frame rather than hung under the video,
   so the caption reads as a wall label on the room instead of a caption
   floating in the middle of it. */
.firing__label {
  position: absolute;
  left: var(--gutter);
  bottom: clamp(1.5rem, 5vh, 3.5rem);
  display: flex; flex-direction: column;
  gap: 0.55rem;
  max-width: 36ch;
  color: var(--paper);
  opacity: calc(1 - var(--leave));
}
.firing__label .tag { color: var(--honey); }
/* Her name for the jar in the firing, set as a wall label would set it. */
.firing__title {
  font-size: clamp(1rem, 1.4vh, 1.25rem);
  font-variation-settings: "SOFT" 50, "WONK" 1;
  line-height: 1.25;
  opacity: 0.78;
}

@media (max-width: 860px) {
  .firing {
    place-items: start center;
    padding-top: max(4.5rem, 9vh);
  }
  .firing__inner {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: clamp(1.25rem, 3vh, 2rem);
  }
  .firing__type, .firing__stage { grid-column: 1; }
  .firing__type { max-width: none; text-align: center; }
  .firing__name { font-size: clamp(2.6rem, 13vw, 4.5rem); }
  .firing__role { font-size: 0.62rem; letter-spacing: 0.1em; }
  .firing__vessel { height: min(58svh, 112vw); }
  .firing__label {
    position: static;
    align-items: center; text-align: center;
    max-width: 30ch;
  }
  .firing__title { font-size: 0.95rem; }
}

/* ------------------------------------------------------------- sections */

.section {
  margin: 0 0 clamp(2rem, 6vh, 4rem);
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--quiet);
}

.work { padding: clamp(4rem, 12vh, 9rem) 0 clamp(2rem, 6vh, 5rem); }
.work > .section { padding-inline: var(--gutter); }

/* Anchor jumps must not land a heading under the fixed ledger. */
:where(#work, #information, #services) { scroll-margin-top: 5rem; }

/* ---------------------------------------------------------- the shelves

   Not a grid. The pots stand on a shared baseline at their true height
   relative to one another — a cup really is a fifth of the tall bottle,
   because the whole set was shot at one distance and that ratio was
   measured rather than composed. `--h`, `--w` and `--drop` come from
   measurements.json by way of the build; do not hand-tune them. */

.shelf { margin-bottom: clamp(3rem, 9vh, 7rem); }
.shelf__label {
  margin: 0 0 0.5rem;
  padding-inline: var(--gutter);
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ember);
}

.shelf__floor {
  display: flex;
  align-items: flex-end;      /* one baseline, always */
  gap: var(--gap);
  padding: 0 var(--gutter) 0.5rem;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--ink-0) 22%, transparent) transparent;
  /* the floor line the pots stand on */
  background:
    linear-gradient(var(--edge), var(--edge)) left bottom / 100% 1px no-repeat;
}

.pot {
  margin: 0;
  flex: 0 0 auto;
  width: calc(var(--unit) * var(--w));
  transform: translateY(calc(var(--unit) * var(--drop)));
}
.pot__open {
  display: block; width: 100%; padding: 0; margin: 0;
  border: 0; background: none; cursor: zoom-in;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.pot__img {
  width: 100%;
  height: calc(var(--unit) * var(--h));
  object-fit: cover;
  /* No card, no border, no shadow, no radius, and no edge mask either:
     process-assets.py rebuilds each plate's backdrop as exactly --paper,
     so the plate's edges already are the page. Feathering them was a
     workaround for a problem that no longer exists, and multi-layer
     mask-composite is a sharp edge to leave lying around. */
  transition: opacity 0.4s var(--ease);
}
.pot__open:hover .pot__img { opacity: 0.86; }

.pot__label {
  margin-top: 0.9rem;
  transform: translateY(calc(var(--unit) * var(--drop) * -1));
}
.pot__title {
  display: block;
  font-size: clamp(0.95rem, 1.6vh, 1.15rem);
  font-variation-settings: "SOFT" 50, "WONK" 1;
  line-height: 1.2;
}
.pot__meta {
  display: block;
  margin-top: 0.15rem;
  font-family: var(--mono);
  font-size: 0.63rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--quiet);
}
.pot__meta i { font-style: normal; opacity: 0.5; margin: 0 0.35em; }

/* On anything wider than a phone the whole shelf is sized to fit, because
   a pot you have to scroll sideways to find is a pot nobody sees. The
   divisor is that shelf's total width in units, written by the build.
   Below this width the shelf pans instead, which keeps the pots big
   enough to read on a phone and is a gesture that belongs there anyway. */
@media (min-width: 861px) {
  .shelf__floor {
    --unit: min(var(--unit-max), var(--fit));
  }
}

/* --------------------------------------------------------- information */

.information, .services, .commissions {
  padding: clamp(3rem, 10vh, 8rem) var(--gutter);
  max-width: 78rem; margin-inline: auto;
}

.prose {
  max-width: 34ch;
  margin: 0;
  font-size: clamp(1.35rem, 1.1rem + 1.4vw, 2.4rem);
  font-weight: 300;
  font-variation-settings: "SOFT" 40, "WONK" 1, "opsz" 90;
  line-height: 1.32;
  text-wrap: pretty;
}

/* Her words and her at the wheel, together. */
.information__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  align-items: end;
  gap: clamp(2rem, 6vw, 6rem);
}

/* Context, not work: phone-shot, small, and deliberately not given a
   plate's room. Sized off their own aspect rather than a shared tile. */
.context {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: clamp(1.5rem, 4vw, 4rem);
  margin-top: clamp(3rem, 9vh, 6rem);
}
.context__item { margin: 0; flex: 0 1 auto; }
.context__item img {
  width: 100%; height: auto;
  /* Pulled back hard on purpose. These are phone snaps of a studio — a
     pink noticeboard, blue jeans, a blue wheel — and at full saturation
     they shout over the pots, which are the only things on this site
     allowed to carry colour. Muted, they read as the room the work came
     out of. */
  filter: saturate(0.42) contrast(1.04) brightness(1.02);
}
.context__item figcaption { margin-top: 0.7rem; color: var(--quiet); }
.context__item--wheel { justify-self: end; width: min(380px, 100%); }
.context__item--kiln { flex-basis: min(300px, 56vw); }
.context__item--misc-work { flex-basis: min(190px, 38vw); }

@media (max-width: 860px) {
  .information__grid { grid-template-columns: 1fr; gap: clamp(2rem, 6vh, 3rem); }
  .context__item--wheel { justify-self: start; width: min(320px, 78vw); }
}

/* ------------------------------------------------------------ services */

.services__list {
  list-style: none; margin: 0; padding: 0;
  max-width: 42ch;
  display: grid; gap: 1.4rem;
}
.services__list li {
  padding-top: 1.4rem;
  border-top: 1px solid var(--edge);
  font-size: clamp(1.05rem, 1vw + 0.85rem, 1.4rem);
  font-weight: 300;
  line-height: 1.4;
  text-wrap: pretty;
}
.services__reach {
  display: flex; flex-wrap: wrap; gap: 0.6rem 2rem;
  margin: clamp(2.5rem, 7vh, 4rem) 0 0;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
}
.services__reach a {
  text-decoration: none;
  border-bottom: 1px solid var(--ember);
  padding-bottom: 2px;
  transition: color 0.25s var(--ease);
}
.services__reach a:hover { color: var(--ember); }

/* ----------------------------------------------------------- statement

   Her words, where she asked for them: the bottom of the main page. No
   image on this screen on purpose — after eleven pots, the most
   confident thing the site can do is show nothing. */

.statement {
  padding: clamp(6rem, 20vh, 14rem) var(--gutter);
  display: grid; place-items: center;
  background: linear-gradient(180deg, var(--paper), var(--ink-3));
}
.statement__text {
  margin: 0;
  max-width: 26ch;
  font-size: clamp(1.6rem, 1rem + 3vw, 3.6rem);
  font-weight: 300;
  font-variation-settings: "SOFT" 70, "WONK" 1, "opsz" 144;
  line-height: 1.22;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* ------------------------------------------------------- commissions */

.page--commissions { background: var(--paper); }
.commissions { padding-top: clamp(7rem, 18vh, 12rem); }
.commissions__waiting {
  max-width: 30ch;
  margin: clamp(2.5rem, 7vh, 4rem) 0 0;
  font-size: clamp(1.3rem, 1rem + 1.4vw, 2.2rem);
  font-weight: 300;
  font-variation-settings: "SOFT" 50, "WONK" 1;
  line-height: 1.3;
  color: var(--quiet);
}
.commissions__kiln {
  margin: clamp(3rem, 8vh, 5rem) 0 0;
  max-width: min(44rem, 90vw);
}
.commissions__kiln img {
  width: 100%; height: auto;
  /* Less muted than the context shots: here the kiln is the subject, not
     the room. Enough to keep the brick from going orange against a cool
     page, not so much that it goes dead. */
  filter: saturate(0.72) contrast(1.03);
}

/* ----------------------------------------------------------- the foot */

.foot {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem;
  padding: clamp(2rem, 6vh, 3.5rem) var(--gutter)
           calc(clamp(2rem, 6vh, 3.5rem) + env(safe-area-inset-bottom));
  background: var(--ink-3);
  color: var(--ink-0);
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

@media (max-width: 640px) { .foot { padding-bottom: 5.5rem; } }

/* ------------------------------------------------------ a closer look */

/* Light, not the usual dark lightbox. Every plate's ground is already
   exactly --paper, so on a dark field the photograph reads as a lit
   rectangle floating in a room. On the page colour the pot simply gets
   bigger, which is the only thing this view is for. */
.look {
  width: 100%; max-width: 100%; height: 100%; max-height: 100%;
  margin: 0; padding: 0; border: 0;
  background: var(--paper);
  color: var(--ink-0);
}
.look::backdrop { background: var(--paper); }
.look__frame {
  height: 100%;
  display: grid; grid-template-rows: 1fr auto;
  gap: 1rem;
  padding: clamp(1rem, 4vh, 3rem) var(--gutter);
}
.look__frame img {
  min-height: 0;
  width: 100%; height: 100%;
  object-fit: contain;
}
.look__bar {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem;
  font-family: var(--mono); font-size: 0.7rem;
  letter-spacing: 0.12em; text-transform: uppercase;
}
.look__close {
  border: 0; background: none; color: inherit; cursor: pointer;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  padding: 0.4rem 0;
}
.look__close:hover { color: var(--ember); }

/* --------------------------------------------------------------- still

   Reduced motion gets the whole site, finished and stationary: the
   firing resolved to its pot, and nothing that moves on scroll. */

@media (prefers-reduced-motion: reduce) {
  .firing-wrap { height: auto; }
  /* relative, not static: the caption is absolutely positioned to the
     corner of the frame and would otherwise escape to the foot of the
     document. */
  .firing { position: relative; height: auto; min-height: 100svh; --morph: 0; }
  .firing__vessel { mask-image: none; }
  .firing::before { opacity: 1; }
  .firing::after { opacity: 0; }
  .pot__img, .ledger, .firing__type { transition: none; }
}
