/* HALDEN, THE LONG DAY. Axiom Studios, The Site tier.
   Governed by world.md. Where this file and world.md disagree, world.md is right.

   Reading order:
     1  The ramp, seven stops
     2  Reset and base
     3  Type scale
     4  Page furniture: skip link, header, nav, footer
     5  Blocks: hero, prose, plates, rails, quotes, prices, form
     6  Reveal states
     7  Reduced motion, which is a composition and not a switch
     8  Narrow

   The default presentation in this file is the STATIC one. Everything the light engine does
   is additive and lives behind .js, so a document with no script is a finished site rather
   than a shell.
*/

@import url('../fonts/fonts.css');

/* ---------------------------------------------------------------------------
   1  The ramp

   Seven stops. Each is a complete token set, including where the light is
   coming from, because a sun that changes colour without moving is a filter
   and a sun that moves is an hour.

   With no script, html[data-hour] paints the route's own stop and nothing
   ever changes. With script, js/light.js writes the same six colour tokens
   plus the three position tokens onto the root element and interpolates them
   in OKLab, so these blocks are the fallback and the source of the numbers
   at the same time.
--------------------------------------------------------------------------- */

:root {
  /**
   * THE ACCENT IS CANON GREASE, and it was the one colour the ramp reconcile missed.
   *
   * `#d9873f` was the retired hour-7 glow doing a second job as the action fill. It survived
   * the first pass because it sits outside the ramp, in `:root`, so neither the hours table
   * nor `tools/tokens.mjs` had any reason to look at it. It was found by reading the LIVE
   * stylesheet's tokens after the deploy rather than by re-reading the source: a value that
   * no gate covers is only visible from outside.
   *
   * `--on-accent` follows it to canon ink. Grease is rationed across HALDEN to the mark, the
   * signature gesture and the primary action; here the sun in the lockup and this fill are
   * exactly two of those three, so the ration holds.
   */
  --accent: #c8400f;
  --on-accent: #ede8df;
  /**
   * THE ACTION'S GROUND, FIXED ACROSS EVERY HOUR, AND CANON INK.
   *
   * The action used to be a grease filled button with dark text on it. Reconciled to canon
   * grease that measures 3.95:1, under the line, and canon says exactly why: grease is
   * permitted on large text, the mark and the ring, and is FORBIDDEN on anything under 18px.
   * This label is 0.92rem. Canon also gives the remedy in the same breath, that where the
   * grease has to carry small text it sits on ink instead, so that is what this is: an ink
   * slab, paper label, grease edge. The label clears at about 17:1 on every hour.
   *
   * It does not track the ramp. Every other surface here changes with the light and this one
   * deliberately does not: the primary action is the one thing that must look identical at
   * 04:52 and at 22:40, so a visitor who scrolled the whole day still recognises it.
   */
  --action-ground: #0a0a0b;

  /* The light, defined once and referenced by both the fixed body layer and the
     home page's in place stops.

     TWO layers, and the second is the one that makes this work at every hour. A
     glow alone is invisible at midday, because a pale light on a pale wall has
     no gradient to show: the first version of this build lost its light source
     entirely on the three bright routes. Real light reads as brightness AND
     falloff, so the second layer darkens away from the source using the hour's
     own ink. The sun now has a direction at 13:20 as well as at 22:40.

     If color-mix is unsupported the layer simply does not paint and the page is
     a flat colour, which is a degradation in decoration only: no text, no
     control and no structural rule depends on it. */
  --light: radial-gradient(
      circle var(--glow-r) at var(--glow-x) var(--glow-y),
      color-mix(in oklab, var(--glow) 46%, transparent) 0%,
      transparent 70%
    ),
    radial-gradient(
      circle calc(var(--glow-r) * 2) at var(--glow-x) var(--glow-y),
      transparent 26%,
      color-mix(in oklab, var(--ink) 17%, transparent) 100%
    );

  --measure: 62ch;
  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  --rhythm: clamp(3.5rem, 9vw, 7.5rem);
  --edge: min(1180px, 100% - (var(--gutter) * 2));
}

