/* FONTS:START — generated by scripts/build-fonts.mjs. Do not hand-edit. */
/* Barlow Condensed and Inter, both SIL Open Font License 1.1 — see
   assets/fonts/OFL.txt. Regenerate with `npm run build:fonts`.

   font-display: swap keeps the old behaviour: text paints immediately in the
   system fallback and is repainted when the webfont arrives. On a sideline that
   matters more than the typeface does.

   Each file carries a content hash, because /assets/fonts/* is cached as
   immutable for a year. */
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/barlow-condensed-latin.e3e520cb.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/barlow-condensed-latin-ext.0c165565.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/barlow-condensed-vietnamese.960aaed3.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('/assets/fonts/barlow-condensed-latin.89eec21b.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('/assets/fonts/barlow-condensed-latin-ext.14480fbc.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('/assets/fonts/barlow-condensed-vietnamese.f5428849.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/inter-latin.c9407645.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/inter-latin-ext.a28eb6d3.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/inter-vietnamese.8db00ff4.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/inter-latin.c9407645.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/inter-latin-ext.a28eb6d3.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/inter-vietnamese.8db00ff4.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/inter-latin.c9407645.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/inter-latin-ext.a28eb6d3.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/inter-vietnamese.8db00ff4.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* FONTS:END */

/* =============================================================================
   SML Designs Photography — style.css
   -----------------------------------------------------------------------------
   Dark by default. One accent color, defined once below: change --accent and
   the whole site rebrands.

   Written mobile-first at 375px. Every interactive target is at least 48x48px
   and body copy never drops below 17px, because this gets used outdoors on a
   phone in daylight.
   ========================================================================== */

/* ---------------------------------------------------------------- 1. TOKENS */
:root {
  /* THE rebrand knob. Everything accented reads from these four.
     --accent      surfaces + link text. 5.45:1 on --bg, and near-black on it
                   is also 5.45:1, so labels stay AA.
     --accent-hover a deliberately DARKER fill for hover. Near-black would only
                   reach 3.65:1 on it, so the label flips to white (5.42:1).
                   Keep these two inks paired if you change the color. */
  --accent: #3e8dc1;
  --accent-hover: #2f6f9c;
  --accent-ink: #0a0a0b; /* text placed ON --accent */
  --accent-hover-ink: #ffffff; /* text placed ON --accent-hover */

  --bg: #0a0a0b;
  --bg-raised: #131316;
  --bg-raised-2: #1b1b20;
  --bg-inset: #08080a;

  --line: #2a2a31;
  --line-strong: #3d3d47;
  /* The edge of a CONTROL, not of a card. WCAG 1.4.11 wants 3:1 for the visual
     boundary of a form field or a checkbox, and --line-strong is 1.6-1.9:1 on
     these backgrounds — on a page filled in on a sideline in daylight, an
     empty input whose fill is 1.08:1 against the panel behind it is a box you
     cannot see until you have already found it. This clears 3:1 on all four
     surfaces (3.22:1 at worst, on --bg-raised-2). --line-strong stays where it
     is for decorative card edges, where 1.4.11 does not apply. */
  --line-control: #6a6a76;

  --ink: #f4f4f5;
  --ink-muted: #a8a8b3; /* 8.4:1 on --bg */
  --ink-dim: #74747f; /* 3.7-4.3:1 — decorative only, never text. Use --ink-muted. */

  --ok: #4ade80;
  --ok-ink: #0a0a0b;
  --warn: #fbbf24;
  --warn-ink: #0a0a0b;
  --danger: #ff6b6b;
  /* Everywhere else --danger is ink or a tint. The moment it becomes a solid
     FILL it needs its own ink: --ink on it is 1.6:1 and unreadable. This is
     7.1:1, same arrangement as --ok-ink and --warn-ink above. */
  --danger-ink: #0a0a0b;

  --font-display: 'Barlow Condensed', 'Oswald', 'Arial Narrow', 'Helvetica Neue', system-ui, sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  --step-0: 1.0625rem; /* 17px — the floor for body copy */
  --step-1: 1.1875rem;
  --step-2: 1.375rem;
  --step-3: 1.75rem;
  --step-4: 2.25rem;
  --step-5: 3rem;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;
  --space-9: 6rem;

  --radius: 6px;
  --radius-lg: 12px;
  --tap: 48px; /* minimum interactive target */

  --wrap: 1180px;
  --wrap-narrow: 720px;

  --shadow: 0 1px 2px rgb(0 0 0 / 0.4), 0 8px 24px rgb(0 0 0 / 0.35);
  --focus: 0 0 0 3px var(--bg), 0 0 0 6px var(--accent);
}

/* ----------------------------------------------------------------- 2. RESET */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* --header-h / --steps-h are measured by initStickyMetrics() in site.js. The
     fallbacks are the old hardcoded numbers, correct at the default text size
     and wrong at every other one: at 125% the header and progress bar measure
     229px, so an anchored field landed underneath the bar that had just told
     the parent to fix it. */
  scroll-padding-top: calc(var(--header-h, 68px) + var(--space-4));
}

/* The contract page stacks a sticky header AND a sticky progress bar, so an
   anchor jump landed the target field underneath both of them — the field the
   parent had just been told to fix was painted over by the progress bar.
   Scoped to pages that actually have the progress bar. */
html:has(.steps) { scroll-padding-top: calc(var(--header-h, 68px) + var(--steps-h, 126px) + var(--space-4)); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  margin: 0 0 var(--space-4);
  text-wrap: balance;
}
h1 { font-size: clamp(2.5rem, 11vw, 4.5rem); }
h2 { font-size: clamp(2rem, 8vw, 3rem); }
h3 { font-size: var(--step-3); }
h4 { font-size: var(--step-2); }

p { margin: 0 0 var(--space-4); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

/* Links use the base accent, not the hover fill — the darker hover is for
   surfaces and would drop body links to 3.65:1 on this background. */
a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--ink); }

/* Buttons inherit nothing useful from the UA — reset it explicitly. */
button, input, select, textarea { font: inherit; color: inherit; }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus);
  border-radius: var(--radius);
}

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

/* ---------------------------------------------------------------- 3. LAYOUT */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--space-5); }
.wrap--narrow { max-width: var(--wrap-narrow); }

.section { padding-block: var(--space-8); }
.section--tight { padding-block: var(--space-7); }
.section__head { margin-bottom: var(--space-6); }
.section__eyebrow {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--space-2);
}
.section__lede { color: var(--ink-muted); max-width: 62ch; font-size: var(--step-1); }

.skip-link {
  position: absolute; left: var(--space-4); top: -100px; z-index: 999;
  background: var(--accent); color: var(--accent-ink);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius); font-weight: 700;
}
.skip-link:focus { top: var(--space-4); }

/* --------------------------------------------------------------- 4. BUTTONS */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-height: var(--tap); padding: var(--space-3) var(--space-5);
  font-family: var(--font-display); font-size: var(--step-2); font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase; text-decoration: none;
  border: 2px solid transparent; border-radius: var(--radius);
  background: var(--accent); color: var(--accent-ink);
  cursor: pointer; transition: transform 120ms ease, background-color 120ms ease, border-color 120ms ease;
}
.btn:hover { background: var(--accent-hover); color: var(--accent-hover-ink); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn[disabled], .btn[aria-disabled='true'] {
  opacity: 0.45; cursor: not-allowed; transform: none; background: var(--bg-raised-2); color: var(--ink-muted);
}
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn--ghost:hover { background: var(--bg-raised-2); color: var(--ink); border-color: var(--accent); }
.btn--full { width: 100%; }
.btn--lg { font-size: var(--step-3); padding: var(--space-4) var(--space-6); min-height: 56px; }
.btn--sm { min-height: var(--tap); font-size: var(--step-1); padding: var(--space-2) var(--space-4); }

/* ---------------------------------------------------------- 5. HEADER / NAV */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
/* flex-wrap is the no-JS fallback. With scripting off there is nothing to open
   the drawer with, so the nav drops to a second row under the brand instead of
   disappearing — which is what it used to do below the breakpoint. */
.site-header__inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-4); min-height: 68px; }
/* min-height because this file's own header promises every interactive target
   is at least 48px, and the brand link was 37. It is the "go home" control on
   every page and it gets tapped with a thumb. */
.brand { display: flex; align-items: center; gap: var(--space-3); min-height: var(--tap); text-decoration: none; color: var(--ink); }
/* THE REAL LOGO, and it is a LOCKUP — the camera mark and the words "SML
   Designs / Photography" are one piece of artwork. So it is sized by HEIGHT
   with the width left to follow (it is roughly 2.33:1), and the wordmark that
   used to sit beside it in markup is gone: two copies of the same name next to
   each other read as a mistake.
   Height, not width, because the header's job is to keep a consistent cap
   height next to the Menu button whatever the artwork's proportions are. It
   grows a little where there is room. */
.brand__mark { height: 40px; width: auto; flex: none; }
@media (min-width: 760px) { .brand__mark { height: 44px; } }

/* THE HEADER IS A FIXED PIXEL BUDGET, and this gap is the slack in it. At any
   width from 1180px up the .wrap content box is exactly 1132px, and the three
   header children have to fit it on one row or the Sign Contract button drops
   underneath the logo and the sticky header grows from 68px to 115px — which
   then moves the hero's calc(100vh - var(--header-h)) and, on the contract page,
   the sticky progress bar.
   The budget: brand 103 + nav + button 156 + two 16px header gaps. Renaming
   "Call" to "Contact" on 2026-09-04 widened the nav by 40px and spent the last
   of it. Dropping this gap from space-5 to space-4 buys back 40px across the
   nav's five gaps — the same value the drawer already uses below 960px — and
   restores the ~28px of slack the header had before. Below 1180px the header
   has wrapped for as long as the inline nav has existed; that is untouched. */
.nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }
/* :not(.nav__cta) throughout — the Sign Contract button lives inside the nav on
   a phone, and `.nav a` outranks `.btn`, so without this it would be repainted
   as a muted text link. */
/* ALL CAPS, with tracking. Uppercase at a small size closes letterforms up and
   costs legibility, so the letter-spacing is not decoration — it is what pays
   the difference back. Same treatment .btn already uses, which is why the nav
   and the Sign Contract button now read as one row rather than two ideas.
   text-transform, never capitals typed into the markup: the accessible name and
   the page source stay in sentence case, so a screen reader says "Games I'll be
   at" instead of spelling out an acronym. */
.nav a:not(.nav__cta) {
  color: var(--ink-muted); text-decoration: none; font-weight: 600; font-size: 1rem;
  text-transform: uppercase; letter-spacing: 0.06em;
  display: inline-flex; align-items: center; min-height: var(--tap);
}
.nav a:not(.nav__cta):hover, .nav a:not(.nav__cta)[aria-current='page'] { color: var(--ink); }
.nav a:not(.nav__cta)[aria-current='page'] { box-shadow: inset 0 -2px 0 var(--accent); }
/* box-shadow is doing two jobs on this element: the current-page marker above
   and the site-wide focus ring, which :where() deliberately leaves at
   specificity (0,1,0). The marker won, outline:none still applied, and the one
   nav link a keyboard user is most likely to land on had no focus indicator at
   all. Restated here at a specificity that can win. */
