/* ============================================================
   ramblings & musings — shared paper stylesheet
   Type: Exposure (variable serif) + Departure Mono, on cream by day
   and on a warm near-black at night — the theme follows the system.
   EB Garamond is kept only as a per-glyph fallback so Vietnamese
   (e.g. identity.html) still renders where Exposure lacks glyphs.
   ============================================================ */

/* --- Exposure: body serif (variable weight) --- */
@font-face{
  font-family:'Exposure';
  src:url(../fonts/Exposure_VAR.woff2) format('woff2-variations'),
      url(../fonts/Exposure_VAR.woff2) format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap;
}
@font-face{
  font-family:'Exposure';
  src:url(../fonts/Exposure_VAR-550-Italic.woff2) format('woff2');
  font-weight:400 600;font-style:italic;font-display:swap;
}
/* Exposure 650: a crisp static instance for titles & emphasis */
@font-face{
  font-family:'Exposure 650';
  src:url(../fonts/Exposure_VAR-650.woff2) format('woff2');
  font-weight:650;font-style:normal;font-display:swap;
}
/* Departure Mono: tracked-caps labels & footer */
@font-face{
  font-family:'Departure Mono';
  src:url(../fonts/DepartureMono-Regular.woff2) format('woff2');
  font-weight:400;font-style:normal;font-display:swap;
}

