/* roadwild.org — layout, type and components.
 *
 * THE ONE RULE: there is no hex and no literal hsl() in this file. Every colour
 * resolves to a token in brand.css, which scripts/sync-brand.mjs regenerates
 * from the CSS the app actually shipped. A hardcoded colour is invisible to the
 * drift check, which makes it a colour that can silently stop matching the app.
 * The only exception in this repo is site/favicon.svg, noted in that file.
 *
 * HOW THE PALETTE IS BUILT
 * The app ships HSL triplets ("153 40% 30%"), so a token is used as
 * hsl(var(--primary)) and tints come free as hsl(var(--primary) / 0.12).
 *
 * The design wants shades and tints the app does not define -- a deeper sage, a
 * page-tinted wash behind a card. Those are DERIVED here with color-mix rather
 * than pasted in as new hex values, because a derived colour follows the app
 * and a pasted one does not. Every mix pulls toward --foreground or
 * --background rather than black or white, so the whole system inverts
 * correctly in dark mode instead of getting muddier.
 */

@import url('./brand.css');

:root {
  /* Semantic aliases. These name the ROLE; brand.css owns the value. */
  --page: hsl(var(--background));
  --page-deep: hsl(var(--muted));
  --surface: hsl(var(--card));
  --ink: hsl(var(--foreground));
  --ink-soft: hsl(var(--muted-foreground));
  --line: hsl(var(--border));

  --sage: hsl(var(--primary));
  --sage-deep: color-mix(in srgb, hsl(var(--primary)) 80%, hsl(var(--foreground)));
  --sage-tint: color-mix(in srgb, hsl(var(--primary)) 12%, hsl(var(--background)));
  --sage-edge: hsl(var(--primary) / 0.22);

  --ember: hsl(var(--cta));
  --ember-deep: color-mix(in srgb, hsl(var(--cta)) 62%, hsl(var(--foreground)));
  --ember-tint: color-mix(in srgb, hsl(var(--cta)) 13%, hsl(var(--background)));
  --ember-edge: hsl(var(--cta) / 0.28);

  --sky: hsl(var(--accent));
  --sky-deep: color-mix(in srgb, hsl(var(--accent)) 62%, hsl(var(--foreground)));
  --sky-tint: color-mix(in srgb, hsl(var(--accent)) 14%, hsl(var(--background)));
  --sky-edge: hsl(var(--accent) / 0.3);

  --bark: hsl(var(--secondary));

  --wrap: 1120px;
  --r: 14px;
  --r-lg: 22px;
  --shadow: 0 1px 2px hsl(var(--foreground) / 0.05), 0 8px 24px -12px hsl(var(--foreground) / 0.18);
  --shadow-lg: 0 2px 4px hsl(var(--foreground) / 0.05), 0 24px 48px -20px hsl(var(--foreground) / 0.28);
}

/* -------------------------------------------------------------- base ----- */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

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

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  letter-spacing: -.02em;
  line-height: 1.15;
  margin: 0;
  text-wrap: balance;
}
h1 { font-size: clamp(2.05rem, 4.9vw, 3.35rem); font-weight: 700; }
h2 { font-size: clamp(1.75rem, 3.4vw, 2.4rem); font-weight: 700; }
h3 { font-size: 1.2rem; font-weight: 600; letter-spacing: -.01em; }
h4 { font-size: 1rem; font-weight: 600; }

p { margin: 0; text-wrap: pretty; }
ul { margin: 0; padding: 0; list-style: none; }

a { color: var(--sage); text-decoration: none; }
a:hover { text-decoration: underline; }

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

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

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--sage); color: hsl(var(--primary-foreground));
  padding: 12px 16px; border-radius: 0 0 var(--r) 0;
}
.skip-link:focus { left: 0; }

/* ---------------------------------------------------------- primitives --- */

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
.narrow { max-width: 760px; margin: 0 auto; padding: 0 24px; }

section { padding: clamp(56px, 7vw, 92px) 0; }
.section-alt { background: var(--page-deep); }
.section-head { max-width: 70ch; margin-bottom: 44px; }
.section-head.center { margin-inline: auto; text-align: center; }
.section-head .lede { margin-top: 14px; }
.center { text-align: center; }
.center .lede { margin-inline: auto; }

.eyebrow {
  font-size: .78rem; font-weight: 700; letter-spacing: .13em;
  text-transform: uppercase; color: var(--sage);
}
.lede { font-size: clamp(1.05rem, 1.8vw, 1.22rem); color: var(--ink-soft); max-width: 62ch; }
.muted { color: var(--ink-soft); }
.small { font-size: .88rem; }