.nav a:not(.nav__cta)[aria-current='page']:focus-visible { box-shadow: var(--focus); }

/* --- The menu button ------------------------------------------------------
   Hidden without JS, because nothing would open. The nav is a plain wrapped
   row in that case, so the links are still reachable. */
.nav-toggle {
  display: none; margin-left: auto;
  align-items: center; gap: var(--space-3);
  min-height: var(--tap); padding: var(--space-2) var(--space-4);
  background: transparent; border: 1px solid var(--line-strong); border-radius: var(--radius);
  color: var(--ink); cursor: pointer;
  font-family: var(--font-display); font-size: var(--step-1); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
}
.nav-toggle:hover { border-color: var(--accent); color: var(--accent); }

/* Three bars that fold into an X. The middle bar is the element itself and the
   outer two are its pseudo-elements, so the icon is one box, not three. */
.nav-toggle__icon { position: relative; flex: none; width: 22px; height: 2px; background: currentColor; transition: background-color 120ms ease; }
.nav-toggle__icon::before, .nav-toggle__icon::after {
  content: ''; position: absolute; left: 0; width: 100%; height: 2px;
  background: currentColor; transition: transform 160ms ease;
}
.nav-toggle__icon::before { top: -7px; }
.nav-toggle__icon::after { top: 7px; }
.site-header.is-open .nav-toggle__icon { background: transparent; }
.site-header.is-open .nav-toggle__icon::before { transform: translateY(7px) rotate(45deg); }
.site-header.is-open .nav-toggle__icon::after { transform: translateY(-7px) rotate(-45deg); }

/* Exactly one "Sign Contract" is visible in the header at any width: the
   standalone button from 620px up, the one inside the menu below it. */
.nav__cta { display: none; }
@media (max-width: 619.98px) { .nav__cta { display: inline-flex; } }

@media (max-width: 959.98px) {
  /* No JS: second row under the brand. */
  .nav { flex-basis: 100%; gap: var(--space-4); padding-bottom: var(--space-3); }

  /* With JS: a drawer hanging off the bottom of the sticky header. Absolute,
     not in flow, so the header stays 68px tall and the contract page's sticky
     progress bar (top: 68px) still lines up. */
  .js .nav-toggle { display: inline-flex; }
  .js .nav {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; flex-basis: auto; gap: 0;
    max-height: calc(100vh - var(--header-h, 68px));
    max-height: calc(100dvh - var(--header-h, 68px)); /* the visible viewport, not the tallest one */
    overflow-y: auto;
    padding: var(--space-2) var(--space-5) var(--space-5);
    background: var(--bg); border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow);
  }
  .js .site-header.is-open .nav { display: flex; animation: nav-open 160ms ease; }
  .js .nav a:not(.nav__cta) { min-height: 56px; font-size: var(--step-1); border-bottom: 1px solid var(--line); }
  .js .nav a:not(.nav__cta)[aria-current='page'] { box-shadow: none; color: var(--accent); }
  .js .nav a:not(.nav__cta)[aria-current='page']:focus-visible { box-shadow: var(--focus); }
  .js .nav a.nav__cta { margin-top: var(--space-5); }
}

/* At 320px the brand (159px) and a "Menu" button (112px) do not fit the 272px
   of content width, so the header wrapped to two rows and grew to 113px —
   which then pushed the sticky progress bar and every anchor target out of
   place. Below 360px the button becomes its icon alone. The word stays in the
   DOM, only visually hidden, so the button keeps "Menu" as its accessible name
   and a voice-control user can still say it. */
@media (max-width: 359.98px) {
  .nav-toggle { padding-inline: var(--space-3); }
  .nav-toggle__label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  /* "Book / Sign Contract" at 22px broke onto four lines and made the fixed bar
     126px tall — a sixth of the screen — on the smallest phone we support. */
  .sticky-cta .btn { font-size: var(--step-1); padding-inline: var(--space-3); }
}

/* display:none cannot be transitioned, so the drawer animates in instead.
   The reduced-motion block near the end of this file flattens it to nothing. */
@keyframes nav-open { from { opacity: 0; transform: translateY(-8px); } }

/* Scoped to a direct child so it never catches the button inside the drawer. */
.site-header__inner > .btn--sm { display: none; }
@media (min-width: 620px) { .site-header__inner > .btn--sm { display: inline-flex; } }

/* ...but the contract and thank-you pages carry no nav at all, so their header
   button is "Contact" and there is no drawer and no sticky bar to hold a copy
   of it. Hiding it below 620px left a parent stuck mid-signature on a phone
   with no way to reach the photographer from the page they were on. */
.site-header__inner:not(:has(.nav)) > .btn--sm { display: inline-flex; }

/* ------------------------------------------------------------------ 6. HERO */
.hero { position: relative; isolation: isolate; display: flex; align-items: flex-end; min-height: min(80vh, 720px); padding-block: var(--space-7) var(--space-6); }
.hero__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; background: var(--bg-inset); }
/* THE PHOTOGRAPH IS KNOCKED BACK IN THE IMAGE ITSELF, not only under a scrim.
   The frame is bright sunlit turf — the brightest thing that has ever been
   behind this text — and a gradient alone could not hold it: at the top of the
   hero the old overlay is only 25% opaque, which is fine over a dark photo and
   nowhere near enough over lit grass.
   Two filters, doing two different jobs:
     brightness — buys the contrast the text needs, everywhere at once, rather
       than relying on the copy staying inside the darkest corner of a gradient.
     saturate   — the turf green and --accent (#3e8dc1, the blue on the kicker
       and in the headline) are near-opposites. At full saturation they vibrate
       against each other and the accent stops reading as the brand color.
   Kept as a filter on the <img> rather than baked into the JPEG so the source
   stays a photograph: the numbers here can be re-judged against a different
   hero without going back to build-hero.mjs. */
.hero__media img { width: 100%; height: 100%; object-fit: cover; filter: brightness(0.82) saturate(0.78); }
.hero::after {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background:
    /* A flat wash first, so the floor is the same everywhere. The gradients
       below it shape where the eye goes; this is what guarantees no bright
       patch of turf ever ends up under a word. */
    linear-gradient(color-mix(in srgb, var(--bg) 10%, transparent), color-mix(in srgb, var(--bg) 10%, transparent)),
    linear-gradient(to top, var(--bg) 3%, color-mix(in srgb, var(--bg) 42%, transparent) 30%, color-mix(in srgb, var(--bg) 14%, transparent) 72%, transparent 100%),
    /* THE ONE DOING THE WORK. Every word in the hero starts at the left edge and
       the longest runs to about two-thirds across, so the readable surface is
       built there and the photograph is left alone on the right — which is where
       the 30-yard line and the numbers are, the part worth showing. Weighting it
       sideways rather than dropping the brightness globally is what keeps this a
       photograph of a field instead of a dark green texture. */
    linear-gradient(to right, var(--bg) 0%, color-mix(in srgb, var(--bg) 90%, transparent) 30%, color-mix(in srgb, var(--bg) 52%, transparent) 60%, transparent 88%);
}
/* NO CAP ON THE CONTAINER. .hero__inner also carries .wrap, and .wrap sets
   margin-inline: auto — so a 40ch cap did not just narrow the column, it
   CENTRED it, leaving the headline floating with a gutter either side while
   every other section on the page started at the left. The ::after gradient
   darkens the LEFT edge, which is the giveaway: the copy was always meant to
   sit there.
   This was first fixed only above 760px, on the assumption that 40ch is wider
   than a phone so the cap never bound there. It is not: `box-sizing:
   border-box` means the cap includes .wrap's own 24px of padding, so the
   element is min(100vw, ~429px) and the centring starts as soon as the viewport
   passes ~429px — every phone held sideways, and every small or split-screen
   window. Capping the container was the wrong tool at any width; the measure
   belongs on the text, which is where it now lives (.hero h1 and .hero__sub
   below), and .wrap's own max-width governs the column. */
.hero h1 { max-width: 20ch; }
.hero__kicker {
  display: flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-display); font-weight: 700; font-size: 1rem;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent);
  margin-bottom: var(--space-3);
  /* No white-space: nowrap. With it, "New Jersey · Youth & High School" is
     318px wide inside a 272px column on a 320px phone — and because
     body{overflow-x:hidden} there was no way to scroll to the end, so the line
     simply lost the word "School". Two tidy lines beat one cut-off one. */
}
.hero__kicker::before { content: ''; flex: none; width: 28px; height: 3px; background: var(--accent); }
@media (min-width: 560px) { .hero__kicker { font-size: var(--step-1); letter-spacing: 0.16em; } }
.hero h1 { margin-bottom: var(--space-4); }
.hero h1 em { font-style: normal; color: var(--accent); }
.hero__sub { font-size: var(--step-1); line-height: 1.5; color: var(--ink-muted); margin-bottom: var(--space-5); max-width: 36ch; }
@media (min-width: 560px) { .hero__sub { font-size: var(--step-2); max-width: 34ch; } }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
/* Grow to fill the row on a phone — a full-width target is the right thing for
   a thumb. That rule was written when the column was capped at 40ch; once the
   hero spans the page the same flex-grow stretched two buttons across 1180px.
   Above the breakpoint they size to their words instead. Kept at 760px on
   purpose even though the container cap is gone: this one is about thumbs, not
   about the measure, and a landscape phone still wants full-width targets. */
.hero__actions .btn { flex: 1 1 auto; min-width: 190px; }
@media (min-width: 760px) {
  .hero__actions .btn { flex: 0 0 auto; }
}

/* ------------------------------------------------- THE HERO, FULL SCREEN
   On a desktop the hero fills the window and the type grows into it. Below
   960px it keeps the old min(80vh, 720px): on a phone a full-height hero is a
   screen of backdrop that pushes the packages — the reason people came — below
   the fold, and browser chrome makes "full height" a moving target anyway.

   svh, not vh. `100vh` on mobile Safari means the LARGEST viewport (toolbars
   retracted), so a 100vh hero is taller than the window until you scroll. This
   rule is desktop-only so it barely matters, but svh is the honest unit and
   costs nothing — vh is kept as the fallback line for anything that lacks it.

   Minus --header-h because the header is position:sticky at the top of the
   page: the hero starts underneath it, so a full 100svh would run exactly one
   header's worth past the fold. --header-h is measured by initStickyMetrics(),
   never hardcoded — see the note there; at 200% text the header is 251px and
   any literal would be wrong by 180. */