/* --- EB Garamond: fallback only (Vietnamese diacritics, etc.) --- */
@font-face{font-family:'EB Garamond';font-style:normal;font-weight:400 600;font-display:swap;src:url(../fonts/eb-garamond-latin.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'EB Garamond';font-style:normal;font-weight:400 600;font-display:swap;src:url(../fonts/eb-garamond-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'EB Garamond';font-style:normal;font-weight:400 600;font-display:swap;src:url(../fonts/eb-garamond-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}
@font-face{font-family:'EB Garamond';font-style:italic;font-weight:400 600;font-display:swap;src:url(../fonts/eb-garamond-latin-italic.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'EB Garamond';font-style:italic;font-weight:400 600;font-display:swap;src:url(../fonts/eb-garamond-latin-ext-italic.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'EB Garamond';font-style:italic;font-weight:400 600;font-display:swap;src:url(../fonts/eb-garamond-vietnamese-italic.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}

/* --- tokens ---
   Cream paper and warm ink by day; a warm near-black and a warm
   off-white by night. Every pair is written once, with light first:
   light-dark() picks a side from the resolved colour scheme, so the
   system preference and a reader's manual override share one palette
   and neither needs to restate it.

   Night contrast sits a little under the day's — light type on dark
   blooms, so .88 reads about as solid as .92 does the other way. */
:root{
  color-scheme:light dark;                    /* no choice made: follow the system */
  --paper:light-dark(#fffbf4,#141210);
  --ink:light-dark(rgba(43,43,43,.92),rgba(243,237,226,.88));
  --ink-soft:light-dark(rgba(43,43,43,.55),rgba(243,237,226,.52));
  --ink-faint:light-dark(rgba(43,43,43,.28),rgba(243,237,226,.26));
  --rule:light-dark(rgba(43,43,43,.12),rgba(243,237,226,.13));
  --selection:light-dark(rgba(43,43,43,.12),rgba(243,237,226,.14));
  --ink-strong:light-dark(#000,#fffbf4);      /* hover/pressed: ink at full strength */
  --serif:"Exposure","EB Garamond","Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
  --serif-bold:"Exposure 650","Exposure","EB Garamond",Georgia,serif;
  --mono:"Departure Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,monospace;
}

/* A manual choice pins the scheme, which is all light-dark() needs.
   js/theme.js sets the attribute; without it the :root rule above
   stands and the system keeps deciding. */
:root[data-theme="light"]{color-scheme:light}
:root[data-theme="dark"]{color-scheme:dark}

/* --- base --- */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{font-size:62.5%;background:var(--paper)}
body{
  min-height:100vh;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--serif);
  font-size:1.9rem;
  line-height:1.65;
  /* kerning and common ligatures are already on; old-style figures are
     the only thing worth asking for, and the property asks for it in a
     way a non-variable fallback still understands */
  font-variant-numeric:oldstyle-nums;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

/* --- paper texture: a vellum wash + a fine, slowly drifting grain,
       both self-contained SVG noise blended into the paper.

   Texture is the one thing light-dark() can't carry — these are images
   and a blend mode, not colours. So the four noise fields are declared
   once here and the theme only chooses between them. Dark specks
   multiplied into cream become light specks screened onto night: same
   fields, repainted, at matched strength so the grain survives the
   inversion instead of going flat. --- */
:root{
  --wash-day:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 800'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.17 0 0 0 0 0.17 0 0 0 0 0.17 0 0 0 0.18 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  --wash-night:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 800'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.96 0 0 0 0 0.93 0 0 0 0 0.86 0 0 0 0.18 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  --grain-day:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='2.4' numOctaves='1' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.45 0'/></filter><rect width='100%' height='100%' filter='url(%23g)'/></svg>");
  --grain-night:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='2.4' numOctaves='1' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.45 0'/></filter><rect width='100%' height='100%' filter='url(%23g)'/></svg>");
  /* day is the default, matching the palette above */
  --wash:var(--wash-day);
  --grain:var(--grain-day);
  --grain-blend:multiply;
  --wash-opacity:.08;
  --grain-opacity:.045;
}
/* night texture — when the system asks and no override says otherwise… */
@media(prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --wash:var(--wash-night);
    --grain:var(--grain-night);
    --grain-blend:screen;
    --wash-opacity:.10;
    --grain-opacity:.05;
  }
}
/* …or when the reader picks dark outright */
:root[data-theme="dark"]{
  --wash:var(--wash-night);
  --grain:var(--grain-night);
  --grain-blend:screen;
  --wash-opacity:.10;
  --grain-opacity:.05;
}

body::before,body::after{content:"";position:fixed;inset:0;pointer-events:none;z-index:-1}
body::before{
  background-image:var(--wash);
  background-size:320px 320px;mix-blend-mode:var(--grain-blend);opacity:var(--wash-opacity);
}
body::after{
  background-image:var(--grain);
  background-size:180px 180px;mix-blend-mode:var(--grain-blend);opacity:var(--grain-opacity);
  animation:grain-drift 14s steps(8) infinite;
}
@keyframes grain-drift{
  0%{transform:translate(0,0)}
  20%{transform:translate(-3%,2%)}
  40%{transform:translate(2%,-1%)}
  60%{transform:translate(-1%,3%)}
  80%{transform:translate(3%,1%)}
  100%{transform:translate(0,0)}
}

/* --- typography --- */
h1,h2,h3,h4,h5,h6{
  font-family:var(--serif-bold);font-weight:650;
  line-height:1.15;letter-spacing:-.02em;margin:0 0 1rem;
}
/* pretty keeps a paragraph from ending on a single stranded word */
p{margin:0 0 1.4em;text-wrap:pretty}
strong,b{font-family:var(--serif-bold);font-weight:650}
em,i{font-style:italic}
a{color:var(--ink);text-decoration:none}
hr{margin:2.6em 0;border:0;border-top:1px solid var(--rule)}
::selection{background:var(--selection)}

/* --- layout --- */
/* the outer bound only has to clear the prose column plus its gutters —
   the measure itself is set on .story-container below */
.container{
  position:relative;z-index:0;
  width:88%;max-width:38em;margin:0 auto;
  padding:11vh 22px 16vh;
}
/* a phone has no width to spare: the 20px gutter is the whole margin,
   which buys the line another five characters */
@media(max-width:599px){.container{width:100%;padding:5.5rem 20px 7rem}}

/* index: the menu block is vertically centred (sitting a touch below
   middle) inside an 800px column, left-aligned — exactly as before */
.index-page{display:flex;flex-direction:column;align-items:center;justify-content:center}
.index-main{width:85%;max-width:800px;padding:0 20px}
.index-main .menu-container{padding-top:20vh;font-size:1.6rem;line-height:1.28}
@media(min-width:768px){.index-main .menu-container{font-size:1.7rem}}
@media(max-height:700px){.index-main .menu-container{padding-top:0}}
/* mobile: keep the block centred, then nudge it a little below centre —
   a transform can't jam it to the top or overflow the way vh padding did */
@media(max-width:599px){
  .index-main .menu-container{padding-top:0;font-size:1.9rem;line-height:1.35}
  .index-main{transform:translateY(9vh)}
}

/* the measure: 34em of Exposure carries ~66 characters a line, the
   middle of the comfortable 60–75 band. Being in em, it tracks the
   fluid body size, so the line length holds at every viewport. */
.story-container{max-width:34em;margin:0 auto}

/* --- shared pieces --- */
.eyebrow{
  font-family:var(--mono);font-size:1.15rem;line-height:1.4;
  letter-spacing:.22em;text-transform:uppercase;
  color:var(--ink-faint);margin:0 0 1.4em;
}
.masthead{margin-bottom:3.4em}

/* one step of the scale above the body, and it rides the same fluid
   curve because the size is in em — no second breakpoint needed.
   balance evens out a title that runs to two lines. */
.page-title{
  font-family:var(--serif-bold);font-weight:650;
  font-size:1.9em;line-height:1.04;letter-spacing:-.03em;
  text-wrap:balance;
  margin:0 0 .3em;
}

.subtitle{font-size:.8em;font-style:italic;color:var(--ink-soft);margin:0 0 2.2em;text-wrap:pretty}
.by{color:var(--ink-soft)}

/* --- index menu ---
   Nothing slides and nothing is drawn underneath: under the pointer the
   word simply takes more ink. Exposure 650 is duplexed against Exposure
   — identical advance widths, heavier stroke — so a bold twin stacks in
   exact register on top of the light one and fades in. Pure opacity: no
   reflow, no smear, and the density only ever rises.

   The twin reads from data-label, so a link added to the index needs
   that attribute carrying the same text as the link itself. */
.menu-container{max-width:30em}
.menu-link{
  position:relative;display:inline-block;color:var(--ink);text-decoration:none;
  line-height:1.3;
  /* release snaps; press can feel slightly more deliberate via :active */
  transition:color .2s ease-out,transform .12s cubic-bezier(.23,1,.32,1);
}
.menu-link::before{
  content:attr(data-label);
  position:absolute;left:0;top:0;white-space:nowrap;pointer-events:none;
  font-family:var(--serif-bold);font-weight:650;color:var(--ink-strong);
  opacity:0;transition:opacity .2s ease-out;
}
.menu-link:hover,.menu-link:focus-visible{color:var(--ink-strong)}
.menu-link:hover::before,.menu-link:focus-visible::before{opacity:1}
/* the row never moves, so keyboard focus needs a mark of its own — the
   same hairline the theme toggle draws */
.menu-link:focus-visible{outline:1px solid var(--ink-faint);outline-offset:.35em;border-radius:.08em}
/* the press is the one moment the word does move: a pixel down, the way
   type is pushed into paper */
.menu-link:active{transition-duration:.16s,.16s;transform:translateY(1px)}

/* first paint: everything fades in softly together */
.index-main *{animation:fade-in 1.4s cubic-bezier(.2,.6,.2,1) backwards}
@keyframes fade-in{from{opacity:0;filter:blur(4px);transform:translateY(6px)}}

/* departure: the page dissolves — blur, fade, gentle upward drift */
.index-main,.site-footer{transition:opacity .4s cubic-bezier(.4,0,1,1),filter .4s cubic-bezier(.4,0,1,1),transform .4s cubic-bezier(.4,0,1,1)}
body.is-leaving .index-main,body.is-leaving .site-footer{opacity:0;filter:blur(4px);transform:translateY(-4px)}
/* coming back (bfcache): drop the departure state instantly, never in reverse */
body.is-resetting,body.is-resetting *,body.is-resetting *::before,body.is-resetting *::after{transition:none!important}

/* navigating: dim siblings quickly; chosen link holds its inked state */
body.is-navigating .menu-link:not(.is-loading){opacity:.3;transition:opacity .15s cubic-bezier(.23,1,.32,1)}
.menu-link.is-loading{color:var(--ink-strong)}
.menu-link.is-loading::before{opacity:1}

/* top progress bar shown while an external page loads */
.page-progress{
  position:fixed;top:0;left:0;height:2px;width:100%;
  background:var(--ink);transform:scaleX(0);transform-origin:left;
  opacity:0;z-index:100;pointer-events:none;
}
.page-progress.is-active{
  opacity:1;transform:scaleX(.92);
  transition:transform 5.5s linear,opacity .2s ease;
}

/* --- theme toggle ---
   One disc, three faces, cycling auto → light → dark. Two masks bite
   into it and the rays fold away, so each mode is the same object in a
   different state rather than three icons swapping places:

     auto   a disc split down the middle — half of each, i.e. whatever
            the system says
     light  the disc opens into a full sun, rays out
     dark   a circle slides across and leaves a crescent

   Driven by data-theme, which is absent exactly when the theme is auto,
   so the default state below is the auto face. Injected by js/theme.js,
   so the control only exists where it can work — with scripting off the
   page still follows the system and no dead button sits in the corner. */
.theme-toggle{
  position:fixed;top:2.4rem;right:2.4rem;z-index:20;
  display:grid;place-items:center;
  width:3.4rem;height:3.4rem;padding:0;
  border:0;border-radius:50%;background:none;cursor:pointer;
  color:var(--ink-faint);
  -webkit-tap-highlight-color:transparent;
  animation:fade-in 1.4s cubic-bezier(.2,.6,.2,1) backwards;
  /* the last two carry the departure dissolve; this rule sits after the
     .index-main transition, so the toggle has to declare it itself */
  transition:color .16s ease,transform .12s cubic-bezier(.23,1,.32,1),
             opacity .4s cubic-bezier(.4,0,1,1),filter .4s cubic-bezier(.4,0,1,1);
}
body.is-leaving .theme-toggle{opacity:0;filter:blur(4px)}
@media(max-width:599px){.theme-toggle{top:1.6rem;right:1.6rem}}
.theme-toggle:hover,.theme-toggle:focus-visible{color:var(--ink)}
.theme-toggle:focus-visible{outline:1px solid var(--ink-faint);outline-offset:.4rem}
.theme-toggle:active{transform:scale(.9)}
.theme-toggle svg{display:block;width:1.9rem;height:1.9rem;overflow:visible}
/* announces the new mode to screen readers; a button's own label change
   goes unspoken, and the icon alone says nothing out loud */
.tm-status{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;
}

/* geometry properties animate as CSS, so every change is a real tween */
.tm-disc{transition:r .5s cubic-bezier(.16,1,.3,1)}
.tm-cut{transition:cx .5s cubic-bezier(.16,1,.3,1),cy .5s cubic-bezier(.16,1,.3,1)}
.tm-half{transition:x .5s cubic-bezier(.16,1,.3,1)}
.tm-ring{transition:opacity .3s ease}
.tm-rays{
  transform-origin:12px 12px;
  transition:opacity .3s ease,transform .5s cubic-bezier(.16,1,.3,1);
}
/* auto — the default, since data-theme is absent only when following the
   system. The ring closes the circle so the half-filled disc reads as a
   whole shape; without it a bare half-disc is too easily mistaken for
   the crescent at this size. */
.tm-disc{r:7}
.tm-cut{cx:30;cy:6}
.tm-half{x:12}
.tm-ring{opacity:1}
.tm-rays{opacity:0;transform:rotate(-45deg) scale(.4)}
/* light — the half-mask slides off and the sun opens up */
:root[data-theme="light"] .tm-disc{r:5}
:root[data-theme="light"] .tm-half{x:24}
:root[data-theme="light"] .tm-ring{opacity:0}
:root[data-theme="light"] .tm-rays{opacity:1;transform:none}
/* dark — the half-mask leaves, the round one arrives, cutting a crescent */
:root[data-theme="dark"] .tm-half{x:24}
:root[data-theme="dark"] .tm-cut{cx:17;cy:7}
:root[data-theme="dark"] .tm-ring{opacity:0}

.site-footer{
  position:fixed;bottom:2.4rem;left:50%;transform:translateX(-50%);
  width:85%;max-width:800px;padding:0 20px;pointer-events:none;
  font-family:var(--mono);font-size:1.1rem;
  letter-spacing:.24em;text-transform:uppercase;color:var(--ink-faint);
}

/* --- recipe (coffee.html) --- */
.recipe{margin:0}
.recipe-header{
  display:flex;align-items:baseline;justify-content:space-between;
  gap:1.5em;margin-bottom:1.6em;padding-bottom:.9em;
  border-bottom:1px solid var(--rule);
}
@media(max-width:480px){.recipe-header{flex-direction:column;align-items:flex-start;gap:.1em}}
.recipe-title{font-family:var(--serif-bold);font-weight:650;font-size:1em;line-height:1.25;margin:0}
.recipe-num{font-family:var(--serif);font-style:italic;font-weight:400;color:var(--ink-faint);margin-right:.5em}
.recipe-ratio{font-style:italic;color:var(--ink-soft);font-size:.85em;white-space:nowrap;margin:0}

.timeline{display:grid;grid-template-columns:5.4em 1fr;column-gap:1.6em;row-gap:.32em;margin:0 0 2em;font-size:.9em}
/* timings line up in a column, so they want lining figures of one width */
.timeline dt{color:var(--ink-faint);font-style:italic;text-align:right;font-variant-numeric:lining-nums tabular-nums;white-space:nowrap}
.timeline dd{margin:0;color:var(--ink)}
.timeline dd.pause{color:var(--ink-faint);font-style:italic}
.timeline .pct{color:var(--ink-faint);font-style:italic;font-size:.94em;margin-left:.15em}

.notes{list-style:none;margin:0;padding:0;font-size:.9em;color:var(--ink-soft)}
.notes li{padding-left:1.2em;text-indent:-1.2em;margin-bottom:.12em}
.notes li::before{content:"\2014\2009";color:var(--ink-faint)}

.divider{text-align:center;color:var(--ink-faint);letter-spacing:1.2em;margin:3.5em 0 3.5em 1.2em;font-size:.85em}

/* --- footer / back link --- */
.page-footer{margin-top:4.5em;padding-top:1.8em;border-top:1px solid var(--rule)}
.back-link{
  position:relative;display:inline-block;
  font-family:var(--mono);color:var(--ink-faint);text-decoration:none;
  font-size:1.15rem;letter-spacing:.22em;text-transform:uppercase;
  transition:color .16s ease,transform .12s cubic-bezier(.23,1,.32,1);
}
.back-link::before{
  content:"\2190";position:absolute;right:100%;margin-right:.5em;
  opacity:0;transform:translateX(.3em);
  transition:opacity .16s ease,transform .2s cubic-bezier(.16,1,.3,1);
}
.back-link:hover,.back-link:focus-visible{color:var(--ink)}
.back-link:focus-visible{transform:translateX(.4em)}
.back-link:focus-visible::before{opacity:1;transform:translateX(0)}
.back-link:active{transition-duration:.16s,.16s;transform:translateX(.4em) translateY(1px)}
@media(hover:hover) and (pointer:fine){
  .back-link:hover{transform:translateX(.4em)}
  .back-link:hover::before{opacity:1;transform:translateX(0)}
}

/* --- reduced motion: keep opacity/color, drop movement --- */
@media(prefers-reduced-motion:reduce){
  body::after{animation:none}
  .index-main *{animation:none}
  /* toggle: the icon still changes, it just stops tweening there */
  .theme-toggle{animation:none;transition:color .16s ease,opacity .2s ease}
  .theme-toggle:active{transform:none}
  .tm-disc,.tm-cut,.tm-half{transition:none}
  .tm-ring{transition:opacity .2s ease}
  .tm-rays{transition:opacity .2s ease;transform:none}
  :root[data-theme="light"] .tm-rays{transform:none}
  body.is-leaving .theme-toggle{filter:none}
  .menu-link,.back-link{transition:color .16s ease}
  .menu-link:active,
  .back-link:hover,.back-link:focus-visible,.back-link:active{transform:none}
  /* the index's ink crossfade is opacity only, so it survives intact —
     just quicker */
  .menu-link::before{transition:opacity .16s ease}
  .back-link::before{transition:opacity .16s ease;transform:none}
  .back-link:hover::before,.back-link:focus-visible::before{transform:none}
  body.is-navigating .menu-link:not(.is-loading){transition:opacity .15s ease}
  /* departure: opacity only — no blur, no drift */
  .index-main,.site-footer{transition:opacity .2s ease;filter:none}
  body.is-leaving .index-main,body.is-leaving .site-footer{filter:none;transform:none}
  /* progress: opacity only — bar appears fully, no sweep */
  .page-progress.is-active{transition:opacity .2s ease;transform:scaleX(.92)}
}
