/* Garden cards — mockup stylesheet. Two pages share it: the harvest front
   and the season's one-sheet. */

@font-face { font-family: Bitter; src: url(fonts/Bitter-Regular.woff2) format("woff2"); font-weight: 400; font-display: block; }
@font-face { font-family: Bitter; src: url(fonts/Bitter-Bold.woff2) format("woff2"); font-weight: 700; font-display: block; }
@font-face { font-family: "Source Serif 4"; src: url(fonts/SourceSerif4-Regular.woff2) format("woff2"); font-weight: 400; font-display: block; }
@font-face { font-family: "Source Serif 4"; src: url(fonts/SourceSerif4-Bold.woff2) format("woff2"); font-weight: 700; font-display: block; }
@font-face { font-family: "Source Serif 4"; src: url(fonts/SourceSerif4-Italic.woff2) format("woff2"); font-style: italic; font-display: block; }
/* Georgia is narrower than Source Serif, so until the face arrives the page
   lays out short of a scroll saved at the bottom and the first frames paint
   the scene. 112% is the scale at which the fallback wraps onto the same
   lines. It has to stay at least that wide: a single line short is enough. */
@font-face {
  font-family: "Source Serif 4 Fallback";
  src: local("Georgia");
  font-weight: 100 900; font-style: normal; size-adjust: 112%;
}
@font-face {
  font-family: "Source Serif 4 Fallback";
  src: local("Georgia Italic"), local("Georgia-Italic"), local("Georgia");
  font-weight: 100 900; font-style: italic; size-adjust: 112%;
}

:root {
  /* card.typ's five inks, verbatim. */
  --ink:   #3a3227;
  --leaf:  #4f6b3e;
  --brick: #a8452c;
  --paper: #f8f3e7;
  --panel: #f0e9d6;

  /* Derived from those, never invented: COLORS.md says a hue outside the
     band will not look wrong on screen, it will look wrong beside a card. */
  --rail:     color-mix(in oklab, var(--ink) 82%, var(--leaf));
  --cast:     color-mix(in oklab, var(--ink) 34%, transparent);
  --hairline: color-mix(in oklab, var(--ink) 28%, var(--paper));
  --deep:     color-mix(in oklab, var(--ink) 92%, var(--leaf));

  --measure: 34rem;
  --card-w: 5.5in;

  --step--1: 0.9rem;
  --step-0:  1.0625rem;
  --step-1:  1.3rem;
  --step-2:  1.85rem;
  --step-3:  3rem;
  --step-4:  clamp(3.2rem, 9vw, 6.5rem);
}

*, *::before, *::after { box-sizing: border-box; }
/* The cue under the title and the ribbon's links all point down one page.
   Travelling it rather than cutting to it is what says the thing they point
   at is further down the same page and not a different one.

   The guard is not only for comfort: scrollIntoView({ behavior: "auto" })
   resolves to whatever this property says, and that is the call the card
   picker already makes for a reader who asked not to be moved. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Source Serif 4", "Source Serif 4 Fallback", Georgia, serif;
  font-size: var(--step-0);
  line-height: 1.6;
}

/* The grade and the sun cover the garden in the splash. The type and its
   wash sit above both, and do not take the hour. */
.grade-def { position: absolute; width: 0; height: 0; }
.view { position: relative; z-index: 1; min-height: 100vh; background: var(--paper); }
/* The splash sky is a hole at midnight, so the night sky behind the view shows through. */
.view:has(.splash) { background: transparent; }
.ground { background: var(--paper); padding-top: 2rem; }

.night {
  display: none;
  position: absolute; z-index: 0; top: 0; left: 0;
  width: 100%; height: 100svh;
}
html[data-hour="midnight"] .night { display: block; }
html[data-hour="midnight"] .sky-fill { opacity: 0; }
html[data-hour="midnight"] .splash,
html[data-hour="midnight"] .splash-image { background: none; }

