/*
 * Common Material — Te Whanganui-a-Tara Wellington · June 5–7 2026
 * Mobile-first implementation of the Figma "Mobile v2" (393px) and
 * "Desktop v2" (1440px) frames.
 *
 * Type scales fluidly across the whole range with clamp(); structural
 * differences (single-column vs two-column designers, asymmetric
 * programme indent, hero line breaks) flip at the 720px breakpoint.
 */

/* ---------- Fonts ---------- */

@font-face {
  font-family: "Times LT Std";
  src: url("Times%20LT%20Std/TimesLTStd-Roman.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Times LT Std";
  src: url("Times%20LT%20Std/TimesLTStd-Bold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Times LT Std";
  src: url("Times%20LT%20Std/TimesLTStd-Italic.otf") format("opentype");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Times LT Std";
  src: url("Times%20LT%20Std/TimesLTStd-BoldItalic.otf") format("opentype");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Grosse Plakatschrift";
  src: url("Grosse%20Plakatschrift/GrossePlakatschrift260503CM-Checking.otf")
    format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */

:root {
  --color-bg: #ffffff;
  --color-ink: #000000;
  --color-ink-inverse: #ffffff;
  --color-yellow: #e5ff00;
  --color-black: #000000;

  --font-serif: "Times LT Std", "Times New Roman", Times, serif;
  --font-display: "Grosse Plakatschrift", "Big Caslon",
    "Bodoni 72", "Didot", "Times New Roman", serif;

  /*
   * Type scales — designed to land on Figma values at the two anchor
   * widths: 393 (mobile) and 1440 (desktop). Continuous across the
   * range, so no breakpoint jumps.
   *
   * body:  20px @ 393–720  →  36px @ 1440
   * poem:  20px @ 393      →  60px @ 1440
   * small: 20px @ 393–720  →  30px @ 1440  (footer meta + event-coda)
   */
  --type-body: clamp(20px, calc(4px + 2.222vw), 36px);
  --type-poem: max(20px, 4.17vw);
  --type-small: clamp(20px, calc(10px + 1.389vw), 30px);

  /* Mobile-first frame widths — desktop max-widths come into play
     above the 720px breakpoint via the section rules. */
  --frame-width: 1440px;
  --inner-width: 1296px;
  /* Essay column expressed in em so it scales with font-size and the
   * words-per-line ratio stays roughly constant when the type bumps. */
  --essay-column: 26em;
  --programme-column: 1082px;

  /* Mobile shadow (16px blur) gets overridden for desktop (8px). */
  --shadow-section: 0 4px 16px rgba(0, 0, 0, 0.75);

  /* Programme sticky peek position — used as the sticky `top` and as
   * the dock-fallback Y in the layered-scroll calcs.
   *
   * Mobile (default): 80lvh — keeps the heading clear of mobile
   * Safari's URL bar (svh ≈ 85–88% of lvh on iOS, so a 90lvh sticky
   * would land inside the URL overlay).
   *
   * Desktop (≥720): calc(100lvh − 158px) — a fixed 158px-tall band
   * below the peek = padding-top (72) + heading line (38) + 48px
   * mirror gap. That puts the bottom of the heading 48px above the
   * viewport bottom, matching the 48px hero padding-top above
   * "Common Material" — top of CM ↔ bottom of "Programme of Events"
   * read as symmetric edges. */
  --peek-top: 80lvh;
}

@media (min-width: 720px) {
  :root {
    --shadow-section: 0 4px 8px rgba(0, 0, 0, 0.75);
    --peek-top: calc(100lvh - 158px);
  }
}

/* ---------- Reset / globals ---------- */

* {
  box-sizing: border-box;
}

/* Thin underlines everywhere a text-decoration-line applies (links,
 * signup button, pinned programme heading, signup placeholder).
 * text-decoration-thickness is non-inheriting and `*` alone doesn't
 * cover pseudo-elements, so ::placeholder is listed explicitly. */
*,
::placeholder {
  text-decoration-thickness: 1px;
}

html,
body {
  margin: 0;
  padding: 0;
  color: var(--color-ink);
  font-family: var(--font-serif);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Block iOS rubber-band overscroll on both ends — no peek-through
   * above the title banner or below the footer. */
  overscroll-behavior: none;
  /* Tracking: 0 by default for the upright Times — italics get a
   * touch (0.01em) lower in the file to keep slanted text from
   * looking cramped against neighbouring upright glyphs. Browser
   * metric kerning is the closest we get to InDesign's optical
   * kerning — the font's tables are the limit. */
  line-height: 1.083;
  letter-spacing: 0;
  font-kerning: normal;
  font-variant-ligatures: common-ligatures;
  text-rendering: optimizeLegibility;
}

em,
.designer-name {
  letter-spacing: -0.01em;
}

/*
 * Pin the html canvas to white. The scrollbar gutter paints against
 * this canvas, so locking it here stops the gutter from following
 * the page color flip. <body> is left transparent so html shows
 * through everywhere outside the .page element.
 */
html {
  background: var(--color-bg);
}

p {
  margin: 0;
}

strong {
  font-weight: 700;
}

/* Line-break helpers — different artistic breaks per viewport. */
.br-desktop {
  display: none;
}
.br-mobile {
  display: inline;
}
@media (min-width: 720px) {
  .br-desktop {
    display: inline;
  }
  .br-mobile {
    display: none;
  }
}

/*
 * Page owns the background. Default white. As the user scrolls past
 * anchor sections (programme, essay) the scroll listener toggles
 * .bg-yellow on .page and CSS fades the bg. Sections themselves are
 * transparent text blocks — no shadows, no panel chrome — so the
 * whole document reads as one continuous plane with the bg reacting
 * underneath.
 */
.page {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  background: var(--color-bg);
  transition: background-color 0.5s ease;
}

.page.bg-yellow {
  background: var(--color-yellow);
}

/*
 * Layered scroll: the hero pins to the top while the poem (transparent
 * bg, outline-cut "Checking" face) scrolls up and overlays it. Every
 * section below the poem has an opaque background plus a higher
 * stacking order, so each one slides up over the hero in turn.
 */

/* ---------- 1. Hero ---------- */

/*
 * Layered scroll, v7 (sticky-only, programme-meets-hero-text).
 *
 *   .banner          (100lvh + (200lvh + Plvh − Y))
 *                              ← .hero (100lvh, sticky top:0)
 *                                 + .banner-stage spacer
 *
 *   .phase1          (P+200lvh, pulled up by margin-top so its top
 *                     sits at doc Plvh)
 *                              ← .programme (sticky top:Plvh)
 *                                 + .phase1-stage (200lvh spacer)
 *
 *   .designers, .colophon — natural flow below phase1, no animation.
 *
 *   P = --peek-offset (number; 80 on mobile, 92 on desktop), so
 *       Plvh = peek position of the programme heading at scroll 0.
 *   Y = bottom of .hero-text in viewport coords, measured by JS and
 *       exposed as --y-dock (in pixels). Falls back to Plvh — i.e.
 *       no climb at all — until JS has measured.
 *
 * Three scroll phases:
 *
 *   1. S = 0 → 200lvh — pin phase
 *      hero pinned at top:0, programme pinned at top:Plvh (peek),
 *      poem rises and clears the viewport.
 *
 *   2. S = 200lvh → (200lvh + Plvh − Y) — climb phase
 *      hero stays pinned at top:0; programme released, slides up the
 *      empty area below the hero text from viewport Plvh to viewport
 *      Y (just under the last line of hero text).
 *
 *   3. S > (200lvh + Plvh − Y) — push-off
 *      hero unsticks; both move up at scroll-rate 1 so the programme
 *      pushes the hero out the top rather than sliding over the text.
 *
 * .section-break is breathing room between flow sections.
 */
.banner-stage {
  height: calc(200lvh + var(--peek-top) - var(--y-dock, var(--peek-top)));
}

.phase1 {
  position: relative;
  z-index: 1;
  /* Pull phase1 up so its top edge sits at doc P (--peek-top). banner
   * is now 100lvh + (200lvh + P − Y), so margin-top must be
   * −(300lvh − Y) to leave a P offset. .programme's sticky top must
   * match P so its pin starts cleanly at S=0; phase1-stage (200lvh)
   * governs the pin end at S=200lvh, while hero only unsticks later
   * at S=(200lvh + P − Y) — that gap is the climb window. */
  margin-top: calc(-300lvh + var(--y-dock, var(--peek-top)));
}

.phase1-stage {
  height: 200vh;
  height: 200lvh;
}

.section-break {
  height: 15vh;
  height: 15lvh;
}
.hero {
  position: sticky;
  top: 0;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  width: 100%;
  min-height: 100vh;
  min-height: 100lvh;
  padding: 48px 26px;
  /* Hero carries an opaque background that mirrors the page bg so the
   * .poem (a child with mix-blend-mode: difference) has a real white
   * backdrop to invert against in hero's stacking context — without
   * this the blend has nothing but transparent siblings to work with
   * and the poem ends up invisible over the empty hero area. The
   * .bg-yellow toggle below keeps it in step with the page when the
   * programme heading triggers yellow. */
  background-color: var(--color-bg);
  transition: background-color 0.5s ease;
}

.bg-yellow .hero {
  background-color: var(--color-yellow);
}

@media (min-width: 720px) {
  .hero {
    padding: 48px clamp(48px, 5vw, 72px) clamp(48px, 5vw, 72px);
  }
}

/*
 * Background image gallery — stacked images centered in the hero.
 * JS cycles a .is-active class through them at intervals to flick
 * between frames. z-index -1 places them behind .hero-inner text.
 */
.hero-gallery {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  /* Fade out alongside the page bg flip — when programme docks and
   * .page goes yellow the rotating images recede. Same 0.5s curve as
   * the background-color transition on .page. */
  transition: opacity 0.5s ease;
}

.page.bg-yellow .hero-gallery {
  opacity: 0;
}

.hero-gallery img {
  position: absolute;
  max-width: 60vw;
  max-height: 60vh;
  max-height: 60lvh;
  object-fit: contain;
  opacity: 0;
}

.hero-gallery img.is-active {
  opacity: 1;
}

.hero-inner {
  position: relative;
  width: 100%;
  max-width: var(--inner-width);
  display: flex;
  justify-content: center;
}

.hero-text {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-serif);
  font-size: var(--type-body);
  line-height: 1.083;
  color: var(--color-ink);
}

.hero-text p {
  font-size: var(--type-body);
  line-height: 1.083;
  margin-bottom: 0;
}

.hero-text .hero-link a {
  color: inherit;
  text-decoration-line: underline;
  text-underline-offset: 0.1em;
}


/*
 * Email signup — sits inline as a single line of Times text. Input
 * mimics the .hero-link underline: em-based border-bottom thickness
 * (≈ browser-auto for Times) and the same 0.1em offset from baseline.
 * Form max-width approximates the typographic width of the
 * "Programme of Events" link above so the underline reads as a
 * paired line.
 */
.signup {
  display: flex;
  align-items: baseline;
  gap: 0.25em;
  margin: 0;
  /* Bumped to body type size so the signup reads as a top-of-footer
   * call-to-action rather than fine print — input and button inherit
   * via font: inherit. */
  font-size: var(--type-body);
  line-height: 1.083;
  /* Right edge sits roughly at the end of "City Gallery Wellington
   * Te Whare Toi" — the venue line above. */
  max-width: 16em;
}

.signup input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: 0;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  /* No left padding — keeps the placeholder flush with the text
   * column. Right padding gives the active-state bg breathing room
   * before the Sign up button. */
  padding: 0 0.25em 0 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.signup input:focus,
.signup input:not(:placeholder-shown) {
  background: rgba(0, 0, 0, 0.05);
  /* Symmetric horizontal padding while active — bg gets equal
   * breathing room on left and right of the typed text. */
  padding-left: 0.25em;
  /* Outset box-shadow above only — extends the highlight upward to
   * balance the descender space at the bottom of the line, without
   * growing the field. */
  box-shadow: 0 -0.1em 0 rgba(0, 0, 0, 0.05);
}

.signup input::placeholder {
  color: inherit;
  opacity: 1;
  text-decoration-line: underline;
  text-underline-offset: 0.1em;
}

/* Hide the placeholder once the field is focused so the underlined
 * "Sign up to our Newsletter" call-to-action doesn't sit beneath
 * the cursor while the user is typing. */
.signup input:focus::placeholder {
  color: transparent;
}

.signup button {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  font: inherit;
  /* Strip iOS Safari's default disabled-button styling — without
   * appearance:none the UA stylesheet wins and the link greys out
   * regardless of opacity/color overrides. */
  -webkit-appearance: none;
  appearance: none;
  color: var(--color-ink);
  letter-spacing: inherit;
  line-height: inherit;
  padding: 0;
  text-decoration-line: underline;
  text-underline-offset: 0.1em;
  cursor: default;
  opacity: 1;
  /* Hidden until the user starts typing. */
  display: none;
}

.signup button:disabled {
  color: var(--color-ink);
  -webkit-text-fill-color: var(--color-ink);
  /* Greyed out until input.checkValidity() is true. */
  opacity: 0.5;
}

/* Reveal the Submit link as soon as the field is active (focus or
 * typing) — always full black; cursor flips to pointer once the
 * email is valid (button.disabled toggled by the JS handler). */
.signup input:focus ~ button,
.signup input:not(:placeholder-shown) ~ button {
  display: inline-block;
}

.signup button:not(:disabled) {
  cursor: pointer;
}

/* "Powered by Buttondown" credit beneath the form — required by
 * Buttondown's free-tier ToS. Sits below the type scale entirely
 * (12px → 16px) so it reads as fine-print microcopy rather than
 * footer-grade text, with soft 50% ink so it's present without
 * competing with the mailing-list call-to-action above it. */
.signup-credit {
  font-size: clamp(12px, calc(8px + 0.55vw), 16px);
  line-height: 1.083;
  margin: 0.5em 0 0;
  opacity: 0.5;
}

.signup-credit a {
  color: inherit;
  text-decoration-line: underline;
  text-underline-offset: 0.1em;
}

/* ---------- 2. Poem ---------- */

/*
 * Poem lives inside .hero between .hero-gallery and .hero-inner so
 * mix-blend-mode: difference only blends against the gallery (the
 * sibling beneath it in z-order). .hero-inner paints after the poem
 * and therefore on top, so the hero text isn't blended with — and
 * .programme (body z-index 1) sits above the entire .hero stack, so
 * the programme heading isn't blended either.
 *
 * Position is absolute (not fixed) — the parent .hero is sticky
 * top:0 during the pin phase, so absolute top:0 inside it lands at
 * viewport top:0 the same way fixed did, while keeping the poem
 * inside the hero stacking context. After hero unsticks the poem
 * has already animated past the top, so the visual result matches.
 *
 * A scroll-driven animation rises the poem from below the viewport
 * up past the top during the first 200lvh of scroll.
 */
.poem {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  width: 100%;
  padding: 48px 0;
  background: transparent;
  /* Difference blend — black glyphs invert against whatever sits
   * behind. On white the type reads white; on the yellow programme
   * background it shifts to a saturated blue. The outline-cut
   * "Checking" face turns the inversion into a moving contour. */
  mix-blend-mode: difference;
  /* Whole subtree must be transparent to clicks so the hero
   * "Sign up to our Newsletter" link below can still be tapped while
   * the poem is overlaid. pointer-events doesn't inherit — descendants
   * default to auto — so we have to scope it down to all children. */
  pointer-events: none;
  transform: translateY(100lvh);
  animation: poem-rise linear forwards;
  animation-timeline: scroll();
  animation-range: 0 200lvh;
}

.poem * {
  pointer-events: none;
}

@keyframes poem-rise {
  /*
   * Travel exactly 100lvh + poem-height over the 200lvh range so the
   * last line clears the viewport just as the sticky pin releases —
   * no dead scrolling between poem-exit and natural scrolling.
   *
   * --poem-h is set by JS to the measured pixel height of the poem
   * element (and updated on resize / after fonts load). Using a
   * concrete pixel value avoids any browser quirks with percent-based
   * translate when the element is much taller than the viewport
   * (e.g., wide screens where the poem font scales to 4.17vw and the
   * box ends up several viewports tall). Falls back to -100% if the
   * variable isn't set yet — same end state, just less robust.
   */
  from {
    transform: translateY(100lvh);
  }
  to {
    transform: translateY(calc(-1 * var(--poem-h, 100%)));
  }
}

@media (min-width: 720px) {
  .poem {
    padding: clamp(32px, 3.33vw, 48px) 0 clamp(48px, 5vw, 72px);
  }
}

.poem-text {
  width: 100%;
  font-family: var(--font-display);
  font-size: var(--type-poem);
  text-align: center;
  text-transform: uppercase;
  /* White paired with mix-blend-mode: difference on .poem so the
   * glyphs invert against whatever sits behind: black against the
   * white default page, dark blue against the yellow programme bg. */
  color: var(--color-ink-inverse);
  line-height: 1.25;
  /* Reset body-level tracking — the display face is drawn tight and
   * shouldn't inherit the +0.007em set on body Times. */
  letter-spacing: normal;
}

.poem-text p {
  line-height: 1.25;
  margin-bottom: 0;
}

/* One-line gap before each new stanza. The closing "Blue rain /
 * Alistair Te Ariki Campbell" couplet uses the same break to
 * separate it from the body of the poem. */
.poem-text .poem-stanza {
  margin-top: 1em;
}

/* ---------- 3. Programme of Events (yellow) ---------- */

.programme {
  /* Sticky pin at --peek-top (80lvh on mobile / 100lvh − 158px on
   * desktop) — heading peeks at bottom from scroll 0 and stays pinned
   * for the 200lvh of the .phase1-stage spacer, then releases as
   * phase1 hits its floor. Must match the margin-top offset on
   * .phase1 so pin starts cleanly at S=0. */
  position: sticky;
  top: var(--peek-top);
  z-index: 1;
  background: transparent;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

@media (min-width: 720px) {
  .programme {
    align-items: flex-start;
  }
}

.programme-inner {
  width: 100%;
  max-width: var(--programme-column);
  padding: 48px 26px;
  display: flex;
  flex-direction: column;
  /* Heading→day gap matches the section's padding-top so mobile and
   * desktop share the same rhythm — clamp floor (48px) covers mobile,
   * scales up to 72px at 1440. */
  gap: clamp(48px, 5vw, 72px);
  font-family: var(--font-serif);
  /* Programme-specific leading: 36/38pt → 1.056 ratio. Tracking
   * inherits the body's 0 (italics still pick up the 0.01em from
   * the global em/.designer-name rule). */
  font-size: var(--type-body);
  line-height: 1.056;
  color: var(--color-ink);
}

@media (min-width: 720px) {
  .programme-inner {
    padding: clamp(48px, 5vw, 72px) 0 clamp(64px, 6.67vw, 96px)
      clamp(40px, 5vw, 72px);
  }
}

.programme-heading {
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--type-body);
  line-height: 1.056;
  cursor: pointer;
  /* Underline only while the programme is pinned at the bottom of the
   * viewport — signals the heading is clickable. Fades out once the
   * sticky releases and programme has docked at the top. Animating
   * text-decoration-color lets us transition; text-decoration itself
   * is not animatable. */
  text-decoration-line: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.1em;
  transition: text-decoration-color 0.3s ease;
}

.programme-heading.is-pinned {
  text-decoration-color: currentColor;
}

.programme-days {
  display: flex;
  flex-direction: column;
  gap: 1em;
  width: 100%;
}

@media (min-width: 720px) {
  .programme-days {
    gap: clamp(48px, 5vw, 72px);
  }
}

.programme-day {
  width: 100%;
}

.programme-day p {
  font-size: var(--type-body);
  line-height: 1.056;
  margin-bottom: 0;
  white-space: pre-wrap;
}

.programme-day .day-heading {
  font-family: var(--font-serif);
  font-weight: 700;
}

.programme-day .event {
  font-family: var(--font-serif);
  font-weight: 400;
  /* Full-line gap before each event — equivalent to a blank line
   * between event blocks. */
  margin-top: 1em;
}

/* Closing italic "Tickets go on sale…" sits on its own; gap above
 * matches the spacing between the programme heading and the first
 * day. Smaller type to read as a footnote rather than an event. */
.programme-day .event-coda {
  margin-top: 26px;
  font-size: var(--type-small);
  /* Em-based cap on the wrapping text so chars-per-line stays roughly
   * constant as the viewport shrinks. The .programme-inner column is
   * a fixed 1082px while type-small scales fluidly — without this cap
   * the first line ends up holding more text at narrow viewports
   * than at 1440. */
  max-width: 33em;
}

@media (min-width: 720px) {
  .programme-day .event-coda {
    margin-top: clamp(48px, 5vw, 72px);
  }
}

.programme-day .event a {
  color: inherit;
  text-decoration-line: underline;
  text-underline-offset: 0.1em;
}

/* ---------- 4. Essay — On Material ---------- */

.essay {
  position: relative;
  z-index: 1;
  background: transparent;
  width: 100%;
  display: flex;
  flex-direction: column;
  /* Bookish setting — column left-aligned, body justified, leading
   * 1.192 (13/15.5 print ratio). Column width is 26em so words-per-
   * line stays constant when the type-body size scales. */
  align-items: flex-start;
  justify-content: flex-start;
  gap: 48px;
  padding: 48px 26px;
  font-family: var(--font-serif);
  font-size: var(--type-body);
  color: var(--color-ink);
  line-height: 1.192;
}

@media (min-width: 720px) {
  .essay {
    gap: clamp(64px, 10vw, 144px);
    padding: clamp(48px, 5vw, 72px) clamp(40px, 5vw, 72px)
      clamp(64px, 6.67vw, 96px);
  }
}

.essay-title {
  width: 100%;
  max-width: var(--essay-column);
}

.essay-title p {
  font-size: var(--type-body);
  line-height: 1.192;
  margin-bottom: 0;
}

.essay-body {
  width: 100%;
  max-width: var(--essay-column);
  text-align: justify;
}

.essay-body p {
  font-size: var(--type-body);
  line-height: 1.192;
  margin-bottom: 0;
  /* Subsequent paragraphs use the same 11mm/13pt tab as the
   * colophon — first-line indent, no blank line between. */
  text-indent: 2.398em;
}

.essay-body p:first-child {
  text-indent: 0;
}

/* ---------- 5. Designers ---------- */

.designers {
  position: relative;
  z-index: 1;
  background: transparent;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Heading → first designer gap, paired with the matching gap on
   * the .colophon-heading below so both "About" headings share the
   * same beat. */
  gap: 36px;
  padding: 48px 26px;
  color: var(--color-ink);
}

@media (min-width: 720px) {
  .designers {
    padding: clamp(48px, 5vw, 72px) clamp(40px, 5vw, 72px)
      clamp(64px, 6.67vw, 96px);
  }
}

.designers-heading {
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--type-body);
  line-height: 1.083;
  width: 100%;
  max-width: var(--essay-column);
}

@media (min-width: 720px) {
  .designers-heading {
    max-width: var(--inner-width);
  }
}

/* Designers grid: single flowing column on mobile (display: contents
 * collapses the column wrappers so all four .designer entries become
 * direct flex-children). Two columns from 720px up. */
.designers-cols {
  display: flex;
  flex-direction: column;
  gap: 1em;
  width: 100%;
  max-width: var(--essay-column);
  font-family: var(--font-serif);
  font-size: var(--type-body);
  line-height: 1.083;
}

@media (min-width: 720px) {
  .designers-cols {
    flex-direction: row;
    align-items: flex-start;
    gap: clamp(40px, 3.33vw, 48px);
    max-width: var(--inner-width);
  }
}

.designers-col {
  display: contents;
}

@media (min-width: 720px) {
  .designers-col {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    flex-direction: column;
    gap: clamp(40px, 3.33vw, 48px);
    align-items: flex-start;
  }
}

.designer {
  width: 100%;
}

.designer p {
  font-size: var(--type-body);
  line-height: 1.083;
  margin-bottom: 0;
}

.designer-name {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
}

.designer-name a {
  color: inherit;
  text-decoration-line: underline;
  text-underline-offset: 0.1em;
}

/* ---------- 6. Colophon (black) ---------- */

.colophon {
  position: relative;
  z-index: 1;
  background: transparent;
  padding: 48px 26px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  width: 100%;
}

@media (min-width: 720px) {
  .colophon {
    padding: clamp(48px, 5vw, 72px) clamp(40px, 5vw, 72px);
  }
}

.colophon-inner {
  width: 100%;
  max-width: var(--inner-width);
  font-family: var(--font-serif);
  font-size: var(--type-small);
  color: var(--color-ink);
  line-height: 1.083;
}

.colophon-inner p {
  font-size: var(--type-small);
  line-height: 1.083;
  margin-bottom: 0;
}

/* Footer heading — same weight/size as the programme and designers
 * headings; bottom margin matches the .designers gap between "About
 * the Designers" and the first designer block so the two read as
 * the same beat. */
.colophon-heading {
  margin: 0 0 36px;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--type-body);
  line-height: 1.083;
}