@media (min-width: 960px) {
  .hero {
    min-height: calc(100vh - var(--header-h, 68px));
    min-height: calc(100svh - var(--header-h, 68px));
    padding-block: var(--space-7);
    /* CENTRED, not bottom-aligned. Pinning to the bottom is right when the hero
       is a band 720px tall — the copy sits on the fold and the photograph reads
       above it. Once the hero is the whole window that same rule pushes the
       block to the floor and leaves most of a screen of empty turf above it,
       which looks like a loading state rather than a composition. */
    align-items: center;
  }
  /* The type grows with the space. clamp()'s 11vw floor was tuned for a phone;
     across a 1600px window it stops scaling long before the hero does, leaving
     a headline marooned in the middle of a very large photograph. */
  .hero h1 { font-size: clamp(4rem, 6.4vw, 6.5rem); max-width: 16ch; }
  .hero__sub { font-size: var(--step-3); max-width: 46ch; line-height: 1.45; }
  .hero__kicker { font-size: var(--step-2); }
}
/* Past ~1500px the vw-driven headline would keep growing past the point where
   a line of it is comfortable to read, so it stops. */
@media (min-width: 1500px) {
  .hero h1 { font-size: 6.5rem; }
}

/* ------------------------------------------------- 7. PRICING / SERVICE CARDS */
.svc-grid { display: grid; gap: var(--space-5); grid-template-columns: 1fr; }
@media (min-width: 900px) { .svc-grid { grid-template-columns: repeat(3, 1fr); } }

.svc-card {
  display: flex; flex-direction: column;
  background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-5); position: relative; overflow: hidden;
}
.svc-card::before { content: ''; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--accent); }
.svc-card__head { padding-bottom: var(--space-4); border-bottom: 1px solid var(--line); }
.svc-card__name { font-size: var(--step-3); margin: 0 0 var(--space-2); }
.svc-card__price { font-family: var(--font-display); font-size: 3.25rem; font-weight: 800; line-height: 0.95; margin: 0 0 var(--space-2); color: var(--ink); }
.svc-card__starting { display: block; font-size: 0.95rem; letter-spacing: 0.14em; color: var(--ink-muted); text-transform: uppercase; }
.svc-card__meta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin: 0; }

/* "per athlete" must be impossible to misread — it gets its own chip, not grey
   small print sitting next to the price. */
.svc-card__unit {
  font-family: var(--font-display); font-weight: 700; font-size: var(--step-1);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-muted);
}
.svc-card__unit--athlete {
  background: var(--accent); color: var(--accent-ink);
  padding: 2px var(--space-3); border-radius: 999px; letter-spacing: 0.08em;
}
.svc-card__dot { color: var(--ink-dim); }
.svc-card__duration { color: var(--ink-muted); font-size: 1rem; }

/* The minimum image count closes the sale and ends the argument later. */
.svc-card__images {
  display: flex; align-items: baseline; gap: var(--space-3);
  margin: var(--space-4) 0; padding: var(--space-4);
  background: var(--bg-inset); border: 1px solid var(--line); border-radius: var(--radius);
}
.svc-card__images-num { font-family: var(--font-display); font-size: 2.75rem; font-weight: 800; line-height: 1; color: var(--accent); }
.svc-card__images--scoped .svc-card__images-num { font-size: 1.75rem; }
.svc-card__images-label { font-size: 1rem; line-height: 1.3; color: var(--ink); font-weight: 600; }
/* Video status, stated on every card — included, or explicitly not. */
.svc-card__video {
  display: flex; align-items: center; gap: var(--space-3);
  margin: 0 0 var(--space-4); padding: var(--space-3) var(--space-4);
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  font-size: 1rem; font-weight: 600; color: var(--ink);
}
.svc-card__video svg { flex: none; width: 22px; height: 22px; color: var(--accent); }
.svc-card__video em { font-style: normal; font-weight: 400; color: var(--ink-muted); }
/* A SEPARATOR THAT DOES NOT DEPEND ON COLOUR.
   The label and the short note are two facts — "Photos only" and "video not
   available yet" — and the only thing dividing them was weight and color. That
   works on the included variant, but .svc-card__video--none deliberately mutes
   the WHOLE chip to 400/--ink-muted, which is exactly what <em> already is: the
   two collapsed into one another and the card read "Photos only video not
   available yet", a run-on with no seam, on the package most likely to sell
   during football season.
   The dot is the same device .svc-card__meta uses one line above ("Per athlete ·
   Full game"), so this is the card's existing punctuation rather than a new
   idea, and being a glyph it survives any color the variant sets. */
.svc-card__video em::before { content: '·'; margin-inline: 0 0.34em; color: var(--ink-dim); }
.svc-card__video--none {
  border-style: dashed; border-color: var(--line-strong);
  background: transparent; color: var(--ink-muted); font-weight: 400;
}
.svc-card__video--none svg { color: var(--ink-dim); }

.svc-card__blurb { color: var(--ink-muted); margin-bottom: var(--space-4); }
.svc-card__list { list-style: none; margin: 0 0 var(--space-5); padding: 0; display: grid; gap: var(--space-2); }
.svc-card__list li { position: relative; padding-left: var(--space-5); color: var(--ink-muted); font-size: 1rem; }
.svc-card__list li::before { content: ''; position: absolute; left: 0; top: 0.55em; width: 8px; height: 8px; background: var(--accent); border-radius: 2px; }
.svc-card__foot { margin-top: auto; }

/* Add-on: visually distinct from the service cards so it never reads as a
   fourth service. */
.addon-card {
  display: grid; gap: var(--space-4); align-items: start;
  grid-template-columns: 1fr;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-left: 4px solid var(--accent); border-radius: var(--radius-lg); padding: var(--space-5);
}
@media (min-width: 720px) { .addon-card { grid-template-columns: 1fr auto; } }
.addon-card__name { font-size: var(--step-3); margin-bottom: var(--space-2); }
.addon-card__blurb { color: var(--ink-muted); }
.addon-card__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.addon-card__list li { color: var(--ink-muted); font-size: 1rem; padding-left: var(--space-4); position: relative; }
.addon-card__list li::before { content: '+'; position: absolute; left: 0; color: var(--accent); font-weight: 700; }
.addon-card__side { text-align: left; }
@media (min-width: 720px) { .addon-card__side { text-align: right; } }
.addon-card__price { font-family: var(--font-display); font-size: 2.75rem; font-weight: 800; line-height: 1; margin: 0; }
.addon-card__from { display: block; font-size: 0.95rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-muted); }
.addon-card__unit { font-size: 0.95rem; color: var(--ink-muted); margin: 0; text-transform: uppercase; letter-spacing: 0.1em; }
.addon-card__combos { grid-column: 1 / -1; padding-top: var(--space-4); border-top: 1px solid var(--line); }
.addon-card__combos-title {
  margin: 0 0 var(--space-2); font-size: 0.85rem; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--ink-muted); font-weight: 700;
}
.addon-card__combos ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.addon-card__combos li {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
  padding-block: var(--space-2); border-bottom: 1px solid var(--line); color: var(--ink-muted); font-size: 1rem;
}
.addon-card__combos li:last-child { border-bottom: 0; }
.addon-card__combos strong { color: var(--ink); font-family: var(--font-display); font-size: var(--step-3); white-space: nowrap; }

/* Travel — deliberately secondary. */
.travel { background: var(--bg-inset); border: 1px dashed var(--line-strong); border-radius: var(--radius-lg); padding: var(--space-5); }
.travel__title { font-size: var(--step-2); margin-bottom: var(--space-4); color: var(--ink-muted); }
.travel__rates { margin: 0 0 var(--space-4); display: grid; gap: var(--space-2); }
.travel__row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4); padding-block: var(--space-2); border-bottom: 1px solid var(--line); }
.travel__row dt { color: var(--ink); }
.travel__row dd { margin: 0; font-family: var(--font-display); font-size: var(--step-3); font-weight: 800; }
.travel__note { color: var(--ink-muted); font-size: 1rem; margin-bottom: var(--space-2); }
.travel__note--muted { color: var(--ink-muted); }

.smallprint { color: var(--ink-muted); font-size: 0.95rem; margin-top: var(--space-5); }

/* "Included with every service" strip */
/* FLEX, not grid, and that is the point. This was repeat(4, 1fr) when there
   were exactly four items; a fifth (prints from the gallery) dropped alone onto
   a second row with three empty columns beside it, directly under the price
   cards. auto-fit does not fix it either — with five items there is always a
   width where the track count is four and the fifth is orphaned again. Wrapping
   flex items grow to fill whatever row they land on, so a short last row reads
   as deliberate at every width instead of as dead space. Same lesson as .quotes
   below: a fixed track count is a promise about how many there will be. */
.included { display: flex; flex-wrap: wrap; gap: var(--space-4); background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: var(--space-5); }
.included__item { flex: 1 1 190px; display: flex; gap: var(--space-3); align-items: flex-start; }
.included__icon { flex: none; width: 26px; height: 26px; color: var(--accent); }
.included__item strong { display: block; font-size: 1rem; }
.included__item span { color: var(--ink-muted); font-size: 0.95rem; line-height: 1.4; }

/* ------------------------------------------------------------ 8. CALCULATOR */
.calc { display: grid; gap: var(--space-5); }
.calc__group { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--bg-raised); padding: var(--space-5); margin: 0; }
.calc__legend {
  font-family: var(--font-display); font-size: var(--step-2); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em; padding-inline: var(--space-2);
  color: var(--accent);
}
.calc__services, .calc__addons, .calc__travel { display: grid; gap: var(--space-2); }

.calc-row { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-inset); transition: border-color 120ms ease, background-color 120ms ease; }
.calc-row.is-selected { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg-inset)); }
.calc-row.is-disabled { opacity: 0.55; }
.calc-row__main { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); min-height: var(--tap); }

/* Custom controls. The UA checkbox is a solid white block on a dark theme —
   too heavy, and accent-color only styles the checked state. */