/**
 * THE DAY, RE-ANCHORED ON THE BRAND'S OWN FIVE COLOURS. 14 August 2026.
 *
 * This ramp ran navy, grey, white, cream, purple, blue, black. It was a good day and it was
 * a different brand: measured on the live CSS, this build shared NOT ONE token and NOT ONE
 * typeface with HALDEN's Page and Film, which both hold canon exactly. The divergence came
 * in when the Site was re-locked on THE LONG DAY and the re-lock took the palette with it.
 *
 * Canon's five hexes already read as a day, which is why the reconcile improves the concept
 * rather than compromising it:
 *
 *   --ink    #0a0a0b  the last song, and the hour before dawn
 *   --slate  #4a4844  dusk
 *   --bone   #bfb8ab  overcast, and every rule at every hour
 *   --paper  #ede8df  midday
 *   --grease #c8400f  the sun
 *
 * So the light still moves and it is still the navigation. What it no longer does is travel
 * through hues this business does not own. Every hour below is a neighbour of those five.
 *
 * THE THREE CONTRAST RULINGS ARE UNCHANGED and `tools/contrast.mjs` is what judges this, not
 * the eye: reading states clear on the design alone, a route's scrolled state is a reading
 * state, and a crossing that cannot serve one ink flattens surface into bg.
 */

/* 1  04:52  first light */
html[data-hour='1'] {
  --bg: #0a0a0b;
  --surface: #131211;
  --ink: #ded8cd;
  --dim: #8a857b;
  --rule: #1e1c19;
  --glow: #3a2a1e;
  --glow-x: 8%;
  --glow-y: 92%;
  --glow-r: 60%;
}

/* 2  07:40  morning */
html[data-hour='2'] {
  --bg: #cfc9bd;
  --surface: #ded9cf;
  --ink: #2a2723;
  --dim: #524d46;
  --rule: #b8b2a5;
  --glow: #d9cdb6;
  --glow-x: 22%;
  --glow-y: 68%;
  --glow-r: 70%;
}

/* 3  13:20  midday */
html[data-hour='3'] {
  --bg: #e6e1d7;
  --surface: #ede8df;
  --ink: #1b1a17;
  --dim: #615c56;
  --rule: #d6d0c4;
  --glow: #fbf3df;
  --glow-x: 50%;
  --glow-y: 8%;
  --glow-r: 95%;
}

/* 4  17:10  golden */
html[data-hour='4'] {
  --bg: #e0cdb1;
  --surface: #ecdcc4;
  --ink: #2b2117;
  --dim: #60503c;
  --rule: #cdb694;
  --glow: #e39a4a;
  --glow-x: 76%;
  --glow-y: 46%;
  --glow-r: 80%;
}

/* 5  19:35  dusk */
html[data-hour='5'] {
  --bg: #453a31;
  --surface: #52453a;
  --ink: #e9e1d4;
  --dim: #c8beaf;
  --rule: #5f5145;
  --glow: #c8400f;
  --glow-x: 92%;
  --glow-y: 78%;
  --glow-r: 70%;
}

/* 6  21:00  blue hour */
html[data-hour='6'] {
  --bg: #1c1815;
  --surface: #24201c;
  --ink: #ddd6c9;
  --dim: #968f85;
  --rule: #2c2723;
  --glow: #8a4a24;
  --glow-x: 96%;
  --glow-y: 96%;
  --glow-r: 55%;
}

/* 7  22:40  night, one lamp. The glow and the accent are the same value here,
   which is the arc's destination: the light you write the enquiry under. */
html[data-hour='7'] {
  --bg: #0a0a0b;
  --surface: #131215;
  --ink: #e4dfd4;
  --dim: #8b867e;
  --rule: #1e1e22;
  --glow: #b8571f;
  --glow-x: 50%;
  --glow-y: 30%;
  --glow-r: 34%;
}

/* ---------------------------------------------------------------------------
   2  Reset and base
--------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.62;
  font-weight: 400;
  /* `clip`, not `hidden`. Both stop a stray sideways scroll, but `hidden` makes
     the body a scroll container and that quietly breaks position:sticky in the
     header below. `clip` does not create one. */
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
}

/* The light source. One soft radial per document, fixed to the viewport so it
   behaves like a window rather than like a decoration glued to the content. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--light);
}

/* Everything readable sits above the light. */
.shell {
  position: relative;
  z-index: 1;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: inherit;
}

:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