/* ------------------------------------------------------------- buttons --- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  font-family: var(--font-heading); font-weight: 600; font-size: 1rem;
  padding: 13px 24px; border-radius: 999px; border: 1.5px solid transparent;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: transform .15s ease, box-shadow .15s ease, background .15s ease;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn[disabled] { opacity: .6; cursor: not-allowed; transform: none; }

.btn-cta { background: var(--ember); color: hsl(var(--cta-foreground)); box-shadow: var(--shadow); }
.btn-cta:hover { background: var(--ember-deep); color: hsl(var(--cta-foreground)); }

.btn-sage { background: var(--sage); color: hsl(var(--primary-foreground)); }
.btn-sage:hover { background: var(--sage-deep); color: hsl(var(--primary-foreground)); }

.btn-ghost { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { background: var(--page-deep); color: var(--ink); }

.btn-sm { padding: 9px 18px; font-size: .93rem; }

/* -------------------------------------------------------------- header --- */

.site-header {
  position: sticky; top: 0; z-index: 30;
  background: color-mix(in srgb, hsl(var(--background)) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.site-header .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding-block: 14px;
}
.wordmark {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-display); font-weight: 700; font-size: 1.1rem;
  color: var(--ink); letter-spacing: -.01em;
}
.wordmark:hover { text-decoration: none; }
.wordmark .mark {
  width: 32px; height: 32px; border-radius: 9px; flex: none;
  background: var(--sage); color: hsl(var(--primary-foreground));
  display: grid; place-items: center;
}
.nav { display: flex; align-items: center; gap: 24px; font-size: .95rem; }
.nav a:not(.btn) { color: var(--ink-soft); font-weight: 500; }
.nav a:not(.btn):hover { color: var(--ink); text-decoration: none; }
.nav a[aria-current='page'] { color: var(--ink); }
@media (max-width: 720px) {
  .nav .nav-hide { display: none; }
}

/* ---------------------------------------------------------------- hero --- */

.hero { padding: clamp(48px, 6vw, 84px) 0 clamp(56px, 7vw, 88px); }
.hero-grid {
  display: grid; gap: clamp(32px, 5vw, 56px); align-items: center;
  grid-template-columns: 1.05fr .95fr;
}
@media (max-width: 900px) { .hero-grid { grid-template-columns: 1fr; } }

.badge {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--ember-tint); color: var(--ember-deep);
  border: 1px solid var(--ember-edge); border-radius: 999px;
  padding: 6px 14px; font-size: .82rem; font-weight: 600; margin-bottom: 22px;
}
.badge .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ember); flex: none; }