.splash .light {
  position: absolute; z-index: 2; inset: 0; pointer-events: none;
  background: radial-gradient(var(--sun-w, 62rem) var(--sun-h, 48rem) at var(--lx, 50%) var(--ly, -4%),
              color-mix(in oklab, var(--sun, #fffdf6) 70%, transparent), transparent 68%);
  opacity: var(--sun-a, 1);
  mix-blend-mode: soft-light;
}

/* ---- Shared furniture -------------------------------------------------- */

.sheet { position: relative; z-index: 1; max-width: 78rem; margin: 0 auto; padding: 0 1.5rem; }

h1, h2, h3 { font-family: Bitter, Georgia, serif; font-weight: 400; }

a { color: inherit; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
a:focus-visible, button:focus-visible { outline: 2px solid var(--brick); outline-offset: 3px; }

.ribbon {
  display: flex; flex-wrap: wrap; gap: 1.5rem;
  align-items: baseline; justify-content: space-between;
  padding: 1.6rem 0; font-size: var(--step--1);
  border-bottom: 1.5px solid var(--hairline);
}

.ribbon .mark { font-family: Bitter, Georgia, serif; font-size: var(--step-0); }
.ribbon nav { display: flex; gap: 1.5rem; }

/* ---- Harvest front: the opening ---------------------------------------- */

/* Two columns while the copy can sit beside the photograph. Below that,
   the copy takes the row and the photograph centers under it. */
.opening {
  container-type: inline-size;
  display: grid; gap: 3rem 4.5rem; padding: 5rem 0 3.5rem;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
}
.opening > :first-child { grid-column: 1 / -1; }
@media (min-width: 62rem) {
  .opening { align-items: center; }
  .opening > :first-child { grid-column: auto; }
}

/* A step under the section's own head, and a step over the quote it
   introduces, so it reads as belonging to both. */
.opening h3 {
  font-size: var(--step-2); line-height: 1.1;
  letter-spacing: -0.015em; margin: 0 0 1rem;
}

blockquote.her {
  margin: 0 0 1.6rem; padding-left: 1.4rem;
  border-left: 2px solid var(--leaf);
  font-style: italic; font-size: var(--step-1);
  max-width: var(--measure);
}

blockquote.her cite { display: block; margin-top: 0.7rem; font-style: normal; font-size: var(--step--1); }

.opening .plain { max-width: var(--measure); margin: 0; }

/* The photograph this page is still missing. Marked rather than faked, so
   the hole in the composition is visible. */
.photo-slot {
  grid-column: 1 / -1;
  justify-self: center;
  /* Two thirds of the side column. The centered photograph steps down
     when the copy takes the row, then keeps shrinking with the window.
     13rem is the narrowest frame that still holds this caption. */
  width: min(100cqi, max(13rem, calc((100cqi - 4.5rem) * 2 / 3 / (1.15 + 1))));
  aspect-ratio: 4 / 5;
  display: grid; place-content: center; gap: 0.5rem;
  padding: 2rem; text-align: center;
  border: 1.5px dashed var(--hairline);
  background: color-mix(in oklab, var(--panel) 70%, var(--paper));
  color: color-mix(in oklab, var(--ink) 62%, var(--paper));
  font-size: var(--step--1);
}
@media (min-width: 62rem) {
  .photo-slot { grid-column: 2; justify-self: stretch; width: auto; }
}

/* ---- The index of emblems ---------------------------------------------- */

/* The emblem row and the clothesline are empty until the module runs,
   which is after first paint. A reload saved at the bottom of the page
   paints the scene for as long as the page is too short to hold that
   scroll, so each list reserves the height it will have once filled.
   The row wraps with the sheet, and this section is that width. */
#cards { container: cards / inline-size; }

/* The rule is this list's top border, and the lede above it has no bottom
   margin, so without the margin here the two sit flush and the rule reads as
   underlining the sentence rather than opening the strip. It wants a little
   less air above than the 2.5rem below, so it still belongs to the emblems.
   One row of the 3.6rem buttons; the queries below add a row as they wrap. */
.emblems {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 0.3rem;
  list-style: none; margin: 2rem 0 0; padding: 2.5rem 0 0;
  border-top: 1.5px solid var(--hairline);
  min-height: calc(2.5rem + 3.6rem + 1.5px);
}

.emblems button {
  display: grid; place-items: center;
  width: 4.1rem; height: 3.6rem; padding: 0.45rem;
  border: 0; border-bottom: 2px solid transparent;
  background: none; cursor: pointer; font: inherit; color: inherit;
  text-decoration: none;
}

/* Every emblem is authored 36pt tall with whatever width it needs — the
   zucchini is 58 wide and the vinaigrette 22 — so a common height is the
   relationship they were drawn with, and the only one that reads as one set.
   They are therefore sized by height here and left to find their own width.

   Fitting them to a box instead would scale the wide ones down against the
   narrow ones and undo exactly that. Sizing by width does something worse:
   it makes a narrow emblem tall, and since a percentage height has no
   definite grid area to resolve against it overflows the button and hangs
   below the row. The height below is the most the widest can take inside
   the button's 3.2rem of content width. */
.emblems img { height: 1.95rem; width: auto; }
.emblems button:hover { border-bottom-color: var(--brick); }
.emblems [aria-current="true"] { border-bottom-color: var(--ink); }

/* Sixteen buttons, 4.1rem wide with 0.3rem between them. They fit one row
   down to 70.1rem of this section, then two, three, four and six. The
   query sits just short of each fit so a row that still fits is not given
   the height of the next wrap. */
@container cards (max-width: 70.09rem) {
  .emblems { min-height: calc(2.5rem + 3.6rem * 2 + 0.3rem + 1.5px); }
}
@container cards (max-width: 34.89rem) {
  .emblems { min-height: calc(2.5rem + 3.6rem * 3 + 0.3rem * 2 + 1.5px); }
}
@container cards (max-width: 26.09rem) {
  .emblems { min-height: calc(2.5rem + 3.6rem * 4 + 0.3rem * 3 + 1.5px); }
}
@container cards (max-width: 17.29rem) {
  .emblems { min-height: calc(2.5rem + 3.6rem * 6 + 0.3rem * 5 + 1.5px); }
}

.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* ---- The rack ----------------------------------------------------------- */

/* The rail and what hangs from it move together: the cards hang off the
   rail by its negative bottom margin, so lifting the rack lifts both.
   The line is the query container, so a sheet can be a share of it. */
.rack {
  position: relative; margin: 2.5rem -1.5rem 5rem;
  container: rack / inline-size;
  --inset: 1.5rem;
}

/* A hairline at each end of the line, so the row reads as something you
   can move along. Not a control: the cards underneath stay the thing you
   press. The one with nowhere to go fades. Centred on the hanging sheet. */
.nudge {
  position: absolute; z-index: 3; pointer-events: none; line-height: 0;
  top: calc(13px + var(--hanger-w) * 612 / 396 / 2); transform: translateY(-50%);
  color: color-mix(in oklab, var(--ink) 38%, transparent);
  transition: opacity 280ms ease;
}
.nudge-prev { left: 0.35rem; }
.nudge-next { right: 0.35rem; }
.nudge.is-spent { opacity: 0; }

.rail { display: block; width: 100%; height: 26px; margin-bottom: -13px; position: relative; z-index: 2; }
.rail .bar { stroke: var(--rail); stroke-width: 2.5; }
.rail .tendril {
  stroke: var(--leaf); stroke-width: 2; fill: none; stroke-linecap: round;
  stroke-dasharray: 1400; stroke-dashoffset: 1400;
  animation: run 2.6s 300ms cubic-bezier(.2,.7,.3,1) forwards;
}
@keyframes run { to { stroke-dashoffset: 0; } }

/* A third of the card it is a picture of, until the line is narrower than
   that. Then it follows the line. Nothing else sets the size: the nudges
   centre on it and the reserved height is measured off it.

   A fraction of --card-w rather than a figure of its own, so a thumbnail
   stays tied to the real card. A fraction of it rather than the thing
   itself, because --card-w is the width the card is held forward at: using
   it here hung sixteen full-size cards off the line, the same size as the
   one below them, two visible at a time. */
.hangers, .nudge {
  --hanger-w: min(calc(var(--card-w) / 3), 100cqi - 2 * var(--inset));
}

.hangers {
  display: flex; gap: 1.1rem; padding: 0 var(--inset) 2.5rem;
  overflow-x: auto; scroll-snap-type: x proximity; overscroll-behavior-x: contain;
  scrollbar-width: thin; scrollbar-color: var(--hairline) transparent;
}
.hanger {
  flex: 0 0 auto; width: var(--hanger-w); scroll-snap-align: center;
  border: 0; padding: 0; background: none; cursor: pointer;
  /* A button keeps the system's own type and colour unless it is told not
     to, the same as the emblems above it. Without this the caption sets in
     Arial and prints pure black, a shade the page has nowhere else. */
  font: inherit; color: inherit;
  transform-origin: 50% 0; transform: rotate(var(--tilt, 0deg));
  transition: transform 260ms cubic-bezier(.2,.7,.3,1);
}
.hanger:nth-child(odd)  { --tilt: -0.9deg; }
.hanger:nth-child(even) { --tilt:  1.1deg; }
.hanger:nth-child(3n)   { --tilt: -0.3deg; }
.hanger:hover, .hanger:focus-visible { transform: rotate(0deg) translateY(4px); outline: none; }
.hanger:focus-visible .paper { box-shadow: 0 0 0 2px var(--brick); }
.hanger .caption { display: block; margin-top: 0.7rem; font-size: var(--step--1); line-height: 1.3; text-align: left; }

/* Each card's own words hang with it — the binomial and the line about
   keeping it — for the aside to copy when the card is picked up. The
   caption is the only one of the three on show. */
.hanger .sub, .hanger .blurb { display: none; }

/* ---- A sheet of paper --------------------------------------------------- */

.paper {
  position: relative; display: block; aspect-ratio: 396 / 612;
  background: var(--paper);
  clip-path: polygon(0.2% 0.1%, 99.9% 0%, 100% 24%, 99.7% 61%, 100% 99.8%,
                     38% 100%, 0.1% 99.9%, 0.3% 48%, 0% 12%);
  box-shadow: 0.4px 1px 2px color-mix(in oklab, var(--cast) 60%, transparent),
              2px 7px 18px -6px var(--cast);
}
.paper img { display: block; width: 100%; height: auto; }

/* ---- The card held forward ---------------------------------------------- */

.forward { display: grid; gap: 3rem 4rem; grid-template-columns: minmax(0, 1fr); align-items: start; }
/* Side by side, the two columns are nothing like the same height: the card
   is a half-letter sheet and the words beside it are a few lines. Hanging
   them from a shared top line leaves the right-hand column stranded against
   a foot of empty paper. Centred against the card, it reads as a caption to
   the thing rather than as a column that ran out. Stacked, there is no
   second column to be level with, so that case keeps its top line. */
@media (min-width: 64rem) {
  .forward { grid-template-columns: var(--card-w) minmax(0, 1fr); align-items: center; }
}

.held { position: relative; width: min(100%, var(--card-w)); }
.voice { position: relative; }
.voice > * { transition: opacity 80ms ease; }
.voice > [data-voice]:not([data-shown]) { position: absolute; inset: 0; opacity: 0; }
.voice > [data-shown] { opacity: 1; }

.aside h2 { font-size: var(--step-2); line-height: 1.1; margin: 0 0 0.9rem; }
.aside p { max-width: var(--measure); }

.switch { display: inline-flex; margin: 1.6rem 0 0; border: 1.5px solid var(--ink); }
.switch button { border: 0; background: none; font: inherit; font-size: var(--step--1); padding: 0.55rem 1.1rem; cursor: pointer; color: var(--ink); }
.switch button + button { border-left: 1.5px solid var(--ink); }
.switch button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* Its own line under the switch, and no wider than its words: a block that
   took the column would put a green band across it. The gap belongs to this
   one margin — the switch gives up its own, so there is a single number
   between them rather than two that have to be added up. */
.take { display: block; width: max-content; margin-top: 2rem; padding: 0.8rem 1.6rem; background: var(--leaf); color: var(--paper); text-decoration: none; font-family: Bitter, Georgia, serif; }
.take:hover { background: color-mix(in oklab, var(--leaf) 86%, var(--ink)); }
.fine { font-size: var(--step--1); margin-top: 0.9rem; }

/* ---- The print script --------------------------------------------------- */

/* The hairline is the middle of the break: the same distance from the
   prose it follows and the prose it opens. */
.script { margin-top: 4rem; padding-top: 4rem; border-top: 1.5px solid var(--hairline);
          display: grid; gap: 2.5rem 4rem; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 52rem) { .script { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.script h2 { font-size: var(--step-1); margin: 0 0 0.75rem; }
.script blockquote { margin: 0; padding-left: 1.1rem; border-left: 2px solid var(--leaf); font-style: italic; }
.script .fine { margin-bottom: 0; }
.script dl { margin: 0; }
.script dt { font-weight: 700; margin-top: 0.8rem; }
.script dd { margin: 0; }

/* ---- The way to the season ---------------------------------------------- */

.doorway {
  display: block; margin: 4rem 0 0; padding: 4rem 0;
  border-top: 1.5px solid var(--hairline); border-bottom: 1.5px solid var(--hairline);
  text-decoration: none;
  transition: border-color 200ms ease;
}
/* The whole block is the link, so the rules it sits between are what answer
   to the pointer. The clothesline's own colour, which is ink carrying a
   little leaf — no second colour enters the page to say a link is under
   the cursor. */
.doorway:hover { border-color: var(--rail); }
.doorway h2 { font-size: var(--step-2); margin: 0 0 0.6rem; }
.doorway p { margin: 0; max-width: var(--measure); }

footer.close { padding: 3rem 0 5rem; font-size: var(--step--1); text-align: center; }
footer.close p { margin: 0; }
/* A step under the line it follows, and quieter than it. Opacity rather
   than a colour: this footer sets on cream on one page and on the season's
   deep green on the other, and a mixed ink would be wrong on one of them. */
footer.close .credits { margin-top: 0.9rem; opacity: 0.78; }

/* ---- The season's one-sheet --------------------------------------------- */
/* A different register on purpose: this page is the film, not the card, so
   it is the one place the set is allowed to wink. */

body.season { background: var(--deep); color: var(--paper); }
body.season .view { background: var(--deep); }
body.season .ribbon { border-bottom-color: color-mix(in oklab, var(--paper) 24%, transparent); }

.one-sheet { padding: 4rem 0 3rem; text-align: center; }

.one-sheet h1 {
  font-family: Bitter, Georgia, serif; font-weight: 700;
  font-size: var(--step-4); line-height: 0.88;
  letter-spacing: -0.03em; margin: 0 0 1.4rem; text-wrap: balance;
}

.one-sheet .tagline { font-style: italic; font-size: var(--step-1); margin: 0 auto 2.5rem; max-width: 40rem; }

/* Sixteen drawings on one line once the sheet is wide enough. A slot as wide
   as the zucchini wraps the line early, so the slot is the drawing. */
.cast {
  display: grid; grid-template-columns: repeat(2, 1fr);
  justify-items: center; align-items: end;
  gap: 0.8rem 0.4rem; margin: 0; padding: 0; list-style: none;
}
@media (min-width: 36rem) { .cast { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 77rem) {
  .cast {
    display: flex; flex-wrap: nowrap; justify-content: center; align-items: flex-end;
    gap: 0.35rem; margin-inline: -1.5rem;
  }
}
.cast li { display: flex; justify-content: center; }
.cast img { height: 4.2rem; width: auto; }

/* The schedule, played dead straight. */
/* The schedule is the one thing on this page that wants the whole desk. The
   sheet caps everything else at 78rem, which on a wide monitor strands the
   table in the middle of the screen at half its width, so this one breaks
   out to three quarters of the viewport and centres itself on the overflow.

   max() is what keeps it honest: below about 1600px three quarters of the
   viewport is narrower than the sheet already is, the width resolves to
   100%, the margin resolves to zero and nothing moves. It can only ever
   widen the table, never pinch it. */
.schedule {
  width: max(100%, 75vw);
  margin: 5rem calc((100% - max(100%, 75vw)) / 2) 0;
  border-top: 1px solid color-mix(in oklab, var(--paper) 24%, transparent);
  container-type: inline-size;
}

/* One grid for the whole table, so the columns stay aligned while they
   compress. Below the composed width the note steps under the title. */
#scenes {
  display: grid; column-gap: 2rem;
  grid-template-columns: max-content max-content minmax(0, 1fr);
}
.scene {
  display: grid; grid-column: 1 / -1; grid-template-columns: subgrid;
  row-gap: 0.3rem; padding: 1.5rem 0;
  border-bottom: 1px solid color-mix(in oklab, var(--paper) 14%, transparent);
  align-items: baseline;
}
.scene .when { grid-column: 1; grid-row: 1; font-family: Bitter, Georgia, serif; }
.scene .mark-slot {
  grid-column: 2; grid-row: 1; align-self: center;
  display: flex; justify-content: center;
}
.scene .mark-slot img { height: 3.2rem; width: auto; }
.scene .what {
  grid-column: 3; grid-row: 1; align-self: center;
  font-family: Bitter, Georgia, serif; font-size: var(--step-1);
}
.scene .note { grid-column: 3; grid-row: 2; max-width: 39rem; }

/* The note keeps the measure of the wide composition, about two lines.
   Narrowing spends the empty width in the date, the emblem and the title
   before that measure moves. 70rem is where those three have reached their
   content and the note still fits. */
@container (min-width: 70rem) {
  #scenes {
    grid-template-columns:
      minmax(max-content, 9rem)
      minmax(max-content, 6.4rem)
      minmax(max-content, 24rem)
      39rem;
  }
  .scene .when,
  .scene .mark-slot,
  .scene .what,
  .scene .note { grid-column: auto; grid-row: auto; }
  /* The date stays on the first line of the note. The emblem and the title
     are single objects, and sitting them on that line strands them against
     the note's second line, so they centre on the row. */
  .scene .mark-slot, .scene .what { align-self: center; }
}
.scene .note { color: color-mix(in oklab, var(--paper) 78%, var(--deep)); }

@media (prefers-reduced-motion: reduce) {
  .rail .tendril { animation: none; stroke-dashoffset: 0; }
  .hanger, .voice > *, .nudge { transition: none; }
}

/* ---- The splash --------------------------------------------------------- */
/* A photograph of the garden, with the near planting drawn over it and moving.
   The drawn layers are flat silhouettes in the card's own greens, so the
   foreground belongs to the set even when the background is a photograph. */

.splash {
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  isolation: isolate;
  /* The drawing covers this. It is here so the title, which comes first,
     has the field before the drawing has arrived. */
  --field: radial-gradient(120% 90% at 50% 12%, #cfd7bd 0%, #b9c5a2 38%, #8fa479 68%, #5f7450 100%);
  background: var(--field);
}

.splash-image,
.canopy,
.petals,
.understory,
.rufus { filter: var(--grade, none); }

/* Where the image goes. Marked, not faked. */
.splash-image {
  position: absolute; inset: 0; z-index: 0;
  background: var(--field);
}

/* Depth: a wash that lifts the type off the picture without a black scrim.
   It is the first element in the splash, so the level has to put it over
   the sun. The grass follows at this same level and paints in front. */
.splash-wash {
  position: absolute; inset: 0; z-index: 3;
  background:
    radial-gradient(44rem 22rem at 50% 38%,
      color-mix(in oklab, var(--ink) 58%, transparent), transparent 76%),
    linear-gradient(180deg,
      color-mix(in oklab, var(--ink) 14%, transparent) 0%,
      transparent 26%,
      transparent 74%,
      color-mix(in oklab, var(--deep) 34%, transparent) 100%);
  pointer-events: none;
}

.canopy, .understory, .petals { position: absolute; z-index: 1; pointer-events: none; }

.canopy { top: -2%; left: -4%; width: min(58rem, 76%); transform-origin: 12% 0%; }
.understory { bottom: -1%; left: 0; width: 100%; aspect-ratio: 1200 / 90;
              transform-origin: 50% 100%; z-index: 3; }

/* Wind. Two layers at different rates, both slow, both tiny — a branch
   loaded with blossom barely moves, and anything larger reads as weather. */
.canopy     { animation: sway-branch 11s ease-in-out infinite alternate; }
.understory { animation: sway-grass  7s ease-in-out infinite alternate; }

@keyframes sway-branch {
  from { transform: rotate(-0.7deg) translateX(-0.3%); }
  to   { transform: rotate(0.9deg)  translateX(0.5%); }
}
@keyframes sway-grass {
  from { transform: skewX(-0.8deg); }
  to   { transform: skewX(1.1deg); }
}

.petals { inset: 0; z-index: 2; }
.petal {
  position: absolute; width: 9px; height: 7px;
  background: #e9c4c9;
  border-radius: 60% 40% 55% 45%;
  opacity: 0;
  animation: fall var(--dur, 14s) linear var(--delay, 0s) infinite;
}
@keyframes fall {
  0%   { opacity: 0; transform: translate3d(0, -8vh, 0) rotate(0deg); }
  8%   { opacity: 0.9; }
  88%  { opacity: 0.75; }
  100% { opacity: 0; transform: translate3d(var(--drift, 14vw), 104vh, 0) rotate(420deg); }
}

/* The type sits in the sky, above the fence line, not on the shrubs. */
.splash { grid-template-rows: 1fr auto 1.35fr; }
.splash-type { position: relative; z-index: 4; text-align: center; padding: 0 1.5rem; grid-row: 2; color: #fff; }

.splash-type h1 {
  font-family: Bitter, Georgia, serif; font-weight: 400;
  font-size: var(--step-4); line-height: 0.95; letter-spacing: -0.02em;
  margin: 0; color: #fff;
  text-shadow: 0 1px 24px color-mix(in oklab, var(--ink) 50%, transparent);
  text-wrap: balance;
}

.splash-type .from {
  display: block; margin-top: 1rem;
  font-style: italic; font-size: var(--step-1);
  color: #fff;
}

.splash-type .cue {
  display: inline-block; margin-top: 3.5rem;
  font-size: var(--step--1); color: #fff;
  text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 0.2rem;
}

/* ---- Section headings across the composition ----------------------------- */

.part { padding: 4.5rem 0 1rem; }
.part > h2 { font-size: var(--step-3); line-height: 1; margin: 0 0 0.8rem; letter-spacing: -0.015em; }
.part > .lede { max-width: var(--measure); margin: 0; font-size: var(--step-1); }

@media (prefers-reduced-motion: reduce) {
  .canopy, .understory, .petal { animation: none; }
  .petal { opacity: 0.6; }
}

/* ---- The backyard, and what is moving through it ------------------------ */

/* Scoped to the splash on purpose. The season page calls each row of its
   schedule a scene too, and both pages load this one stylesheet, so an
   unscoped rule here reached sixteen schedule rows two hundred lines above
   and laid every one of them absolutely at inset 0, stacked, with their
   container collapsed to nothing. The backyard SVG is only ever inside the
   splash; the rows are never in it. Keep the descendant. */
.splash .scene { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* Wings and bodies pivot where they actually join. The flight paths, the
   flapping and the butterflies' wandering are generated into birds.css,
   because they are too many keyframes to write by hand and too regular to
   fake with a few. */

.butterfly .flutter, .bee .flutter {
  transform-box: fill-box; transform-origin: 50% 50%;
}
/* Off screen they stop. Six insects beating eleven to fourteen times a
   second is the yard's whole standing cost, and the canopy, the grass and
   the petals are the same redraw through the hour's filter. None of it is
   watched once the splash has scrolled away. The birds stay: they are on
   keyframes the page waits to end. */
.splash.resting .butterfly, .splash.resting .butterfly *,
.splash.resting .bee, .splash.resting .bee *,
.splash.resting .canopy,
.splash.resting .understory,
.splash.resting .petal { animation-play-state: paused; }

.butterfly .flutter { animation: flutter 95ms ease-in-out infinite alternate; }
.bee .flutter { transform-origin: 50% 90%; animation: flutter 70ms linear infinite alternate; }
@keyframes flutter { from { transform: scaleX(1); } to { transform: scaleX(0.24); } }

/* The up-and-down a butterfly never stops doing, independent of where it is
   heading. */
@keyframes bob { from { transform: translateY(-6px); } to { transform: translateY(6px); } }
.butterfly .bob { transform-box: fill-box; }

.bird .wing { transform-box: fill-box; transform-origin: 94% 34%; }
.bird .hop  { transform-box: fill-box; transform-origin: 22% 62%; }

/* A stature turns on four joints, each at the place the bird actually bends:
   the hip it stands up on, the neck, the base of the tail, the shoulder.
   Measured in the bird's own units, where its body centre is the origin. */
.bird .stance { transform-origin: 0px 6px; }
.bird .poise  { transform-origin: 7px -3px; }
.bird .plume  { transform-origin: -8px -1px; }
.bird .fold   { transform-origin: 1px -4px; }

/* Bees work a flower at a time: dart, hold, dart. */
.bee-a { animation: work-a 11s steps(1, end) infinite; }
.bee-b { animation: work-b 13s steps(1, end) infinite; }
.bee-c { animation: work-c 9s  steps(1, end) infinite; }

@keyframes work-a {
  0%, 18%   { transform: translate(246px, 604px); }
  20%, 42%  { transform: translate(300px, 588px); }
  44%, 66%  { transform: translate(268px, 624px); }
  68%, 100% { transform: translate(212px, 610px); }
}
@keyframes work-b {
  0%, 22%   { transform: translate(612px, 622px); }
  24%, 48%  { transform: translate(560px, 596px); }
  50%, 74%  { transform: translate(650px, 606px); }
  76%, 100% { transform: translate(604px, 632px); }
}
@keyframes work-c {
  0%, 20%   { transform: translate(1004px, 616px); }
  22%, 46%  { transform: translate(1062px, 598px); }
  48%, 72%  { transform: translate(1024px, 632px); }
  74%, 100% { transform: translate(980px, 604px); }
}

@media (prefers-reduced-motion: reduce) {
  .butterfly, .bee, .bird, .bird .hop, .bird .wing,
  .butterfly .bob, .flutter, .squirrel, .squirrel-run, .squirrel-climb,
  .squirrel-tail, .squirrel-plume, .squirrel-head,
  .squirrel-fore, .squirrel-forearm,
  .squirrel-hind, .squirrel-shank { animation: none; }
  .fly-a { transform: translate(300px, 612px); }
  .fly-b { transform: translate(660px, 600px); }
  .fly-c { transform: translate(1030px, 620px); }
  .bee-a { transform: translate(246px, 604px); }
  .bee-b { transform: translate(612px, 622px); }
  .bee-c { transform: translate(1004px, 616px); }
}

/* Rufus, out of his own card, sitting at the very edge of the frame with
   most of himself still off it. Pinned to the splash rather than drawn into
   the scene, because the scene's left edge moves with the viewport and his
   does not. The emblem faces left, so he is flipped to look into the garden. */
.rufus {
  position: absolute;
  left: -8px;
  bottom: 72px;        /* the grass starts at 75px, so his feet are on the
                          mulch's lower edge */
  width: 33px;
  z-index: 2;
  transform: scaleX(-1);
  pointer-events: none;
}

@media (max-width: 40rem) {
  .rufus { width: 26px; bottom: 56px; left: -6px; }
}

/* A bird driven off does not finish its errand: the page freezes it where it
   stands, drops the errand, and transitions it off. Wings go the whole way —
   a hand-written flap, because this one is not on any generated clock. */
.bird.fleeing .wing {
  animation: panic 80ms linear infinite alternate;
}

@keyframes panic {
  from { transform: rotate(-58deg); }
  to   { transform: rotate(26deg); }
}