.wrap {
  width: var(--edge);
  margin-inline: auto;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--surface);
  color: var(--ink);
  padding: 0.75rem 1rem;
  z-index: 99;
}
.skip:focus {
  left: 0.5rem;
  top: 0.5rem;
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   3  Type

   Bodoni Moda carries an optical size axis, so font-optical-sizing does real
   work here: the same face is set at 12px in the nav and at 90px in a hero
   without the hairlines clogging at one end or vanishing at the other.
--------------------------------------------------------------------------- */

.display,
h1,
h2,
.h2 {
  font-family: 'Instrument Serif', ui-serif, Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: -0.012em;
  margin: 0;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.6rem, 1.5rem + 5.4vw, 5.6rem);
}

h2,
.h2 {
  font-size: clamp(1.9rem, 1.2rem + 2.9vw, 3.2rem);
}

h3 {
  font-family: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
  margin: 0 0 1rem;
}

p {
  margin: 0 0 1.15em;
  max-width: var(--measure);
}

.lead {
  font-size: clamp(1.12rem, 1rem + 0.62vw, 1.42rem);
  line-height: 1.5;
  max-width: 46ch;
}

.dim {
  color: var(--dim);
}

.meta {
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  color: var(--dim);
  margin: 0;
}

/* ---------------------------------------------------------------------------
   4  Page furniture
--------------------------------------------------------------------------- */

/* The menu follows you down.

   `/work/` is nearly 11000px tall on a phone, so the nav being a thing you
   scroll back to the top for was a real cost. It sticks instead.

   The bar takes its colour from `--bg`, which means it is not a chrome layer
   sitting on top of the concept: it is lit by the same light as everything
   else and gets later as you go down the page, exactly like the room does.

   Sticky with NO javascript, because it is position rather than motion, and a
   visitor with no script has the furthest to scroll. The `.stuck` class only
   adds the hairline, so its absence costs nothing. */
.site-head {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: clamp(1.25rem, 3vw, 2rem) 0 clamp(0.75rem, 2vw, 1.1rem);
  transition: padding 260ms ease, background-color 260ms ease;
}

/* Translucent rather than solid so the hour's light still reads through the
   bar and the page does not get a hard opaque lid. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .site-head {
    background: color-mix(in oklab, var(--bg) 78%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(1.08);
    backdrop-filter: blur(14px) saturate(1.08);
  }
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-head {
    background: var(--bg);
  }
}

/* Seamless at the top of a page, a bar once you have left it. */
.site-head::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--rule);
  opacity: 0;
  transition: opacity 260ms ease;
}

.site-head.stuck::after {
  opacity: 1;
}

/* Condensed once it is carrying, because a full lockup pinned to the top of a
   phone screen costs more than it gives. */
.site-head.stuck {
  padding-top: clamp(0.6rem, 1.6vw, 0.9rem);
  padding-bottom: clamp(0.6rem, 1.6vw, 0.9rem);
}

.site-head.stuck .brand .mark {
  width: 40px;
}

.brand .mark {
  transition: width 260ms ease;
}

/* On the home page in the still composition, every section paints its own hour
   and the root holds only the first, so a bar tinted with `--bg` would carry
   04:52 across a page that ends at rose brown. All six of home's stops are
   night, so one fixed dark tint is right for the whole of it. */
.still[data-stops] .site-head {
  background: rgba(12, 12, 18, 0.72);
}

/* The skip link's destination must not land under the bar. */
#main {
  scroll-margin-top: 6rem;
}

/* On a phone the header wraps to two rows, and pinned that measured 122px, or
   14.5% of the screen, given up on every scroll. Once it is carrying, the
   wordmark goes and the mark alone holds the row with the nav, which brings it
   to one line. The brand keeps its aria-label, so the link is still "HALDEN,
   home" to a screen reader with or without the word on screen. */
@media (max-width: 40rem) {
  .site-head.stuck .head-inner {
    flex-wrap: nowrap;
    gap: 0.75rem;
  }

  /* Collapsing the row must not collapse the TARGET. Zeroing this took the
     brand to 34x13, and it is the only link back to the home page anywhere in
     the build. The box keeps 44px; only the mark inside it shrinks. */
  .site-head.stuck .brand {
    min-height: 48px;
    min-width: 48px;
    justify-content: center;
  }

  .site-head.stuck .brand .wordmark {
    display: none;
  }

  .site-head.stuck .brand .mark {
    width: 34px;
  }
}

