/* ==========================================================================
   The garden gate.

   Three things vary independently, and each owns strictly separate visual
   territory so that they cannot multiply into thirty two hand made palettes:

     data-theme   light | night     every text and interactive colour
     data-season  spring ... winter the drawing, and nothing else
     data-light   dawn ... night    a wash over the paper, and nothing else

   Because season and light never touch a text colour, the contrast surface
   stays the two themes it has always been. The wash is mixed into the paper
   at a capped strength rather than hand authored, so every intermediate
   value is bounded by the two extremes and checking those is enough.

   Tokens keep the names used in site/identity.css so the three stylesheets
   in this repository read alike.
   ========================================================================== */

:root {
  color-scheme: light;

  /* The palette proper. `-base` means "before the daily wash", and those two
     are the only paper tokens anything may use as a FOREGROUND. */
  --paper-base: #e8eee8;
  --paper-raised-base: #f8f7f0;
  --ink: #26372e;
  /* The dashboard's #68766d measures 4.05:1 on this paper, which is under AA
     before any wash and worse after one. Darkened here rather than there:
     the landing sets long-form muted text on washed paper, and the dashboard
     does not. 5.45:1 clean, 5.15:1 under the heaviest wash. */
  --ink-muted: #556257;
  --line: #bdcabf;
  /* The control border. #7f8f84 measured 2.85:1 against the field at the
     night wash and 2.96:1 at dawn, under the 3:1 that a form field needs:
     the wash darkens the field faster than it darkens the border, and the
     border is the only thing telling the control from the card. This holds
     3.55:1 at the worst band. */
  --line-strong: #717f75;
  --blue: #315d50;
  --blue-soft: #d7e3d8;
  --blossom: #e7a6ba;
  --blossom-lt: #f2c8d4;
  --blossom-dp: #d98ea6;
  --bark: #5f4150;
  /* Likewise #9c4f70 lands at 4.47:1 under the dusk wash, and every use of
     plum here is small uppercase mono. 4.96:1 at worst. */
  --plum: #94486a;
  --brass: #c7a342;
  --mint: #52806a;

  /* Text on a dark fill. Never --paper or --paper-raised: those carry the
     daily wash, and a wash must never reach a foreground. */
  --on-ink: var(--paper-base);
  --on-accent: var(--paper-raised-base);

  --shadow: 0 24px 64px rgba(38, 55, 46, 0.11);
  --display: "Newsreader", Georgia, serif;
  --body: "IBM Plex Sans", "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", Consolas, monospace;

  /* Daylight, which is to say no wash at all. Overridden per data-light. */
  --wash: #e8d8b0;
  --wash-paper: 0%;
  --wash-raised: 0%;
  --glow: #f2e6c4;
  --glow-strength: 0%;
  /* The brass, as the hour finds it. Only ever used inside the drawing, so
     it can move freely: nothing reads it as text. */
  --glint: var(--brass);
  --bud-fill: var(--blossom-dp);

  /* Leaves in their spring colour. Overridden per data-season, and set
     just below inside the color-mix feature query, mixed against
     --paper-raised-BASE on purpose: against the washed token the leaf
     would become a function of season and hour together, and the two axes
     would start multiplying again. */
  --leaf-stroke: var(--blue);
}

[data-theme="night"] {
  color-scheme: dark;
  --paper-base: #14221b;
  --paper-raised-base: #1d2d25;
  --ink: #eef1e9;
  --ink-muted: #acb9ae;
  --line: #405448;
  --line-strong: #6d8377;
  --blue: #9bc2ad;
  --blue-soft: #29463a;
  --blossom: #dda5b2;
  --blossom-lt: #ecc5cf;
  --blossom-dp: #c9899a;
  --bark: #b58f9b;
  --plum: #e0afc0;
  --brass: #ddc66d;
  --mint: #85b79c;
  --shadow: 0 24px 64px rgba(0, 0, 0, 0.28);
}

/* --------------------------------------------------------------------------
   The hour of the day.

   Dusk is deliberately the warmest and the strongest: it is the hour the
   whole idea is for. Night is not simply darker, it is lamplit, which is why
   its wash is warm in both themes rather than blue. Dawn is the only cool
   one. Strengths stay at or under 9% so no pairing can drift far.
   -------------------------------------------------------------------------- */

[data-light="dawn"] {
  --wash: #b9c8dd;
  --wash-paper: 6%;
  --wash-raised: 4%;
  --glow: #cddaea;
  --glow-strength: 30%;
  --glint: #b39a5e;
}

[data-light="day"] {
  --wash: #e8d8b0;
  --wash-paper: 0%;
  --wash-raised: 0%;
  --glow: #f2e6c4;
  --glow-strength: 16%;
  --glint: #c7a342;
}

[data-light="dusk"] {
  --wash: #e9a45e;
  --wash-paper: 9%;
  --wash-raised: 7%;
  --glow: #f0b268;
  --glow-strength: 46%;
  --glint: #d98a28;
}

[data-light="night"] {
  --wash: #c98b46;
  --wash-paper: 7%;
  --wash-raised: 6%;
  --glow: #d99a4e;
  --glow-strength: 34%;
  --glint: #bd8f3c;
}

/* In the night theme the paper is already dark, so the same washes read as
   embers rather than daylight. Dusk stays the warmest of the four. */
[data-theme="night"][data-light="dawn"] {
  --wash: #7d93b4;
  --wash-paper: 7%;
  --wash-raised: 6%;
  --glow: #46597a;
  --glow-strength: 34%;
}