.calc-row__check, .checkfield input[type='checkbox'], .toggle input[type='checkbox'] {
  appearance: none; -webkit-appearance: none; flex: none;
  width: 28px; height: 28px; margin: 0;
  border: 2px solid var(--line-control); border-radius: 6px;
  background: var(--bg-raised-2); cursor: pointer;
  display: grid; place-content: center;
  transition: background-color 120ms ease, border-color 120ms ease;
}
.calc-row__check[type='radio'] { border-radius: 50%; }
.calc-row__check:hover:not(:disabled), .checkfield input[type='checkbox']:hover, .toggle input[type='checkbox']:hover { border-color: var(--accent); }
.calc-row__check:checked, .checkfield input[type='checkbox']:checked, .toggle input[type='checkbox']:checked { background: var(--accent); border-color: var(--accent); }
.calc-row__check[type='checkbox']:checked::after, .checkfield input[type='checkbox']:checked::after, .toggle input[type='checkbox']:checked::after {
  content: ''; width: 7px; height: 13px; margin-top: -3px;
  border: solid var(--accent-ink); border-width: 0 3px 3px 0; transform: rotate(45deg);
}
.calc-row__check[type='radio']:checked::after {
  content: ''; width: 12px; height: 12px; border-radius: 50%; background: var(--accent-ink);
}
.calc-row__check:disabled { cursor: not-allowed; background: var(--bg-inset); border-color: var(--line); }
.calc-row__label { display: flex; flex: 1; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-2); cursor: pointer; min-height: var(--tap); align-content: center; }
.calc-row__name { font-weight: 600; }
.calc-row__price { font-family: var(--font-display); font-size: var(--step-2); font-weight: 700; color: var(--accent); white-space: nowrap; }
.calc-row__help { margin: 0; padding: 0 var(--space-4) var(--space-3) calc(var(--space-4) + 26px + var(--space-3)); color: var(--warn); font-size: 0.95rem; }
.calc-row__extra { padding: 0 var(--space-4) var(--space-4); border-top: 1px solid var(--line); margin-top: 0; padding-top: var(--space-4); }

.stepper-wrap { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
.stepper__label { font-weight: 600; font-size: 1rem; }
.stepper { display: inline-flex; align-items: stretch; border: 1px solid var(--line-strong); border-radius: var(--radius); overflow: hidden; background: var(--bg-raised-2); }
.stepper__btn {
  width: var(--tap); min-height: var(--tap); border: none; background: transparent; color: var(--ink);
  font-size: 1.5rem; line-height: 1; cursor: pointer;
}
.stepper__btn:hover { background: var(--accent); color: var(--accent-ink); }
.stepper__input {
  width: 64px; min-height: var(--tap); text-align: center; border: none; border-inline: 1px solid var(--line-strong);
  background: var(--bg-inset); font-family: var(--font-display); font-size: var(--step-3); font-weight: 700;
  -moz-appearance: textfield;
}
.stepper__input::-webkit-outer-spin-button, .stepper__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ---- Athlete roster (appears once more than one athlete is booked) ------ */
.roster { margin-top: var(--space-5) !important; border-color: var(--accent) !important; }
.roster__intro { margin: 0 0 var(--space-4); color: var(--ink-muted); font-size: 1rem; }
.roster__intro strong { color: var(--ink); }
.roster__list { display: grid; gap: var(--space-3); margin-bottom: var(--space-4); }
.roster__notice {
  padding: var(--space-3) var(--space-4); border-radius: var(--radius); margin-bottom: var(--space-4);
  background: color-mix(in srgb, var(--warn) 15%, var(--bg-raised)); border: 1px solid var(--warn); font-size: 1rem;
}

.athlete-card { border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--bg-inset); padding: var(--space-4); }
.athlete-card--primary { border-style: dashed; }
.athlete-card__head { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); }
.athlete-card__title {
  flex: 1; margin: 0; font-family: var(--font-display); font-size: var(--step-1);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent);
}
.athlete-card__summary { margin: 0 0 var(--space-3); font-size: 1rem; color: var(--ink-muted); }
.athlete-card__missing { color: var(--warn); }
.athlete-card__fields { display: grid; gap: var(--space-4); }
.athlete-card__remove {
  flex: none; width: var(--tap); height: var(--tap); display: grid; place-items: center;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: transparent; color: var(--ink-muted); cursor: pointer; font-size: 1rem;
}
.athlete-card__remove:hover { border-color: var(--danger); color: var(--danger); }
/* Armed for its second tap. A square ✕ has no room for a label, so it grows
   into one — a glyph swap would not read as a question to anyone. */
.athlete-card__remove.is-armed {
  width: auto; padding: 0 var(--space-3);
  background-color: var(--danger); border-color: var(--danger);
  color: var(--danger-ink); font-weight: 700; font-size: 0.95rem;
}

.athlete-card__coverage { margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--line); }
.athlete-card__coverage-label {
  margin: 0 0 var(--space-2); font-size: 0.8rem; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--ink-muted); font-weight: 700;
}
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: var(--tap); padding: var(--space-2) var(--space-4);
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--bg-raised-2); cursor: pointer; font-size: 1rem;
}
.chip input { width: 22px; height: 22px; accent-color: var(--accent); cursor: pointer; }
.chip.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-raised-2)); }

.scope-fields { display: grid; gap: var(--space-4); }
.scope-fields__intro { font-size: 0.95rem; color: var(--warn); margin: 0; font-weight: 600; }

/* Printing turns the poster into a physical product with its own price. */
.print-options {
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-inset));
  padding: var(--space-3);
}
@media (min-width: 560px) { .print-options { padding: var(--space-4); } }

/* A dropdown can always truncate on a narrow screen, so the running print fee
   is stated in plain text underneath where it cannot be cut off. */
.print-options__total {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3);
  margin: var(--space-4) 0 0; padding-top: var(--space-3);
  border-top: 1px solid var(--line-strong); font-size: 1rem;
}
.print-options__total strong { font-family: var(--font-display); font-size: var(--step-2); color: var(--accent); }
.print-options__title {
  margin: 0 0 var(--space-2); font-family: var(--font-display); font-size: var(--step-1);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent);
}
.print-options__groups { display: grid; gap: var(--space-4); margin-top: var(--space-3); }

/* Sits in the same slot as .print-options and answers the same question, so it
   shares the shape — but it quotes no price, because the print is bought in the
   gallery store. Deliberately NOT styled as a warning: choosing a print is the
   expensive, wanted outcome, and a yellow box would read as something gone
   wrong. Neutral panel, not accent-bordered, so it does not compete with the
   priced rows above it for attention. */
.print-elsewhere {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--bg-inset); padding: var(--space-3);
}
@media (min-width: 560px) { .print-elsewhere { padding: var(--space-4); } }
.print-elsewhere__title {
  margin: 0 0 var(--space-2); font-family: var(--font-display); font-size: var(--step-1);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink);
}
.print-elsewhere .btn { margin-top: var(--space-3); }

.calc__travel-note { margin: var(--space-4) 0 0; color: var(--ink-muted); font-size: 0.95rem; }
.calc__notice { padding: var(--space-3) var(--space-4); border-radius: var(--radius); background: color-mix(in srgb, var(--warn) 15%, var(--bg-raised)); border: 1px solid var(--warn); color: var(--ink); font-size: 1rem; }
/* Hidden while empty rather than [hidden] while idle: a live region has to be
   in the accessibility tree BEFORE its text changes or nothing is announced. */
.calc__notice:empty { display: none; }

/* Travel waived because the shoot is somewhere Seth already goes. Styled as
   good news rather than a warning — it is money off, and it replaces the
   travel picker, so it needs to read as an answer and not as a missing step. */
.calc__travel-exempt {
  margin: 0 0 var(--space-5); padding: var(--space-3) var(--space-4);
  border-radius: var(--radius); border: 1px solid var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, var(--bg-raised));
  color: var(--ink); font-size: 1rem;
}
.calc__venue-prompt { margin: var(--space-2) 0 0; color: var(--ink-muted); font-size: 0.95rem; }

/* A quote built on a starting price is a floor, not a promise — say so where
   the number is, not only in the contract text. */
.qline__from {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em;
  font-size: 0.72em; color: var(--accent); margin-left: var(--space-2);
}
.calc__starting-note {
  margin: var(--space-3) 0 0; padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--accent); background: var(--bg-inset);
  border-radius: var(--radius-sm); color: var(--ink-muted); font-size: 0.95rem;
}
.calc__venue-other { margin-top: var(--space-3); }

.calc__summary { border: 2px solid var(--accent); border-radius: var(--radius-lg); background: var(--bg-raised); padding: var(--space-5); }
.calc__summary-title { font-size: var(--step-2); margin-bottom: var(--space-4); }
.calc__empty { color: var(--ink-muted); margin: 0; }
.calc__lines { display: grid; gap: var(--space-1); }

.qline { display: grid; grid-template-columns: 1fr auto; gap: var(--space-2) var(--space-3); align-items: baseline; padding-block: var(--space-2); font-size: 1rem; }
@media (min-width: 560px) { .qline { grid-template-columns: 1fr 8rem auto; } }
.qline__label { color: var(--ink); }
.qline__qty { color: var(--ink-muted); font-family: var(--font-mono); font-size: 0.9rem; grid-row: 2; grid-column: 1; }
@media (min-width: 560px) { .qline__qty { grid-row: auto; grid-column: auto; text-align: right; } }
.qline__amt { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; grid-row: 1; grid-column: 2; }
.qline--tax .qline__label { color: var(--ink-muted); }
.qline--total { border-top: 2px solid var(--line-strong); margin-top: var(--space-2); padding-top: var(--space-3); font-family: var(--font-display); font-size: var(--step-3); font-weight: 800; text-transform: uppercase; }
.qline--total .qline__amt { font-family: var(--font-display); font-size: var(--step-3); color: var(--accent); }
.qline--deposit { font-weight: 700; }
.qline--deposit .qline__amt { color: var(--ok); }
.qline--balance { color: var(--ink-muted); }

/* --------------------------------------------------------------- 9. FIELDS */
/* align-content keeps the label/input packed at the top of the cell. Without it
   a field paired with a taller one (a sibling carrying a hint or an error) has
   its spare height shared between the rows, and the input floats down out of
   line with the field beside it. */
.field { display: grid; gap: var(--space-2); align-content: start; }
.field__label { font-weight: 600; font-size: 1rem; }
.req { color: var(--accent); }
.field__input, .field textarea, .field select {
  width: 100%; min-height: var(--tap); padding: var(--space-3) var(--space-4);
  background: var(--bg-inset); color: var(--ink);
  border: 1px solid var(--line-control); border-radius: var(--radius);
  font-size: var(--step-0); /* >=16px stops iOS zooming on focus */
}
.field__input:focus, .field textarea:focus, .field select:focus { border-color: var(--accent); }
.field__input::placeholder { color: var(--ink-muted); }
.field__hint { margin: 0; font-size: 0.9rem; color: var(--ink-muted); }
.field__error { margin: 0; font-size: 0.95rem; color: var(--danger); font-weight: 600; }
.field.has-error .field__input, .field.has-error select, .field.has-error textarea { border-color: var(--danger); }
.field textarea { min-height: 120px; resize: vertical; }
select.field__input {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-muted) 50%), linear-gradient(135deg, var(--ink-muted) 50%, transparent 50%);
  background-position: calc(100% - 20px) calc(50% + 2px), calc(100% - 14px) calc(50% + 2px);
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  /* Just enough room for the arrow. A wider gutter truncates option text on a
     375px screen, which is how a price ends up rendering as "— +$". */
  padding-right: 42px;
}
.field-grid { display: grid; gap: var(--space-4); grid-template-columns: 1fr; }
@media (min-width: 640px) { .field-grid--2 { grid-template-columns: 1fr 1fr; } .field-grid--3 { grid-template-columns: 2fr 1fr 1fr; } }