.head-inner {
  width: var(--edge);
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  text-decoration: none;
  flex: 0 0 auto;
}

.brand .mark {
  width: 52px;
  height: auto;
  overflow: visible;
  flex: 0 0 auto;
}

.brand .wordmark {
  font-family: 'Instrument Serif', ui-serif, Georgia, serif;
  font-optical-sizing: auto;
  font-size: 1.02rem;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  text-transform: uppercase;
  line-height: 1;
}

/* Seven items do not fit on a phone and a disclosure menu would put the whole
   day behind a tap. The six route links scroll inside themselves so the page
   never scrolls sideways and the day stays visible as a sequence.

   The ACTION is not in that scroller. Measured at 390px, it was landing off
   screen with nothing to signal the rail scrolled, which hid the site's single
   primary action behind a gesture nobody knows to make. Six route links can
   wait behind a swipe. The action cannot. */
.nav {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: clamp(0.5rem, 1.7vw, 1.5rem);
  justify-content: flex-end;
}

.nav ul {
  list-style: none;
  display: flex;
  gap: clamp(0.5rem, 1.7vw, 1.5rem);
  margin: 0;
  padding: 0;
  justify-content: flex-end;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  overscroll-behavior-x: contain;
}
.nav ul::-webkit-scrollbar {
  display: none;
}

.nav li {
  flex: 0 0 auto;
}

/* 48px of target in BOTH axes, which is rubric line 3's number rather than the
   44 taken from Apple's guidance. Measured at 27x27 before any of this. Height
   comes from padding so the type is untouched; width comes from a minimum plus
   inline padding, because "Alone" is a short word and was still only 37px wide
   after the height was fixed. The gap shrinks to absorb the new padding so the
   nav's visual rhythm does not change. */
.nav a {
  text-decoration: none;
  font-size: 0.83rem;
  letter-spacing: 0.05em;
  color: var(--dim);
  white-space: nowrap;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  min-width: 48px;
  padding-inline: 0.3rem;
  border-bottom: 1px solid transparent;
}

.nav a:hover {
  color: var(--ink);
}

/* The current route is marked by structure, not by the accent. The accent has
   exactly one meaning in this build and it is the action. */
.nav a[aria-current='page'] {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

.nav-action {
  flex: 0 0 auto;
}

.site-foot {
  border-top: 1px solid var(--rule);
  margin-top: var(--rhythm);
  padding: clamp(2rem, 5vw, 3.5rem) 0 clamp(2.5rem, 5vw, 4rem);
}

.site-foot .wrap {
  display: grid;
  gap: 1.25rem;
}

.disclaimer {
  font-size: 0.86rem;
  color: var(--dim);
  max-width: 68ch;
  margin: 0;
}

.credit {
  font-size: 0.76rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  margin: 0;
}

/* ---------------------------------------------------------------------------
   5  Blocks
--------------------------------------------------------------------------- */

main {
  display: block;
}

.band {
  padding-block: var(--rhythm);
}

.band + .band {
  padding-top: 0;
}

/* The page head already carries its own bottom space. Left to stack, the two
   ran to about 11rem of nothing between a lead and the first thing under it. */
.page-head + .band {
  padding-top: 0;
}

.band-rule {
  border-top: 1px solid var(--rule);
  padding-top: var(--rhythm);
}

/* Hero ------------------------------------------------------------------ */

.hero {
  min-height: min(88svh, 900px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: clamp(3rem, 12vh, 8rem);
}

.hero h1 {
  max-width: 15ch;
}

.hero .lead {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
}

.cue {
  margin-top: clamp(2.5rem, 7vh, 5rem);
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.cue::after {
  content: '';
  height: 1px;
  width: clamp(2.5rem, 8vw, 6rem);
  background: var(--rule);
}

/* Page head, every route except / -------------------------------------- */

.page-head {
  padding-block: clamp(3rem, 10vh, 6.5rem) clamp(2rem, 5vw, 3.5rem);
}

.page-head .lead {
  margin-top: 1.5rem;
}

/* Prose ----------------------------------------------------------------- */

.prose h2 {
  margin-bottom: 1.1rem;
  max-width: 20ch;
}

.prose + .prose {
  margin-top: var(--rhythm);
}

/* Two column: a heading holding its own column beside the prose. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.5rem);
}

@media (min-width: 62rem) {
  .split {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: clamp(2rem, 5vw, 5rem);
  }
  .split > h2 {
    position: sticky;
    top: 2rem;
    align-self: start;
  }
}

/* Plates, which are the photographs -------------------------------------- */

/* Greybox until the imagery ceiling is approved. A plate with no image is a
   tonal block that reads as a designed slot rather than as a broken picture,
   and it declares itself in the accessible name so nobody reads a screenshot
   as a finished page. */
.plate {
  margin: 0;
  position: relative;
}

.plate .frame {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  aspect-ratio: 3 / 2;
}

/* The tonal wash is what makes an EMPTY slot read as a designed one. On a slot
   that has its photograph it would be a filter over somebody's work. */
.plate.has-image .frame::after {
  display: none;
}

.plate .frame::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    130deg,
    color-mix(in oklab, var(--glow) 22%, transparent) 0%,
    transparent 52%,
    color-mix(in oklab, var(--ink) 12%, transparent) 100%
  );
}

.plate.portrait .frame {
  aspect-ratio: 4 / 5;
}
.plate.tall .frame {
  aspect-ratio: 2 / 3;
}
.plate.wide .frame {
  aspect-ratio: 16 / 9;
}

.plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.plate figcaption {
  margin-top: 0.7rem;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--dim);
  max-width: 34ch;
}