.hero h1 { margin-bottom: 18px; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
.hero-note { font-size: .88rem; color: var(--ink-soft); margin-top: 16px; }

/* --------------------------------------------------------------- cards --- */

.grid { display: grid; gap: 22px; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 28px; box-shadow: var(--shadow);
}
.card h3 { margin-bottom: 9px; }
.card p { color: var(--ink-soft); font-size: .97rem; }

.icon-badge {
  width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center;
  margin-bottom: 18px; background: var(--sage-tint); color: var(--sage);
}
.icon-badge.ember { background: var(--ember-tint); color: var(--ember-deep); }
.icon-badge.sky { background: var(--sky-tint); color: var(--sky-deep); }

/* ---------------------------------------------------------- step / list -- */

.steps { display: grid; gap: 28px; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.step-num {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  background: var(--sage); color: hsl(var(--primary-foreground));
  display: grid; place-items: center;
  font-family: var(--font-heading); font-weight: 700; font-size: .88rem;
  margin-bottom: 12px;
}
.step h3 { margin-bottom: 7px; }
.step p { color: var(--ink-soft); font-size: .95rem; }

.ticks { display: grid; gap: 12px; }
.ticks li { display: grid; grid-template-columns: 20px 1fr; gap: 11px; align-items: start; }
.ticks svg { margin-top: 5px; color: var(--sage); }
.ticks .pending { color: var(--ink-soft); }
.ticks li.pending svg { color: var(--ink-soft); }

.pill {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 999px;
  padding: 7px 15px; font-size: .9rem; font-weight: 500; color: var(--ink);
}
.pill.sage { background: var(--sage-tint); border-color: var(--sage-edge); color: var(--sage-deep); }
.pill.sky { background: var(--sky-tint); border-color: var(--sky-edge); color: var(--sky-deep); }
.pill-row { display: flex; flex-wrap: wrap; gap: 10px; }

/* NOT DECORATION. Offline maps, meetups and the AI activity finder do not
   exist, and the terms of service say so in writing. Anything wearing this tag
   is unbuilt, and removing the tag without shipping the feature makes a
   published legal document false. */
.tag-dev {
  display: inline-block; font-family: var(--font-heading);
  font-size: .66rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--bark); background: hsl(var(--secondary) / 0.12);
  border-radius: 999px; padding: 2px 8px; margin-left: 7px;
  white-space: nowrap; vertical-align: .06em;
}

/* ------------------------------------------------------------- pricing --- */

.plan { display: flex; flex-direction: column; }
.plan-featured { border-color: var(--sage); box-shadow: var(--shadow-lg), 0 0 0 1px var(--sage); }
.plan-tag {
  align-self: flex-start; margin-bottom: 14px;
  background: var(--sage-tint); color: var(--sage-deep); border: 1px solid var(--sage-edge);
  border-radius: 999px; padding: 4px 12px; font-size: .74rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
}
.plan-price { font-family: var(--font-display); font-size: 2.1rem; font-weight: 700; line-height: 1.1; margin: 6px 0 20px; }
.plan-price span { font-size: .92rem; font-weight: 400; color: var(--ink-soft); }
.plan .ticks { margin-bottom: 26px; font-size: .96rem; }
.plan-foot { margin-top: auto; font-size: .86rem; color: var(--ink-soft); }

/* ---------------------------------------------------------------- faq ---- */

.faq { display: grid; gap: 14px; max-width: 780px; margin-inline: auto; }
.faq details {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); padding: 20px 24px;
}
.faq summary {
  font-family: var(--font-heading); font-weight: 600; font-size: 1.03rem;
  cursor: pointer; list-style: none; display: flex; justify-content: space-between; gap: 16px;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; color: var(--sage); font-size: 1.3rem; line-height: 1; flex: none; }
.faq details[open] summary::after { content: '\2212'; }
.faq details p { margin-top: 12px; color: var(--ink-soft); font-size: .97rem; }

/* ----------------------------------------------------------------- cta --- */

.cta-band {
  background: var(--sage); color: hsl(var(--primary-foreground));
  border-radius: var(--r-lg); padding: clamp(32px, 5vw, 56px); text-align: center;
}
.cta-band h2 { margin-bottom: 14px; }
.cta-band p { color: hsl(var(--primary-foreground) / .82); max-width: 54ch; margin-inline: auto; }
.cta-band .btn { margin-top: 26px; }

/* ---------------------------------------------------------------- form --- */

