/* meetgeeoh.ca — "Houselights up."
   The sister site is the house at black with one gel lit. This is the same rack with
   the houselights up: warm paper ground, the whole spectrum loaded, Fraunces with the
   wonk turned on.

   Two rules govern everything below and breaking either one breaks the site:
     BEAM values are surfaces and never carry text.  INK values are the legible ramp.
     Pink is not in the spectrum, which is why it is ours: it is the action colour and
     the seven spectral gels are a navigational index, never a button palette.

   Same three families as heathercornick.ca, different setting. That is the sibling
   signal, and it is why the font stack below is byte-identical to hers. */

@font-face {
  font-family: 'Fraunces';
  src: url('/assets/fonts/fraunces-var.woff2') format('woff2-variations');
  font-weight: 400 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Archivo';
  src: url('/assets/fonts/archivo-var.woff2') format('woff2-variations');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/assets/fonts/plexmono-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/assets/fonts/plexmono-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

/* ─────────────────────────────────────────────────────────── tokens ── */

:root {
  /* Neutrals. The ground is warm paper — a gel envelope, a festival programme
     left in the sun. Never pure white, never pure black. */
  --paper:       #FDF8F1;
  --paper-2:     #F6EEE3;
  --ink:         #1A1420;   /* plum-black: a trace of the violet end of the rack */
  --haze:        #5C5260;
  --rule:        #E3D8CA;
  --house-dark:  #14101A;   /* deliberately near-identical to the sister site's */
  --frame:       rgb(26 20 32 / 22%);

  /* Gel rack — BEAM. Surfaces only. Contrast against paper runs 1.37 (canary) to
     11.35 (congo), which is exactly why every one of them carries the 1px frame:
     it satisfies WCAG 1.4.11 for the low-contrast members regardless of ground. */
  --gel-red-beam:      #E8332A;  /* R26  Light Red      */
  --gel-amber-beam:    #F5851F;  /* R21  Golden Amber   */
  --gel-canary-beam:   #FFD21E;  /* R312 Canary         */
  --gel-green-beam:    #0E8A47;  /* L139 Primary Green  */
  --gel-blue-beam:     #0B63C5;  /* R80  Primary Blue   */
  --gel-congo-beam:    #3B1E8C;  /* L181 Congo Blue     */
  --gel-lavender-beam: #7B3FA8;  /* R58  Deep Lavender  */
  --gel-pink-beam:     #E8388C;  /* L128 Bright Pink    */

  /* Gel rack — INK. Every one clears 5.4:1 on both paper tones and 6.2:1 for
     white on it. Use these and you cannot fail AA. */
  --gel-red-ink:      #B81F16;
  --gel-amber-ink:    #8F4E08;
  --gel-canary-ink:   #6B5200;
  --gel-green-ink:    #0A6E38;
  --gel-blue-ink:     #0A4A96;
  --gel-congo-ink:    #33197A;
  --gel-lavender-ink: #63318A;
  --gel-pink-ink:     #B81864;

  --action:       var(--gel-pink-ink);
  --action-hover: var(--gel-pink-beam);
  --focus:        var(--ink);
  --error:        var(--gel-red-ink);

  /* Per-page override sets these two; everything downstream reads them. */
  --gel:     var(--gel-canary-beam);
  --gel-ink: var(--ink);

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body:    'Archivo', system-ui, -apple-system, sans-serif;
  --font-mono:    'Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --step--1: clamp(0.833rem, 0.82rem + 0.06vw, 0.875rem);
  --step-0:  clamp(1rem, 0.97rem + 0.15vw, 1.125rem);
  --step-1:  clamp(1.25rem, 1.19rem + 0.3vw, 1.5rem);
  --step-2:  clamp(1.563rem, 1.44rem + 0.6vw, 2rem);
  --step-3:  clamp(1.953rem, 1.73rem + 1.1vw, 2.75rem);
  --step-4:  clamp(2.441rem, 2.05rem + 1.95vw, 3.75rem);
  --step-5:  clamp(3.052rem, 2.4rem + 3.25vw, 5.25rem);
  --step-6:  clamp(3.815rem, 2.7rem + 5.5vw, 7rem);

  --space-2xs: 0.25rem;  --space-xs:  0.5rem;   --space-sm: 0.75rem;
  --space-md:  1rem;     --space-lg:  1.5rem;   --space-xl: 2.5rem;
  --space-2xl: 4rem;     --space-3xl: 6.5rem;

  --radius-chip: 2px;  --radius-card: 6px;
  --radius-btn:  4px;  --radius-pill: 999px;

  --measure: 66ch;
  --container: 1200px;
  --gutter: clamp(1.25rem, 5vw, 4rem);

  --transition: 120ms ease-out;
}

/* One gel per route. Components reference var(--gel) for mats and rules and
   var(--gel-ink) for eyebrows and in-body links. Buttons ignore both. */
[data-gel="parties"]   { --gel: var(--gel-red-beam);      --gel-ink: var(--gel-red-ink); }
[data-gel="festivals"] { --gel: var(--gel-amber-beam);    --gel-ink: var(--gel-amber-ink); }
[data-gel="media-kit"] { --gel: var(--gel-amber-beam);    --gel-ink: var(--gel-amber-ink); }
[data-gel="workshops"] { --gel: var(--gel-green-beam);    --gel-ink: var(--gel-green-ink); }
[data-gel="gallery"]   { --gel: var(--gel-blue-beam);     --gel-ink: var(--gel-blue-ink); }
[data-gel="about"]     { --gel: var(--gel-congo-beam);    --gel-ink: var(--gel-congo-ink); }
[data-gel="fergus"]    { --gel: var(--gel-lavender-beam); --gel-ink: var(--gel-lavender-ink); }
[data-gel="book"]      { --gel: var(--gel-pink-beam);     --gel-ink: var(--gel-pink-ink); }
/* Home binds canary, which is 1.37:1 on paper and may never set type at any size.
   The fallback is handled here, in the token, so no component needs a special case:
   canary appears as chips and mats, and Home's eyebrows fall back to plain ink. */
[data-gel="home"]      { --gel: var(--gel-canary-beam);   --gel-ink: var(--ink); }

/* ────────────────────────────────────────────────────────────── base ── */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-variation-settings: 'wdth' 100;
  font-weight: 400;
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

h1, h2, h3 { font-family: var(--font-display); margin: 0 0 var(--space-md); }
h1 { font-variation-settings: 'SOFT' 80, 'WONK' 1, 'opsz' 96; font-weight: 800;
     font-size: var(--step-5); line-height: 1.02; letter-spacing: -0.02em; }
h2 { font-variation-settings: 'SOFT' 60, 'WONK' 1, 'opsz' 48; font-weight: 700;
     font-size: var(--step-3); line-height: 1.08; }
h3 { font-variation-settings: 'SOFT' 40, 'WONK' 0, 'opsz' 24; font-weight: 600;
     font-size: var(--step-1); line-height: 1.2; }
/* Fraunces stops at H3. Below that it is Archivo — a display face in a form label
   is how a playful site starts looking like a birthday invitation. */
h4, h5, h6 { font-family: var(--font-body); font-variation-settings: 'wdth' 108;
             font-weight: 700; font-size: var(--step-0); margin: 0 0 var(--space-sm); }

p { margin: 0 0 var(--space-md); max-width: var(--measure); }
ul, ol { max-width: var(--measure); }

a { color: var(--gel-ink); text-underline-offset: 0.15em; text-decoration-thickness: 1px;
    transition: color var(--transition), text-decoration-thickness var(--transition); }
a:hover { text-decoration-thickness: 2px; }

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--paper); padding: var(--space-sm) var(--space-md);
}
.skip:focus { left: var(--space-sm); top: var(--space-sm); }