/* The six frame rail on / ------------------------------------------------ */

.rail {
  display: grid;
  gap: clamp(1.25rem, 2.5vw, 2rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: clamp(2rem, 4vw, 3rem);
}

@media (min-width: 48rem) {
  .rail {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 72rem) {
  .rail {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
  .rail .plate:nth-child(even) {
    margin-top: clamp(1.5rem, 4vw, 3.5rem);
  }
}

/* Gallery groups on /work/ ----------------------------------------------- */

.group + .group {
  margin-top: var(--rhythm);
  border-top: 1px solid var(--rule);
  padding-top: var(--rhythm);
}

.group-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem 2rem;
  margin-bottom: 0.9rem;
}

.group-head h2 {
  font-size: clamp(1.6rem, 1.1rem + 2.1vw, 2.5rem);
}

.group > p {
  max-width: 52ch;
}

.plates {
  display: grid;
  gap: clamp(1rem, 2.2vw, 1.75rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: clamp(1.75rem, 4vw, 2.75rem);
}

@media (min-width: 56rem) {
  .plates {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
  /* An editorial rhythm rather than a grid of equal tiles: the first frame
     takes half the width, the rest fall into thirds and pairs. */
  .plates > *:nth-child(1) {
    grid-column: span 3;
  }
  .plates > *:nth-child(2) {
    grid-column: span 3;
  }
  .plates > *:nth-child(3) {
    grid-column: span 2;
  }
  .plates > *:nth-child(4) {
    grid-column: span 2;
  }
  .plates > *:nth-child(5) {
    grid-column: span 2;
  }
  .plates > *:nth-child(6) {
    grid-column: span 3;
  }
}

/* The hours rail on /hours/ ---------------------------------------------- */

.moments {
  display: grid;
  gap: 0;
  margin-top: clamp(1rem, 3vw, 2rem);
}

.moment {
  display: grid;
  gap: clamp(0.9rem, 2vw, 2rem);
  padding-block: clamp(2rem, 5vw, 3.25rem);
  border-top: 1px solid var(--rule);
  align-items: start;
}

@media (min-width: 54rem) {
  .moment {
    grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
  }
}

.moment h2 {
  font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.3rem);
  max-width: 14ch;
}

.moment p {
  margin: 0;
  max-width: 54ch;
}

/* Quotes ----------------------------------------------------------------- */

.quotes {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3rem);
}

@media (min-width: 56rem) {
  .quotes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(2.5rem, 5vw, 4.5rem);
  }
}

blockquote {
  margin: 0;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
}

blockquote p {
  font-family: 'Instrument Serif', ui-serif, Georgia, serif;
  font-optical-sizing: auto;
  font-size: clamp(1.25rem, 1.05rem + 0.9vw, 1.72rem);
  line-height: 1.32;
  margin: 0 0 1.1rem;
  max-width: 34ch;
}

blockquote cite {
  font-style: normal;
  font-size: 0.84rem;
  letter-spacing: 0.04em;
  color: var(--dim);
}

blockquote cite strong {
  color: var(--ink);
  font-weight: 600;
}

/* Lists ------------------------------------------------------------------ */

.notes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.35rem;
  max-width: var(--measure);
}