.checkfield { display: flex; gap: var(--space-3); align-items: flex-start; padding: var(--space-4); border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--bg-inset); }
.checkfield input[type='checkbox'] { margin-top: 2px; }
.checkfield label { cursor: pointer; font-size: 1rem; line-height: 1.5; }
.checkfield.has-error { border-color: var(--danger); }
.checkfield--optout { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, var(--bg-inset)); }

/* -------------------------------------------------------- 10. CONTRACT PAGE */
.contract-shell { padding-block: var(--space-6) var(--space-9); }

.steps { position: sticky; top: var(--header-h, 68px); z-index: 20; background: var(--bg); padding-block: var(--space-4); border-bottom: 1px solid var(--line); margin-bottom: var(--space-6); }
.steps__bar { height: 6px; background: var(--bg-raised-2); border-radius: 999px; overflow: hidden; margin-bottom: var(--space-3); }
.steps__fill { height: 100%; background: var(--accent); width: 25%; transition: width 240ms ease; }
.steps__list { display: flex; gap: var(--space-2); list-style: none; margin: 0; padding: 0; justify-content: space-between; }
.steps__item { flex: 1; text-align: center; font-size: 0.8rem; color: var(--ink-muted); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }
.steps__item[aria-current='step'] { color: var(--accent); }
.steps__item.is-done { color: var(--ink-muted); }
.steps__num { display: block; font-family: var(--font-display); font-size: var(--step-2); }

/* Padding tightens on phones so nested panels don't starve the signature
   canvas of width. */
.step-panel { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--bg-raised); padding: var(--space-4); margin-bottom: var(--space-5); }
@media (min-width: 560px) { .step-panel { padding: var(--space-5); } }
.step-panel[hidden] { display: none; }
.step-panel__title { font-size: var(--step-3); margin-bottom: var(--space-2); }
.step-panel__lede { color: var(--ink-muted); margin-bottom: var(--space-5); }
.step-panel__fields { display: grid; gap: var(--space-5); }
/* min-width:0 and wrapping on every flex/grid child. At 200% text (WCAG
   1.4.4) the submit button and its status line set a min-content width wider
   than a 375px viewport, and body{overflow-x:hidden} then made the overflow
   unreachable — the right edge of the signature pad and most of the Clear
   button were simply gone with no way to scroll to them. */
.step-nav { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }
.step-nav > * { min-width: 0; flex: 1 1 12rem; }
.step-nav .btn { flex: 1; }

.fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: var(--space-5); margin: 0; }
.fieldset legend { font-family: var(--font-display); font-size: var(--step-2); text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); padding-inline: var(--space-2); }

.banner { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; justify-content: space-between; padding: var(--space-4); border-radius: var(--radius); margin-bottom: var(--space-5); border: 1px solid var(--line-strong); background: var(--bg-raised-2); }
.banner--draft { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--bg-raised)); }
.banner--offline { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--bg-raised)); }
.banner--error { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, var(--bg-raised)); }
.banner__text { margin: 0; flex: 1 1 240px; font-size: 1rem; }
.banner__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.age-note { padding: var(--space-4); border-radius: var(--radius); border: 1px solid var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--bg-inset)); font-size: 1rem; }

/* Terms box — scrollable, bordered, readable in daylight. */
.terms {
  max-height: 380px; overflow-y: auto; -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--bg-inset); padding: var(--space-5);
  font-size: 1rem; line-height: 1.6;
}
.terms h3 { font-size: var(--step-1); margin: var(--space-5) 0 var(--space-2); letter-spacing: 0.04em; }
.terms h3:first-child { margin-top: 0; }
.terms p { margin-bottom: var(--space-3); color: var(--ink-muted); }
.terms strong { color: var(--ink); }
.terms__meta { color: var(--ink); background: var(--bg-raised-2); padding: var(--space-4); border-radius: var(--radius); margin-bottom: var(--space-5); }
.terms__meta dl { display: grid; grid-template-columns: auto 1fr; gap: var(--space-2) var(--space-4); margin: 0; font-size: 0.95rem; }
.terms__meta dt { color: var(--ink-muted); }
.terms__meta dd { margin: 0; font-weight: 600; }
.terms__declined { color: var(--danger); font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; }

/* Signature */
.sig { display: grid; gap: var(--space-3); }
.sig__canvas-wrap { position: relative; border: 2px dashed var(--line-strong); border-radius: var(--radius); background: #ffffff; overflow: hidden; }
.sig__canvas-wrap.is-disabled { opacity: 0.4; pointer-events: none; }
.sig__canvas-wrap.has-error { border-color: var(--danger); border-style: solid; }
/* touch-action:none is what stops the page scrolling while a finger signs. */
.sig__canvas { display: block; width: 100%; height: 220px; touch-action: none; cursor: crosshair; }
.sig__line { position: absolute; left: 6%; right: 6%; bottom: 46px; height: 1px; background: #c9c9d1; pointer-events: none; }
.sig__hint { position: absolute; left: 0; right: 0; bottom: 16px; text-align: center; color: #8a8a95; font-size: 0.95rem; pointer-events: none; transition: opacity 150ms ease; }
.sig__canvas-wrap.has-ink .sig__hint, .sig__canvas-wrap.has-ink .sig__line { opacity: 0; }
.sig__tools { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.sig__tools .btn { flex: 1 1 8rem; min-width: 0; }
.sig__alt { margin-top: var(--space-4); display: grid; gap: var(--space-2); }
.sig__alt .btn { min-width: 0; }

.consent-block { display: grid; gap: var(--space-4); padding: var(--space-4); border: 2px solid var(--accent); border-radius: var(--radius-lg); background: var(--bg-inset); }
@media (min-width: 560px) { .consent-block { padding: var(--space-5); } }
.contract-id { font-family: var(--font-mono); font-size: 1rem; color: var(--accent); letter-spacing: 0.06em; }

.submit-state { display: grid; gap: var(--space-3); margin-top: var(--space-5); }
.submit-state > * { min-width: 0; overflow-wrap: anywhere; }
.btn { overflow-wrap: anywhere; }
.error-summary { border: 2px solid var(--danger); border-radius: var(--radius); padding: var(--space-4); background: color-mix(in srgb, var(--danger) 10%, var(--bg-raised)); }
.error-summary h3 { color: var(--danger); font-size: var(--step-1); margin-bottom: var(--space-2); }
.error-summary ul { margin: 0; padding-left: var(--space-5); }
.error-summary a { color: var(--ink); }

/* Success screen */
.success { text-align: center; padding: var(--space-7) var(--space-5); border: 2px solid var(--ok); border-radius: var(--radius-lg); background: var(--bg-raised); }
.success__check { width: 64px; height: 64px; margin: 0 auto var(--space-5); color: var(--ok); }

/* ---------------------------------------------------- 11. TIMELINE / FAQ */
.timeline { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-5); counter-reset: step; }
@media (min-width: 900px) { .timeline { grid-template-columns: repeat(5, 1fr); } }
.timeline__item { position: relative; padding: var(--space-5); background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg); counter-increment: step; }
.timeline__item::before {
  content: counter(step); position: absolute; top: calc(-1 * var(--space-4)); left: var(--space-5);
  width: 40px; height: 40px; display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink); border-radius: 50%;
  font-family: var(--font-display); font-weight: 800; font-size: var(--step-2);
}
/* h2, and it has to move with the markup: this is a tag selector, so leaving it
   on h3 would hand the five steps the global h2 size — clamp(2rem, 8vw, 3rem),
   about three times what they should be.
   They are h2 because the page's h1 is "Five steps, start to finish" and these
   ARE those five steps: as h3 they skipped a level, claiming a section between
   the title and "Book" that does not exist. The explicit font-size here is why
   the level can change without the page changing. .timeline is used on this
   page only, so this selector has one caller. */
.timeline__item h2 { margin: var(--space-4) 0 var(--space-2); font-size: var(--step-2); }
.timeline__item p { color: var(--ink-muted); font-size: 1rem; margin: 0; }

.faq { display: grid; gap: var(--space-2); }
.faq details { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-raised); overflow: hidden; }
.faq summary {
  cursor: pointer; padding: var(--space-4) var(--space-5); min-height: var(--tap);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  font-weight: 600; list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; font-family: var(--font-display); font-size: var(--step-3); color: var(--accent); flex: none; line-height: 1; }
.faq details[open] summary::after { content: '−'; }
.faq details[open] summary { border-bottom: 1px solid var(--line); }
.faq__body { padding: var(--space-4) var(--space-5) var(--space-5); color: var(--ink-muted); }
.faq__body p:not(:last-child) { margin-bottom: var(--space-3); }

.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); counter-reset: c; }
.checklist li { position: relative; padding-left: var(--space-7); counter-increment: c; color: var(--ink-muted); }
.checklist li::before {
  content: counter(c); position: absolute; left: 0; top: 0;
  width: 30px; height: 30px; display: grid; place-items: center;
  border: 1px solid var(--accent); color: var(--accent); border-radius: 50%;
  font-family: var(--font-display); font-weight: 700; font-size: 1rem;
}
.checklist strong { color: var(--ink); display: block; }

.delivery-grid { display: grid; gap: var(--space-5); grid-template-columns: 1fr; }
@media (min-width: 760px) { .delivery-grid { grid-template-columns: 1fr 1fr; } }
.delivery-card { padding: var(--space-5); background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg); }
.delivery-card h3 { font-size: var(--step-2); }
.delivery-card p { color: var(--ink-muted); font-size: 1rem; }

/* ------------------------------------------------------ 12. TRUST / PROOF */
/* auto-fit, not repeat(3): there are two real quotes and a fabricated third was
   removed, and a fixed three-column track left the survivors two-thirds of a row
   with a hole beside them. This fits whatever number is genuine. */
.quotes { display: grid; gap: var(--space-5); grid-template-columns: 1fr; }
@media (min-width: 720px) { .quotes { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); } }
.quote-card { padding: var(--space-5); background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg); display: flex; flex-direction: column; gap: var(--space-4); }
.quote-card__stars { color: var(--accent); letter-spacing: 0.2em; font-size: 1.1rem; }
.quote-card blockquote { margin: 0; font-size: var(--step-1); line-height: 1.5; }
.quote-card figcaption { display: flex; align-items: center; gap: var(--space-3); margin-top: auto; color: var(--ink-muted); font-size: 0.95rem; }
.quote-card figcaption img { width: 44px; height: 44px; border-radius: 50%; flex: none; }