.eyebrow {
  font-family: var(--font-body); font-variation-settings: 'wdth' 112; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.75rem;
  color: var(--gel-ink); margin: 0 0 var(--space-sm);
}
.deck { font-size: var(--step-1); color: var(--haze); max-width: var(--measure); }
.mono, .credit, .rack__no, .hint { font-family: var(--font-mono); font-size: 0.8125rem;
  letter-spacing: 0.02em; color: var(--haze); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ──────────────────────────────────────────────────────────── layout ── */

.wrap { max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.prose { max-width: var(--measure); }

/* Shorter sections, more of them — the page should read like walking a midway,
   not riding an elevator. Do not turn these into full-viewport heroes. */
.band { padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.band--paper  { background: var(--paper); }
.band--paper2 { background: var(--paper-2); }
.band--dark   { background: var(--house-dark); color: var(--paper); }
.band--dark h1, .band--dark h2, .band--dark h3 { color: var(--paper); }
.band--dark .deck, .band--dark .credit, .band--dark .mono { color: #B9AFC2; }
.band--dark a { color: #F1B9D3; }

.band__head { margin-bottom: var(--space-lg); }

/* ───────────────────────────────────────────────── the gel rack strip ── */
/* The one thing this site is remembered by. Eight chips in gel order on paper-2:
   seven spectral with 2px gaps, then a 16px break, then the house pink — the gap
   is the sentence "this one is not part of the spectrum."                        */

.rack {
  display: flex; align-items: flex-start; gap: 2px;
  background: var(--paper-2);
  padding: var(--space-xs) var(--gutter);
  border-block: 1px solid var(--rule);
  overflow-x: auto; scrollbar-width: none;
}
.rack::-webkit-scrollbar { display: none; }
.rack--flush { border-top: 0; }

.rack__item { display: flex; flex-direction: column; align-items: center; gap: 3px;
  text-decoration: none; flex: 0 0 auto; }
/* All eight chips sit on the same 2px rhythm. The style guide originally opened a
   16px break before L128 to say "pink is not part of the spectrum"; Scott's call is
   that an even rack reads better, and the point still lands — pink is the only chip
   that is never a page's colour and the only one buttons ever use. */

.rack__chip {
  display: block; width: 44px; height: 28px;
  border: 1px solid var(--frame);          /* the accessibility fix AND the diegesis:
                                              real gels live in metal frames */
  border-radius: var(--radius-chip);
  opacity: 0.18;
  transition: opacity var(--transition);
}
.rack__no { font-size: 0.6875rem; line-height: 1; }

/* Resting state: this page's chip is lit, the rest are out. This is the site's
   primary navigation feedback — you always know where you are in the rack. */
.rack__item.is-lit .rack__chip { opacity: 1; }

/* Each nav item is bound to its chip: hovering or focusing the link raises that chip
   to full, like a channel coming up on a fader. The chips are decorative markers, not
   controls, so the relationship is made here rather than by duplicating eight links
   into the accessibility tree. In a browser without :has() the chips simply stay at
   their resting state, which is the correct picture anyway. */
.masthead:has(.nav a[href="/festivals/"]:hover)         .chip-festivals,
.masthead:has(.nav a[href="/festivals/"]:focus-visible) .chip-festivals,
.masthead:has(.nav a[href="/parties/"]:hover)           .chip-parties,
.masthead:has(.nav a[href="/parties/"]:focus-visible)   .chip-parties,
.masthead:has(.nav a[href="/workshops/"]:hover)         .chip-workshops,
.masthead:has(.nav a[href="/workshops/"]:focus-visible) .chip-workshops,
.masthead:has(.nav a[href="/fergus/"]:hover)            .chip-fergus,
.masthead:has(.nav a[href="/fergus/"]:focus-visible)    .chip-fergus,
.masthead:has(.nav a[href="/about/"]:hover)             .chip-about,
.masthead:has(.nav a[href="/about/"]:focus-visible)     .chip-about,
.masthead:has(.nav a[href="/gallery/"]:hover)           .chip-gallery,
.masthead:has(.nav a[href="/gallery/"]:focus-visible)   .chip-gallery,
.masthead:has(.nav a[href="/book/"]:hover)              .chip-house,
.masthead:has(.nav a[href="/book/"]:focus-visible)      .chip-house,
.masthead:has(.wordmark:hover)                          .chip-home,
.masthead:has(.wordmark:focus-visible)                  .chip-home { opacity: 1; }

.rack--full .rack__chip { opacity: 1; }     /* footer band: the show is over */

.chip-parties   { background: var(--gel-red-beam); }
.chip-festivals { background: var(--gel-amber-beam); }
.chip-home      { background: var(--gel-canary-beam); }
.chip-workshops { background: var(--gel-green-beam); }
.chip-gallery   { background: var(--gel-blue-beam); }
.chip-about     { background: var(--gel-congo-beam); }
.chip-fergus    { background: var(--gel-lavender-beam); }
.chip-house     { background: var(--gel-pink-beam); }
.chip-media-kit { background: var(--gel-amber-beam); }
.chip-book      { background: var(--gel-pink-beam); }

/* The dimmer check. Before a show a crew brings each channel up in turn, confirms
   it, and takes it back out. The header rack performs one on first load and never
   again in the session. It is garnish on a finished state: the page is complete and
   correct if it never runs, which is exactly what happens under reduced motion. */
.rack--check .rack__chip { animation: dimmer 800ms ease-out both; }
.rack--check .rack__item:nth-child(1) .rack__chip { animation-delay: 0ms; }
.rack--check .rack__item:nth-child(2) .rack__chip { animation-delay: 40ms; }
.rack--check .rack__item:nth-child(3) .rack__chip { animation-delay: 80ms; }
.rack--check .rack__item:nth-child(4) .rack__chip { animation-delay: 120ms; }
.rack--check .rack__item:nth-child(5) .rack__chip { animation-delay: 160ms; }
.rack--check .rack__item:nth-child(6) .rack__chip { animation-delay: 200ms; }
.rack--check .rack__item:nth-child(7) .rack__chip { animation-delay: 240ms; }
.rack--check .rack__item:nth-child(8) .rack__chip { animation-delay: 280ms; }

@keyframes dimmer {
  0%   { opacity: 0.18; }
  15%  { opacity: 1; }
  55%  { opacity: 1; }
  100% { opacity: 0.18; }
}
/* The lit chip must land back at full, not at the resting 0.18 the keyframe ends on. */
.rack--check .rack__item.is-lit .rack__chip { animation-name: dimmer-hold; }
@keyframes dimmer-hold {
  0%   { opacity: 0.18; }
  15%  { opacity: 1; }
  100% { opacity: 1; }
}

@media (max-width: 640px) {
  .rack { padding-inline: var(--space-md); }
  .rack__chip { width: 28px; height: 20px; }
}
@media (max-width: 480px) {
  .rack__no { display: none; }              /* the chips alone carry it */
}

/* ──────────────────────────────────────────────────────────── header ── */

.masthead {
  position: sticky; top: 0; z-index: 50;
  background: rgb(253 248 241 / 96%);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--rule);
}
.masthead__bar {
  max-width: var(--container); margin-inline: auto;
  padding: var(--space-sm) var(--gutter);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-md);
}
.wordmark {
  font-family: var(--font-display);
  font-variation-settings: 'SOFT' 100, 'WONK' 1, 'opsz' 144;
  font-weight: 900; font-size: var(--step-1); line-height: 1;
  color: var(--ink); text-decoration: none; white-space: nowrap;
}
/* The exclamation mark in pink is the entire logo idea. Do not outline it, shadow
   it, rotate it or animate it. */
.wordmark__bang { color: var(--gel-pink-ink); }
.wordmark__sub {
  display: block; font-family: var(--font-body); font-variation-settings: 'wdth' 112;
  font-weight: 700; font-size: 0.6875rem; text-transform: uppercase;
  letter-spacing: 0.14em; color: var(--haze); margin-top: 0.35em;
}

.nav { display: flex; align-items: center; gap: var(--space-md); flex-wrap: wrap; }
.nav a {
  font-family: var(--font-body); font-variation-settings: 'wdth' 108; font-weight: 700;
  font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink); text-decoration: none; padding: var(--space-2xs) 0;
  border-bottom: 2px solid transparent;
}
.nav a:hover { border-bottom-color: var(--rule); }
.nav a[aria-current="page"] { border-bottom-color: var(--gel-ink); }
.nav .btn { margin-left: var(--space-xs); }

/* Mobile: a labelled disclosure, not a bare hamburger. It is a <details>, so it
   opens and closes with no JavaScript at all. */
.menu { display: none; }
.menu > summary {
  list-style: none; cursor: pointer;
  font-family: var(--font-body); font-variation-settings: 'wdth' 112; font-weight: 700;
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em;
  border: 1px solid var(--ink); border-radius: var(--radius-btn);
  padding: 0.6rem 0.9rem; min-height: 44px; display: inline-flex; align-items: center;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu__sheet {
  position: absolute; left: 0; right: 0;
  background: var(--paper); border-bottom: 1px solid var(--rule);
  padding: var(--space-md) var(--gutter) var(--space-xl);
  display: flex; flex-direction: column; gap: var(--space-sm);
}
.menu__sheet a { font-size: var(--step-0); padding: var(--space-xs) 0; }

@media (max-width: 900px) {
  .nav--wide { display: none; }
  .menu { display: block; }
}

/* ─────────────────────────────────────────────────────────── buttons ── */
/* Three variants, one colour. Buttons never take --gel: one action colour sitewide,
   or actions stop reading as actions. */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-xs);
  min-height: 44px; padding: 0.7rem 1.1rem;
  font-family: var(--font-body); font-variation-settings: 'wdth' 112; font-weight: 700;
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em;
  border-radius: var(--radius-btn); border: 1px solid transparent;
  text-decoration: none; cursor: pointer;
  transition: background-color var(--transition), color var(--transition),
              border-color var(--transition);
}
.btn--primary { background: var(--gel-pink-ink); color: #fff; }
.btn--primary:hover { background: var(--gel-pink-beam); color: #fff; }
.btn--secondary { background: transparent; border-color: var(--ink); color: var(--ink); }
.btn--secondary:hover { background: var(--ink); color: var(--paper); }
.btn--quiet {
  background: none; border: 0; padding: 0; min-height: 0; color: var(--gel-ink);
  text-decoration: underline; text-underline-offset: 0.15em; text-decoration-thickness: 1px;
}
.btn--quiet:hover { text-decoration-thickness: 2px; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--space-md); align-items: center;
  margin-top: var(--space-lg); }

/* ───────────────────────────────────────────────────────────── cards ── */

.cards { display: grid; gap: var(--space-lg); grid-template-columns: repeat(3, 1fr); }
.cards--2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .cards, .cards--2 { grid-template-columns: 1fr; } }

.card {
  background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius-card);
  padding: var(--space-lg);
  display: flex; flex-direction: column; gap: var(--space-sm);
  transition: border-color var(--transition);
}
/* Border colour only. No lift, no scale, no shadow — a card that jumps is a card
   that will jump on every one of the nine hundred hovers a programmer makes. */
