/* Gardens at Mathews — photo-forward prototype.
   Ground is near-black on purpose: 26 of the 35 curated photographs are portrait phone
   shots, and dark ground is what lets a tall image read as a picture rather than a gap.
   Chrome colours are the app's own (constants.js) so the two still read as one family;
   every other colour on the page is sampled from a photograph at build time. */
:root{
  --ink:#0d0f0d; --ink-2:#151815; --ink-3:#1e221e;
  --cream:#f8f5f0; --paper:#efeae1;
  --green:#2d6a4f; --green-light:#52b788; --sage:#7c9885;
  --terra:#b7532a; --gold:#c9a227;
  /* --fg-dimmer was #6f6b63 = 3.63:1 on --ink, failing WCAG 1.4.3 AA for normal text across the
     kickers, week labels, chips and stat captions. #8d8880 is 5.47:1 on --ink and 5.08:1 on
     --ink-2. Computed, not eyeballed. */
  --fg:#f2efe9; --fg-dim:#a9a49a; --fg-dimmer:#8d8880;
  --rule:#2c302c;
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,ui-serif,serif;
  --sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
  --measure:36rem;
  --pad:clamp(1.25rem,5vw,4rem);
  /* No photograph on this page had an edge: `grep box-shadow assets/site.css` returned zero
     matches across the whole sheet, so every image was pasted flat onto the dark ground rather
     than sitting on it. One shared treatment — an inset hairline that gives the image a border
     against a near-black field, and a contact shadow that puts it above the ground rather than
     in it. Applied to the log figures, the mosaic tiles and the lightbox. */
  --photo-surface:0 0 0 1px rgba(255,255,255,.08) inset,0 14px 34px -14px rgba(0,0,0,.8);
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{
  margin:0;background:var(--ink);color:var(--fg);
  font-family:var(--sans);font-size:clamp(1rem,.95rem+.22vw,1.09rem);line-height:1.6;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
img{display:block;max-width:100%}
.wrap{width:min(100% - 2*var(--pad),74rem);margin-inline:auto}
/* Visually hidden, NOT hidden. display:none and visibility:hidden both remove a node from the
   accessibility tree, which would defeat the entire point: these tables exist because the charts'
   role="img" already prunes their <text> children, leaving a screen-reader user with one <title>
   string instead of the season. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}
/* `width:1px` does not constrain a TABLE: the used width of a table box is never less than its
   min-content width, whatever `width`, `max-width` or `table-layout` say. And the `white-space:
   nowrap` above — carried along from the 1px-clip recipe, where it stops text from wrapping into
   a tall box — makes that min-content the sum of every cell's UNWRAPPED text. The still-hidden
   cumulative-curve table therefore laid out at 402.8px, absolutely positioned at left 19.6 and
   ending at 422.4 against a 412px viewport. clip-path hides paint, not layout, so the page
   scrolled 10px sideways on a phone with nothing visible to explain it, and every fixed element
   sized itself to 422 instead of 412 — including this bar.

   Letting the hidden table wrap is what shrinks it; it is clipped, so wrapping costs nothing.
   The revealed tables under 46rem already set `white-space:normal` themselves, so this is the
   same declaration in both states rather than a special case. Measured 2026-08-26. */
table.vh{white-space:normal}
.skip{position:absolute;left:-9999px}
.skip:focus{left:1rem;top:1rem;z-index:99;background:var(--cream);color:var(--ink);
  padding:.6rem 1rem;border-radius:6px}
:focus-visible{outline:2.5px solid var(--gold);outline-offset:3px;border-radius:3px}

.kicker{font-size:.74rem;letter-spacing:.19em;text-transform:uppercase;
  color:var(--gold);font-weight:700;margin:0 0 .9rem}
.eyebrow{font-size:.76rem;letter-spacing:.19em;text-transform:uppercase;
  color:rgba(255,255,255,.82);font-weight:600;margin:0 0 1.1rem;
  text-shadow:0 1px 12px rgba(0,0,0,.7)}

/* ── top bar ──────────────────────────────────────────────────────── */
/* The bar is always on screen. Over the hero it carries no backing at all, so it sits on the
   photograph without covering it; past the hero it takes the opaque blurred panel it needs to
   stay readable over arbitrary content. The links keep a text-shadow in the transparent state —
   without it, white-on-photograph is legible over a dark frame and invisible over a bright one,
   and the hero rotates. */
.topbar{position:fixed;top:0;left:0;right:0;z-index:50;
  display:flex;align-items:center;justify-content:space-between;gap:1rem;
  padding:.6rem var(--pad);background:transparent;border-bottom:1px solid transparent;
  text-shadow:0 1px 6px rgba(0,0,0,.75);
  transition:background .28s ease,border-color .28s ease,backdrop-filter .28s ease}
.topbar--solid{background:rgba(13,15,13,.86);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom-color:var(--rule);text-shadow:none}
@media (prefers-reduced-motion:reduce){.topbar{transition:none}}
.topbar__home{font-family:var(--serif);font-size:1.02rem;color:var(--fg);
  text-decoration:none;white-space:nowrap}
.topbar__home em{font-style:italic;color:var(--gold)}
/* A single-row rail that scrolls sideways, NOT a wrapping list. `flex-wrap:wrap` fitted the
   destinations by growing the bar downwards: at 412px the wordmark plus three chips need 436.8px
   against 412px available, so the bar stood permanently at 78px — two rows, 8.3% of the screen,
   on every screen, made permanent by the (correct) decision to keep the bar always visible.
   Fitting them by shaving padding was possible with about 1px to spare, which is not a fit; it
   is a fit until someone renames a link or a font falls back. A rail cannot wrap at any width,
   for any label set, so a fourth destination could finally be added.
   `margin-left:auto` rather than `justify-content:flex-end` — an overflowing flex container
   justified to the end pushes its FIRST item out of the scrollable area where nothing can reach
   it. There is deliberately no `min-width:0` here despite the usual advice: a flex item's
   automatic minimum size applies only while its overflow is `visible`, so `overflow-x:auto` has
   already made this item shrinkable and the extra declaration is dead. (Mutation-tested: removing
   `min-width:0` changes nothing measurable. `overflow-x:auto` is the load-bearing half — take it
   away and the chips simply run off the right of the bar where nothing can reach them, silently,
   with the page still reporting no horizontal scroll. `rail overflows at 412 and wears the fade`
   in test-nav.js is the assertion that catches that; no page-overflow assertion does.)
   `overscroll-behavior-x:contain` matters more than it looks: without it, flicking the rail past
   its end chains to the browser, and on Android Chrome that gesture is Back — this session's
   history work would have been undone by its own navigation. */
.topbar__links{display:flex;gap:.4rem;list-style:none;margin:0 0 0 auto;padding:0;
  flex-wrap:nowrap;justify-content:flex-start;
  overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;
  scrollbar-width:none;-ms-overflow-style:none}
.topbar__links::-webkit-scrollbar{display:none}
.topbar__links>li{flex:0 0 auto}
.topbar__links a{display:block;white-space:nowrap;line-height:1.2;
  color:var(--fg-dim);text-decoration:none;font-size:.82rem;
  padding:.34rem .6rem;border-radius:999px;border:1px solid transparent;transition:.15s}
.topbar__links a:hover{color:var(--fg);border-color:var(--rule)}
/* The fade is the only thing telling a reader the rail continues, so it is applied ONLY when the
   rail actually overflows (site.js sets the class). Applied unconditionally it would quietly
   dim the first and last chip on a desktop where nothing is hidden. */
.topbar__links--rail{
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 15px,#000 calc(100% - 15px),transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0,#000 15px,#000 calc(100% - 15px),transparent 100%)}
@media (prefers-reduced-motion:no-preference){.topbar__links{scroll-behavior:smooth}}
/* The section you are actually in. `aria-current="true"` (an item within a set), never "page" —
   "page" is reserved for the gallery chip on the gallery page, which is a different claim. */
.topbar__links a[aria-current]{color:var(--ink);background:var(--cream);border-color:var(--cream)}
.topbar--solid .topbar__links a[aria-current]{color:var(--ink)}
/* Taller chips where the pointer is a thumb. Kept off the desktop bar deliberately: growing the
   padding everywhere took a 48px bar to 57px on a 1280px screen, which is the opposite of the
   thing being fixed. 46rem is this stylesheet's existing narrow boundary (it is where the chart
   tables reveal), not a new one invented here. */
@media (max-width:46rem){
  .topbar__links a{padding:.5rem .62rem}
}
@media (max-width:26rem){
  .topbar{padding:.5rem var(--pad)}
  .topbar__links a{font-size:.76rem;padding:.55rem .56rem}
  .topbar__home{font-size:.92rem}
}

/* ── hero ─────────────────────────────────────────────────────────── */
/* ?cap pins the hero so a full-page headless screenshot (tall window) doesn't let
   100svh swallow the entire capture. Review scaffolding only. */
html.cap .hero{min-height:820px}
.hero{position:relative;min-height:100svh;display:grid;align-items:end;
  padding:var(--pad) var(--pad) clamp(2.5rem,7vh,5rem);overflow:hidden;isolation:isolate}
.hero__stack{position:absolute;inset:0;z-index:-2;display:grid;
  grid-template-columns:repeat(3,1fr);gap:2px}
.hero__stack img{width:100%;height:100%;object-fit:cover}
.hero__stack img:nth-child(n+2){display:none}
@media (min-width:52rem){.hero__stack img:nth-child(n+2){display:block}}
@media (max-width:52rem){.hero__stack{grid-template-columns:1fr}}
/* Scrim earns its keep only where type sits. A full-height wash turns the photographs
   into mud, which is the opposite of the point of this page. */
.hero__scrim{position:absolute;inset:0;z-index:-1;
  background:
    linear-gradient(180deg,rgba(6,8,6,.34) 0%,rgba(6,8,6,0) 26%,rgba(6,8,6,0) 40%,rgba(6,8,6,.62) 78%,rgba(6,8,6,.88) 100%),
    radial-gradient(120% 62% at 12% 100%,rgba(6,8,6,.72) 0%,rgba(6,8,6,0) 70%)}
.hero__body{max-width:44rem}
.hero h1{font-family:var(--serif);font-weight:400;margin:0 0 1.2rem;
  font-size:clamp(3rem,1.4rem+7.4vw,7.5rem);line-height:.94;letter-spacing:-.025em;
  text-shadow:0 2px 40px rgba(0,0,0,.6)}
.hero h1 em{font-style:italic;color:var(--gold)}
.hero__lede{max-width:var(--measure);margin:0 0 1.8rem;color:rgba(255,255,255,.9);
  font-size:clamp(1.05rem,.98rem+.42vw,1.28rem);text-shadow:0 1px 18px rgba(0,0,0,.75)}
.hero__stat{margin:0;font-size:.95rem;color:rgba(255,255,255,.82);letter-spacing:.01em;
  text-shadow:0 1px 14px rgba(0,0,0,.8)}
.hero__stat b{font-family:var(--serif);font-size:1.65em;color:#fff;font-weight:400;
  letter-spacing:-.02em}
.hero__stat span{opacity:.45;padding:0 .45rem}
.hero__fine{margin:.5rem 0 0;font-size:.83rem;color:rgba(255,255,255,.76);
  text-shadow:0 1px 14px rgba(0,0,0,.85)}
.hero__down{position:absolute;left:50%;bottom:1.1rem;transform:translateX(-50%);
  color:rgba(255,255,255,.6);text-decoration:none;font-size:1.4rem;line-height:1;
  padding:.5rem .8rem;border-radius:999px}
.hero__down:hover{color:#fff}

/* ── the gallery page ─────────────────────────────────────────────── */
/* /gallery/ has no hero, so its first section starts at y=0 underneath a `position:fixed` bar.
   Measured: the bar is 78px at 412px wide (two rows) and 46px on one row, so the clearance is
   expressed in rem and left generous rather than pinned to a number that changes with the link
   count. Only the FIRST section needs it — everything after is pushed down by normal flow. */
.section--first{padding-top:calc(78px + clamp(1.5rem,5vw,3rem))}

/* The one call to action on the season page. Deliberately a link that looks like a link and not
   a button: it navigates to another page, and a button-shaped control that changes the address
   is the small lie that makes Back feel broken even when it is not. */
.bigLink{display:inline-block;margin-top:.4rem;color:var(--gold);text-decoration:none;
  font-size:1.05rem;border-bottom:1px solid rgba(201,162,39,.42);padding-bottom:.15rem;
  transition:border-color .15s,color .15s}
.bigLink:hover{color:#e2c76a}

/* ── the season curve, hoisted under the hero ──────────────────────── */
.curve{padding:clamp(2.5rem,6vw,4.5rem) 0 clamp(1rem,3vw,2rem);background:var(--ink)}
.curve__h{font-family:var(--serif);font-weight:400;margin:0 0 1.6rem;
  font-size:clamp(1.35rem,1.1rem+1.2vw,2.1rem);line-height:1.15;letter-spacing:-.015em;
  max-width:26ch}
.curve svg{width:100%;height:auto;display:block;overflow:visible}
/* The chart tokens above are tuned for the pale section; on the dark ground they invert. */
.curve .gl{stroke:rgba(255,255,255,.11)}
.curve .ax{fill:var(--fg-dimmer)}
.curve .val{fill:var(--fg);font-weight:600}

/* ── landscape break between log entries ───────────────────────────── */
/* These were full-bleed, edge to edge, in a band of clamp(320px,52vh,620px). Every one of the
   four is a 4:3 photograph (1.33), and at 1280px wide that band is 2.06:1 — so `object-fit:cover`
   was throwing away 35% OF THE FRAME, top and bottom, and the subject stopped being where the
   photographer put it. Dave's read was exactly right: too wide, and cut off.
     • 3:2 instead of a fixed band. Still wider than the 4:5 portraits, so the rhythm survives,
       but it trims 11% rather than 35% — a gentle crop, not a re-composition.
     • Inset to the same 74rem measure as the log entries, with the same radius and surface, so
       a landscape frame reads as a sibling of the portrait figures instead of a banner.
   The HERO stays full-bleed and untouched: edge to edge is right for the opening frame, and it
   is the one place a photograph should own the whole viewport. */
.bleed{margin:clamp(1rem,3vw,2rem) auto;position:relative;
  width:min(100% - 2*var(--pad),74rem);border-radius:14px;overflow:hidden;
  box-shadow:var(--photo-surface)}
.bleed img{width:100%;aspect-ratio:3/2;height:auto;object-fit:cover;
  object-position:center;display:block}
.bleed figcaption{position:absolute;left:0;right:0;bottom:0;
  padding:3.5rem 1rem .9rem;display:flex;gap:.6rem;align-items:baseline;
  background:linear-gradient(180deg,transparent,rgba(6,8,6,.8));
  color:#fff;font-size:.85rem}
.bleed figcaption em{font-style:normal;color:rgba(255,255,255,.66)}
.bleed figcaption i{font-style:italic;color:var(--gold)}
/* Per-photograph subject placement. `cover` crops from the centre by default, which is wrong
   whenever the subject is not centred — set the axis that matters and leave the other alone. */
.bleed--low img{object-position:center 62%}
.bleed--high img{object-position:center 38%}

/* ── sections ─────────────────────────────────────────────────────── */
.section{padding:clamp(3.5rem,9vw,7rem) 0}
.section__h{font-family:var(--serif);font-weight:400;margin:0 0 1rem;
  font-size:clamp(2rem,1.4rem+2.6vw,3.5rem);line-height:1.08;letter-spacing:-.02em}
.section__lede{max-width:var(--measure);color:var(--fg-dim);margin:0}
/* The gallery's honesty line — what is shown, out of what exists, and why the difference. Sits
   between the heading and the filter bar so it is read before the grid rather than discovered
   after it. --fg-dim, not --fg-dimmer: this is normal-size body text and --fg-dimmer fails
   WCAG 1.4.3 AA on --ink (see the token comment at the top of this file). */
.gallery__note{max-width:var(--measure);color:var(--fg-dim);margin:0 0 1.25rem;
  font-size:.9rem;line-height:1.55}
.section--pale{background:var(--paper);color:#232622}
.section--pale .kicker{color:var(--terra)}
.section--pale .section__lede{color:#5b5f59}
.section--dark{background:var(--ink-2)}

/* ── the log ──────────────────────────────────────────────────────── */
.log{margin-top:clamp(2.5rem,6vw,4.5rem);display:grid;gap:clamp(2.5rem,6vw,5rem)}
.entry{width:min(100% - 2*var(--pad),74rem);margin-inline:auto;
  display:grid;gap:clamp(1.25rem,3vw,2.5rem);align-items:center}
@media (min-width:54rem){
  .entry{grid-template-columns:1fr 1fr}
  .entry:nth-child(even) .entry__fig{order:2}
}
.entry__fig{margin:0;position:relative;border-radius:14px;overflow:hidden;
  background:var(--ink-3);aspect-ratio:4/5;box-shadow:var(--photo-surface)}
.entry__fig img{width:100%;height:100%;object-fit:cover;
  transition:transform .7s cubic-bezier(.2,.7,.3,1)}
.entry__fig:hover img{transform:scale(1.035)}
.entry__tape{position:absolute;left:0;right:0;bottom:0;height:5px}
/* Caption every log photograph with its own crop and date — the cheapest integrity fix on the
   page. Without it a picture beside a paragraph silently claims to depict that paragraph. */
.entry__cap{position:absolute;left:0;right:0;bottom:5px;padding:2.4rem .9rem .7rem;
  background:linear-gradient(180deg,transparent,rgba(6,8,6,.82));
  color:#fff;font-size:.82rem;display:flex;gap:.55rem;align-items:baseline}
.entry__cap em{font-style:normal;color:rgba(255,255,255,.68)}
/* The cultivar. Italic and gold so it reads as the specific name rather than a second crop
   label — "Tomatillos · Pineapple Tomatillo" is one fact refined, not two facts listed.
   --gold on the near-black caption gradient measures 7.96:1. */
.entry__cap i,.bleed figcaption i{font-style:italic;color:var(--gold)}
/* A week with no honest photograph gets none. Centre the text instead of leaving a void. */
@media (min-width:54rem){
  .entry--textonly{grid-template-columns:minmax(0,34rem);justify-content:center;text-align:left}
}
.entry--textonly .entry__txt{border-left:2px solid var(--rule);padding-left:1.4rem}
.entry__wk{font-size:.74rem;letter-spacing:.17em;text-transform:uppercase;
  color:var(--fg-dimmer);font-weight:700;margin:0 0 .55rem}
.entry__h{font-family:var(--serif);font-weight:400;margin:0 0 .75rem;
  font-size:clamp(1.6rem,1.25rem+1.5vw,2.5rem);line-height:1.1;letter-spacing:-.015em}
.entry__body{color:var(--fg-dim);margin:0 0 1.4rem;max-width:32rem}
.entry__nums{display:flex;flex-wrap:wrap;gap:0 2rem;margin:0 0 1.1rem;padding:0;list-style:none}
.entry__nums div{display:flex;flex-direction:column}
.entry__nums b{font-family:var(--serif);font-weight:400;font-size:1.9rem;line-height:1.1;
  letter-spacing:-.02em}
.entry__nums span{font-size:.72rem;letter-spacing:.13em;text-transform:uppercase;
  color:var(--fg-dimmer);margin-top:.15rem}
.chips{display:flex;flex-wrap:wrap;gap:.4rem;padding:0;margin:0;list-style:none}
/* These are buttons now — "first Cucumbers" is the most specific claim on the card and it was the
   one thing you could not follow. "first" is a real <span> (.chips__k) rather than a ::before, so
   the announced name matches the visible text; generated content lands in the accessible name
   inconsistently across engines. */
.chips button{font:inherit;font-size:.76rem;letter-spacing:.04em;padding:.26rem .62rem;
  border-radius:999px;border:1px solid var(--rule);color:var(--fg-dim);  /* 7.76:1 on --ink */
  background:rgba(255,255,255,.03);cursor:pointer;transition:.15s}
.chips button:hover{border-color:var(--sage);color:var(--fg)}
.chips__k{color:var(--gold);font-weight:600}

/* ── charts ───────────────────────────────────────────────────────── */
.charts{display:grid;gap:clamp(2rem,4vw,3rem);margin-top:clamp(2rem,5vw,3.5rem);
  grid-template-columns:1fr}
@media (min-width:54rem){.charts{grid-template-columns:1fr 1fr}
  .chart--wide{grid-column:1 / -1}}
.chart{margin:0}
.chart figcaption{margin-bottom:1.1rem;display:flex;flex-direction:column;gap:.15rem}
.chart figcaption b{font-family:var(--serif);font-weight:400;font-size:1.35rem}
.chart figcaption span{font-size:.78rem;letter-spacing:.11em;text-transform:uppercase;
  color:#7d8178}

/* ── chart data on a narrow screen ─────────────────────────────────────
   These tables were built for screen readers and hidden from everyone else. On a phone that
   inverts who is served: a blind visitor gets every crop and every figure, while a sighted one
   gets labels rendered at 4.0 CSS px (first-pick) and 7.0 px (crop bars). Measured at 412px —
   58 of the page's 70 chart labels fall below any legible floor, in the section called "The
   numbers". Desktop is unaffected and was verified legible at 1280px (12px / 11px / 35px,
   nothing under the floor), so this is scoped to the width where the problem exists.

   The charts stay. Their shape is real information — the July cluster, the tomato bar — and it
   survives being small in a way that a crop name does not. What the reader gains is the ability
   to actually read the numbers underneath.

   Compound `.chart__data.vh` deliberately: `.vh` is a single class, and matching it with one
   class here would leave the two rules on equal specificity and decide this by source order.
   Only chart tables are un-hidden; every other visually-hidden element on the page stays so. */
@media (max-width:46rem){
  /* Scoped to `.chart` figures — the two that measured illegible. The cumulative curve carries a
     table too, but its own labels render at 11px here, so revealing it would add a redundant
     table under a chart the reader can already read. Which tables appear is decided by the
     measurement, not by symmetry. */
  .chart .chart__data.vh{position:static;width:100%;height:auto;margin:1rem 0 0;overflow:visible;
    clip-path:none;white-space:normal;border-collapse:collapse;font-size:.88rem}
  /* Every rule below is scoped to `.chart` as well. Left unscoped they also applied cell padding
     to the cumulative table, which stays hidden — inflating the layout box of an element the
     reader never sees. Harmless, since it is clipped, but a hidden element should not be paying
     layout for styling it cannot show. */
  .chart .chart__data caption{text-align:left;font-size:.78rem;letter-spacing:.06em;
    text-transform:uppercase;color:#7d8178;padding-bottom:.5rem}
  .chart .chart__data th,.chart .chart__data td{padding:.42rem .5rem;text-align:left;
    border-bottom:1px solid rgba(35,38,34,.14)}
  .chart .chart__data thead th{font-weight:600;font-size:.72rem;letter-spacing:.06em;
    text-transform:uppercase;color:#7d8178}
  /* Figures right-aligned so they compare down the column; the crop name stays left. */
  .chart .chart__data td{text-align:right;font-variant-numeric:tabular-nums}
  .chart .chart__data tbody th{font-weight:400}
  .chart .chart__data tbody tr:last-child th,
  .chart .chart__data tbody tr:last-child td{border-bottom:0}
}
.chart svg{width:100%;height:auto;display:block;overflow:visible}
.chart .ax{font-size:11px;fill:#5f6359;letter-spacing:.04em}  /* was #83877e = 3.06:1 on --paper; now 5.13:1 */
.chart .val{font-size:11px;fill:#4a4e46;font-weight:600}
.chart .gl{stroke:#d9d2c6;stroke-width:1}

/* ── what didn't make it ──────────────────────────────────────────── */
/* Sits inside the pale section, so it is quieter than the log: this is a fact the record owes
   the reader, not a second climax. Deliberately NOT styled as a warning — no red, no icon. */
.loss{margin-top:clamp(2.5rem,6vw,4rem);display:grid;gap:clamp(1.25rem,3vw,2.5rem);
  align-items:center;border-top:1px solid #d9d2c6;padding-top:clamp(2rem,5vw,3rem)}
@media (min-width:54rem){.loss{grid-template-columns:minmax(0,22rem) 1fr}}
.loss__fig{margin:0;position:relative;border-radius:14px;overflow:hidden;
  background:var(--ink-3);aspect-ratio:4/5;box-shadow:var(--photo-surface)}
.loss__fig img{width:100%;height:100%;object-fit:cover}
.loss__h{font-family:var(--serif);font-weight:400;margin:0 0 .75rem;
  font-size:clamp(1.4rem,1.15rem+1.1vw,2rem);line-height:1.15;letter-spacing:-.015em;
  color:#232622}
.loss__body{margin:0;color:#5b5f59;max-width:34rem}   /* 5.43:1 on --paper (computed, not eyeballed) */

/* ── what the scale never sees ────────────────────────────────────── */
/* Small cards, not mosaic tiles. These are a roll-call — every plant the same size, none more
   important than another, which is the opposite of the weight-ranked charts above. */
.uw__h{font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--gold);
  font-weight:600;margin:clamp(2rem,4vw,3rem) 0 1rem}
.uw{display:grid;gap:.7rem;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr))}
.uwcard{margin:0;border-radius:10px;overflow:hidden;background:var(--ink-3);position:relative;
  box-shadow:var(--photo-surface)}
/* `height:auto` is load-bearing. img() sets width/height ATTRIBUTES (they prevent layout shift
   while the file loads), and the height attribute is a presentational hint that aspect-ratio does
   not override — so without this the square became a 2048px column. */
.uwcard img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block}
.uwcard__none{width:100%;aspect-ratio:1;
  background:repeating-linear-gradient(135deg,#1a1e1a 0 6px,#151815 6px 12px)}
.uwcard figcaption{position:absolute;left:0;right:0;bottom:0;
  padding:1.7rem .55rem .5rem;display:flex;flex-wrap:wrap;gap:.3rem;align-items:baseline;
  background:linear-gradient(180deg,transparent,rgba(6,8,6,.88));color:#fff;font-size:.76rem}
.uwcard__fate{font-style:normal;font-size:.66rem;letter-spacing:.06em;text-transform:uppercase;
  padding:.1rem .34rem;border-radius:3px;background:rgba(255,255,255,.14)}
/* Colour carries the distinction the words also carry — never colour alone. */
.uwcard__fate--failed{background:rgba(183,83,42,.5)}
.uwcard__fate--coming{background:rgba(45,106,79,.6)}
.uwcard__fate--next{background:rgba(201,162,39,.34)}
.uw__rest{margin:1.4rem 0 0;color:var(--fg-dimmer);font-size:.84rem;max-width:60ch}
.uw__rest[hidden]{display:none}

/* ── mosaic ───────────────────────────────────────────────────────── */
.filters{display:flex;flex-wrap:wrap;gap:.45rem;margin-top:1.9rem}
.filters button{font:inherit;font-size:.82rem;cursor:pointer;
  padding:.42rem .9rem;border-radius:999px;border:1px solid var(--rule);
  background:transparent;color:var(--fg-dim);transition:.15s;
  display:inline-flex;align-items:baseline;gap:.45rem}
.filters button:hover{border-color:var(--sage);color:var(--fg)}
.filters button[aria-pressed="true"]{background:var(--cream);color:var(--ink);
  border-color:var(--cream);font-weight:600}
/* The count. Eight of the thirteen crop chips hold exactly one photograph and the bar said
   nothing about it. Tabular figures so the chips stop twitching as the numbers change. */
.filters button b{font-weight:600;font-size:.74rem;font-variant-numeric:tabular-nums;
  color:var(--fg-dimmer)}                                    /* 5.08:1 on --ink-2 */
.filters button[aria-pressed="true"] b{color:#5a5750}        /* 6.63:1 on --cream */
.filters button[hidden]{display:none}
/* The fold. Styled as a control rather than a chip so it does not read as another filter. */
.filters__more{border-style:dashed !important;color:var(--gold) !important}
.filters__more:hover{border-color:var(--gold) !important}
/* On a phone the bar is the primary way into 1,161 photographs, so it gets its own scroll rather
   than growing to a third of the screen. Snap keeps chips from stranding half-visible. */
@media (max-width:40rem){
  .filters{flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x proximity;
    padding-bottom:.5rem;-webkit-overflow-scrolling:touch}
  .filters button{flex:0 0 auto;scroll-snap-align:start}
}
.mosaic{margin-top:clamp(1.75rem,4vw,2.75rem);padding-inline:var(--pad);
  columns:2;column-gap:10px}
@media (min-width:40rem){.mosaic{columns:3}}
@media (min-width:64rem){.mosaic{columns:4;column-gap:14px}}
.tile{break-inside:avoid;margin:0 0 10px;position:relative;display:block;width:100%;
  padding:0;border:0;cursor:zoom-in;border-radius:10px;overflow:hidden;background:var(--ink-3);
  box-shadow:var(--photo-surface)}
@media (min-width:64rem){.tile{margin-bottom:14px}}
.tile img{width:100%;height:auto;transition:transform .6s cubic-bezier(.2,.7,.3,1),filter .3s}
.tile:hover img{transform:scale(1.04)}
.tile__tag{position:absolute;left:0;right:0;bottom:0;padding:1.6rem .7rem .55rem;
  background:linear-gradient(180deg,transparent,rgba(0,0,0,.78));
  color:#fff;font-size:.78rem;text-align:left;display:flex;justify-content:space-between;
  gap:.5rem;opacity:0;transition:opacity .22s}
.tile:hover .tile__tag,.tile:focus-visible .tile__tag{opacity:1}
/* Three fields on a phone tile: crop, cultivar, date. One row cannot hold them — measured at
   412px the cultivar truncated to "Golden …" and the date wrapped to a second line anyway, so
   the single row cost the variety its name AND still spilled. Dave asked to SEE the variety
   ("tomatoes and Moskvich"); a name cut to two characters is not a name.
   So crop and cultivar share the first line and the date drops to its own. The tag grows by one
   line, which is affordable — it is a scrim over the bottom of the image, not a bar. */
.tile__tag{display:block;line-height:1.32}
.tile__tag span{font-weight:500}
.tile__tag i{font-style:italic;color:var(--gold);margin-left:.4em}
.tile__tag em{display:block;font-style:normal;color:rgba(255,255,255,.68);font-size:.94em}
/* Captions were hover-only, which means they did not exist on a touch screen — and Dave is
   Android-only, so the person this was built for never saw a single one. On a coarse pointer
   there is no hover state to reveal them with, so show them outright and deepen the scrim
   enough to carry white text over an arbitrary photograph. */
@media (hover:none),(pointer:coarse){
  .tile__tag{opacity:1;background:linear-gradient(180deg,transparent,rgba(0,0,0,.86))}
}
.mosaic__none{text-align:center;color:var(--fg-dimmer);padding:3rem 0 0}
/* Sentinel for progressive loading. It also reports how many are left, so a very long gallery
   tells you its size instead of just going on. */
/* Sentinel AND control. Appending happens on scroll, but it is a real button so the next page is
   always one tap away — a reader who never triggers a scroll event is never stranded. */
.mosaic__more{display:block;margin:1.5rem auto .5rem;font:inherit;font-size:.78rem;
  letter-spacing:.12em;text-transform:uppercase;cursor:pointer;
  color:var(--fg-dim);background:transparent;border:1px solid var(--rule);
  border-radius:999px;padding:.6rem 1.4rem;transition:.15s}
.mosaic__more:hover{border-color:var(--sage);color:var(--fg)}
.mosaic__more[hidden]{display:none}

/* ── lightbox ─────────────────────────────────────────────────────── */
.lb{position:fixed;inset:0;z-index:60;background:rgba(6,8,6,.965);
  display:grid;place-items:center;padding:clamp(.75rem,3vw,2.5rem)}
.lb[hidden]{display:none}
.lb__fig{margin:0;max-width:min(100%,72rem);max-height:100%;display:flex;
  flex-direction:column;gap:.9rem;min-height:0}
.lb__fig img{max-height:78svh;width:auto;max-width:100%;margin-inline:auto;
  border-radius:8px;object-fit:contain;box-shadow:var(--photo-surface)}
.lb__cap{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;
  justify-content:space-between}
.lb__meta{display:flex;flex-direction:column}
.lb__meta b{font-family:var(--serif);font-weight:400;font-size:1.3rem}
.lb__meta i{font-style:italic;color:var(--gold);font-size:.95rem;margin:.05rem 0 .15rem}
.lb__meta i[hidden]{display:none}
.lb__meta span{font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;color:var(--fg-dimmer)}
.lb__acts{display:flex;align-items:center;gap:1rem;flex-wrap:wrap}
.lb__x,.lb__nav,.lb__play{font:inherit;cursor:pointer;color:var(--fg);
  background:rgba(255,255,255,.07);border:1px solid var(--rule);border-radius:999px;transition:.15s}
.lb__x:hover,.lb__nav:hover,.lb__play:hover{background:rgba(255,255,255,.16)}
.lb__x{position:absolute;top:1rem;right:1rem;width:2.6rem;height:2.6rem;font-size:1.5rem;line-height:1}
.lb__nav{position:absolute;top:50%;transform:translateY(-50%);
  width:3rem;height:3rem;font-size:1.8rem;line-height:1}
.lb__nav--prev{left:clamp(.4rem,2vw,1.5rem)}
.lb__nav--next{right:clamp(.4rem,2vw,1.5rem)}
.lb__play{padding:.42rem 1rem;font-size:.82rem}
.lb__play[aria-pressed="true"]{background:var(--cream);color:var(--ink);border-color:var(--cream)}
/* .lb__stub and .reactions lived here until 2026-08-24. Reactions were retired by decision, not
   by oversight: with no notification path in either direction a reaction degrades to an anonymous
   integer, and the stub rendered a "1" indistinguishable from one other person having reacted.
   The markup went; these rules were left behind styling nothing. */

/* ── footer ───────────────────────────────────────────────────────── */
.foot{border-top:1px solid var(--rule);padding:2.5rem 0 3.5rem;color:var(--fg-dimmer);
  font-size:.87rem}
.foot p{margin:0 0 .35rem}
.foot b{color:var(--fg-dim);font-weight:600}
.foot__note{font-size:.8rem;color:var(--fg-dimmer)}  /* was #5b5850 = 2.71:1 — failed even large-text */

/* ── reveal ───────────────────────────────────────────────────────── */
/* Reveal-on-scroll must DEGRADE TO VISIBLE. Hiding content behind an IntersectionObserver means
   that if the observer never fires — script error, unsupported, or simply a timing edge — the
   entire log is a blank page with no error. Observed during verification. `html.js-reveal` is set
   by script immediately before the observer is attached, so no-script and failed-script both
   leave the content on screen. */
@media (prefers-reduced-motion:no-preference){
  html.js-reveal .rv{opacity:0;transform:translateY(18px);
    transition:opacity .7s ease,transform .7s cubic-bezier(.2,.7,.3,1)}
  html.js-reveal .rv.in{opacity:1;transform:none}
}