[data-theme="night"][data-light="dusk"] {
  --wash: #d08238;
  --wash-paper: 9%;
  --wash-raised: 8%;
  --glow: #a8622a;
  --glow-strength: 52%;
}

[data-theme="night"][data-light="night"] {
  --wash: #a86a2e;
  --wash-paper: 6%;
  --wash-raised: 5%;
  --glow: #8a5222;
  --glow-strength: 40%;
}

/* The wash reaches the page only here. Everything else uses --paper and
   --paper-raised exactly as it would without a clock.

   The @supports guard is load bearing and the obvious shortcut does not
   work. Declaring the flat value and then the color-mix value in the same
   block looks like a fallback, but custom property declarations are not
   syntax-checked when they are parsed: an engine without color-mix() keeps
   the second declaration, it wins the cascade, and the var() substitution
   fails later at computed-value time. background-color then resolves to
   transparent, which in the night theme is pale text on a white page.
   Measured in Chrome: doubling works for a normal property and never for
   a custom one. The feature query is the only thing that actually keeps
   the flat colour. Affects Safari before 16.2 and iOS before 16.2. */
:root {
  --paper: var(--paper-base);
  --paper-raised: var(--paper-raised-base);
  --leaf-fill: #dfe7dd;
}

@supports (color: color-mix(in srgb, red, blue)) {
  :root {
    --paper: color-mix(in srgb, var(--wash) var(--wash-paper), var(--paper-base));
    --paper-raised: color-mix(in srgb, var(--wash) var(--wash-raised), var(--paper-raised-base));
    --leaf-fill: color-mix(in srgb, var(--mint) 26%, var(--paper-raised-base));
  }
}

/* --------------------------------------------------------------------------
   The time of year. The drawing only.
   -------------------------------------------------------------------------- */

[data-season="summer"] {
  --leaf-fill: color-mix(in srgb, var(--mint) 52%, var(--paper-raised-base));
  --leaf-stroke: color-mix(in srgb, var(--blue) 86%, var(--ink));
}

/* A turning leaf needs its outline to stay well clear of its blade or the
   midrib and veins disappear and it reads as a seed pod. Both themes get an
   explicit pairing rather than a mix of two tokens that happen to sit close
   together. */
[data-season="autumn"] {
  --leaf-fill: color-mix(in srgb, var(--brass) 54%, var(--blossom-dp));
  --leaf-stroke: #7a4526;
}

[data-theme="night"][data-season="autumn"] {
  --leaf-fill: #8d5a2c;
  --leaf-stroke: #e3b473;
}

/* A winter bud is tight and woody, not pink. Nothing is about to open. */
[data-season="winter"] {
  --bud-fill: color-mix(in srgb, var(--bark) 68%, var(--blossom-dp));
}

/* The night theme's bark is a mauve pink, which is fine as a hairline on the
   dashboard's plate and wrong as a bough eight pixels wide: it reads as
   blossom, and the fruit hanging off it came out the same colour. This was
   scoped to winter on the strength of a review note; a phone screenshot in
   August showed it is every season.

   Contrast was never the problem, the mauve measured 5.78 against the night
   paper. Hue was. This keeps the luminance and takes the pink out, so the
   wood reads as wood and the blossom has something to sit against. */
[data-theme="night"] {
  --bark: #9d8378;
}

/* Fruit needs its own pairing on dark ground for the same reason the autumn
   leaf did: mixed from plum and bark it landed inside the branch's own
   colour family. A lighter rim is what separates a dark object from a dark
   background. */
[data-theme="night"] .cherry {
  fill: #b0455f;
  stroke: #e2a3b6;
}

/* Show one season's parts and hide the other three. A page that never runs
   boot.js carries no data-season at all, so the bare rule below has to be a
   finished composition rather than a fallback: it gets the tree in blossom. */
html:not([data-season]) :is(.only-summer, .only-autumn, .only-winter) { display: none; }
[data-season="spring"] :is(.only-summer, .only-autumn, .only-winter) { display: none; }
[data-season="summer"] :is(.only-spring, .only-autumn, .only-winter) { display: none; }
[data-season="autumn"] :is(.only-spring, .only-summer, .only-winter) { display: none; }
[data-season="winter"] :is(.only-spring, .only-summer, .only-autumn) { display: none; }
[data-season="winter"] .not-winter { display: none; }

/* The field note changes monthly. It is done with twelve spans and an
   attribute rather than by script so that it is correct in the first frame
   and cannot flash the wrong month. */
.field-note > span { display: none; }
.field-note > .fn-any { display: inline; }
[data-month] .field-note > .fn-any { display: none; }
[data-month="1"] .field-note > .fn-1,
[data-month="2"] .field-note > .fn-2,
[data-month="3"] .field-note > .fn-3,
[data-month="4"] .field-note > .fn-4,
[data-month="5"] .field-note > .fn-5,
[data-month="6"] .field-note > .fn-6,
[data-month="7"] .field-note > .fn-7,
[data-month="8"] .field-note > .fn-8,
[data-month="9"] .field-note > .fn-9,
[data-month="10"] .field-note > .fn-10,
[data-month="11"] .field-note > .fn-11,
[data-month="12"] .field-note > .fn-12 { display: inline; }

/* ==========================================================================
   Base
   ========================================================================== */

* { box-sizing: border-box; }

img { max-width: 100%; height: auto; }