/* ---------------------------------------------------------- 13. THANK YOU */
.pay-card { border: 2px solid var(--ok); border-radius: var(--radius-lg); background: var(--bg-raised); padding: var(--space-5); display: grid; gap: var(--space-5); }
.pay-amount { text-align: center; }
.pay-amount__label { text-transform: uppercase; letter-spacing: 0.14em; color: var(--ink-muted); font-size: 0.95rem; margin: 0 0 var(--space-2); }
.pay-amount__value { font-family: var(--font-display); font-size: clamp(3rem, 16vw, 4.5rem); font-weight: 800; line-height: 1; margin: 0; color: var(--ok); }
.pay-note { border: 2px solid var(--accent); border-radius: var(--radius); padding: var(--space-4); background: color-mix(in srgb, var(--accent) 10%, var(--bg-inset)); }
.pay-note code { font-family: var(--font-mono); font-size: 1.15em; background: var(--bg); padding: 2px 8px; border-radius: 4px; color: var(--accent); font-weight: 700; }
.qr { display: grid; justify-items: center; gap: var(--space-3); padding: var(--space-5); background: #ffffff; border-radius: var(--radius); }
.qr canvas { image-rendering: pixelated; width: 200px; height: 200px; }
.qr__caption { color: #3a3a44; font-size: 0.95rem; margin: 0; text-align: center; }

/* ------------------------------------------------------------- 14. FOOTER */
.site-footer { border-top: 1px solid var(--line); background: var(--bg-inset); padding-block: var(--space-7) var(--space-6); margin-top: var(--space-8); }
.site-footer__grid { display: grid; gap: var(--space-6); grid-template-columns: 1fr; }
@media (min-width: 760px) { .site-footer__grid { grid-template-columns: 2fr 1fr 1fr; } }
/* h3, not h4, and the level is the whole point. The heading before the footer
   is an h2 on every page that has these, so an h4 skipped a level — the outline
   a screen-reader user navigates by said there was a missing section between
   the last one on the page and "Contact".
   The SIZE is unchanged: --step-1 was always set here explicitly, overriding
   whatever the global h4 rule said, so moving the element up a level changes
   the document outline and nothing a sighted reader sees. 404.html is not in
   this set — its footer carries no headings at all. */
.site-footer h3 { font-size: var(--step-1); letter-spacing: 0.1em; margin-bottom: var(--space-3); }
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
/* The footer links get the same treatment as the nav. NOT applied to
   .site-footer__legal, which is prose — a paragraph of copyright terms in caps
   is unreadable, and the Admin link inside it is deliberately discreet.
   AND NO FONT-SIZE HERE. Caps run wider than sentence case and the reflex is to
   shrink the type to win the width back; that took these from 17px to 15.2px,
   under the --step-0 floor the palette sets for body copy, in the one typeface
   treatment that is already harder to read. The links wrap instead. Width is
   cheaper than legibility on a page whose whole premise is a phone in sunlight. */
.site-footer a { color: var(--ink-muted); text-decoration: none; text-transform: uppercase; letter-spacing: 0.06em; display: inline-flex; align-items: center; min-height: var(--tap); }
.site-footer__legal a { text-transform: none; letter-spacing: normal; }
/* AN ADDRESS AND A HANDLE ARE LITERALS, not labels, so they keep the case they
   are written in. Uppercasing them changes nothing functional — text-transform
   never touches the href, and the mailto: still works — but "SLUTSKY79@ME.COM"
   reads as shouting, and something a parent might copy off the screen should
   look exactly like the thing they will type back.
   The tracking goes with it: 0.06em is there to open capitals up, and on
   lowercase it only makes the address look spaced out.
   The address is the only contact link left in the footer: the phone number
   came out on 2026-09-04, and it had needed no rule of its own anyway — digits
   and hyphens have no case to change.
   The Instagram handle was in this selector until 2026-08-13, when the footer
   link was removed on Seth's instruction — work is shared through private
   Pixieset galleries and Drive links now, not a public feed. The rule went with
   the link rather than being left behind to match nothing. */
.site-footer a[data-contact="email"] { text-transform: none; letter-spacing: normal; }
.site-footer a:hover { color: var(--accent); text-decoration: underline; }
.site-footer__legal { margin-top: var(--space-6); padding-top: var(--space-5); border-top: 1px solid var(--line); color: var(--ink-muted); font-size: 0.9rem; display: grid; gap: var(--space-2); }

/* -------------------------------------------------- 15. STICKY MOBILE CTA */
.sticky-cta {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: flex; gap: var(--space-3); align-items: center;
  padding: var(--space-3) var(--space-4);
  padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}
.sticky-cta .btn { flex: 1; }
.sticky-cta__contact { flex: none !important; width: var(--tap); padding-inline: 0; }
.sticky-cta__contact svg { width: 22px; height: 22px; }
@media (min-width: 768px) { .sticky-cta { display: none; } }

/* The bar is fixed, so the page has to reserve its height or the last of the
   content sits underneath it forever. 92px is only its height at the default
   text size — at 200% it is nearly three times that, and the reserved 92px
   left 299px of the page permanently unreachable. */
body:has(.sticky-cta) { padding-bottom: calc(var(--cta-h, 92px) + var(--space-3)); }
@media (min-width: 768px) { body:has(.sticky-cta) { padding-bottom: 0; } }

/* A phone in landscape is about 360px tall, and the header plus this bar were
   taking 44% of it. From 620px wide the header carries its own Sign Contract
   button, so on a short-and-wide screen this bar is redundant as well as
   expensive. BOTH conditions are required, so a narrow screen never loses its
   only call to action.

   This block sits AFTER the padding rules above deliberately: it has the same
   specificity as them, so putting it first meant the unconditional rule below
   simply won and the page kept reserving space for a bar that was no longer
   there. */
@media (max-height: 430px) and (min-width: 620px) {
  .sticky-cta { display: none; }
  body:has(.sticky-cta) { padding-bottom: 0; }
}

/* ---------------------------------------------------------- 16. UTILITIES */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
[hidden] { display: none !important; }
.stack { display: grid; gap: var(--space-5); }
.text-center { text-align: center; }
.muted { color: var(--ink-muted); }
.hr { border: 0; border-top: 1px solid var(--line); margin-block: var(--space-6); }
.pill { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-1) var(--space-3); border-radius: 999px; background: var(--bg-raised-2); border: 1px solid var(--line-strong); font-size: 0.9rem; color: var(--ink-muted); }
.legal-note { font-size: 0.9rem; color: var(--ink-muted); border-left: 3px solid var(--line-strong); padding-left: var(--space-4); }

/* Scroll reveal. Guarded behind .js so content is never invisible when
   JavaScript fails to run. */
.js .reveal { opacity: 0; transform: translateY(14px); transition: opacity 500ms ease, transform 500ms ease; }
.js .reveal.is-visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
  .js .reveal { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------- 17. PRINT */
@media print {
  .site-header, .sticky-cta, .site-footer, .step-nav, .btn { display: none !important; }
  body { background: #fff; color: #000; }
  .terms { max-height: none; overflow: visible; border: none; }

  /* The page is designed for a dark background, so every token that reads as
     "muted" or "bright" is near-white. Printed on white with background
     graphics off — the default — the contract's own values (Contract ID,
     parties, event, TOTAL, deposit) came out at 1.10:1: a printed agreement
     with no visible numbers on it. Force ink to black across the terms and
     their metadata. */
  .terms, .terms *,
  .terms__meta, .terms__meta dt, .terms__meta dd,
  .contract-id, .legal-note, .success-panel, .success-panel * {
    color: #000 !important;
    background: #fff !important;
    border-color: #999 !important;
  }
  .terms h2, .terms h3, .terms strong { color: #000 !important; }

  /* The declined state must survive without color. */
  .terms__declined { text-decoration: underline; font-weight: 700; }

  /* Nothing on a printed contract should be clipped to a scroll box. */
  .terms-box, .terms__body { max-height: none !important; overflow: visible !important; }

  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; }
}

/* A list item whose only content is a hidden link must collapse, or the footer
   keeps a blank row where "Find my photos" will eventually go. initGalleryLinks()
   hides the <a>, not the <li>, because it is the <a> that needs the href — so
   the row is removed here rather than by teaching that function about markup. */
.site-footer li:has(> [hidden]) { display: none; }

/* ------------------------------------------------------ 18. CALENDAR (private)
   The booking calendar at /calendar.html. Seth-only, so it optimises for
   scanning a month at a glance and for spotting a clash immediately, rather
   than for the marketing look of the public pages.
   ------------------------------------------------------------------------ */

.cal__private-tag {
  font-family: var(--font-display); letter-spacing: 0.08em; text-transform: uppercase;
  font-size: 0.8rem; color: var(--warn); border: 1px solid var(--line-strong);
  border-radius: 999px; padding: var(--space-1) var(--space-3);
}

.cal__status {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--space-4);
  color: var(--ink-muted); margin-bottom: var(--space-5);
}
.cal__status--warn { border-color: var(--warn); color: var(--ink); }

/* --- conflicts: the reason the page exists, so it sits above the grid --- */
.cal__conflicts {
  border: 1px solid var(--danger); border-radius: var(--radius);
  background: color-mix(in srgb, var(--danger) 8%, var(--bg-raised));
  padding: var(--space-5); margin-bottom: var(--space-6);
}
.cal__conflicts-title { font-size: 1.3rem; margin: 0 0 var(--space-4); color: var(--ink); }
.cal__conflict-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-4); }
.cal__conflict {
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  padding: var(--space-4); background: var(--bg-raised);
}
.cal__conflict--clash { border-left: 4px solid var(--danger); }
.cal__conflict--tight { border-left: 4px solid var(--warn); }
.cal__conflict--unclear { border-left: 4px solid var(--line-strong); }
.cal__conflict-badge {
  display: inline-block; font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.06em; font-size: 0.8rem; color: var(--ink-muted); margin-bottom: var(--space-2);
}
.cal__conflict-reason { margin: 0 0 var(--space-2); color: var(--ink); }
.cal__conflict-when { margin: 0 0 var(--space-3); color: var(--ink-muted); font-size: 0.9rem; }
.cal__conflict-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.cal__ok {
  border: 1px solid var(--ok); border-radius: var(--radius);
  background: color-mix(in srgb, var(--ok) 8%, var(--bg-raised));
  padding: var(--space-4); margin-bottom: var(--space-6); color: var(--ink);
}

/* --- toolbar ----------------------------------------------------------- */
.cal__toolbar {
  display: flex; flex-wrap: wrap; gap: var(--space-4);
  align-items: center; justify-content: space-between; margin-bottom: var(--space-4);
}
.cal__nav { display: flex; align-items: center; gap: var(--space-3); }
.cal__month { font-size: 1.4rem; margin: 0; min-width: 9ch; }
.cal__views { display: flex; gap: var(--space-2); }