.colophon-inner .colophon-lede {
  /* Lede paragraph reads as a top-of-footer headline at body size;
   * the address columns sit at the same size, while the attribution
   * and design credit drop to type-small. */
  font-size: var(--type-body);
  line-height: 1.083;
}

.colophon-inner .colophon-meta p {
  font-size: var(--type-body);
  line-height: 1.083;
}

/* Two breaks inside the footer:
 *  • signup → address columns: smaller gap (matches the programme
 *    heading → Saturday rhythm), since the columns belong with the
 *    lede + signup as the "main" footer block.
 *  • address columns → Blue Rain attribution: full inter-section
 *    gap, separating that block from the credits/design line. */
.colophon-inner .colophon-cols {
  margin-top: 48px;
}

.colophon-inner .colophon-attribution {
  margin-top: 96px; /* 48 (designers bottom) + 48 (colophon top) */
}

@media (min-width: 720px) {
  .colophon-inner .colophon-cols {
    margin-top: clamp(48px, 5vw, 72px);
  }

  .colophon-inner .colophon-attribution {
    margin-top: calc(
      clamp(64px, 6.67vw, 96px) + clamp(48px, 5vw, 72px)
    );
  }
}

.colophon-inner .colophon-meta a,
.colophon-inner .colophon-credit a {
  color: inherit;
  text-decoration-line: underline;
  text-underline-offset: 0.1em;
}

/* Two-column row for venue + media blocks. Stacks on mobile,
 * splits side-by-side from 720px up — same model as .designers-cols. */
.colophon-cols {
  display: flex;
  flex-direction: column;
  gap: 1em;
  width: 100%;
}

@media (min-width: 720px) {
  .colophon-cols {
    flex-direction: row;
    align-items: flex-start;
    gap: clamp(40px, 3.33vw, 48px);
  }

  .colophon-cols .colophon-meta {
    flex: 1 1 0;
    min-width: 0;
  }
}