.card:hover { border-color: var(--card-ink, var(--gel-ink)); }
.card h3 { margin: 0; }
.card p { margin: 0; }
.card__foot { margin-top: auto; padding-top: var(--space-sm); }

/* The service card's chip and eyebrow use THAT service's gel, not the page's.
   This is the one permitted place where three gels meet inside one section — the
   three cards are the rack, split in three. */
.card__eyebrow { display: flex; align-items: center; gap: var(--space-xs);
  color: var(--card-ink, var(--gel-ink)); }
.card__chip { width: 22px; height: 14px; border: 1px solid var(--frame);
  border-radius: var(--radius-chip); flex: 0 0 auto; }
.card--festivals { --card-ink: var(--gel-amber-ink); }
.card--parties   { --card-ink: var(--gel-red-ink); }
.card--workshops { --card-ink: var(--gel-green-ink); }

/* Workshop card spec strip. A card without ages, duration and group size never
   renders — repo.php refuses it, because a booker cannot act on it. */
.spec { font-family: var(--font-mono); font-size: 0.8125rem; color: var(--haze);
  border-top: 1px solid var(--rule); padding-top: var(--space-sm); margin-top: var(--space-sm); }

/* ────────────────────────────────────────────────────── the gel mat ── */
/* Photographs inside content are matted, not bordered: an 8px band of the page's
   gel with the frame hairline outside it. Full-bleed and gallery images are never
   matted — and never put a photograph on paper without one. */