/* --- month grid -------------------------------------------------------- */
.cal__grid-wrap { overflow-x: auto; }
.cal__weekdays, .cal__grid {
  display: grid; grid-template-columns: repeat(7, minmax(88px, 1fr)); gap: 2px;
}
.cal__weekdays {
  margin-bottom: 2px; font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-muted); font-size: 0.85rem;
}
.cal__weekdays span { padding: var(--space-2); }
.cal__cell {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius-sm); min-height: 108px; padding: var(--space-2);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.cal__cell--muted { background: transparent; border-color: transparent; min-height: 0; }
.cal__cell.is-today { border-color: var(--accent); }
.cal__cell.has-conflict { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, var(--bg-raised)); }

/* 44px target: this gets tapped on a phone at a field. */
.cal__daynum {
  align-self: flex-start; min-width: 44px; min-height: 44px;
  background: none; border: 0; color: var(--ink-muted);
  font-family: var(--font-display); font-size: 1.1rem; cursor: pointer;
  border-radius: var(--radius-sm); text-align: left; padding: var(--space-2);
}
.cal__daynum:hover { color: var(--ink); background: var(--bg-raised-2); }
.is-today .cal__daynum { color: var(--accent); }

.cal__chips { display: grid; gap: 2px; }
.cal__chip {
  display: grid; gap: 1px; width: 100%; text-align: left; cursor: pointer;
  background: var(--bg-raised-2); border: 1px solid var(--line-strong);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm); padding: var(--space-2);
  color: var(--ink); font-size: 0.8rem; line-height: 1.25;
}
.cal__chip:hover { background: var(--bg-inset); }
.cal__chip--game { border-left-color: var(--ok); }
.cal__chip--hold { border-left-color: var(--ink-dim); }
.cal__chip.is-conflicted { border-left-color: var(--danger); }
.cal__chip.is-canceled { opacity: 0.55; text-decoration: line-through; }
/* A BOOKING WAITING ON SETH. A signed contract lands tentative, so this is the
   state that means "someone has paid a deposit and is waiting to hear back" — it
   has to be findable at a glance in a month grid, not only once the card is open.
   A dashed amber edge reads as provisional at chip size where a color alone does
   not, and it stays distinct from .is-conflicted (solid red) and .is-canceled
   (faded and struck through).
   NOT opacity: fading the one booking that needs action is backwards.
   The ✎ is positioned ABSOLUTELY rather than added with a ::before flow item,
   for two reasons found by measuring: .cal__chip is display:grid, so a flow
   ::before becomes a grid item and pushes the time and title into new rows; and
   it was first written as ::after on .cal__chip-title, which is
   overflow:hidden + text-overflow:ellipsis — on a title longer than the cell it
   was clipped and never drawn at all (352px of text in a 119px box). The padding
   reserves its corner so a long title truncates before reaching it. */
.cal__chip.is-tentative {
  border-left-style: dashed;
  border-left-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  position: relative;
  padding-right: 1.3em;
}
.cal__chip.is-tentative::before {
  content: '✎';
  position: absolute; top: var(--space-2); right: var(--space-2);
  color: var(--warn); font-size: 0.85em; line-height: 1;
}
.cal__row.is-tentative { border-left: 3px dashed var(--warn); }

/* The "tell the parent" panel, shown after a booking is confirmed. Amber like
   the tentative chip it just stopped being, so the two read as the two halves of
   one job: he has decided, and now the Client has to be told. */
.cal__confirm {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--warn);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--warn) 8%, transparent);
  display: grid; gap: var(--space-3);
}
.cal__confirm-title { font-size: var(--step-1); margin: 0; }
.cal__confirm-why { margin: 0; color: var(--ink-muted); font-size: 0.95rem; }
/* Readonly, not disabled: a disabled textarea is unselectable, and selecting the
   text is the fallback when the clipboard API is unavailable. */
.cal__confirm-text { width: 100%; resize: vertical; font-size: 0.95rem; line-height: 1.45; }
.cal__confirm-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.cal__confirm-note { margin: 0; color: var(--ink-muted); font-size: 0.9rem; }
/* A CLASH OUTRANKS "NEEDS CONFIRMING". .cal__chip.is-conflicted is declared above
   this block at equal specificity (0,2,0), so the later amber rule was winning
   border-left-color and a booking that was BOTH tentative and double-booked lost
   its red edge — hiding the more urgent of the two facts. Doubling the class
   raises specificity without moving either rule. The dash is kept, so such a chip
   reads as "provisional AND clashing", which is exactly what it is. */
.cal__chip.is-conflicted.is-conflicted { border-left-color: var(--danger); }
.cal__chip-time { color: var(--ink-muted); font-size: 0.75rem; }
.cal__chip-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- list view --------------------------------------------------------- */
.cal__list-title { font-size: 1.2rem; margin: var(--space-5) 0 var(--space-3); }
.cal__rows { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.cal__row {
  display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: var(--space-4);
}
.cal__row.is-conflicted { border-color: var(--danger); }
.cal__row.is-canceled { opacity: 0.6; }
/* The row body as one control. Transparent and unpadded so the row looks
   exactly as it did before it became clickable — the affordance is the cursor
   and the hover, not a button that competes with Edit beside it. */
.cal__row-open {
  display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center;
  flex: 1 1 20rem; min-width: 0; text-align: left; cursor: pointer;
  background: none; border: 0; padding: 0; margin: 0;
  border-radius: var(--radius-sm);
}
.cal__row-open:hover .cal__row-what > strong { color: var(--accent); }
.cal__row-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.cal__row-when { display: grid; min-width: 12rem; }
.cal__row-when span { color: var(--ink-muted); font-size: 0.9rem; }
.cal__row-what { display: grid; flex: 1 1 14rem; }
.cal__row-what span { color: var(--ink-muted); font-size: 0.9rem; }
/* Who the shoot covers. Brighter than the venue and the parent's name beside
   it: on a sideline this is the line that stops him photographing one athlete
   when the contract was signed for three. */
.cal__row-who { color: var(--ink) !important; }
.cal__tag {
  justify-self: start; text-transform: uppercase; font-size: 0.75rem !important;
  letter-spacing: 0.06em; color: var(--warn) !important;
}
/* The family declined public use of these images. Loud on purpose and using
   --danger as a FILL, so it takes --danger-ink: this is the one line on the
   page that must not be skimmed past, and it is read outdoors. */
.cal__norelease {
  justify-self: start; margin-top: var(--space-1);
  padding: 2px var(--space-2); border-radius: var(--radius);
  background: var(--danger); color: var(--danger-ink) !important;
  font-size: 0.8rem !important; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.cal__chip-norelease { color: var(--danger); }

.cal__empty { color: var(--ink-muted); }

/* --- editor + saving --------------------------------------------------- */
.cal__editor, .cal__save {
  margin-top: var(--space-7); background: var(--bg-raised);
  border: 1px solid var(--line); border-radius: var(--radius); padding: var(--space-5);
}
.cal__editor-title { font-size: 1.3rem; margin: 0 0 var(--space-4); }
.cal__save-lede { color: var(--ink-muted); margin-top: 0; }
.cal__form-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-4); }
.cal__delete { color: var(--danger); border-color: var(--danger); }
/* A destructive button waiting for its second tap. Filled, not outlined, so it
   cannot be mistaken for its resting state at arm's length in sunlight.
   background-COLOR rather than the shorthand, so it tints .btn's background
   without also resetting everything else the shorthand carries.
   --danger is a FILL here, so it takes --danger-ink: 7.1:1. --ink would be
   1.6:1 on it, which is a label you cannot read. */
.btn.is-armed {
  background-color: var(--danger);
  border-color: var(--danger);
  color: var(--danger-ink);
  font-weight: 700;
}

.cal__warn {
  margin-top: var(--space-4); padding: var(--space-4);
  border: 1px solid var(--warn); border-left-width: 4px;
  border-radius: var(--radius-sm); background: var(--bg-raised-2); color: var(--ink);
}
.cal__warn p { margin: 0 0 var(--space-2); }
.cal__warn p:last-child { margin-bottom: 0; }
.cal__warn--clash { border-color: var(--danger); }
.cal__warn--unclear { border-color: var(--line-strong); }

.cal__unsaved {
  border: 1px solid var(--warn); border-radius: var(--radius-sm);
  padding: var(--space-4); background: var(--bg-raised-2); color: var(--ink);
}
.cal__details { margin-top: var(--space-5); border-top: 1px solid var(--line); padding-top: var(--space-4); }
.cal__details summary {
  cursor: pointer; font-family: var(--font-display); font-size: 1.05rem;
  display: flex; align-items: center; min-height: var(--tap);
}
.cal__steps { color: var(--ink-muted); padding-left: var(--space-5); }
.cal__steps strong { color: var(--ink); }

@media (max-width: 640px) {
  /* A 7-column month is unreadable on a phone, so the list becomes the
     default there and the grid scrolls sideways if it is opened. */
  .cal__weekdays, .cal__grid { grid-template-columns: repeat(7, minmax(76px, 1fr)); }
  .cal__cell { min-height: 84px; }
  .cal__row-when { min-width: 100%; }
}

/* ------------------------------------------------------ 19. WORK / GALLERY
   /work.html. A grid of fixed-ratio tiles rather than a masonry column flow:
   the crop is decided here, the full frame is what the lightbox opens, and a
   fixed ratio means the page never reflows under a thumb as photos arrive.
   ------------------------------------------------------------------------ */

.work-filters { margin-bottom: var(--space-5); }
.chip--filter { color: var(--ink); font-family: inherit; }
.chip--filter:hover { border-color: var(--accent); }
.chip--filter[aria-pressed='true'] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-raised-2)); color: var(--ink); }

.work-grid { display: grid; gap: var(--space-3); grid-template-columns: 1fr; }
@media (min-width: 560px) { .work-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .work-grid { grid-template-columns: repeat(3, 1fr); gap: var(--space-4); } }

.work-tile {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg);
}
.work-tile__img {
  width: 100%; height: 100%; object-fit: cover; transition: transform 300ms ease;
  /* Matches the lightbox: no drag-to-desktop, no iOS long-press save sheet. */
  -webkit-user-drag: none;
  user-select: none; -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.work-tile:hover { border-color: var(--accent); }
.work-tile:hover .work-tile__img { transform: scale(1.03); }

/* A featured frame earns two columns — but only once there are two to take.
   Two ROWS as well, and no aspect-ratio of its own: the row height is already
   set by the ordinary 4:3 tiles, so spanning two of them lands a clean square
   block instead of a tall tile with dead space beside it. */
@media (min-width: 560px) {
  .work-tile--featured { grid-column: span 2; grid-row: span 2; aspect-ratio: auto; }
}

/* The sport label sits on the photo, so it carries its own scrim. Nothing here
   is the only copy of anything: the same sport is in the alt text. */
.work-tile__sport {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--space-6) var(--space-4) var(--space-3);
  font-family: var(--font-display); font-size: 1rem; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: #ffffff;
  background: linear-gradient(to top, rgb(0 0 0 / 0.78), transparent);
  pointer-events: none;
}