/* Keep the botanical artwork inside the visual viewport. Several SVG paths
   deliberately extend beyond their viewBox; without a root clip Safari can
   turn that decorative overflow into a horizontally pannable page. `hidden` is the older Safari fallback; `clip` avoids creating a scroll container.
   Touch manipulation removes accidental double-tap zoom while preserving
   intentional pinch-to-zoom for accessibility. */
html {
  width: 100%;
  max-width: 100%;
  overflow-x: hidden;
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
  touch-action: manipulation;
}

body {
  width: 100%;
  max-width: 100%;
  min-height: 100dvh;
  overflow-x: hidden;
  overflow-x: clip;
  /* No min-width. A 320px viewport has about 305px of layout width once a
     classic scrollbar is subtracted, so a 320px floor forced the body wider
     than the viewport and produced a horizontal scrollbar on every page.
     Nothing here needs a floor. */
  margin: 0;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background:
    radial-gradient(120% 70% at 50% 0%, color-mix(in srgb, var(--glow) var(--glow-strength), transparent), transparent 70%),
    radial-gradient(circle at 12% 18%, color-mix(in srgb, var(--ink) 4%, transparent) 0 0.7px, transparent 0.9px),
    radial-gradient(circle at 74% 62%, color-mix(in srgb, var(--ink) 3%, transparent) 0 0.8px, transparent 1px),
    var(--paper);
  background-size: auto, 17px 19px, 23px 29px, auto;
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
  transition: background-color 600ms ease, color 400ms ease;
}

button { font: inherit; }
a { color: inherit; }

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

.skip-link {
  position: fixed;
  z-index: 100;
  top: 12px;
  left: 12px;
  padding: 10px 14px;
  background: var(--ink);
  color: var(--on-accent);
  text-decoration: none;
  transform: translateY(-160%);
}
.skip-link:focus { transform: translateY(0); }

/* ==========================================================================
   Top bar
   ========================================================================== */

.topbar {
  width: min(100% - 44px, 1320px);
  min-height: 84px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.wordmark {
  display: flex;
  align-items: baseline;
  gap: 2px;
  color: var(--ink);
  text-decoration: none;
  font-family: var(--display);
  font-size: 27px;
  font-weight: 500;
  letter-spacing: -0.035em;
}

.wordmark-mark {
  width: 36px;
  height: 36px;
  margin-right: 9px;
  align-self: center;
}
.wordmark-domain { color: var(--plum); }

.sakura-mark .mark-petals use { stroke: var(--plum); stroke-width: 3.4; stroke-linejoin: round; }
.sakura-mark .petal-lt { fill: var(--blossom-lt); }
.sakura-mark .petal-md { fill: var(--blossom); }
.sakura-mark .mark-stamens path { fill: none; stroke: var(--brass); stroke-width: 2.6; stroke-linecap: round; }
.sakura-mark .mark-stamens circle { fill: var(--brass); }
.sakura-mark .mark-eye { fill: var(--brass); stroke: var(--paper-raised); stroke-width: 3.4; }

/* Wraps rather than overflowing. The contact page keeps both links at
   every width, and with the WCAG text-spacing overrides applied the row
   grew past a 320px viewport and pushed the theme control off the side.
   The bar is min-height, so a second row is safe. */
.topbar-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  row-gap: 6px;
}

.topbar-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid color-mix(in srgb, var(--plum) 34%, transparent);
  border-radius: 999px;
  color: var(--plum);
  text-decoration: none;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: border-color 160ms ease, background-color 160ms ease;
}

.topbar-link:hover,
.topbar-link:focus-visible {
  border-color: var(--plum);
  background: color-mix(in srgb, var(--blossom) 16%, transparent);
}

.icon-button {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid color-mix(in srgb, var(--ink) 24%, transparent);
  border-radius: 50%;
  background: color-mix(in srgb, var(--paper-raised) 52%, transparent);
  color: var(--ink-muted);
  cursor: pointer;
  transition: color 160ms ease, border-color 160ms ease, transform 300ms ease;
}

.icon-button:hover { border-color: var(--plum); color: var(--plum); transform: rotate(18deg); }
.icon-button svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.icon-moon { display: none; }
[data-theme="night"] .icon-sun { display: none; }
[data-theme="night"] .icon-moon { display: block; }

/* ==========================================================================
   The sheet
   ========================================================================== */

.sheet {
  width: min(100% - 44px, 1320px);
  margin: 0 auto;
  display: grid;
  grid-template-rows: auto 1fr;
  align-content: center;
}

.specimen-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* The bough is sized against the height as well as the width. Its aspect
   ratio is fixed, so on a short window an unconstrained width is what
   pushes the tags and the footer off the bottom: 116vh is the largest
   value that still leaves room for the whole gate at 1440 by 900. */
.canopy {
  width: min(100%, 1240px, 116vh);
  margin: 0 auto;
  pointer-events: none;
}

.specimen { display: block; width: 100%; height: auto; overflow: visible; }
.specimen-tall { display: none; }

/* The pointer nudges the canopy a little, and the two custom properties it
   writes are the only thing the handler touches.

   Registering them matters more than it looks. As ordinary custom
   properties they inherit, so each write invalidated style for all 274
   nodes below .canopy, including 59 <use> elements and their shadow
   trees: measured at 47x to 470x the cost of writing the transform
   directly, and at or over a 60fps frame budget on a desktop machine.
   inherits: false confines that to .canopy. An engine without @property
   falls back to the old behaviour rather than breaking. */