.mat { border: 1px solid var(--frame); background: var(--gel); padding: 8px;
  border-radius: 2px; }
.mat img { border-radius: 0; }
.figure { margin: 0 0 var(--space-lg); }
.figure .credit { margin-top: var(--space-xs); }

.hero { background: var(--house-dark); }
.hero__media { aspect-ratio: 16 / 9; overflow: hidden; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }

/* ───────────────────────────────────────────── the technical rider ── */
/* The highest-value component on the site. A festival technical director does not
   read marketing copy; they scan a rider. Same cue-sheet device as the sister site,
   set in Plex Mono, two columns, unconfirmed rows omitted rather than printed. */

.rider { font-family: var(--font-mono); font-size: 0.8125rem; line-height: 1.7;
  display: grid; grid-template-columns: 10.5rem 1fr; gap: 0 var(--space-md);
  max-width: 44rem; }
.rider dt { color: var(--haze); text-transform: uppercase; letter-spacing: 0.04em; }
.rider dd { color: var(--ink); margin: 0; }
.rider dt, .rider dd { padding-block: 0.3rem; border-top: 1px solid var(--rule); }
.rider dt:first-of-type, .rider dt:first-of-type + dd { border-top: 0; }
@media (max-width: 560px) {
  .rider { grid-template-columns: 1fr; gap: 0; }
  .rider dd { padding-top: 0; border-top: 0; margin-bottom: var(--space-xs); }
}