.notes li {
  padding-left: 1.6rem;
  position: relative;
}

.notes li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 0.7rem;
  height: 1px;
  background: var(--rule);
}

.facts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  max-width: 44ch;
}

.facts li {
  padding-block: 0.85rem;
  border-top: 1px solid var(--rule);
  font-size: 0.95rem;
}

.facts li:last-child {
  border-bottom: 1px solid var(--rule);
}

/* Collections ------------------------------------------------------------ */

.collections {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2rem);
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
}

@media (min-width: 58rem) {
  .collections {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.collection {
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: clamp(1.5rem, 3vw, 2.25rem);
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.collection h2 {
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
}

.price {
  font-family: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: 1.5rem;
  font-weight: 300;
  letter-spacing: 0.01em;
  margin: 0;
}

.collection p {
  margin: 0;
  max-width: none;
}

.most {
  align-self: flex-start;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  border: 1px solid var(--rule);
  padding: 0.3rem 0.6rem;
}

/* Actions ---------------------------------------------------------------- */

.action {
  display: inline-block;
  background: var(--action-ground);
  color: var(--on-accent);
  text-decoration: none;
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 0.95rem 1.7rem;
  border: 1px solid var(--accent);
  cursor: pointer;
}

.action:hover {
  background: transparent;
  color: var(--ink);
  border-color: var(--ink);
}

.textlink {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.15rem;
  font-size: 0.92rem;
  letter-spacing: 0.03em;
}

.textlink:hover {
  border-bottom-color: var(--ink);
}

.close-band {
  padding-block: var(--rhythm);
  border-top: 1px solid var(--rule);
}

.close-band h2 {
  margin-bottom: 1.1rem;
}

.close-band .action {
  margin-top: 1rem;
}

/* Form ------------------------------------------------------------------- */

.enquire-grid {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}

@media (min-width: 60rem) {
  .enquire-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  }
}

.notice {
  border: 1px solid var(--rule);
  background: var(--surface);
  padding: clamp(1.25rem, 3vw, 1.85rem);
  margin-bottom: clamp(1.75rem, 4vw, 2.5rem);
}

.notice h2 {
  font-size: clamp(1.2rem, 1rem + 0.9vw, 1.55rem);
  margin-bottom: 0.7rem;
}

.notice p {
  margin: 0;
  font-size: 0.95rem;
  color: var(--dim);
  max-width: 60ch;
}

.field {
  display: grid;
  gap: 0.45rem;
  margin-bottom: 1.35rem;
}

.field label {
  font-size: 0.82rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
}

.field .hint {
  font-size: 0.84rem;
  color: var(--dim);
  margin: 0;
  max-width: 46ch;
}

input,
select,
textarea {
  font: inherit;
  font-size: 1rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: 0.8rem 0.9rem;
  width: 100%;
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
}

select {
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 1.25rem) 1.25rem, calc(100% - 0.9rem) 1.25rem;
  background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
  background-repeat: no-repeat;
}

textarea {
  min-height: 9rem;
  resize: vertical;
}

::placeholder {
  color: var(--dim);
  opacity: 1;
}

/* Chrome pays a one off long frame the first time a native input, a textarea
   and separately a date input paints. These are pre warmed at load rather than
   at first scroll, because the cost lands somewhere either way and the load
   frame is the one nobody is watching. visibility:hidden does NOT pre raster,
   so they are painted at a real opacity. */
.prewarm {
  position: fixed;
  left: 0;
  top: 0;
  width: 1px;
  height: 1px;
  opacity: 0.01;
  pointer-events: none;
  z-index: -1;
  border: 0;
  padding: 0;
}

.outcome {
  margin-top: 1.25rem;
  font-size: 0.95rem;
  color: var(--dim);
  max-width: 52ch;
  min-height: 1.5em;
}

.aside-block h2 {
  font-size: clamp(1.2rem, 1rem + 0.9vw, 1.55rem);
  margin-bottom: 1rem;
}

.aside-block ul {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  display: grid;
  gap: 0.6rem;
  font-size: 0.95rem;
}

.last-line {
  font-family: 'Instrument Serif', ui-serif, Georgia, serif;
  font-optical-sizing: auto;
  font-size: clamp(1.15rem, 1rem + 0.8vw, 1.5rem);
  line-height: 1.35;
  max-width: 30ch;
}

/* ---------------------------------------------------------------------------
   5b  The entrance

   The room starts dark and the light comes up. For that to be true the content
   has to be down BEFORE the first paint, which is why a two line script in the
   head sets this class rather than js/main.js at the end of the body: setting
   it from down there would show the page, hide it, and show it again.

   Three ways out, because a class that hides content is the most dangerous
   thing in this stylesheet:

     - No script at all: the class is never added and nothing is ever hidden.
     - Script, but js/main.js never runs or throws: the failsafe animation
       fires at 2.6s and puts the content back with no javascript involved.
     - Reduced motion: no entrance, content up from the start.

   js/main.js REMOVES this class the moment it takes control, and it has to:
   an animation with a forwards fill outranks an inline style, so leaving it on
   would let the failsafe pin the page at full exposure and silently defeat the
   crossing's own fade.
--------------------------------------------------------------------------- */

.enter #main {
  opacity: 0;
  animation: enter-failsafe 0s linear 2.6s forwards;
}