.work-empty {
  border: 1px dashed var(--line-strong); border-radius: var(--radius-lg);
  background: var(--bg-inset); padding: var(--space-6) var(--space-5);
  display: grid; gap: var(--space-5); justify-items: start;
}
.work-empty__text { margin: 0; color: var(--ink-muted); font-size: var(--step-1); max-width: 52ch; }
.work-empty__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* --- lightbox -----------------------------------------------------------
   A modal <dialog> leaves the page behind it scrollable, which on iOS reads as
   the photo sliding around under your finger. The lock is declarative on the
   [open] attribute rather than a class added and removed by JavaScript: a
   scroll lock released by a handler is a page that can never be scrolled again
   if that handler ever fails to run, and where :has() is unsupported the worst
   case is merely a page that scrolls behind the photo. */
html:has(.work-box[open]) { overflow: hidden; }

.work-box {
  /* The UA gives a dialog a fit-content box with its own margins and border. */
  width: 100%; height: 100%; max-width: 100%; max-height: 100%;
  margin: 0; padding: 0; border: 0; overflow: hidden;
  background: transparent; color: var(--ink);
}
.work-box::backdrop { background: rgb(0 0 0 / 0.92); }

.work-box__frame {
  position: absolute; inset: 0; display: grid; place-items: center;
  padding: 64px var(--space-4) 96px;
  /* minmax(0, 1fr), not the implicit `auto`. An auto-sized grid row grows to
     its content and has an INDEFINITE height, so the image's `max-height: 100%`
     had nothing to resolve against and was ignored entirely. A 1536 × 2304
     portrait frame rendered 1628px tall inside a 1000px window: dead space at
     the top, and the bottom of the photo below the fold with no way to scroll
     to it. It looked fine for two years because every test image was landscape,
     where max-width binds first and max-height never has to do anything. */
  grid-template-rows: minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
}
.work-box__img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  min-width: 0; min-height: 0;
  object-fit: contain; border-radius: var(--radius);

  /* Friction against casually walking off with the file. This is NOT
     protection — see the note in work.js — it just means the obvious gestures
     do nothing: no drag-to-desktop, no long-press "Save Image" sheet on iOS,
     no accidental text selection while swiping between photos. */
  -webkit-user-drag: none;
  user-select: none; -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.work-box__bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  padding-bottom: max(var(--space-4), env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border-top: 1px solid var(--line);
}
.work-box__caption { margin: 0; font-size: 1rem; color: var(--ink); flex: 1 1 16rem; min-width: 0; }
.work-box__count { margin: 0; font-family: var(--font-mono); font-size: 0.9rem; color: var(--ink-muted); flex: none; }

.work-box__close, .work-box__step {
  position: absolute; display: grid; place-items: center;
  width: var(--tap); height: var(--tap);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  border: 1px solid var(--line-strong); border-radius: 50%;
  color: var(--ink); cursor: pointer;
}
.work-box__close:hover, .work-box__step:hover { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.work-box__close svg, .work-box__step svg { width: 24px; height: 24px; }
.work-box__close { top: var(--space-4); right: var(--space-4); }
.work-box__step { top: 50%; transform: translateY(-50%); }
.work-box__step--prev { left: var(--space-3); }
.work-box__step--next { right: var(--space-3); }
@media (min-width: 720px) {
  .work-box__step--prev { left: var(--space-5); }
  .work-box__step--next { right: var(--space-5); }
}

/* --------------------------------------------------- 20. ADMIN HUB (private)
   /admin/tools.html — the page behind the footer's Admin link. Seth-only, and
   he reaches it from a phone at a field as often as from a laptop.
   ------------------------------------------------------------------------ */

.tool-grid { display: grid; gap: var(--space-4); grid-template-columns: 1fr; }
@media (min-width: 720px) { .tool-grid { grid-template-columns: 1fr 1fr; } }

.tool-card {
  display: flex; gap: var(--space-4); align-items: flex-start;
  padding: var(--space-5);
  background: var(--bg-raised); border: 1px solid var(--line);
  border-left: 4px solid var(--accent); border-radius: var(--radius-lg);
  color: var(--ink); text-decoration: none;
  transition: border-color 120ms ease, background-color 120ms ease;
}
.tool-card:hover { background: var(--bg-raised-2); border-color: var(--accent); color: var(--ink); }
.tool-card__icon { flex: none; width: 28px; height: 28px; color: var(--accent); margin-top: 2px; }
.tool-card h2 { font-size: var(--step-2); margin: 0 0 var(--space-2); }
.tool-card p { color: var(--ink-muted); font-size: 1rem; margin: 0; }
.tool-card__note { margin-top: var(--space-2) !important; color: var(--ink-muted); font-size: 0.9rem !important; font-style: italic; }

/* The one link on the public site that leads somewhere a parent cannot go.
   Deliberately quiet: it is a door for one person, not a call to action. */
.site-footer__admin {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: var(--tap);
  color: var(--ink-dim); text-decoration: none; font-size: 0.85rem;
}
.site-footer__admin:hover { color: var(--accent); text-decoration: underline; }
.site-footer__admin svg { width: 14px; height: 14px; }

/* --- the public season list (/season.html) -------------------------------- */
.season { list-style: none; margin: var(--space-6) 0 0; padding: 0; display: grid; gap: var(--space-3); }
.season__game {
  display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--bg-raised); padding: var(--space-4);
}
.season__when { display: grid; min-width: 11rem; }
.season__when span { color: var(--ink-muted); font-size: 0.95rem; }
.season__what { display: grid; flex: 1 1 14rem; }
.season__what span { color: var(--ink-muted); font-size: 0.95rem; }
/* --ok is a FILL, so it takes --ok-ink. This is the line that says the shoot
   costs no travel, which is the whole argument of the page. */
.season__tag {
  justify-self: start; margin-top: var(--space-1);
  padding: 2px var(--space-2); border-radius: var(--radius);
  background: var(--ok); color: var(--ok-ink) !important;
  font-size: 0.78rem !important; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.season__empty { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--bg-raised); padding: var(--space-5); }

/* ------------------------------------- 20. BOOKING QUICK-REFERENCE CARD -- */
/* Read-only detail for one booking, opened by tapping it in the grid or the
   list. Same <dialog> shape as the photo lightbox, and the same scroll lock:
   a CSS :has() rule rather than a class a handler adds, so a page can never
   be left unscrollable by a handler that failed to run. */
html:has(.bk-card[open]) { overflow: hidden; }

.bk-card {
  border: 0; padding: 0; background: none; max-width: none; max-height: none;
  width: 100%; height: 100%; margin: 0;
  display: grid; place-items: center;
  /* NOT decoration. A <dialog> gets `color: CanvasText` from the UA sheet,
     which is BLACK, and everything inside that does not set its own color
     inherits it — the title rendered at about 1.05:1 on the panel, i.e. not
     visible at all. Same trap as a native <button> inheriting buttontext. */
  color: var(--ink);
}
.bk-card::backdrop { background: rgb(0 0 0 / 0.75); }

.bk-card__inner {
  width: min(34rem, calc(100vw - 2 * var(--space-4)));
  max-height: calc(100dvh - 2 * var(--space-5));
  overflow-y: auto;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: var(--space-5);
  display: grid; gap: var(--space-4);
}

.bk-card__head { display: flex; gap: var(--space-3); align-items: flex-start; justify-content: space-between; }
.bk-card__eyebrow {
  margin: 0 0 var(--space-1); font-family: var(--font-mono); font-size: 0.85rem;
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent);
}
.bk-card__title { margin: 0; font-size: var(--step-3); overflow-wrap: anywhere; }

.bk-card__close {
  flex: none; width: 40px; height: 40px; display: grid; place-items: center;
  background: none; border: 1px solid var(--line-control); border-radius: var(--radius-sm);
  cursor: pointer;
}
.bk-card__close:hover { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.bk-card__close svg { width: 20px; height: 20px; }

/* The body had NO rule, and everything inside it carries margin: 0 — so on the
   booking that matters most, the one that both declined the model release and
   clashes with something, the red box sat flush against the yellow box sat
   flush against the details. Three warnings reading as one block is how you
   miss the second one. */
.bk-card__body { display: grid; gap: var(--space-4); }
.bk-card__rows { margin: 0; display: grid; gap: var(--space-3); }
.bk-card__row { display: grid; gap: 2px; }
.bk-card__row dt {
  font-family: var(--font-mono); font-size: 0.78rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-muted);
}
.bk-card__row dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
/* Who the shoot covers, at the top and bigger than everything under it. This
   is the line he opened the card to read. */
.bk-card__row--who dd { font-size: var(--step-1); font-family: var(--font-display); }

.bk-card__alert {
  margin: 0; padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  border: 1px solid var(--danger); background: color-mix(in srgb, var(--danger) 15%, var(--bg-raised));
  color: var(--ink); font-size: 1rem;
}
.bk-card__warn {
  margin: 0; padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  border: 1px solid var(--warn); background: color-mix(in srgb, var(--warn) 15%, var(--bg-raised));
  color: var(--ink); font-size: 1rem;
}
.bk-card__muted { margin: 0; color: var(--ink-muted); font-size: 0.95rem; }

/* Stuck to the bottom of the scrolling panel. On a phone a contract booking is
   taller than the screen, so Edit and Close sat below the fold behind a scroll
   nobody looks for — reachable, but not while standing on a sideline holding a
   camera. The negative margin and padding let the panel's own background run
   under the buttons as the content scrolls past. */
.bk-card__actions {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  position: sticky; bottom: calc(-1 * var(--space-5));
  margin: 0 calc(-1 * var(--space-5)) calc(-1 * var(--space-5));
  padding: var(--space-4) var(--space-5) var(--space-5);
  background: var(--bg-raised);
  border-top: 1px solid var(--line);
}
/* 8rem, so two buttons share a row at 375px instead of each taking one.
   Measured on the real card: at 10rem a four-button bar was 282px tall and
   sticky — over a third of the screen, permanently covering the booking it
   belongs to. At 8rem with one-word labels it is 156px and nothing wraps.
   Anything between 6.5rem and 8rem gives the same two rows; 8rem is the top of
   that range, so the buttons stay as wide as they can while still pairing. */
.bk-card__actions .btn { flex: 1 1 8rem; }
/* CANCEL READS AS CANCEL. It sits last in the row, after Edit and Close, so a
   thumb reaching for Confirm cannot land on it — and it still takes two taps
   (armDestructive) before anything happens. The color is on the border and
   the text rather than a filled background: a solid red button is the loudest
   thing on the card, and this is a rarely-correct action, not the main one.
   --danger is the same token the "do not publish" alert above it uses, so the
   card has one vocabulary for "careful" rather than two. */
.bk-card__danger { border-color: var(--danger); color: var(--danger); }
.bk-card__danger:hover,
.bk-card__danger:focus-visible {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 15%, transparent);
  color: var(--danger);
}