/* ───────────────────────────────────────────── pronunciation note ── */

.pron {
  display: flex; gap: var(--space-md); align-items: flex-start;
  background: var(--paper-2); border: 1px solid var(--rule); border-radius: var(--radius-card);
  padding: var(--space-md); max-width: 34rem;
}
.pron__chip { width: 14px; height: 34px; background: var(--gel-pink-beam);
  border: 1px solid var(--frame); border-radius: var(--radius-chip); flex: 0 0 auto; }
.pron__say { font-family: var(--font-mono); font-size: var(--step-0); color: var(--ink); }
.pron__ipa { color: var(--haze); }
.pron p { margin: 0 0 var(--space-2xs); }
.pron__origin { font-size: var(--step--1); color: var(--haze); }

/* ───────────────────────────────────────────────────────── gallery ── */
/* House-dark ground, mixed orientations, no carousel. The mixture of wide event
   context and expressive close-up IS the content — do not crop it to a uniform grid. */

/* Columns rather than a grid. The style guide asks for "masonry-ish", and a grid
   gives that only if every tile is the same height — with a third of the catalogue
   portrait, grid rows size to the tallest tile and leave a hand's width of dead
   space under every landscape one. CSS columns pack them properly, need no
   JavaScript, and keep the frames at a consistent width.

   The trade is reading order: a screen reader and the tab order go down each column
   rather than across. For an unordered set of photographs that is not a loss —
   there is no sequence to preserve. */