@keyframes enter-failsafe {
  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .enter #main {
    opacity: 1;
    animation: none;
  }
}

/* The exposure is driven as an inline style by js/main.js. The transition here
   is only for the tail of a crossing, so the recovery is not a hard step if a
   frame is dropped. */
#main {
  transition: opacity 90ms linear;
}

/* ---------------------------------------------------------------------------
   6  Reveal states

   Opacity only. world.md section 6 forbids transform on anything carrying
   words, and every reveal in this build carries words or sits beside them.

   Held at 0.001 rather than 0, and painted from load, because a large layer
   first rastered mid scroll costs a long frame. will-change keeps it on its
   own layer for the duration and is removed once the element has arrived.
--------------------------------------------------------------------------- */

.js .rise {
  opacity: 0.001;
  will-change: opacity;
}

.js .rise.in {
  opacity: 1;
  transition: opacity 900ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.js .rise.settled {
  will-change: auto;
}

/* ---------------------------------------------------------------------------
   7  Reduced motion, which is a composition

   The light does not move. Instead the home page paints each of its six
   sections at its own hour, so the day is still legible as a progression down
   the page with zero measured movement. The other six routes already sit at a
   single hour, so they are unchanged and were composed for it.

   Same treatment when there is no script at all, since the ramp needs one.
--------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .js .rise,
  .js .rise.in {
    opacity: 1;
    transition: none;
    will-change: auto;
  }

  html {
    scroll-behavior: auto;
  }
}

/* The six stops of the home page, painted in place. Applied when the light
   engine is not driving, which is either of the two cases above. */
/* The home page's own arc, 04:52 to a little after seven.

   It stays in the DARK half for its whole length, and that is a correction made
   on a measured failure rather than a preference. The first version climbed to a
   bright morning across the same six stops, which meant crossing the mid tones,
   and at the crossing `--dim` on `--bg` measured about 2.8:1 with the six frame
   captions sitting in it. A continuous ramp that crosses the middle cannot keep
   a single ink legible at both ends: either the ink flips somewhere, which is a
   visible discontinuity, or the middle is unreadable.

   So the home page is a night page that warms rather than brightens. Blue black
   through indigo, violet and mauve to a rose brown, with the sun climbing toward
   a horizon it does not reach until you click. The ink stays light throughout
   and never has to flip. The daylight arrives on /alone/ at 07:40, which makes
   the first crossing the moment the sun comes up. */
.still .stop[data-stop='1'] { --bg: #0a0a0b; --surface: #131211; --ink: #ded8cd; --dim: #8a857b; --rule: #1e1c19; --glow: #3a2a1e; --glow-x: 8%;  --glow-y: 92%; --glow-r: 60%; }
.still .stop[data-stop='2'] { --bg: #12100e; --surface: #1a1714; --ink: #e0dacf; --dim: #8d887d; --rule: #241f1a; --glow: #4a3320; --glow-x: 12%; --glow-y: 88%; --glow-r: 62%; }
.still .stop[data-stop='3'] { --bg: #1a1613; --surface: #221d18; --ink: #e2ddd1; --dim: #938c81; --rule: #2b2520; --glow: #5d3d22; --glow-x: 17%; --glow-y: 85%; --glow-r: 64%; }
.still .stop[data-stop='4'] { --bg: #241d17; --surface: #2c241c; --ink: #e5dfd3; --dim: #9b9487; --rule: #342b23; --glow: #74491f; --glow-x: 22%; --glow-y: 81%; --glow-r: 68%; }
.still .stop[data-stop='5'] { --bg: #2e241b; --surface: #372c21; --ink: #e7e1d5; --dim: #a49c90; --rule: #3e3227; --glow: #8d5419; --glow-x: 27%; --glow-y: 77%; --glow-r: 72%; }
.still .stop[data-stop='6'] { --bg: #3a2c20; --surface: #443426; --ink: #e9e3d7; --dim: #b0a89a; --rule: #4a3b2b; --glow: #a85f14; --glow-x: 33%; --glow-y: 72%; --glow-r: 78%; }

.still .stop {
  background: var(--bg);
  color: var(--ink);
  position: relative;
}

/* Each stop carries its own light when the page is still, since the fixed one
   on the body can only be a single hour at a time. */
.still .stop::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--light);
}

.still .stop > * {
  position: relative;
  z-index: 1;
}

/* The body light would sit under every stop and fight them. Scoped to the one
   route that has stops, because the other six need their single fixed light
   in the still composition exactly as much as they do in the moving one. */
.still[data-stops] body::before {
  display: none;
}

/* ---------------------------------------------------------------------------
   8  Narrow

   The contract is 360px in BOTH axes. Height is the scarcer one in landscape
   and is where a build that passes a width check still collides on a real
   phone, so the hero is capped by viewport height rather than by a fixed min.
--------------------------------------------------------------------------- */

/* A photographer's gallery is not a thumbnail grid. At 390px the two column
   plates measured about 165px wide, which shows the work at the size of a
   contact print and undersells every frame in it. One column on a phone gives
   each photograph the full measure, and the page being longer is the correct
   trade: people scroll, and they do not squint. */
@media (max-width: 34rem) {
  .plates,
  .rail {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(1.5rem, 5vw, 2.25rem);
  }
}

@media (max-width: 30rem) {
  .head-inner {
    gap: 0.9rem;
  }

  .nav ul {
    justify-content: flex-start;
    /* The affordance. Scoped to the width where the rail is certainly
       overflowing, so the last item is never faded on a nav that fits. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 1.75rem), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 1.75rem), transparent);
  }

  .hero {
    min-height: 0;
    padding-block: clamp(2rem, 8vh, 4rem) clamp(2.5rem, 8vh, 4rem);
  }

  /* On desktop this space is the hero breathing. On a phone it is a screen of
     nothing between the lead and the scroll cue. */
  .cue {
    margin-top: clamp(1.75rem, 5vh, 2.5rem);
  }

  .group-head {
    gap: 0.35rem;
  }

  .collection {
    padding: 1.25rem;
  }
}

/* The lockup measured 20px tall, which is both a small mark and a small target.
   The mark carries the whole brand on the one surface where it is the only
   thing on screen with the wordmark. */
@media (max-width: 40rem) {
  .brand {
    min-height: 48px;
  }
  .brand .mark {
    width: 62px;
  }
  .brand .wordmark {
    font-size: 1.08rem;
  }

  /* A 20px high inline link is a thumb sized problem, not a type one. The rule
     stays on the text; the target grows around it. */
  .textlink {
    display: inline-flex;
    align-items: center;
    min-height: 48px;
  }
}

@media (max-height: 34rem) and (orientation: landscape) {
  .hero {
    min-height: 0;
    padding-block: 2rem;
  }
  .cue {
    display: none;
  }
}

@media print {
  body::before,
  .still .stop::before {
    display: none;
  }
}