@property --px {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@property --py {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
.canopy {
  transform: translate3d(calc(var(--px, 0) * 7px), calc(var(--py, 0) * 4px), 0);
  /* Without this the bough is welded 1:1 to the cursor and reads as twitch.
     The lag is what makes it feel like weight. */
  transition: transform 520ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* ==========================================================================
   The specimen drawing
   ========================================================================== */

.bough { fill: none; stroke: var(--bark); stroke-linecap: round; }
.w9 { stroke-width: 10; }
.w7 { stroke-width: 7.6; }
.w5 { stroke-width: 5.4; }
.w4 { stroke-width: 4; }
.w3 { stroke-width: 3; }
.w2 { stroke-width: 2.1; }

.junctions circle { fill: var(--paper-raised); stroke: var(--glint); stroke-width: 1.6; }

/* Petals in the light DOM: the three or four that fall. Everything inside a
   <use> is painted by the inline styles in the defs block, because a
   selector cannot reach in there. */
.petal-lt, .petal-md {
  stroke: color-mix(in srgb, var(--plum) 85%, transparent);
  stroke-width: 3.4;
  stroke-linejoin: round;
}
.petal-lt { fill: var(--blossom-lt); }
.petal-md { fill: var(--blossom); }

.leaf-fallen { fill: var(--leaf-fill); stroke: var(--leaf-stroke); stroke-width: 1.6; stroke-linejoin: round; }

/* Deeper than the blossom on purpose. In --blossom-dp the fruit sat in the
   same pink family, at a similar size, in the same places, and spring and
   summer were hard to tell apart from three feet away. Four states that
   cannot be distinguished at a glance are not a clock. */
.cherry {
  fill: color-mix(in srgb, var(--plum) 62%, var(--bark));
  stroke: color-mix(in srgb, var(--bark) 80%, transparent);
  stroke-width: 2.2;
}
.stalk { fill: none; stroke: var(--blue); stroke-width: 1.6; stroke-linecap: round; }

/* ==========================================================================
   The gate: what the page actually says
   ========================================================================== */

.gate {
  max-width: 620px;
  margin: 0 auto;
  padding: clamp(4px, 1.5vh, 20px) 0 clamp(16px, 3vh, 36px);
  text-align: center;
}

.eyebrow {
  margin: 0 0 14px;
  color: var(--plum);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

h1 {
  margin: 0 0 18px;
  font-family: var(--display);
  font-size: clamp(40px, 5.6vw, 64px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.028em;
}

h1 em { color: var(--blue); font-style: normal; }

.lede {
  max-width: 46ch;
  margin: 0 auto clamp(22px, 3vh, 34px);
  color: var(--ink-muted);
  font-size: 16px;
  line-height: 1.75;
  text-wrap: pretty;
}

/* ==========================================================================
   The two specimen tags
   ========================================================================== */

.tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(16px, 3vw, 30px);
}

.tag {
  position: relative;
  min-width: 190px;
  padding: 26px 22px 18px;
  border: 1px solid color-mix(in srgb, var(--ink) 24%, transparent);
  border-radius: 3px;
  background: var(--paper-raised);
  box-shadow: 0 10px 24px color-mix(in srgb, var(--ink) 10%, transparent);
  color: var(--ink);
  text-align: left;
  text-decoration: none;
  transform: rotate(-1.4deg);
  transition: transform 260ms cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 260ms ease, border-color 200ms ease;
}

.tag-second { transform: rotate(1.6deg); }

/* Pressed corners, the way a real tag is punched for its thread. */
.tag::before {
  content: "";
  position: absolute;
  top: 9px;
  left: 50%;
  width: 11px;
  height: 11px;
  margin-left: -5.5px;
  border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent);
  border-radius: 50%;
  background: var(--paper);
}

.tag-thread {
  position: absolute;
  bottom: 100%;
  left: 50%;
  width: 40px;
  height: 46px;
  margin-left: -20px;
  overflow: visible;
}

.thread {
  fill: none;
  stroke: color-mix(in srgb, var(--bark) 60%, transparent);
  stroke-width: 1.2;
  stroke-linecap: round;
}
.pin { fill: var(--glint); }

.tag-no {
  display: block;
  margin-bottom: 9px;
  color: var(--plum);
  font-family: var(--mono);
  font-size: 8.5px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.tag-label {
  display: block;
  font-family: var(--display);
  font-size: 25px;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.tag-sub {
  display: block;
  margin-top: 7px;
  color: var(--ink-muted);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The bud on the contact tag. Both states are drawn and one is faded out
   rather than swapping markup, so there is nothing to load and nothing to
   reflow when it opens. transform-origin is explicit because the initial
   50% 50% resolves against the whole viewBox and would send the flower
   toward the corner of the sheet. */
.tag-bud {
  position: absolute;
  top: -20px;
  right: -16px;
  width: 56px;
  height: 56px;
  overflow: visible;
  pointer-events: none;
}

.tag-bud .bud-shut,
.tag-bud .bud-open {
  transform-origin: 0 0;
  transition: opacity 240ms ease, transform 380ms cubic-bezier(0.2, 0.8, 0.3, 1.15);
}

.tag-bud .bud-open {
  opacity: 0;
  transform: scale(0.5) rotate(-16deg);
}

.tag-second:hover .bud-shut,
.tag-second:focus-visible .bud-shut {
  opacity: 0;
  transform: scale(0.62) rotate(12deg);
}

.tag-second:hover .bud-open,
.tag-second:focus-visible .bud-open {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}

.tag:hover,
.tag:focus-visible {
  transform: rotate(-0.4deg) translateY(-4px);
  border-color: color-mix(in srgb, var(--plum) 60%, transparent);
  box-shadow: 0 18px 34px color-mix(in srgb, var(--ink) 15%, transparent);
}

.tag-second:hover,
.tag-second:focus-visible { transform: rotate(0.5deg) translateY(-4px); }

/* The two primary tags remain the gate's destinations. These links are a
   quieter map to the site's less sensible corners, so they use no card,
   specimen number, icon or motion of their own. */
.other-corners {
  /* Wide enough for the label and four links on one line at desktop and
     tablet widths; below that they wrap two and two. */
  width: min(100%, 600px);
  margin: clamp(24px, 3vh, 38px) auto 0;
  padding-top: 16px;
  border-top: 1px solid color-mix(in srgb, var(--ink) 18%, transparent);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0 20px;
  color: var(--ink-muted);
}

.other-corners-label {
  color: var(--plum);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.other-corners a {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--ink-muted);
  font-family: var(--body);
  font-size: 13px;
  text-decoration-color: color-mix(in srgb, var(--plum) 48%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.other-corners a:hover,
.other-corners a:focus-visible {
  color: var(--ink);
  text-decoration-color: var(--plum);
}

/* ==========================================================================
   Footer
   ========================================================================== */

.sheet-foot {
  width: min(100% - 44px, 1320px);
  min-height: 68px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  color: var(--ink-muted);
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

/* The sign to the cat archive. Small, screwed to the fence at a slight
   angle, and noticed rather than announced. Only the gate carries it. */
.cat-sign {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 0 11px;
  border: 1px solid color-mix(in srgb, var(--plum) 40%, transparent);
  border-radius: 3px;
  color: var(--plum);
  text-decoration: none;
  transform: rotate(-1.5deg);
  transition: transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1), border-color 160ms ease, background-color 160ms ease;
}

.cat-sign:hover,
.cat-sign:focus-visible {
  border-color: var(--plum);
  background: color-mix(in srgb, var(--blossom) 16%, transparent);
  transform: rotate(0deg);
}

/* ==========================================================================
   Not found. One loose petal and two ways back.
   ========================================================================== */

.lost {
  width: min(100% - 44px, 640px);
  margin: 0 auto;
  padding: clamp(24px, 8vh, 80px) 0;
  align-self: center;
  text-align: center;
}

.lost-petal { width: 60px; height: 60px; margin-bottom: 18px; }
.lost h1 {
  margin: 0 0 18px;
  font-family: var(--display);
  font-size: clamp(34px, 4.6vw, 52px);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.026em;
}
.lost h1 em { color: var(--blue); font-style: normal; }
.lost .lede { margin-bottom: clamp(24px, 4vh, 38px); }

@media (max-width: 780px) {
  .lost { width: min(100% - 32px, 640px); }
}

/* ==========================================================================
   The contact page. Another sheet from the same field book.
   ========================================================================== */

.page-contact { grid-template-rows: auto 1fr auto; }

.contact-sheet {
  width: min(100% - 44px, 1060px);
  margin: 0 auto;
  padding: clamp(16px, 4vh, 48px) 0 clamp(32px, 6vh, 64px);
}

.contact-head {
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 36px);
  margin-bottom: clamp(30px, 5vh, 52px);
}

.sprig {
  width: clamp(120px, 15vw, 190px);
  height: auto;
  flex: 0 0 auto;
}

.contact-head h1 {
  margin: 0 0 12px;
  font-family: var(--display);
  font-size: clamp(38px, 5.4vw, 62px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.028em;
}

.contact-head .lede { margin: 0; max-width: 40ch; }

.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(300px, 0.95fr);
  gap: clamp(28px, 4vw, 56px);
  align-items: start;
}

.section-title {
  margin: 0 0 16px;
  color: var(--plum);
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.contact-pitch p {
  margin: 0 0 16px;
  max-width: 58ch;
  color: var(--ink-muted);
  line-height: 1.8;
  text-wrap: pretty;
}

.contact-facts {
  margin: 28px 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.contact-facts div {
  display: grid;
  grid-template-columns: 128px minmax(0, 1fr);
  gap: 16px;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.contact-facts dt {
  color: var(--ink);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.9;
}

.contact-facts dd {
  margin: 0;
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1.7;
}

.channels { margin: 0; padding: 0; list-style: none; }

.channels li {
  display: grid;
  grid-template-columns: 128px minmax(0, 1fr);
  gap: 16px;
  align-items: center;
}

.channel-label {
  color: var(--ink-muted);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.9;
}

/* Comfortable to hit with a thumb. These measured 26px tall as plain inline
   links, which is fine for a mouse and mean on a phone. */
.channels a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--plum);
  text-decoration-color: color-mix(in srgb, var(--plum) 40%, transparent);
  text-underline-offset: 3px;
}

.channels a:hover { text-decoration-color: var(--plum); }

/* ---------------------------------------------------------- the post box */

.postbox-form {
  padding: clamp(20px, 3vw, 30px);
  border: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);
  border-radius: 4px;
  background: var(--paper-raised);
  box-shadow: 0 12px 30px color-mix(in srgb, var(--ink) 9%, transparent);
}

.postbox-intro {
  margin: -4px 0 22px;
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1.7;
}

.postbox-form label { display: block; margin-bottom: 14px; }

.postbox-form label > span {
  display: block;
  margin-bottom: 7px;
  color: var(--ink-muted);
  font-family: var(--mono);
  /* These are the only visible labels on the form. 9px was too small for
     the one piece of text a sender has to read to fill it in. */
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.postbox-form input,
.postbox-form textarea {
  width: 100%;
  padding: 11px 12px;
  /* --line is 1.44:1 on paper, which is fine for a rule between rows and
     not enough for a control boundary. */
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: color-mix(in srgb, var(--paper) 55%, var(--paper-raised));
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}

.postbox-form textarea { resize: vertical; line-height: 1.6; }

/* No outline of its own: the global :focus-visible already draws the right
   ring, and overriding it here at (0,2,1) meant the wider rings under
   prefers-contrast and forced-colors never reached the form fields. */
.postbox-form input:focus-visible,
.postbox-form textarea:focus-visible {
  outline-offset: 1px;
  border-color: var(--plum);
}

/* An invalid field has to look invalid, not just announce it. */
.postbox-form [aria-invalid="true"] {
  border-color: var(--plum);
  box-shadow: 0 0 0 1px var(--plum);
}

/* Off screen rather than display:none: a bot that reads the stylesheet is
   told less by this than by a hidden field it can obviously skip. */
.postbox-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.turnstile-slot:not(:empty) { margin-bottom: 14px; }

.postbox-submit {
  width: 100%;
  min-height: 46px;
  margin-top: 4px;
  border: 1px solid var(--blue);
  border-radius: 3px;
  background: var(--blue);
  color: var(--on-accent);
  cursor: pointer;
  font-weight: 500;
  transition: box-shadow 180ms ease, transform 180ms ease;
}

.postbox-submit:hover:not(:disabled) {
  box-shadow: 4px 4px 0 color-mix(in srgb, var(--blossom) 80%, transparent);
  transform: translate(-1px, -1px);
}

.postbox-submit[aria-disabled="true"] { opacity: 0.65; cursor: default; }
.postbox-submit[aria-disabled="true"]:hover { box-shadow: none; transform: none; }

.field-hint {
  display: block;
  margin-top: 6px;
  color: var(--ink-muted);
  font-size: 12.5px;
  line-height: 1.5;
}

.postbox-status {
  margin: 12px 0 0;
  min-height: 1.4em;
  font-size: 13.5px;
  line-height: 1.6;
}

.postbox-status[data-state="ok"] { color: var(--blue); }
.postbox-status[data-state="error"] { color: var(--plum); }

.postbox-noscript {
  margin: 12px 0 0;
  color: var(--ink-muted);
  font-size: 13px;
  line-height: 1.6;
}

@media (max-width: 860px) {
  .contact-grid { grid-template-columns: 1fr; }
}

@media (max-width: 780px) {
  .contact-sheet { width: min(100% - 32px, 1060px); }
  .contact-head { flex-direction: column; align-items: flex-start; gap: 14px; }
  .sprig { width: 128px; }
  .contact-facts div,
  .channels li { grid-template-columns: 1fr; gap: 2px; }
}

/* ==========================================================================
   Motion. Growth, unfurling, and what is in the air.
   ========================================================================== */

.grow {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: grow 1000ms var(--d, 0s) cubic-bezier(0.3, 0.6, 0.3, 1) forwards;
}

/* Every organ is drawn around its own 0,0 and placed by an outer group, so
   scaling about 0,0 in the local space is both correct and free. The
   alternative, transform-box: fill-box, needs the bounding box of a <use>
   and Safari has never been reliable about that. An explicit origin is
   required either way: the initial 50% 50% would resolve against the whole
   viewBox and shrink every organ toward the middle of the sheet. */
.unfurl {
  opacity: 0;
  transform-origin: 0 0;
  animation: unfurl 760ms var(--d, 0s) cubic-bezier(0.2, 0.8, 0.3, 1.15) forwards;
}

.gate > * {
  animation: settle 700ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
/* The text arrives while the bough is still drawing, rather than waiting
   for it. `both` fills backwards, so every one of these is opacity:0 until
   its delay: the old 1.15s to 1.6s stagger meant the h1 was the largest
   contentful paint and could not paint before 1.3s, and below 380px, where
   the top bar links are hidden, there was no navigable element on the page
   at all for the first 1.6 seconds. */
.gate > .eyebrow { animation-delay: 0.35s; }
.gate > h1 { animation-delay: 0.45s; }
.gate > .lede { animation-delay: 0.6s; }
.gate > .tags { animation-delay: 0.72s; }

@keyframes grow { to { stroke-dashoffset: 0; } }

@keyframes unfurl {
  from { opacity: 0; transform: scale(0.5) rotate(-8deg); }
  to { opacity: 1; transform: scale(1) rotate(0deg); }
}

@keyframes settle {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

.fall { opacity: 0; }
.fall .sway { animation: sway 3.8s ease-in-out infinite alternate; }
.fall-1 { animation: fall-1 19s 3.4s linear infinite; }
.fall-2 { animation: fall-2 26s 12s linear infinite; }
.fall-3 { animation: fall-3 31s 21s linear infinite; }

@keyframes fall-1 {
  0% { opacity: 0; transform: translate(268px, 240px) rotate(6deg); }
  7% { opacity: 0.9; }
  84% { opacity: 0.9; }
  100% { opacity: 0; transform: translate(206px, 348px) rotate(212deg); }
}
@keyframes fall-2 {
  0% { opacity: 0; transform: translate(534px, 270px) rotate(-18deg); }
  7% { opacity: 0.85; }
  84% { opacity: 0.85; }
  100% { opacity: 0; transform: translate(606px, 352px) rotate(-238deg); }
}
@keyframes fall-3 {
  0% { opacity: 0; transform: translate(882px, 216px) rotate(30deg); }
  7% { opacity: 0.8; }
  84% { opacity: 0.8; }
  100% { opacity: 0; transform: translate(822px, 344px) rotate(276deg); }
}
@keyframes sway {
  from { transform: translateX(-10px) rotate(-15deg); }
  to { transform: translateX(10px) rotate(15deg); }
}

/* The narrow specimen has its own coordinate space, so its drift needs its
   own path across it rather than the wide one scaled down. */
.drift-tall .fall-1 { animation-name: fall-tall-1; }
.drift-tall .fall-2 { animation-name: fall-tall-2; }

@keyframes fall-tall-1 {
  0% { opacity: 0; transform: translate(144px, 178px) rotate(8deg); }
  7% { opacity: 0.9; }
  84% { opacity: 0.9; }
  100% { opacity: 0; transform: translate(96px, 322px) rotate(206deg); }
}
@keyframes fall-tall-2 {
  0% { opacity: 0; transform: translate(278px, 196px) rotate(-16deg); }
  7% { opacity: 0.85; }
  84% { opacity: 0.85; }
  100% { opacity: 0; transform: translate(336px, 330px) rotate(-232deg); }
}

/* A petal or two let go when a visitor leaves through a tag. The navigation
   is never held up for it; the animation simply does not get to finish. */
.leaving .drift .fall { animation-duration: 2.4s; }

/* Shaking the tree. Five taps on the mark, once per visit, announced
   nowhere. In a season with nothing to shake loose the bough still sways,
   which is the honest result. */
.shaken .boughs { animation: bough-shake 1.5s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
.shaken .foliage,
.shaken .blooms,
.shaken .fruit { animation: bough-shake 1.7s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
.shaken .drift .fall { animation-duration: 4.5s; animation-delay: 0.2s; }

@keyframes bough-shake {
  0%, 100% { transform: rotate(0deg); }
  12% { transform: rotate(0.8deg); }
  28% { transform: rotate(-0.6deg); }
  46% { transform: rotate(0.4deg); }
  66% { transform: rotate(-0.25deg); }
  84% { transform: rotate(0.12deg); }
}

/* Only one of the two compositions is ever on screen, and the other one's
   petals would otherwise go on falling in a subtree nobody is rendering.
   Same reason as the reduced-motion block below: display:none does not stop
   an animation, it only stops it being painted. */
/* These two must match the breakpoint that swaps the compositions, at
   460px below. They said 780px, which left a band between 461px and 780px
   where the wide specimen was on screen with its drift switched off and the
   tall one was hidden with its drift switched off too: no falling petals at
   all on a tablet or a landscape phone. */
@media (min-width: 461px) {
  .specimen-tall .fall,
  .specimen-tall .sway { animation: none; }
}

@media (max-width: 460px) {
  .specimen-wide .fall,
  .specimen-wide .sway { animation: none; }
}

/* Out of season for the same reason. */
[data-season="spring"] :is(.only-summer, .only-autumn, .only-winter) :is(.fall, .sway),
[data-season="summer"] :is(.only-spring, .only-autumn, .only-winter) :is(.fall, .sway),
[data-season="autumn"] :is(.only-spring, .only-summer, .only-winter) :is(.fall, .sway),
[data-season="winter"] :is(.only-spring, .only-summer, .only-autumn) :is(.fall, .sway),
html:not([data-season]) :is(.only-summer, .only-autumn, .only-winter) :is(.fall, .sway) {
  animation: none;
}

/* ==========================================================================
   Narrower screens.

   The wide canopy is replaced rather than reflowed: a closer crop of the
   same specimen, with fewer and larger blossoms, which is how a herbarium
   sheet would show a detail anyway.
   ========================================================================== */

@media (max-width: 780px) {
  .topbar { width: min(100% - 32px, 1320px); min-height: 72px; gap: 10px; }
  .wordmark { font-size: 23px; }
  .wordmark-mark { width: 30px; height: 30px; margin-right: 6px; }
  .topbar-nav { gap: 8px; }
  .topbar-link { padding: 0 11px; font-size: 9px; letter-spacing: 0.06em; }
}

/* Below this the pill chrome is what stops the bar fitting, not the words.
   Measured at 390px: two pills and the toggle came to 374px inside a 358px
   bar. Dropping the border and the side padding gives back about 48px and
   keeps "Work with me" spelled out, which is the phrase worth keeping.
   The links stay 44px tall, so they are no harder to hit for being flat. */
@media (max-width: 460px) {
  .topbar-link {
    padding: 0 2px;
    border: 0;
    background: none;
  }

  .topbar-link:hover,
  .topbar-link:focus-visible { background: none; color: var(--ink); }

  .sheet { width: min(100% - 32px, 1320px); }
  .sheet-foot {
    width: min(100% - 32px, 1320px);
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    padding: 18px 0;
    font-size: 9px;
  }

  .specimen-wide { display: none; }
  .specimen-tall { display: block; }

  .gate { padding-top: 6px; }
  .tags { gap: 14px; }
  .tag { min-width: 0; flex: 1 1 140px; padding: 22px 14px 14px; }
  .tag-label { font-size: 19px; }
  .tag-thread { height: 32px; bottom: 100%; }
}

/* At the narrowest widths even flat links do not fit beside the wordmark.
   On the gate and the 404 they do not need to: the two specimen tags below
   are the same two destinations, larger and easier to hit, and saying it
   twice on a 320px screen was never worth the room. The contact page has no
   tags, so it keeps its links at every width. */
@media (max-width: 380px) {
  .page-gate .topbar-nav .topbar-link { display: none; }

  /* The contact page keeps both of its links here, and at 320px they came
     to 287px inside a 288px bar. One pixel is not a margin, so the wordmark
     gives some back. */
  .wordmark { font-size: 20px; }
  .wordmark-mark { width: 26px; height: 26px; margin-right: 5px; }
}

@media (max-width: 350px) {
  /* Two tags side by side stop being comfortable targets below this. */
  .tags { flex-direction: column; align-items: stretch; }
  .tag, .tag-second { transform: rotate(-0.8deg); }
  .tag-second { transform: rotate(0.9deg); }

  .other-corners { gap: 0; }
  .other-corners-label { flex-basis: 100%; }
  .other-corners a { flex: 1 1 100%; justify-content: center; }
}

/* Short and wide, such as a laptop with a lot of browser chrome. The canopy
   is the part that can afford to lose height. */


/* ==========================================================================
   Reduced motion.

   Not a disabled page. The specimen is simply already grown: this is the
   pressed sheet rather than the growing one, which is arguably the more
   honest herbarium. Season and light still apply, because they are states
   rather than motion.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .grow { animation: none; stroke-dasharray: none; stroke-dashoffset: 0; }
  .unfurl { animation: none; opacity: 1; transform: none; }
  .gate > * { animation: none; }
  /* Hiding the drift is not enough on its own. display:none stops an
     animation painting but not running: measured with getAnimations(), the
     twelve fall and sway animations were still ticking behind it. They have
     to be switched off by name. */
  .drift { display: none; }
  .drift .fall,
  .drift .sway { animation: none; }
  .canopy { transform: none; }
  .icon-button, .tag, .cat-sign { transition: none; }
  .cat-sign:hover, .cat-sign:focus-visible { transform: rotate(-1.5deg); }
  /* The bud still opens, it just does not travel to get there. */
  .tag-bud .bud-shut, .tag-bud .bud-open { transition: opacity 120ms ease; transform: none; }
  .tag-second:hover .bud-shut, .tag-second:focus-visible .bud-shut { transform: none; }
  .tag-second:hover .bud-open, .tag-second:focus-visible .bud-open { transform: none; }
  .tag:hover, .tag:focus-visible,
  .tag-second:hover, .tag-second:focus-visible { transform: rotate(0deg); }

  /* The light changing is a colour crossfade rather than movement, and
     cutting it entirely turns the hour boundary into a jarring flip.
     Shortened rather than removed. */
  body { transition-duration: 200ms; }
}

/* ==========================================================================
   When contrast is asked for, decoration goes.

   That the whole clock can be switched off by setting one wash strength to
   zero is the useful proof that it never had a foreground to begin with.
   ========================================================================== */

@media (prefers-contrast: more) {
  /* The night theme sets these on [data-theme="night"][data-light="..."],
     which is (0,2,0) and outranks a bare :root here whatever the source
     order. Without the second selector the wash and a 52% glow survived
     the one mode whose whole job is to remove them. */
  :root,
  [data-theme="night"][data-light] {
    --wash-paper: 0%;
    --wash-raised: 0%;
    --glow-strength: 0%;
    --ink-muted: #3e4a43;
    --line: var(--line-strong);
  }

  [data-theme="night"] {
    --ink-muted: #d4ddd6;
    --line: var(--line-strong);
  }

  :focus-visible { outline-width: 3px; }
}

@media (forced-colors: active) {
  :root { --wash-paper: 0%; --wash-raised: 0%; --glow-strength: 0%; }
  body { background-image: none; }
  /* The specimen is a drawing. Letting the system recolour it to two system
     colours makes it unreadable rather than more readable. */
  .specimen, .sprig, .lost-petal, .tag-bud, .tag-thread, .sakura-mark { forced-color-adjust: none; }
  :focus-visible { outline: 3px solid Highlight; }
}

/* ==========================================================================
   The mark turns when you reach for the wordmark.

   The same gesture on all four surfaces, so the identity behaves the same
   wherever you meet it: the gate, the catalogue, and the herbarium's three
   pages. Written against `.wordmark .sakura-mark` rather than any one
   site's own class because the markup differs between them and the
   behaviour should not.

   Suppressed rather than merely un-animated under reduced motion. Both
   sheets already kill transition-duration globally there, which would have
   left a 24 degree snap: instant, but not what someone asking for less
   movement had in mind.
   ========================================================================== */

.wordmark .sakura-mark {
  transition: transform 420ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.wordmark:hover .sakura-mark,
.wordmark:focus-visible .sakura-mark {
  transform: rotate(24deg);
}

@media (prefers-reduced-motion: reduce) {
  .wordmark .sakura-mark { transition: none; }
  .wordmark:hover .sakura-mark,
  .wordmark:focus-visible .sakura-mark { transform: none; }
}