.gallery { columns: 3 300px; column-gap: var(--space-md); }
.gallery__item { margin: 0 0 var(--space-md); break-inside: avoid; }
@media (max-width: 640px) { .gallery { columns: 1; } }
.gallery__frame { border-top: 2px solid var(--g, var(--gel-pink-beam)); background: #0d0a12; }
.gallery__item img { width: 100%; height: auto; }
.gallery .credit { margin-top: var(--space-2xs); font-size: 0.75rem; }

/* The one place the full spectrum appears inside content: a 1px gel accent per
   tile, cycling through the rack in order. */
.gallery__item:nth-child(8n+1) .gallery__frame { --g: var(--gel-red-beam); }
.gallery__item:nth-child(8n+2) .gallery__frame { --g: var(--gel-amber-beam); }
.gallery__item:nth-child(8n+3) .gallery__frame { --g: var(--gel-canary-beam); }
.gallery__item:nth-child(8n+4) .gallery__frame { --g: var(--gel-green-beam); }
.gallery__item:nth-child(8n+5) .gallery__frame { --g: var(--gel-blue-beam); }
.gallery__item:nth-child(8n+6) .gallery__frame { --g: var(--gel-congo-beam); }
.gallery__item:nth-child(8n+7) .gallery__frame { --g: var(--gel-lavender-beam); }
.gallery__item:nth-child(8n+8) .gallery__frame { --g: var(--gel-pink-beam); }

/* Preview strip on Home. */
.strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--space-sm); }
.strip img { aspect-ratio: 3 / 2; object-fit: cover; width: 100%; }

/* ─────────────────────────────────────────────────────────── lists ── */

.ticks { list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-sm) var(--space-lg); }
.ticks--2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) { .ticks { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ticks, .ticks--2 { grid-template-columns: 1fr; } }
.ticks li { position: relative; padding-left: 1.4rem; max-width: none; }
/* A chip glyph, not a colour: colour never carries meaning alone on this site. */
.ticks li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 12px; height: 8px; background: var(--gel);
  border: 1px solid var(--frame); border-radius: 1px;
}

.note {
  border-left: 3px solid var(--gel); background: var(--paper-2);
  padding: var(--space-md) var(--space-lg); border-radius: 0 var(--radius-card) var(--radius-card) 0;
  max-width: var(--measure);
}
.note h3 { margin-bottom: var(--space-xs); }
.note p:last-child { margin-bottom: 0; }

/* ──────────────────────────────────────────────── the follow spot ── */
/* The link back to heathercornick.ca, and the answer to her /contact balloon.
   Decoration that happens to be a link — so it is CSS only, and nothing about the
   page depends on it running. See views/partials/followspot.php.

   Three sweeps and then rest. Motion that starts by itself, runs for more than five
   seconds and cannot be paused fails WCAG 2.2.2; the sister site's balloon is bounded
   for exactly the same reason. The starting angle is randomised per request in PHP —
   the six variants below — because CSS has no randomness and the CSP forbids the
   inline style a JS version would need. */

/* The footer is the house at black. `overflow: hidden` clips the beam where it
   leaves the room, which is both correct and what stops the overhanging SVG from
   giving the page a horizontal scrollbar. */
.footer--spot { position: relative; overflow: hidden; padding-bottom: 3rem; }

.spot {
  position: absolute; right: 3rem; bottom: 2.5rem; z-index: 1;
  display: block; width: 300px; max-width: 60vw; text-decoration: none;
  /* The beam overhangs the fixture by a long way to the left; without this the
     bounding box would catch clicks across half the footer. */
  pointer-events: none;
}
.spot__svg { overflow: visible; }
/* Only the fixture itself is the target — you click the lamp, not the light. */
.spot__body { pointer-events: auto; cursor: pointer; }

.spot__beam {
  transform-box: view-box;
  transform-origin: 252px 96px;   /* the lens; matches the polygon apex in the SVG */
  transform: rotate(-24deg);
  animation: spot-sweep 7s ease-in-out 6 alternate both;
  will-change: transform;
}

.spot__body { transition: filter var(--transition); }
.spot:hover .spot__body,
.spot:focus-visible .spot__body { filter: drop-shadow(0 0 10px rgb(245 133 31 / 70%)); }
.spot:hover .spot__beam,
.spot:focus-visible .spot__beam { animation-play-state: paused; }
.spot:focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; }

@keyframes spot-sweep { from { transform: rotate(var(--from, -38deg)); }
                        to   { transform: rotate(var(--to, -8deg)); } }

/* Six starts, so the lamp is not doing the same thing every time you land here.
   The pairs are deliberately uneven in both angle and duration — two sweeps of
   identical length read as a machine rather than an operator finding someone. */
.spot--a .spot__beam { --from: -40deg; --to: -6deg;  animation-duration: 7s; }
.spot--b .spot__beam { --from: -12deg; --to: -36deg; animation-duration: 6s; }
.spot--c .spot__beam { --from: -30deg; --to: -4deg;  animation-duration: 8s; }
.spot--d .spot__beam { --from: -22deg; --to: -44deg; animation-duration: 5.5s; }
.spot--e .spot__beam { --from: -46deg; --to: -18deg; animation-duration: 6.5s; }
.spot--f .spot__beam { --from: -8deg;  --to: -34deg; animation-duration: 7.5s; }

