/* ==========================================================================
   foundation.css — fonts, reset and tokens

   Everything both the home and the pages behind it agree on. The home loads
   this then style.css; every other page loads this then pages.css, so the
   two never drift.
   Hand-authored CSS. No build step: edit this file directly.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts
   Louize (205TF) sets the quotes and headlines; GT Mechanik Semi sets every
   label, chip and nav item. Both ship from dist/fonts/.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Louize';
  src: url('../fonts/205TF-Louize-Regular.woff2') format('woff2'),
       url('../fonts/205TF-Louize-Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Louize';
  src: url('../fonts/205TF-Louize-Italic.woff2') format('woff2'),
       url('../fonts/205TF-Louize-Italic.woff') format('woff');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

/* GT Mechanik Semi (Grilli Type) — the UI face. Licensed in Regular and its
   oblique only; the oblique is declared italic so `em` and
   `font-style: italic` pick it up instead of synthesising a slant. Any other
   weight asked for is synthesised from Regular. */
@font-face {
  font-family: 'GT Mechanik Semi';
  src: url('../fonts/GT-Mechanik/GT-Mechanik-Semi-Regular.woff2') format('woff2'),
       url('../fonts/GT-Mechanik/GT-Mechanik-Semi-Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'GT Mechanik Semi';
  src: url('../fonts/GT-Mechanik/GT-Mechanik-Semi-Regular-Oblique.woff2') format('woff2'),
       url('../fonts/GT-Mechanik/GT-Mechanik-Semi-Regular-Oblique.woff') format('woff');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

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

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

body,
h1, h2, p, figure, blockquote {
  margin: 0;
  padding: 0;
}

h1, h2 { font-size: inherit; font-weight: inherit; }

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

img { -webkit-user-drag: none; user-select: none; }

a { color: inherit; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   3. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Surfaces */
  --paper: #f3f1e5;                       /* the home */
  --card: #fff7ed;                        /* postcard stock */
  --emulsion: #e9e3d7;                    /* behind a photo while it loads */

  /* Ink */
  --ink: #291913;
  /* A step darker than the artboard's #8e857c, which is 3.2:1 on the paper
     and too faint for the small labels it sets. This is 4.6:1. The hairline
     below keeps the drawn grey. */
  --warm-grey: #736b62;
  --red: #e15151;                         /* an inline link under the cursor */
  --hairline: rgba(142, 133, 124, .1);
  --chip-wash: rgba(243, 241, 229, .4);
  /* Under the cursor a title tag stops being a wash and becomes printed
     stock: solid, knocked-out type, a quarter-pixel of light around the
     edge. Only on hover — at rest the tag is still the hairline chip the
     artboard draws. The stock is the artboard's Dark and the type its
     Light (node 311:278), so the tag and the visit chip under it print in
     the same two inks. */
  --label-stock: var(--ink);
  --label-ink: var(--paper);
  --label-stroke: rgba(255, 255, 255, .9);
  /* Meta chips sit denser than the tags. They carry no blur, and they are
     the one tag that regularly overhangs onto a neighbouring card. */
  --meta-wash: rgba(243, 241, 229, .9);
  --nav-wash: rgba(243, 241, 229, .3);
  --nav-frost: rgba(243, 241, 229, .2);     /* a nav item under the cursor */

  /* Type
     Every absolute font-size in the three stylesheets is written as its drawn
     value times --type-scale, so the artboard sizes stay readable in the
     source and the scale moves in one place. Sizes in em, and anything
     inheriting, ride along on their parent.

     Two dials, because the home runs hotter than the pages behind it:
     --type-site is the scale every page is set at, and --type-scale is what
     the stylesheets actually multiply by. style.css raises the second one
     without touching the first, so moving the site scale still moves home. */
  --type-site: 1.05;
  --type-scale: var(--type-site);
  /* The home's own step above the site, kept here rather than in style.css
     so About can set its wordmark and nav at the home's size too. */
  --type-home: calc(var(--type-site) * 1.05);

  --font-ui: 'GT Mechanik Semi', ui-sans-serif, system-ui, 'Helvetica Neue', Arial, sans-serif;
  --font-serif: 'Louize', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Postcard shadow — three stacked drops, straight from the design */
  --shadow-rest:
    0 0 3px rgba(0, 0, 0, .15),
    1px 1px 1px rgba(0, 0, 0, .25),
    1px 2px 2px rgba(0, 0, 0, .2);

  /* Lifted a few millimetres off the home */
  --shadow-lift:
    0 0 4px rgba(0, 0, 0, .12),
    2px 3px 3px rgba(0, 0, 0, .18),
    3px 7px 12px rgba(0, 0, 0, .16);

  /* In hand */
  --shadow-drag:
    0 0 6px rgba(0, 0, 0, .10),
    5px 9px 10px rgba(0, 0, 0, .16),
    14px 30px 52px rgba(0, 0, 0, .22);

  /* Motion */
  --ease-settle: cubic-bezier(.2, .8, .25, 1);
  --ease-magnet: cubic-bezier(.22, .61, .36, 1);
  /* How far a chip that hangs off something — a card's path and visit chips,
     the nav's Coming Soon — falls into place as it fades in. Reduced motion
     takes it to nothing, and the chip only fades. */
  --drop: -2px;

  /* Home geometry (matches the artboard) */
  --home-w: 1728;
  --home-h: 1307;
}

/* --------------------------------------------------------------------------
   3b. Hung punctuation
   A quotation mark that opens a line steps back by its own width, so it sits
   out in the margin and the type starts on the same left edge as every line
   beneath it. Set on a mark the writer typed; pages.css does the same to the
   marks it sets itself around a passage.

   .43em is measured rather than guessed: both double marks have an advance of
   .4294em in Louize, and it scales with the type. It is a serif measurement —
   this belongs on quoted type set in Louize, not on anything in the UI face.
   -------------------------------------------------------------------------- */
.hang { margin-left: -.43em; }

/* --------------------------------------------------------------------------
   4. Tags
   The one component the home and the pages behind it genuinely share: a
   hairline chip that straddles the trim of whatever it names. On the home it
   is a postcard's title; on a page it is a work plate's caption, an entry's
   type, a line of metadata.
   -------------------------------------------------------------------------- */
/* Labels and meta chips --------------------------------------------------- */
.postcard__label,
.chip {
  display: inline-flex;
  align-items: flex-start;
  gap: 4px;
  padding: 3px 10px 4px;
  border: .5px solid var(--hairline);
  border-radius: 3px;
  background: var(--chip-wash);
  font-family: var(--font-ui);
  line-height: 1.28;
  color: var(--warm-grey);
  white-space: nowrap;
}

/* The label straddles the bottom trim of its card, as drawn. The 12px rise is
   measured off the trim, not off the tag, so type can grow without the tag
   climbing the card — the extra height just hangs below the edge. */
.postcard__label {
  position: absolute;
  left: calc(var(--label-x, 24) * 1px);
  top: calc(100% - 12px);
  font-size: calc(14px * var(--type-scale));
}

/* Progressive blur curtain ------------------------------------------------
   Straight from the artboard: every Postcard_Title carries a PROGRESSIVE
   background blur, startRadius 18, ramping along x from 1 (full blur, right
   trim) to 0 (clear, left trim). So the type end of the tag stays sharp and
   the card art dissolves as it runs out to the right.

   Two stacked backdrop layers stand in for the ramp — the same idiom as the
   filmstrip curtains, mirrored. Each blurs harder and is masked tighter to
   the right trim. Blur compounds in quadrature, so 2px over 8px lands at
   ~8.2px at the right edge, tapering to nothing by the left: Figma's radius
   is about twice the CSS one, which puts 18 at ~9px.
   ------------------------------------------------------------------------- */
.postcard__label::before,
.postcard__label::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}

.postcard__label::before {
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  -webkit-mask-image: linear-gradient(to left, #000 0%, transparent 100%);
  mask-image: linear-gradient(to left, #000 0%, transparent 100%);
}

.postcard__label::after {
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  -webkit-mask-image: linear-gradient(to left, #000 0%, transparent 55%);
  mask-image: linear-gradient(to left, #000 0%, transparent 55%);
}

/* The curtains are positioned, so the type has to be lifted over them —
   otherwise it lands in the backdrop and gets blurred with the card art. */
.postcard__label span {
  position: relative;
  z-index: 1;
}

/* Difference keeps the keys legible against whatever the tag straddles — but
   only as far as the blend can see. Figma composites these against the whole
   artboard, so `added` inverts against the photo showing under the note card
   and reads dark. In CSS the blend is trapped inside the card: .object is
   transformed, which makes it a stacking context, and a stacking context is an
   isolation group. Below the trim the group holds nothing but the chip's own
   wash, so there is no dark backdrop to invert against and the key comes out
   flat grey on grey. Hence --meta-wash: give the blend a ground it can rely on
   rather than a backdrop it cannot reach. */
.postcard__label span,
.chip__key { mix-blend-mode: difference; }

/* Hover: the wash becomes printed stock ------------------------------------
   The three hosts are listed together because this is one state of one
   component — a card on the home, a plate on a section page and a slip in the
   journal all wear the same tag, and it should light the same way in all
   three. Each world keeps its own hover elsewhere: the home's pop, the
   plate's and slip's 1.05.

   Three things change together and nothing moves:

   - the wash goes solid, which also retires the progressive blur behind it.
     The curtains are still there and still running; there is simply nothing
     left to see through.
   - the type goes Light, and drops the difference blend. Against Dark the
     blend would hand back #cad8d2, so the tag would carry a cold title
     while claiming a warm one. On solid stock the tag brings its own ground
     and Light is just Light.
   - a .25px white stroke comes up around the edge. It is an outline, not the
     border: the border is .5px and holds the tag's geometry, and an outline
     draws inside that without reflowing anything. It follows border-radius.

   On the home it is the link that lights, not the card: a card whose section
   is closed prints its tag inside an <a> with no href, and that tag stays a
   wash, because lighting up is how a tag says it opens.
   ------------------------------------------------------------------------- */
@media (hover: hover) and (pointer: fine) {
  .object:hover a[href] > .postcard__label,
  .plate:hover .plate__label,
  .slip:hover .slip__label {
    background: var(--label-stock);
    color: var(--label-ink);
    outline-color: var(--label-stroke);
  }

  .object:hover a[href] > .postcard__label span,
  .plate:hover .plate__label > span,
  .slip:hover .slip__label span { mix-blend-mode: normal; }

  /* A quote has no title, so its chips are its tag: on the home the meta
     is the card's way in, and out in the garden the whole card is a link and
     the credit under the passage is the same pair. Only those two — a meta
     row on a card that isn't a way in stays a wash, and so does every other
     chip on the pages behind the home.

     The keys drop the same blend, and the values give up --ink. Both halves
     of the chip go to the one white, which is what makes two chips read as
     one lit block rather than a key and a value. */
  .object:hover .postcard__open .chip,
  .sprig:hover .credit .chip {
    background: var(--label-stock);
    outline-color: var(--label-stroke);
  }

  .object:hover .postcard__open .chip__key,
  .sprig:hover .credit .chip__key { mix-blend-mode: normal; }

  .object:hover .postcard__open .chip__key,
  .object:hover .postcard__open .chip__value,
  .sprig:hover .credit .chip__key,
  .sprig:hover .credit .chip__value { color: var(--label-ink); }
}

/* Pick up a card and its tag leans in with it */
.postcard__label,
.postcard__meta {
  transform-origin: 0 50%;
  transition: transform 240ms var(--ease-magnet);
}

/* The tag's own three properties ease on the same curve, so the stock comes
   up rather than snapping. The outline is declared here at full width and
   transparent: an outline never affects layout, so carrying it at rest costs
   nothing visually and gives the stroke a colour to ease from instead of
   appearing all at once under a background that is still arriving. */
.postcard__label,
.postcard__open .chip,
.credit .chip {
  outline: .25px solid transparent;
  outline-offset: -.25px;
  transition:
    transform 240ms var(--ease-magnet),
    background-color 220ms var(--ease-magnet),
    color 220ms var(--ease-magnet),
    outline-color 220ms var(--ease-magnet);
}

/* Meta tracks the label — 10:12 in the artboard, held at 12:14 */
.chip {
  font-size: calc(12px * var(--type-scale));
  background: var(--meta-wash);
}
.chip__value { color: var(--ink); }

/* The display above outranks the hidden attribute, which is how the home
   takes the source chip off a quote that has none. */
.chip[hidden] { display: none; }

/* --------------------------------------------------------------------------
   5. Nav
   The same floating chip of links wherever it lands: pinned to the home on
   the front page, sat in the masthead everywhere else, hung off the plate in
   the garden. Drawn at node 277:132, its items at 277:148.

   It is two layers of one row, stacked in a single grid cell
   (partials/nav.njk). The plate is the chip: the wash, the hairline, the
   shadow. The type is the links, and the whole layer is set in difference
   against whatever lies under the chip — which is the point of the 30% wash:
   over a photograph the type goes dark where the picture is light and light
   where it is dark, and the chip stays glass.

   A blend only reaches what is beneath it inside its own stacking context.
   So .nav itself must never become one: no transform, opacity, filter or
   z-index on it, anywhere. Position it with left/top; put any z-index on the
   two layers instead. The context it ends up in also needs a ground painted
   in it — a blend over nothing is no blend — which is why .home and .garden
   carry the paper themselves.
   -------------------------------------------------------------------------- */
.nav {
  display: inline-grid;
  width: auto;
  height: auto;
  /* 14, with the postcard labels rather than the meta chips: the nav is a way
     in, and it should read at the weight of the other ways in. */
  font-size: calc(14px * var(--type-scale));
  line-height: 1.28;
  color: var(--warm-grey);
  white-space: nowrap;
}

/* Both layers are the same box, so the plate's items land exactly under the
   type's. The border is carried on both for that reason; only the plate
   colours it. */
.nav__layer {
  grid-area: 1 / 1;
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 4px 10px;
  border: .5px solid transparent;
  border-radius: 4px;
}

.nav__plate {
  border-color: var(--hairline);
  background: var(--nav-wash);
  box-shadow:
    0 0 40px rgba(0, 0, 0, .08),
    1px 1px 1px rgba(0, 0, 0, .03);
  color: transparent;
  -webkit-user-select: none;
  user-select: none;
}

.nav__type { mix-blend-mode: difference; }

/* An item is a small tag of its own inside the chip, in three states. At rest
   it is only its type. Under the cursor a quarter-pixel stroke comes up round
   it, the type drops back to 70%, and the item frosts: a 20% wash of paper
   over a 4px blur of whatever the chip is lying on (node 277:146). Where you
   are, it sits on ink with the type in paper.

   The ink is the plate's, not the link's. Inside the blended layer it would
   be differenced along with the type and come out pale; on the plate it is
   simply ink, and the paper type above it blends against that. The frost is
   the plate's for the same reason, and one more: the blended layer is a
   backdrop root, so a backdrop-filter in there would have nothing but the
   type to sample.

   The cursor is on the link, though, and the link is in the other layer, so
   the plate's item is reached by position: the nth link hovered frosts the
   nth item of the plate. Six covers the longest row this is called with.

   The stroke is an outline pulled in by its own width, for the reason the
   tag's is: it draws inside the item without reflowing the row, and carried
   transparent at rest it has a colour to ease from. It is on the link, so it
   takes the blend with the type. */
.nav__item {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3px 10px 4px;
  border-radius: 3px;
}

.nav__plate .nav__item {
  transition:
    background-color 200ms var(--ease-magnet),
    -webkit-backdrop-filter 200ms var(--ease-magnet),
    backdrop-filter 200ms var(--ease-magnet);
}

.nav__plate .is-here { background: var(--ink); }

/* A section that isn't open yet (node 299:62): an ink chip with paper type,
   the whole item at a fifth of its strength. The artboard fades the chip and
   its label as one, so both are drawn on the plate, where nothing is
   blended; through the difference layer the paper label would come out
   nearly invisible against the faded ink. The type layer keeps the label
   only for its measure. It is a span in both layers, so none of the hover or
   focus states below reach it. */
.nav__plate .is-closed {
  background: var(--ink);
  color: var(--paper);
  opacity: .2;
}
.nav__type .is-closed { color: transparent; cursor: default; }

/* Under the cursor a closed item says why: a "Coming Soon" chip hangs just
   below it, on the printed-stock finish of the home's path and visit chips,
   and fades in the same way. It lives on the tips layer (partials/nav.njk),
   which repeats the labels transparent for their measure and takes no
   pointer, so the cursor stays on the type below and the nth closed item
   hovered lights the nth tip. Hover only: a closed item is not focusable,
   and screen readers get the words inside the item itself. */
.nav__tips {
  border-color: transparent;
  color: transparent;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}

.nav__tips .nav__item { position: relative; }

.nav__tip {
  position: absolute;
  left: 50%;
  top: calc(100% + 8px);
  transform: translateX(-50%);
  background: var(--label-stock);
  outline: .25px solid var(--label-stroke);
  outline-offset: -.25px;
  opacity: 0;
  translate: 0 var(--drop);
  transition:
    opacity 120ms var(--ease-magnet),
    translate 120ms var(--ease-magnet);
}

.nav__tip .chip__value { color: var(--label-ink); }

.nav:has(.nav__link:nth-child(1):not(.is-here):focus-visible) .nav__plate .nav__item:nth-child(1),
.nav:has(.nav__link:nth-child(2):not(.is-here):focus-visible) .nav__plate .nav__item:nth-child(2),
.nav:has(.nav__link:nth-child(3):not(.is-here):focus-visible) .nav__plate .nav__item:nth-child(3),
.nav:has(.nav__link:nth-child(4):not(.is-here):focus-visible) .nav__plate .nav__item:nth-child(4),
.nav:has(.nav__link:nth-child(5):not(.is-here):focus-visible) .nav__plate .nav__item:nth-child(5),
.nav:has(.nav__link:nth-child(6):not(.is-here):focus-visible) .nav__plate .nav__item:nth-child(6) {
  background-color: var(--nav-frost);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

.nav__link {
  outline: .25px solid transparent;
  outline-offset: -.25px;
  text-decoration: none;
  transition:
    color 200ms var(--ease-magnet),
    outline-color 200ms var(--ease-magnet);
}

.nav__link span { transition: opacity 200ms var(--ease-magnet); }

.nav__link.is-here { color: var(--paper); }

.nav__link:not(.is-here):focus-visible { outline-color: var(--warm-grey); }
.nav__link:not(.is-here):focus-visible span { opacity: .7; }

@media (hover: hover) and (pointer: fine) {
  .nav__link:not(.is-here):hover { outline-color: var(--warm-grey); }
  .nav__link:not(.is-here):hover span { opacity: .7; }

  .nav:has(.nav__link:nth-child(1):not(.is-here):hover) .nav__plate .nav__item:nth-child(1),
  .nav:has(.nav__link:nth-child(2):not(.is-here):hover) .nav__plate .nav__item:nth-child(2),
  .nav:has(.nav__link:nth-child(3):not(.is-here):hover) .nav__plate .nav__item:nth-child(3),
  .nav:has(.nav__link:nth-child(4):not(.is-here):hover) .nav__plate .nav__item:nth-child(4),
  .nav:has(.nav__link:nth-child(5):not(.is-here):hover) .nav__plate .nav__item:nth-child(5),
  .nav:has(.nav__link:nth-child(6):not(.is-here):hover) .nav__plate .nav__item:nth-child(6) {
    background-color: var(--nav-frost);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
  }

  .nav:has(.nav__type > .is-closed:nth-child(1):hover) .nav__tips .nav__item:nth-child(1) .nav__tip,
  .nav:has(.nav__type > .is-closed:nth-child(2):hover) .nav__tips .nav__item:nth-child(2) .nav__tip,
  .nav:has(.nav__type > .is-closed:nth-child(3):hover) .nav__tips .nav__item:nth-child(3) .nav__tip,
  .nav:has(.nav__type > .is-closed:nth-child(4):hover) .nav__tips .nav__item:nth-child(4) .nav__tip,
  .nav:has(.nav__type > .is-closed:nth-child(5):hover) .nav__tips .nav__item:nth-child(5) .nav__tip,
  .nav:has(.nav__type > .is-closed:nth-child(6):hover) .nav__tips .nav__item:nth-child(6) .nav__tip {
    opacity: 1;
    translate: none;
    transition-duration: 200ms;
  }
}

/* Pressed, the type gives back a little more than it does under the cursor,
   quickly, so the click is felt before the page goes. Strength rather than
   scale, because a scale on live type shakes the glyphs. */
.nav__link:not(.is-here):active span {
  opacity: .45;
  transition-duration: 80ms;
}

@media (prefers-reduced-motion: reduce) {
  :root { --drop: 0px; }
}

/* --------------------------------------------------------------------------
   6. Passage
   A quoted passage reads the same wherever it turns up — the quote's own
   page, a card on the garden's canvas, or the one the home keeps for itself.
   Only the type's size is drawn differently in each world (pages.css,
   style.css); the marks, the spacing and the cut are one component.
   -------------------------------------------------------------------------- */
.passage {
  margin: 0;
  font-family: var(--font-serif);
  text-wrap: pretty;
}

.passage p { margin: 0 0 .75em; }
.passage p:last-child { margin-bottom: 0; }

/* A passage that counts things out keeps the count. */
.passage ol,
.passage ul {
  margin: 0 0 .75em;
  padding-left: 1.2em;
}

.passage li { margin: 0; }

/* Quote marks come from here rather than from the writing, so the markdown
   stays the passage and nothing else. A passage cut to length closes on …”
   which is what it should be — the ellipsis is the missing words, and the
   mark still closes the quotation. */
.passage > p:first-of-type::before { content: "\201C"; }
.passage > p:last-of-type::after { content: "\201D"; }

/* Hung, not set: the opening mark steps back by exactly its own width, so it
   sits out in the padding and the first line of type starts on the same left
   edge as every line beneath it. .43em is measured, not guessed — both marks
   have an advance of .4294em in Louize, and it scales with the type. */
.passage > p:first-of-type::before { margin-left: -.43em; }
.passage > p:last-of-type::after { margin-right: -.43em; }

/* …unless the passage brought its own, or has no source and so is nobody's
   words but Kody's. */
.passage--self-opened > p:first-of-type::before,
.passage--self-closed > p:last-of-type::after,
.passage--own > p:first-of-type::before,
.passage--own > p:last-of-type::after { content: none; }

/* A card shows only so many lines and keeps the rest, which is why the whole
   passage is always in the markup: opening one, or drawing a fresh one, is
   never a fetch mid-read. The cut is a height rather than a line clamp
   because a clamp wants one run of text and a passage can be several
   paragraphs, a letter, or a poem. The garden sets its line count and
   line-height alongside its own font-size; the home sets none, and lets the
   card's trim do the cutting. This is only the mechanics.

   The cut is a plain crop, with no fade above it. The box is widened by the width of a hung mark and the width
   given straight back as margin: overflow clips both axes, and CSS gives no
   way to clip one and not the other, so the mark has to sit inside the box
   that does the clipping instead of hanging outside it. */
.passage--card {
  overflow: hidden;
  padding-inline: .5em;
  margin-inline: -.5em;
}


/* --------------------------------------------------------------------------
   7. Messenger
   A round + in the bottom-left corner of every page that opens into a sheet
   of paper to write on (node 311:1097). The markup is a <details>
   (partials/messenger.njk), so it opens and shuts with or without
   messenger.js; the script only adds the exit, the draft and the post.

   It is drawn once and reads the same on every page, so it pins its own type
   to the site scale rather than taking the home's step above it.

   It is drawn on the home's artboard, so it takes the home's scale for the
   window, which messenger.js writes as --messenger-s. Zoom rather than a
   transform, as About's nav does: the type is set again at the smaller
   size instead of being shrunk as a picture. Without the script it is
   simply drawn size.

   Both anchor square in the bottom-left corner, as drawn: the sheet 20px
   in from the bottom and the left, the + 44px. The sheet's 20 is screen
   pixels whatever the scale — --edge is divided back out of the zoom, as
   are the window units that cap the sheet's size — and the + sits 24 of the
   sheet's own pixels further in, so it is 44 at full size.

   The + and the x are the same button. Opened, it shrinks to the x's 38.9px
   and turns a quarter about its own centre, so it never leaves the spot it
   was pressed in; its hairline shrinks with it, as drawn. That puts the x
   54.75px in from both edges of the sheet, level with the email and send
   row. (The artboard draws it 28px lower, under the row.)

   --plus-k is the +'s size against the drawn 61.5px, and everything the +
   draws — the glyph, the hairline, the shadow, where the sheet opens from —
   is multiplied by it, so a phone can set it smaller and the whole button
   comes down in proportion.
   -------------------------------------------------------------------------- */
.messenger {
  --type-scale: var(--type-site);
  --plus-k: 1;
  --plus: calc(61.5px * var(--plus-k));
  --edge: calc(20px / var(--messenger-s, 1));
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 1000;
  zoom: var(--messenger-s, 1);
  font-family: var(--font-ui);
  color: var(--warm-grey);
}

.messenger__toggle {
  position: absolute;
  left: calc(var(--edge) + 24px);
  bottom: calc(var(--edge) + 24px);
  z-index: 1;
  display: grid;
  place-items: center;
  width: var(--plus);
  height: var(--plus);
  border: calc(.25px * var(--plus-k)) solid var(--warm-grey);
  border-radius: 50%;
  background: var(--paper);
  color: var(--warm-grey);
  box-shadow: 0 0 calc(3px * var(--plus-k)) rgba(0, 0, 0, .15);
  list-style: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform 240ms var(--ease-settle),
    color 200ms var(--ease-magnet),
    background-color 200ms var(--ease-magnet),
    border-color 200ms var(--ease-magnet),
    box-shadow 200ms var(--ease-magnet);
}

.messenger__toggle::-webkit-details-marker { display: none; }
.messenger__toggle svg {
  display: block;
  width: calc(29px * var(--plus-k));
  height: calc(29px * var(--plus-k));
}

.messenger[open] > .messenger__toggle {
  transform: scale(.6324) rotate(45deg);
}

/* Shutting: messenger.js plays the sheet out while it is still open, and
   the x turns back into the + alongside it. */
.messenger[open].is-closing > .messenger__toggle { transform: none; }

/* Under the cursor the + is inked and lifts onto the postcard shadow; the x
   goes red, the one place the site spends it outside an inline link. */
.messenger__toggle:focus-visible { outline: none; }

.messenger:not([open]) > .messenger__toggle:focus-visible {
  border-color: var(--ink);
  color: var(--ink);
  box-shadow: var(--shadow-rest);
}

.messenger[open]:not(.is-closing) > .messenger__toggle:focus-visible {
  border-color: var(--paper);
  background: var(--red);
  color: var(--paper);
  box-shadow: none;
}

@media (hover: hover) and (pointer: fine) {
  .messenger:not([open]) > .messenger__toggle:hover {
    border-color: var(--ink);
    color: var(--ink);
    box-shadow: var(--shadow-rest);
  }

  .messenger[open]:not(.is-closing) > .messenger__toggle:hover {
    border-color: var(--paper);
    background: var(--red);
    color: var(--paper);
    box-shadow: none;
  }
}

/* The sheet. Its corner sits under the x, and it opens out of that corner. */
.messenger__panel {
  position: absolute;
  left: var(--edge);
  bottom: var(--edge);
  display: flex;
  flex-direction: column;
  width: min(512px, calc(100vw / var(--messenger-s, 1) - 2 * var(--edge)));
  height: min(518px, calc(100dvh / var(--messenger-s, 1) - 2 * var(--edge)));
  padding: 40px 40px 39px;
  border: .25px solid var(--warm-grey);
  border-radius: 4px;
  background: var(--paper);
  box-shadow: var(--shadow-rest);
  --x-at: calc(24px + var(--plus) / 2);
  transform-origin: var(--x-at) calc(100% - var(--x-at));
}

.messenger[open] > .messenger__panel {
  animation: messenger-open 220ms var(--ease-settle);
}

@keyframes messenger-open {
  from { opacity: 0; transform: scale(.94); }
}

.messenger__message {
  flex: 1;
  min-height: 0;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  resize: none;
  background: none;
  font: inherit;
  font-size: calc(18px * var(--type-scale));
  line-height: 1.36;
  color: var(--ink);
  caret-color: var(--ink);
  text-wrap: pretty;
}

.messenger__message::placeholder,
.messenger__email input::placeholder {
  color: var(--warm-grey);
  opacity: 1;
}

/* The bottom row clears the x, then the email, then the send pinned right. */
.messenger__row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 43px;
  padding-left: 75px;
}

/* Writing an address, and for as long as one is written, the key lifts off
   the line and the field takes the room it leaves, out to the send. The key
   is a one-column grid so its width can run from 1fr to 0fr — a track can
   be eased where an auto width can't — while the word itself overflows the
   shrinking track and rises straight up, so it ends over the first letter
   of the address. The email fills the row either way; nothing is drawn
   there, so at rest it reads as it did. A draft brought back with an
   address in it opens already lifted. */
.messenger__email {
  --field: 220ms var(--ease-settle);
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  transition: gap var(--field);
}

.messenger__key {
  display: grid;
  grid-template-columns: 1fr;
  font-size: calc(10px * var(--type-scale));
  line-height: 1.28;
  letter-spacing: 1px;
  text-transform: uppercase;
  opacity: .7;
  transition: grid-template-columns var(--field);
}

.messenger__key > span {
  min-width: 0;
  white-space: nowrap;
  transition: transform var(--field);
}

.messenger__email:focus-within,
.messenger__email:has(input:not(:placeholder-shown)) { gap: 0; }

.messenger__email:focus-within .messenger__key,
.messenger__email:has(input:not(:placeholder-shown)) .messenger__key { grid-template-columns: 0fr; }

.messenger__email:focus-within .messenger__key > span,
.messenger__email:has(input:not(:placeholder-shown)) .messenger__key > span { transform: translateY(-2.3em); }

.messenger__email input {
  width: 178px;
  min-width: 0;
  margin: 0;
  padding: 3px 10px 4px;
  transition: width var(--field), padding-left var(--field);
  border: 0;
  outline: 0;
  background: none;
  font: inherit;
  font-size: calc(16px * var(--type-scale));
  line-height: 1.36;
  color: var(--ink);
  caret-color: var(--ink);
}

.messenger__email:focus-within input,
.messenger__email:has(input:not(:placeholder-shown)) input {
  width: 100%;
  padding-left: 0;
}

/* The send button says how near the message is to going, without a word.
   Nothing written, it is frost; written, it is a hairline on paper; with an
   address too, the form is valid and it fills. Under the cursor it inks.
   All of it is read off the form itself, so it holds without the script. */
.messenger__send {
  flex: none;
  margin: 0 0 0 auto;
  padding: 3px 10px 4px;
  border: .25px solid var(--warm-grey);
  border-radius: 3px;
  background: var(--paper);
  font: inherit;
  font-size: calc(16px * var(--type-scale));
  line-height: 1.28;
  color: var(--warm-grey);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color 200ms var(--ease-magnet),
    color 200ms var(--ease-magnet),
    box-shadow 200ms var(--ease-magnet);
}

.messenger__panel:has(.messenger__message:placeholder-shown) .messenger__send {
  background: var(--nav-frost);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

.messenger__panel:valid .messenger__send {
  background: var(--warm-grey);
  color: var(--paper);
}

.messenger__panel:valid .messenger__send:focus-visible {
  outline: none;
  background: var(--ink);
  box-shadow: var(--shadow-rest);
}

@media (hover: hover) and (pointer: fine) {
  .messenger__panel:valid .messenger__send:hover {
    background: var(--ink);
    box-shadow: var(--shadow-rest);
  }
}

.messenger__send[aria-disabled="true"] { cursor: progress; }

/* A phone: the sheet takes the width, 12px in from the corner rather than
   20, and the email and the send each get a line of their own, centred in
   the sheet. The field keeps its drawn width when the key lifts, so the
   pair narrows and recentres, and the key still ends over the first letter.
   The + comes in with the sheet and down to 44px, a fingertip, so the x is
   27.8 and sits 46px in from the sheet's corner. The bottom padding brings
   the send level with it: the x's centre, less the sheet's hairline, less
   half the send — its line, padding and hairline — so it follows the type
   scale. A phone never zooms (messenger.js), so --edge needs no division. */
@media (max-width: 40rem) {
  .messenger { --edge: 12px; --plus-k: calc(44 / 61.5); }

  .messenger__panel {
    padding: 24px 24px calc(var(--x-at) - .25px - (16px * var(--type-scale) * 1.28 + 7.5px) / 2);
  }

  .messenger__row {
    flex-wrap: wrap;
    row-gap: 16px;
    margin-top: 24px;
    padding-left: 0;
  }

  .messenger__email { flex: 1 0 100%; justify-content: center; }

  .messenger__email input,
  .messenger__email:focus-within input,
  .messenger__email:has(input:not(:placeholder-shown)) input { width: 178px; }

  .messenger__send { margin: 0 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .messenger__toggle { transition-property: color, background-color, border-color, box-shadow; }
  .messenger__email { --field: 0s; }

  @keyframes messenger-open {
    from { opacity: 0; }
  }
}

@media print {
  .messenger { display: none; }
}