.signup {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: clamp(24px, 4vw, 36px); box-shadow: var(--shadow);
}
.field { display: grid; gap: 6px; margin-bottom: 18px; }
.field label { font-family: var(--font-heading); font-weight: 600; font-size: .95rem; }
.field .hint { font-size: .82rem; color: var(--ink-soft); }
.field-row { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

input[type='email'], input[type='text'], select, textarea {
  font: inherit; font-size: 1rem; width: 100%;
  color: var(--ink); background: var(--page);
  border: 1px solid hsl(var(--input)); border-radius: 11px; padding: 12px 14px;
}
textarea { resize: vertical; min-height: 96px; }
input:focus, select:focus, textarea:focus {
  border-color: var(--sage); outline: 2px solid hsl(var(--ring) / .35); outline-offset: 0;
}

/* The honeypot: out of the layout but still focusable to a form-filling bot.
   Not display:none -- some fillers skip those. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* A FAILED SUBMIT MUST LOOK LIKE A FAILURE. Never a quiet no-op and never a
   success message for a request that did not land: someone who believes they
   are on the list and is not will simply never hear from us again. */
.status { margin-top: 18px; padding: 14px 16px; border-radius: 11px; font-size: .95rem; border: 1px solid transparent; }
.status[hidden] { display: none; }
.status-ok { background: var(--sage-tint); border-color: var(--sage-edge); color: var(--ink); }
.status-err { background: hsl(var(--destructive) / .1); border-color: hsl(var(--destructive) / .4); color: var(--ink); }
.form-note { font-size: .86rem; color: var(--ink-soft); margin-top: 16px; }

/* -------------------------------------------------------------- footer --- */

.site-footer { border-top: 1px solid var(--line); background: var(--page-deep); padding: 44px 0 36px; }
.footer-grid { display: flex; flex-wrap: wrap; gap: 24px 48px; justify-content: space-between; align-items: flex-start; }
.footer-links { display: flex; flex-wrap: wrap; gap: 18px; font-size: .93rem; }
.site-footer a { color: var(--ink-soft); }
.site-footer a:hover { color: var(--ink); }
.footer-note { font-size: .86rem; color: var(--ink-soft); margin-top: 20px; max-width: 60ch; }

/* --------------------------------------------------------------- legal --- */

.legal h2 { margin-top: 40px; font-size: 1.3rem; }
.legal h2:first-of-type { margin-top: 28px; }
.legal p, .legal ul { margin-top: 14px; color: var(--ink-soft); }
.legal ul { padding-left: 22px; list-style: disc; }
.legal li { margin-bottom: 7px; }
.legal strong { color: var(--ink); }
.legal .updated { font-size: .9rem; }

/* --------------------------------------------------------- illustration -- */
/* The hero art is DRAWN, not photographed -- no stock sunset, no licence to
   buy, ~3kB. It is also fully tokenised: the sky, the ridgelines and the van
   are all mixes of the app's own accent, primary, secondary and cta, so the
   picture follows the brand instead of pinning it. Add a real photograph and
   an og:image together, or neither. */

.scene-sky      { fill: color-mix(in srgb, hsl(var(--accent)) 55%, hsl(var(--foreground))); }
.scene-sky-low  { fill: color-mix(in srgb, hsl(var(--accent)) 72%, hsl(var(--background))); }
.scene-sun      { fill: color-mix(in srgb, hsl(var(--cta)) 76%, hsl(var(--background))); }
.scene-star     { fill: hsl(var(--background) / .85); }
.scene-ridge-far  { fill: color-mix(in srgb, hsl(var(--accent)) 60%, hsl(var(--foreground))); }
.scene-ridge-mid  { fill: color-mix(in srgb, hsl(var(--primary)) 62%, hsl(var(--foreground))); }
.scene-ridge-near { fill: hsl(var(--primary)); }
.scene-ground   { fill: color-mix(in srgb, hsl(var(--primary)) 78%, hsl(var(--foreground))); }
.scene-road     { fill: color-mix(in srgb, hsl(var(--background)) 82%, hsl(var(--secondary))); }
.scene-tree     { fill: color-mix(in srgb, hsl(var(--primary)) 55%, hsl(var(--foreground))); }
.scene-van      { fill: hsl(var(--cta)); }
.scene-van-dark { fill: color-mix(in srgb, hsl(var(--cta)) 55%, hsl(var(--foreground))); }
.scene-glass    { fill: color-mix(in srgb, hsl(var(--accent)) 70%, hsl(var(--background))); }
.scene-pin-sleep{ fill: hsl(var(--primary)); }
.scene-pin-act  { fill: hsl(var(--cta)); }
.scene-pin-nomad{ fill: hsl(var(--accent)); }

.scene {
  border-radius: var(--r-lg); overflow: hidden;
  box-shadow: var(--shadow-lg); border: 1px solid var(--line);
}

/* ---------------------------------------------------- rhythm helpers ----- */
.lede + .ticks,
.lede + .pill-row { margin-top: 26px; }
.card .eyebrow + h3 { margin-top: 10px; }
.card h3 + p,
.card p + p { margin-top: 12px; }
.card .ticks { margin-top: 16px; }
/* Utilities. These exist because netlify.toml ships a CSP without
   'unsafe-inline' for styles, so a style="" attribute on an element would be
   silently dropped by the browser. Class, never attribute. */
.mt-l { margin-top: 18px; }
.actions-center { justify-content: center; }

/* ============================================================ scene ======
 * The map illustration on the landing page. The HERO landscape that used to
 * live here is gone -- it is a photograph now (see .hero-photo, and the note in
 * index.html), and the two theme-flipping poles it mixed against went with it.
 * Nothing else referenced them.
 *
 * What remains is still drawn and still tokenised: 11 hardcoded hex values in
 * the original became mixes of the app's own tokens, so the artwork tracks the
 * brand instead of pinning it -- the same reason nothing else in this file
 * carries a literal colour.
 */

.scene {
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--line);
}