/* Narrow: it would sweep straight through the contact details, so it stops being
   absolute and sits under them, still pointing off to the left. */
@media (max-width: 860px) {
  .footer--spot { padding-bottom: 1rem; }
  .spot { position: static; margin: 1rem 0 0 auto; right: auto; bottom: auto;
          width: 220px; }
}

/* Asked for less motion: the lamp is lit and still, pointing where it would have
   finished. Not a faster sweep, and not a fade — just the resting state. */
@media (prefers-reduced-motion: reduce) {
  .spot__beam { animation: none; transform: rotate(-20deg); }
  .spot__body { transition: none; }
}

/* ─────────────────────────────────────────────────────────── forms ── */

.form { max-width: 40rem; }
.field { margin-bottom: var(--space-lg); }
.field > label, .fieldset > legend {
  display: block; font-family: var(--font-body); font-variation-settings: 'wdth' 108;
  font-weight: 700; font-size: 0.8125rem; margin-bottom: var(--space-2xs);
}
.field .hint { display: block; margin-top: var(--space-2xs); }
.req { color: var(--haze); font-weight: 400; }

input[type="text"], input[type="email"], input[type="tel"], input[type="date"],
input[type="password"], input[type="search"], textarea, select {
  width: 100%; padding: 12px 14px;
  font-family: var(--font-body); font-size: var(--step-0);   /* never below 16px: iOS zooms */
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--rule); border-radius: var(--radius-btn);
  transition: border-color var(--transition);
}
textarea { min-height: 9rem; resize: vertical; }
input:focus, textarea:focus, select:focus { border-color: var(--ink); }

.fieldset { border: 0; padding: 0; margin: 0 0 var(--space-lg); }
.choice { display: flex; align-items: flex-start; gap: var(--space-xs);
  padding: var(--space-2xs) 0; }
.choice input { margin-top: 0.35em; }
.choice label { font-weight: 400; }

.error {
  color: var(--error); font-size: var(--step--1);
  display: flex; gap: var(--space-2xs); align-items: baseline;
}
.error::before { content: "!"; font-weight: 700; }
.errors { border-left: 3px solid var(--error); padding: var(--space-sm) var(--space-md);
  background: var(--paper-2); margin-bottom: var(--space-lg); }
.notes { border-left: 3px solid var(--gel-green-ink); padding: var(--space-sm) var(--space-md);
  background: var(--paper-2); margin-bottom: var(--space-lg); }

/* The honeypot. Hidden from sight and from assistive technology, and never
   focusable — a keyboard user must not be able to tab into a trap. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.success { border: 1px solid var(--rule); border-left: 3px solid var(--gel-green-ink);
  background: var(--paper-2); padding: var(--space-lg); border-radius: var(--radius-card);
  max-width: var(--measure); }

/* ────────────────────────────────────────────────────────── footer ── */