/* ---- map illustration ---- */
.m-bg-0  { stop-color: color-mix(in srgb, hsl(var(--primary)) 6%, var(--surface)); }
.m-bg-1  { stop-color: color-mix(in srgb, hsl(var(--primary)) 12%, var(--surface)); }
.m-grid  { stroke: color-mix(in srgb, hsl(var(--primary)) 24%, var(--surface)); }
.m-hwy   { stroke: color-mix(in srgb, hsl(var(--accent)) 46%, var(--surface)); }
.m-dirt  { stroke: color-mix(in srgb, hsl(var(--secondary)) 34%, var(--surface)); }
.m-trail { stroke: hsl(var(--primary)); }
.m-scrub { fill: color-mix(in srgb, hsl(var(--primary)) 32%, var(--surface)); }
.m-card  { fill: var(--surface); }
.m-label { fill: hsl(var(--foreground)); }
.m-ring  { stroke: var(--surface); }
.m-legend { font-family: var(--font-body); }
/* The success panel is scrolled to after a submit, and the header is sticky --
   without this the top of the panel lands underneath it. */
#beta-done { scroll-margin-top: 96px; }

/* ==================================================== photographs ========
 * Real photographs, finally. Amanda and Robyn's own pictures of their own
 * rigs -- nothing licensed, nothing stock, so the "no photo until a licensed
 * one exists" rule in the README is satisfied by ownership rather than by a
 * purchase. They live in site/photos/, NOT site/assets/, because /assets/* is
 * deliberately cached for ten minutes on account of brand.css and a 200kB
 * photograph has no business being re-fetched that often.
 *
 * NO COLOUR IS INTRODUCED HERE. The frames, captions and shadows all resolve
 * to the same tokens as everything else in this file, so a photo block follows
 * the app's brand in both themes exactly like a card does.
 */

figure { margin: 0; }

.photo {
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  background: var(--page-deep);
}
.photo img { width: 100%; height: 100%; object-fit: cover; }

/* Portrait phone photographs at full height tower over the text beside them.
   4:5 for the two big ones, 3:4 for the grid. */
.photo-lead img { aspect-ratio: 4 / 5; }

figcaption {
  font-size: .86rem; color: var(--ink-soft);
  margin-top: 11px; line-height: 1.5; max-width: 46ch;
}

/* Text one side, photograph the other. Same breakpoint as .hero-grid. */
.split {
  display: grid; gap: clamp(32px, 5vw, 56px);
  grid-template-columns: 1.05fr .95fr; align-items: center;
}
.split-top { align-items: start; }
.split > .stack > * + * { margin-top: 18px; }
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  /* On one column the picture reads better above the wall of text. */
  .split-lead > figure { order: -1; }
}

/* ---- the two of us ---- */
.bios { display: grid; gap: clamp(28px, 4vw, 44px); grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); }
.bio { display: flex; flex-direction: column; gap: 18px; }
.bio .photo img { aspect-ratio: 3 / 4; }
.bio .role { font-family: var(--font-heading); font-weight: 600; font-size: 1.2rem; letter-spacing: -.01em; margin-top: 4px; }
.bio .eyebrow + .role { margin-top: 6px; }
.bio p + p { margin-top: 12px; }
.bio .body { color: var(--ink-soft); font-size: .97rem; }

/* ---- the hero photograph ---- */
/* Same frame as every other photo on the site; only the shadow is heavier,
   because this one carries the top of the landing page. */
.hero-photo { margin: 0; }
.hero-photo .photo { box-shadow: var(--shadow-lg); }
.hero-photo .photo img { aspect-ratio: 1 / 1; }

/* ---- the gallery ---- */
.gallery { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.gallery .photo img { aspect-ratio: 3 / 4; }
/* auto-fill drops to ONE column on a phone, which turns eleven portrait
   photographs into eleven full-screen scrolls. Two-up instead. */
@media (max-width: 560px) {
  .gallery { grid-template-columns: 1fr 1fr; gap: 12px; }
  .gallery figcaption { font-size: .8rem; margin-top: 8px; }
}

/* ---- a photograph used as a band or a pair, on pages other than /about ---- */
/* Same frame, caption and token rules as above; only the crop changes. */
.photo-band .photo img { aspect-ratio: 5 / 2; }
/* 5:2 on a phone is a letterbox slot with nothing legible in it. */
@media (max-width: 700px) { .photo-band .photo img { aspect-ratio: 4 / 3; } }

.photo-pair { display: grid; gap: 22px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.photo-pair .photo img { aspect-ratio: 4 / 3; }

/* 404 only. Held at the photograph's own 360px so it is never upscaled. */
.photo-small { max-width: 360px; margin-inline: auto; }