.footer { background: var(--house-dark); color: var(--paper); padding-block: var(--space-xl); }
.footer__inner { max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter);
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xl); }
@media (max-width: 700px) { .footer__inner { grid-template-columns: 1fr; } }
.footer a { color: #F1B9D3; }
.footer .wordmark { color: var(--paper); font-size: var(--step-2); }
.footer .mono, .footer .credit { color: #B9AFC2; }
.footer__fine { max-width: var(--container); margin: var(--space-xl) auto 0;
  padding-inline: var(--gutter); font-family: var(--font-mono); font-size: 0.75rem;
  color: #B9AFC2; }
.footer dl { display: grid; grid-template-columns: 7rem 1fr; gap: 0 var(--space-md);
  font-family: var(--font-mono); font-size: 0.8125rem; margin: 0; }
.footer dt { color: #B9AFC2; text-transform: uppercase; letter-spacing: 0.04em; }
.footer dd { margin: 0; }

/* ─────────────────────────────────────────────── focal-point classes ── */
/* object-position is a shorthand with no per-axis longhand, so two composable
   classes cannot set it between them — and a style="object-position:…" attribute is
   silently dropped by the CSP (style-src 'self' governs inline attributes too). So
   each class sets a custom property and one rule reads both: 42 rules instead of the
   441 a class-per-pair would need. assets/js/admin.js must move classes for the
   same reason, never element.style. */

.hero__media img, .strip img, .card__media img, .fp__out img {
  object-position: var(--fx, 50%) var(--fy, 50%);
}
.fx-0{--fx:0%}.fx-5{--fx:5%}.fx-10{--fx:10%}.fx-15{--fx:15%}.fx-20{--fx:20%}
.fx-25{--fx:25%}.fx-30{--fx:30%}.fx-35{--fx:35%}.fx-40{--fx:40%}.fx-45{--fx:45%}
.fx-50{--fx:50%}.fx-55{--fx:55%}.fx-60{--fx:60%}.fx-65{--fx:65%}.fx-70{--fx:70%}
.fx-75{--fx:75%}.fx-80{--fx:80%}.fx-85{--fx:85%}.fx-90{--fx:90%}.fx-95{--fx:95%}
.fx-100{--fx:100%}
.fy-0{--fy:0%}.fy-5{--fy:5%}.fy-10{--fy:10%}.fy-15{--fy:15%}.fy-20{--fy:20%}
.fy-25{--fy:25%}.fy-30{--fy:30%}.fy-35{--fy:35%}.fy-40{--fy:40%}.fy-45{--fy:45%}
.fy-50{--fy:50%}.fy-55{--fy:55%}.fy-60{--fy:60%}.fy-65{--fy:65%}.fy-70{--fy:70%}
.fy-75{--fy:75%}.fy-80{--fy:80%}.fy-85{--fy:85%}.fy-90{--fy:90%}.fy-95{--fy:95%}
.fy-100{--fy:100%}

/* ─────────────────────────────────────────────────────────── admin ── */
/* Deliberately plainer than the public site. It is a working tool, and the rack
   belongs to visitors. */

.admin { background: var(--paper-2); min-height: 100vh; }
.admin__bar { background: var(--ink); color: var(--paper); }
.admin__bar .wrap { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-md); padding-block: var(--space-sm); flex-wrap: wrap; }
.admin__bar a { color: var(--paper); text-decoration: none; font-size: 0.8125rem;
  font-family: var(--font-body); font-variation-settings: 'wdth' 108; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; }
.admin__bar a:hover, .admin__bar a[aria-current="page"] { color: #F1B9D3; }
.admin__nav { display: flex; gap: var(--space-md); flex-wrap: wrap; }

.sheet { background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius-card);
  padding: var(--space-lg); margin-bottom: var(--space-lg); }
.sheet > h2 { font-size: var(--step-2); }
.sheet__head { display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--space-md); flex-wrap: wrap; }

.panel { max-width: 26rem; margin: 3rem auto; }
.panel__h1 { font-size: var(--step-2); }

table.grid { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
table.grid th, table.grid td { text-align: left; padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--rule); vertical-align: top; }
table.grid th { font-family: var(--font-body); font-variation-settings: 'wdth' 112;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.6875rem;
  color: var(--haze); }
table.grid tr:last-child td { border-bottom: 0; }

.tag { display: inline-block; border-radius: var(--radius-pill); padding: 0.1rem 0.6rem;
  font-family: var(--font-body); font-variation-settings: 'wdth' 112; font-weight: 700;
  font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.06em;
  border: 1px solid var(--frame); }
.tag--live  { background: #DFF0E5; color: var(--gel-green-ink); }
.tag--draft { background: var(--paper-2); color: var(--haze); }
.tag--todo  { background: #FBE3EC; color: var(--gel-pink-ink); }

.stat { display: flex; gap: var(--space-xl); flex-wrap: wrap; margin-bottom: var(--space-lg); }
.stat__n { font-family: var(--font-display); font-variation-settings: 'SOFT' 60, 'WONK' 1;
  font-weight: 700; font-size: var(--step-3); line-height: 1; display: block; }
.stat__k { font-family: var(--font-body); font-variation-settings: 'wdth' 112; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.6875rem; color: var(--haze); }

.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); }
@media (max-width: 700px) { .row2 { grid-template-columns: 1fr; } }

.imgrow { display: grid; grid-template-columns: 180px 1fr; gap: var(--space-md);
  padding-block: var(--space-md); border-top: 1px solid var(--rule); }
@media (max-width: 700px) { .imgrow { grid-template-columns: 1fr; } }
.imgrow img { width: 100%; aspect-ratio: 3/2; object-fit: cover; border-radius: 3px; }

/* The focal-point picker degrades to two range sliders with JavaScript off, and the
   sliders ARE the form fields — the marker is a readout, never the input. */
.fp { position: relative; display: inline-block; }
.fp__out { position: relative; width: 100%; }
.fp__dot { position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px;
  border: 2px solid #fff; border-radius: 50%; background: var(--gel-pink-ink);
  left: var(--fx, 50%); top: var(--fy, 50%); pointer-events: none; }

/* ──────────────────────────────────────────────────────────── motion ── */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* The dimmer check is skipped entirely rather than played fast: the resting
     state is rendered directly, with no fade and no substitute. */
  .rack--check .rack__chip { animation: none; }
}

@media print {
  .masthead, .rack, .footer, .btn, .skip { display: none !important; }
  body { background: #fff; color: #000; }
  .rider { font-size: 10pt; }
}
