/* ==========================================================================
   heydmitri.com

   1. TYPE PRESETS      <- swap the whole type direction here
   2. Tokens
   3. Base
   4. Hero
   5. Split-flap board
   6. Sections
   7. Career timeline
   8. Projects
   9. Bookshelf
  10. Footer + sailboat
  11. Terminal
  12. Theme toggle
  13. Reduced motion
   ========================================================================== */


/* ==========================================================================
   1. TYPE PRESETS

   Five complete type directions, switchable at runtime — no editing needed
   to compare them. In the terminal (press "/"):

       type            list them
       type editorial  apply one
       type next       cycle to the next

   The choice persists, and ?type=editorial works as a URL too.

   Three independent slots, so a preset can mix families by role:

       --font-display   the name, and headings in presets that use a serif
       --font-body      running prose
       --label-family   section labels, dates, status tags, the colophon

   Nothing below this section hardcodes a font.

   swiss is the one the site ships. Only its two families — Inter and IBM
   Plex Mono — load in the <head>; mono needs neither beyond those, and the
   other three fetch their family the first time someone picks them. Kept
   switchable rather than deleted, because the choice may yet be revisited.
   ========================================================================== */

/* ---- 1 · swiss — one grotesk, mono for the small caps stuff (DEFAULT) --- */
/* Bare :root carries the default so the first paint is already right. The
   type is applied by JS at the end of the body; if the fallback here were a
   different preset, every visit would flash the wrong face first. */
:root,
:root[data-type="swiss"] {
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display: var(--font-body);
  --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --name-size: clamp(2.1rem, 6vw, 3.1rem);
  --name-weight: 600;
  --name-spacing: -0.028em;
  --name-transform: none;
  --name-lh: 1.05;

  --label-family: var(--font-mono);
  --label-size: 0.72rem;
  --label-spacing: 0.16em;
  --label-transform: uppercase;
  --label-weight: 500;

  --body-size: 1.02rem;
  --body-lh: 1.62;
}

/* ---- 2 · editorial — high-contrast serif name, sans prose --------------- */
:root[data-type="editorial"] {
  --font-body: 'Inter', -apple-system, sans-serif;
  --font-display: 'Instrument Serif', Georgia, serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

  --name-size: clamp(2.8rem, 8vw, 4.4rem);
  --name-weight: 400;
  --name-spacing: -0.012em;
  --name-transform: none;
  --name-lh: 0.98;

  --label-family: var(--font-mono);
  --label-size: 0.72rem;
  --label-spacing: 0.16em;
  --label-transform: uppercase;
  --label-weight: 500;

  --body-size: 1.04rem;
  --body-lh: 1.66;
}

/* ---- 3 · warm — Fraunces, the flavour dahbiahmed.com uses --------------- */
:root[data-type="warm"] {
  --font-body: 'Inter', -apple-system, sans-serif;
  --font-display: 'Fraunces', Georgia, serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

  --name-size: clamp(2.2rem, 6.4vw, 3.3rem);
  --name-weight: 600;
  --name-spacing: -0.02em;
  --name-transform: none;
  --name-lh: 1.06;

  --label-family: var(--font-display);
  --label-size: 1.12rem;
  --label-spacing: 0;
  --label-transform: none;
  --label-weight: 600;

  --body-size: 1.02rem;
  --body-lh: 1.64;
}

/* ---- 4 · grotesk — geometric display, quieter body ---------------------- */
:root[data-type="grotesk"] {
  --font-body: 'Inter', -apple-system, sans-serif;
  --font-display: 'Space Grotesk', 'Inter', sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

  --name-size: clamp(2.2rem, 6.4vw, 3.4rem);
  --name-weight: 700;
  --name-spacing: -0.035em;
  --name-transform: none;
  --name-lh: 1.02;

  --label-family: var(--font-display);
  --label-size: 0.82rem;
  --label-spacing: 0.08em;
  --label-transform: uppercase;
  --label-weight: 500;

  --body-size: 1.02rem;
  --body-lh: 1.62;
}

/* ---- 5 · mono — IBM Plex Mono all the way down -------------------------- */
:root[data-type="mono"] {
  --font-body: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --font-display: var(--font-body);
  --font-mono: var(--font-body);

  --name-size: clamp(1.5rem, 4.4vw, 2.15rem);
  --name-weight: 600;
  --name-spacing: 0.01em;
  --name-transform: uppercase;
  --name-lh: 1.18;

  --label-family: var(--font-mono);
  --label-size: 0.74rem;
  --label-spacing: 0.12em;
  --label-transform: uppercase;
  --label-weight: 500;

  --body-size: 0.92rem;
  --body-lh: 1.68;
}


/* ==========================================================================
   2. Tokens
   ========================================================================== */

:root {
  --bg:      #F7F7F5;
  --fg:      #0B0B0C;
  --muted:   #6B6B70;
  --line:    #E2E2DE;
  --raised:  #FFFFFF;

  /* Split-flap stays a dark board with light glyphs in BOTH themes — a
     full inversion turns it into a glaring white bar in dark mode. */
  --flap-bg:   var(--fg);
  --flap-fg:   var(--bg);
  --flap-seam: var(--bg);

  --measure: 680px;
  --gutter: clamp(1.25rem, 5vw, 2rem);
  --section-gap: clamp(4.5rem, 11vh, 7.5rem);

  --dur: 0.22s;
  --ease: cubic-bezier(0.2, 0.6, 0.3, 1);
}

[data-theme="dark"] {
  --bg:      #0B0B0C;
  --fg:      #F2F2F0;
  --muted:   #8A8A90;
  --line:    #232326;
  --raised:  #141416;

  --flap-bg:   #1E1E22;
  --flap-fg:   #F2F2F0;
  --flap-seam: #0B0B0C;
}


/* ==========================================================================
   3. Base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--body-size);
  line-height: var(--body-lh);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul, ol { list-style: none; padding: 0; }

a {
  color: inherit;
  text-decoration: none;
  text-decoration-color: var(--line);
}

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

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

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--fg);
  color: var(--bg);
  padding: 0.6rem 1rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
}
.skip-link:focus { left: 0.5rem; top: 0.5rem; }

.noscript {
  max-width: var(--measure);
  margin: 0 auto;
  padding: clamp(3rem, 10vh, 6rem) var(--gutter);
}
.noscript h1 {
  font-size: var(--name-size);
  font-weight: var(--name-weight);
  letter-spacing: var(--name-spacing);
  margin-bottom: 0.7rem;
}
.noscript p { margin-bottom: 1rem; max-width: 60ch; }
.noscript a { border-bottom: 1px solid var(--line); }

/* Any TODO left in content.js renders visibly. */
.todo {
  border-bottom: 1px dashed currentColor;
  opacity: 0.72;
}
.todo::before {
  content: 'TODO';
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  vertical-align: 0.35em;
  margin-right: 0.4em;
  padding: 0.1em 0.35em;
  border: 1px solid currentColor;
  border-radius: 2px;
}


/* ==========================================================================
   4. Hero — exactly one viewport
   ========================================================================== */

/* The page is one centred column. With the sections collapsed, the whole
   site fits a screen, so the hero no longer reserves a full viewport. */
.page {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.hero {
  padding-top: clamp(1rem, 2.5vh, 1.75rem);
  padding-bottom: clamp(1.25rem, 3vh, 1.9rem);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* --- avatar --- */

.avatar-stage {
  width: clamp(98px, 13vh, 134px);
  aspect-ratio: 1;
  margin-bottom: clamp(0.9rem, 2.5vh, 1.5rem);
}

.avatar {
  display: block;
  width: 100%;
  height: 100%;
  cursor: pointer;
  user-select: none;
  -webkit-user-drag: none;
  will-change: transform;
  /* One frame set serves both themes — the reference site ships two, but
     greyscale plus a per-theme tweak gets there for a third of the work.
     Remove the filter entirely to show the photo in colour. */
  filter: grayscale(1) contrast(1.04);
  transition: transform 0.14s var(--ease);
}

/* The dark theme used to need a brightness lift, because a black t-shirt
   vanished into the background and the shoulders stopped reading. The frames
   are cropped to the face now and fade out at the neck, so there is nothing
   left to rescue — a small lift is enough to keep the darker side of the face
   off the background. */
[data-theme="dark"] .avatar {
  filter: grayscale(1) contrast(1.02) brightness(1.06);
}

.hero__name {
  font-family: var(--font-display);
  font-size: var(--name-size);
  font-weight: var(--name-weight);
  letter-spacing: var(--name-spacing);
  text-transform: var(--name-transform);
  line-height: var(--name-lh);
  margin-bottom: 0.7rem;
}

/* The job title, between the name and what the job means. Set as a label
   rather than a sentence so the three lines read as three different kinds of
   thing — who, what, and what that amounts to — instead of one paragraph. */
.hero__role {
  font-family: var(--label-family);
  font-size: var(--label-size);
  letter-spacing: var(--label-spacing);
  text-transform: uppercase;
  font-weight: var(--label-weight);
  color: var(--muted);
  margin-top: 0.5rem;
  margin-bottom: 0.7rem;
}

.hero__tagline {
  font-size: clamp(1rem, 2.4vw, 1.2rem);
  line-height: 1.45;
  max-width: 34ch;
  color: var(--fg);
  margin-bottom: clamp(1rem, 2.5vh, 1.4rem);
  text-wrap: balance;
}

.hero__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 2.1rem;
  margin-top: clamp(1.35rem, 3vh, 1.9rem);
}

.hero__links a,
.footer__links a {
  font-family: var(--label-family);
  font-size: var(--label-size);
  letter-spacing: var(--label-spacing);
  text-transform: var(--label-transform);
  font-weight: var(--label-weight);
  color: var(--muted);
  padding: 0.35rem 0;
  position: relative;
  transition: color var(--dur) var(--ease);
}
.hero__links a::after,
.footer__links a::after {
  content: '';
  position: absolute;
  left: 0; right: 100%; bottom: 0.15rem;
  height: 1px;
  background: currentColor;
  transition: right var(--dur) var(--ease);
}
.hero__links a:hover,
.footer__links a:hover { color: var(--fg); }
.hero__links a:hover::after,
.footer__links a:hover::after { right: 0; }

/* ==========================================================================
   4b. Accordion sections
   ========================================================================== */

.sections { display: block; }

.section {
  border-top: 1px solid var(--line);
}
.section:last-of-type { border-bottom: 1px solid var(--line); }

.section__label {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.7rem 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--label-family);
  /* a step up from the other mono labels — these are the page's headings */
  font-size: calc(var(--label-size) * 1.3);
  letter-spacing: var(--label-spacing);
  text-transform: var(--label-transform);
  font-weight: var(--label-weight);
  color: var(--fg);
  transition: opacity var(--dur) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
/* hide the native disclosure triangle */
.section__label::-webkit-details-marker { display: none; }
.section__label::marker { content: ''; }

.section__label:hover { opacity: 0.62; }
.section__label:focus-visible { outline-offset: -2px; }

.section__num { color: var(--muted); }
.section__title { flex: 1; }

/* the +/- indicator */
.section__mark {
  position: relative;
  width: 11px;
  height: 11px;
  flex: 0 0 auto;
}
.section__mark::before,
.section__mark::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  background: var(--fg);
  transition: transform 0.28s var(--ease), opacity 0.28s var(--ease);
}
.section__mark::before { width: 100%; height: 1px; }   /* the bar   */
.section__mark::after  { width: 1px;  height: 100%; }  /* the stem  */
.section[open] .section__mark::after { transform: rotate(90deg); opacity: 0; }

.section__body {
  overflow: hidden;
  transition: height 0.34s var(--ease), opacity 0.28s var(--ease);
}
.section__body > * { padding-bottom: 2.5rem; }


/* ==========================================================================
   5. Split-flap board
   ========================================================================== */

.flapboard {
  display: block;
  min-height: 1.9rem;
  margin-bottom: clamp(0.4rem, 1.2vh, 0.8rem);
}

/* One row, always. Wrapping broke words across lines — a 28-column board on
   a phone split CADENCE into CA / DENCE, which reads as a rendering fault
   rather than a design. A grid of shrinkable tracks keeps the line intact:
   minmax(0, --flap-w) lets every cell narrow together when the board is
   wider than the screen, so it never has to wrap. --flap-cols is set by
   main.js from the longest status. */
.flapboard__cells {
  /* on the container, not on .flap — custom properties inherit downward,
     and grid-template-columns is read here */
  --flap-w: 1.02rem;
  display: grid;
  grid-template-columns: repeat(var(--flap-cols, 28), minmax(0, var(--flap-w)));
  gap: 2px;
  padding: 3px;
  background: var(--line);
  border-radius: 3px;
  width: fit-content;
  max-width: 100%;
}

.flap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  height: 1.7rem;
  background: var(--flap-bg);
  color: var(--flap-fg);
  font-family: var(--font-mono);
  /* Derived from the flap, not set beside it. A monospace glyph advances
     about 0.6em, so at this ratio the character clears its cell even once
     the grid has squeezed the tracks — and the two can never drift apart
     the way a separately-tuned font size would. */
  font-size: calc(var(--flap-w) * 0.84);
  font-weight: 500;
  line-height: 1;
  border-radius: 2px;
  /* The seam across the middle of a real flap. Kept faint — at full
     strength it reads as a strike-through across the words. */
  position: relative;
  backface-visibility: hidden;
}
.flap::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--flap-seam);
  opacity: 0.16;
  pointer-events: none;
}

.flap.is-flipping {
  animation: flap-step 78ms ease-in-out;   /* must stay under FLAP_STEP_MS */
}
@keyframes flap-step {
  0%   { transform: rotateX(0deg);   }
  49%  { transform: rotateX(-68deg); }
  50%  { transform: rotateX(68deg);  }
  100% { transform: rotateX(0deg);   }
}


/* ==========================================================================
   6. Sections
   ========================================================================== */

.section__intro {
  color: var(--muted);
  margin-bottom: 1.75rem;
  max-width: 54ch;
}

.prose p { margin-bottom: 1.05em; max-width: 62ch; }
.prose p:last-child { margin-bottom: 0; }

/* Track record. Hanging indent so the dash sits in the margin and the
   wrapped lines stay flush with the first — a paragraph would bury these. */
.about__points {
  list-style: none;
  margin: 0 0 1.45em;
  max-width: 62ch;
}
.about__points li {
  position: relative;
  padding-left: 1.5em;
  margin-bottom: 0.85em;
}
.about__points li:last-child { margin-bottom: 0; }
.about__points li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: var(--muted);
}


/* ==========================================================================
   7. Career timeline
   ========================================================================== */

.timeline > li + li {
  margin-top: 2.4rem;
  padding-top: 2.4rem;
  border-top: 1px solid var(--line);
}

.job__head {
  display: grid;
  grid-template-columns: 9.5rem 1fr;
  gap: 0 1.5rem;
  align-items: baseline;
}

.job__years {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.05em;
  color: var(--muted);
  white-space: nowrap;
  padding-top: 0.25em;
}

.job__role {
  font-weight: 600;
  font-size: 1.03rem;
  letter-spacing: -0.011em;
}

.job__org {
  font-weight: 600;
  font-size: 1.03rem;
  letter-spacing: -0.011em;
}
a.job__org {
  border-bottom: 1px solid var(--line);
  transition: border-color var(--dur) var(--ease);
}
a.job__org:hover { border-bottom-color: var(--fg); }

.job__place {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 0.15rem;
}

.job__note {
  color: var(--muted);
  margin-top: 0.55rem;
  max-width: 58ch;
}

/* Several roles at one company. Indented under the company and hung off a
   rule, so the hierarchy is unmistakable. */
.job__roles {
  margin-top: 1.15rem;
  margin-left: 11rem;
  padding-left: 1.35rem;
  border-left: 1px solid var(--line);
}
.job__roles > li + li { margin-top: 1.15rem; }
.job__roles .job__head { grid-template-columns: 6.5rem 1fr; gap: 0 1rem; }
.job__roles .job__role { font-weight: 500; font-size: 0.95rem; }
.job__roles .job__years { font-size: 0.68rem; }
.job__roles .job__note { font-size: 0.95rem; max-width: 46ch; }


/* ==========================================================================
   8. Projects
   ========================================================================== */

.projects > li + li {
  margin-top: 1.6rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--line);
}

.project__head {
  display: flex;
  align-items: baseline;
  gap: 0.8rem;
  flex-wrap: wrap;
}

.project__name {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: -0.005em;
}
a.project__name {
  border-bottom: 1px solid var(--line);
  transition: border-color var(--dur) var(--ease);
}
a.project__name:hover { border-bottom-color: var(--fg); }
a.project__name::after { content: ' ↗'; font-size: 0.8em; color: var(--muted); }

.project__status {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 0.16em 0.45em;
}
/* Shipped carries the filled chip, in progress the outline. The weight goes to
   the thing that exists — an unfinished project shouldn't shout louder than a
   finished one. build.py always writes the attribute, defaulting to shipped,
   so this selector never misses. */
.project__status[data-status="shipped"] {
  color: var(--bg);
  background: var(--fg);
  border-color: var(--fg);
}

.project__note {
  color: var(--muted);
  margin-top: 0.45rem;
  max-width: 58ch;
}

/* Secondary link on a card whose primary link is the live thing. Same mono as
   the status chip beside it, not --label-family, so it doesn't turn serif in
   the editorial and warm presets while the chip stays mono. */
.project__source {
  display: inline-block;
  margin-top: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.project__source:hover { color: var(--fg); border-bottom-color: var(--fg); }
.project__source::after { content: ' ↗'; letter-spacing: 0; }


/* ==========================================================================
   9. Bookshelf — spines with the selected book faced out
   ========================================================================== */

.shelf-wrap {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.75rem;
  margin: 0 calc(var(--gutter) * -1);
  padding: 1.5rem calc(var(--gutter) - 0.75rem) 0;
}

/* Rounded squares, not faint circles — these are the primary control for
   the section and have to look pressable. */
.shelf-arrow {
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  background: var(--raised);
  /* --line is too faint for the section's primary control; these were
     reported as barely visible against the page */
  border: 1px solid var(--muted);
  border-radius: 6px;
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.shelf-arrow:hover { border-color: var(--fg); background: var(--fg); color: var(--bg); transform: translateY(-1px); }
.shelf-arrow:active { transform: translateY(0); }
.shelf-arrow[disabled] { opacity: 0.3; cursor: default; transform: none; }

.shelf-stage {
  overflow: hidden;
  min-width: 0;
}

.shelf {
  display: flex;
  align-items: flex-end;
  /* flex-start, not center: centring is centre()'s job, and a centred flex
     row that overflows spills off BOTH edges — the left overflow being
     unreachable, which sliced the faced-out cover in half on a phone. It
     also silently double-centred on desktop, where the offset was small
     enough to miss. */
  justify-content: flex-start;
  gap: 3px;
  min-height: 300px;
  padding-bottom: 2px;
  transition: transform 0.42s var(--ease);
  /* centre() translates and scales from here; the books must stay sitting on
     the shelf, so the origin is the bottom-left corner, not the middle. */
  transform-origin: 0 100%;
}

.book { display: block; flex: 0 0 auto; }

.book__btn {
  position: relative;
  display: block;
  width: var(--w, 2.3rem);
  height: var(--h, 250px);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  transition: width 0.42s var(--ease), height 0.42s var(--ease),
              transform 0.3s var(--ease);
}

/* --- spine ------------------------------------------------------------ */

.book__spine {
  --paper: color-mix(in srgb, var(--fg) calc((1 - var(--tone, 0.6)) * 100%), var(--bg));
  --ink: color-mix(in srgb, var(--fg) calc((1 - var(--ink-l, 0.05)) * 100%), var(--bg));

  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  padding: 1.05rem 0.25rem 0.95rem;
  border-radius: 1px 2px 2px 1px;
  color: var(--ink);
  background:
    linear-gradient(to right, transparent 0 82%,
      color-mix(in srgb, var(--paper) 70%, #ffffff) 82% 100%),
    linear-gradient(to right,
      rgba(0,0,0,0.22) 0%, rgba(0,0,0,0.04) 14%,
      rgba(255,255,255,0.10) 46%, rgba(0,0,0,0.06) 82%, rgba(0,0,0,0.16) 100%),
    var(--paper);
  box-shadow: 0 1px 2px rgba(0,0,0,0.18);
  overflow: hidden;
  transition: opacity 0.2s var(--ease);
}
.book__spine::before,
.book__spine::after {
  content: '';
  position: absolute;
  left: 0; right: 18%;
  height: 3px;
  background: color-mix(in srgb, var(--ink) 45%, transparent);
}
.book__spine::before { top: 7px; }
.book__spine::after  { bottom: 7px; }

.book__title,
.book__author {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  position: relative;
}
.book__title {
  font-family: var(--font-body);
  font-size: 0.64rem;
  font-weight: 600;
  max-height: calc(var(--h, 250px) - 76px);
}
.book__author {
  font-family: var(--font-mono);
  font-size: 0.5rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.72;
  max-height: 54px;
  margin-top: 0.4rem;
  flex-shrink: 0;
}

/* --- the faced-out book ------------------------------------------------ */

.book__cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  opacity: 0;
  border-radius: 2px;
  transition: opacity 0.3s var(--ease) 0.08s;
  pointer-events: none;
}

/* Varied heights and thicknesses so the row reads as a shelf. The active
   book overrides both, so this only shapes the spines beside it. */
.book:nth-child(7n+1) .book__btn { --h: 250px; --w: 2.3rem; }
.book:nth-child(7n+2) .book__btn { --h: 228px; --w: 2rem; }
.book:nth-child(7n+3) .book__btn { --h: 264px; --w: 2.65rem; }
.book:nth-child(7n+4) .book__btn { --h: 240px; --w: 2.15rem; }
.book:nth-child(7n+5) .book__btn { --h: 256px; --w: 2.45rem; }
.book:nth-child(7n+6) .book__btn { --h: 222px; --w: 2.1rem; }
.book:nth-child(7n+7) .book__btn { --h: 244px; --w: 2.5rem; }

/* Must outweigh the nth-child sizing above: those are .book + :nth-child +
   .book__btn, which scores the same as .book + .is-active + .book__btn, and
   whichever comes last would win. The extra .shelf settles it rather than
   leaving the faced book at spine width. */
.shelf .book.is-active .book__btn {
  --w: 200px;
  --h: 300px;
  z-index: 2;
}
.book.is-active .book__spine { opacity: 0; }
.book.is-active .book__cover {
  opacity: 1;
  filter: drop-shadow(14px 10px 16px rgba(0,0,0,0.22));
}

.book:not(.is-active) .book__btn:hover { transform: translateY(-10px); }

.book.is-todo .book__spine { outline: 1px dashed var(--muted); outline-offset: -3px; }

/* --- meta under the shelf --------------------------------------------- */

.shelf-meta {
  position: relative;
  min-height: 132px;
  padding-top: 1.4rem;
}

.meta { display: none; }
.meta.is-active { display: block; }

.meta__count {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--fg);
  margin-bottom: 0.6rem;
}
.meta__count span { color: var(--muted); }

.meta__title {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: var(--name-spacing);
  line-height: 1.25;
  margin-bottom: 0.3rem;
}

.meta__author {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-bottom: 1rem;
}

.meta__why {
  background: none;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.45rem 0.95rem;
  cursor: pointer;
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  transition: border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.meta__why:hover { background: var(--fg); color: var(--bg); border-color: var(--fg); }

/* position ticks, bottom right */
.shelf-ticks {
  position: absolute;
  right: 0;
  bottom: 0.35rem;
  display: flex;
  gap: 9px;
  align-items: flex-end;
}
.shelf-ticks .tick {
  width: 1px;
  height: 12px;
  background: var(--muted);
  opacity: 0.5;
  transition: height var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.shelf-ticks .tick.is-active { height: 22px; background: var(--fg); opacity: 1; }

/* --- reader card ------------------------------------------------------- */

.reader {
  margin-top: 1.5rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--raised);
  /* .section__body > * pads every child, which lands that 2.5rem INSIDE this
     border — a wedge of empty card under the link. The tall cover used to
     disguise it. Same spacing, moved outside the box. */
  padding-bottom: 0;
  margin-bottom: 2.5rem;
}
.reader[hidden] { display: none; }

.reader__book { display: none; }
.reader__book.is-open { display: block; }

/* Text only — the cover lives on the shelf above. One column, so the note
   is not stranded in a half-width gutter beside empty space. */
.reader__side {
  padding: 1.5rem 1.6rem 1.75rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.reader__back {
  background: none; border: 0;
  padding: 0 0 1.25rem;
  cursor: pointer;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  transition: color var(--dur) var(--ease);
}
.reader__back:hover { color: var(--fg); }

/* The note is the card now — title, author and cover all live on the shelf
   above — so it carries a little more weight than a caption would. */
.reader__note {
  color: var(--fg);
  font-size: 1rem;
  max-width: 58ch;
}

.reader__link {
  display: inline-block;
  margin-top: 1.2rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  color: var(--fg);
  border-bottom: 1px solid var(--fg);
  padding-bottom: 1px;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.reader__link:hover { color: var(--muted); border-bottom-color: var(--muted); }

@media (max-width: 620px) {
  .shelf-arrow { width: 36px; height: 36px; }
  .shelf .book.is-active .book__btn { --w: 150px; --h: 236px; }
  .shelf { min-height: 244px; }
  .shelf-ticks { position: static; margin-top: 1.2rem; justify-content: flex-start; }
  .shelf-meta { min-height: 0; }
}


/* ==========================================================================
   10. Footer + sailboat
   ========================================================================== */

.footer {
  padding: 1.1rem 0 1.3rem;
  position: relative;
}

.footer__note {
  color: var(--muted);
  max-width: 60ch;
  margin-bottom: 0;
}

/* The only contact link in the body copy — it has to read as clickable. */
.footer__note a {
  color: var(--fg);
  border-bottom: 1px solid var(--fg);
  padding-bottom: 1px;
  white-space: nowrap;   /* a two-word link split across lines reads as broken */
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.footer__note a:hover {
  color: var(--muted);
  border-bottom-color: var(--muted);
}

.footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0 1.4rem;
}

.footer__colophon {
  margin-top: 1.3rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  justify-content: space-between;
  border-top: 1px solid var(--line);
  padding-top: 1.25rem;
}
.footer__colophon a { border-bottom: 1px solid var(--line); }
.footer__colophon a:hover { border-bottom-color: var(--fg); }

.footer__colophon kbd {
  font-family: inherit;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 3px;
  padding: 0.05em 0.35em;
}

/* --- sailboat --- */

.sea {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.35rem;
  height: 48px;
  pointer-events: none;
  overflow: hidden;
}

.sea__line {
  position: absolute;
  left: 0; right: 0; bottom: 4px;
  height: 1px;
  background: var(--line);
}

.boat {
  position: absolute;
  bottom: 4px;
  left: 0;
  width: 34px;
  height: 26px;
  color: var(--muted);
  opacity: 0;
  transform: translateX(-60px);
}

.sea.is-sailing .boat {
  animation: sail 9s linear 1 both;
}

@keyframes sail {
  0%       { transform: translateX(-60px) rotate(-2deg); opacity: 0; }
  6%       { opacity: 1; }
  25%      { transform: translateX(25vw)  rotate(2deg);  }
  50%      { transform: translateX(50vw)  rotate(-2deg); }
  75%      { transform: translateX(75vw)  rotate(2deg);  }
  94%      { opacity: 1; }
  100%     { transform: translateX(102vw) rotate(-1deg); opacity: 0; }
}


/* ==========================================================================
   11. Terminal
   ========================================================================== */

.terminal {
  position: fixed;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  width: min(640px, calc(100vw - 2rem));
  max-height: 60vh;
  z-index: 60;
  background: var(--bg);
  border: 1px solid var(--fg);
  border-radius: 4px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.55;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
[data-theme="dark"] .terminal { box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6); }

.terminal[hidden] { display: none; }

.terminal__out {
  padding: 0.85rem 0.9rem 0.25rem;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.terminal__out .is-dim { color: var(--muted); }
.terminal__out .is-echo { color: var(--fg); }

.terminal__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.9rem 0.8rem;
}

.terminal__prompt { color: var(--muted); white-space: nowrap; }

.terminal__input {
  flex: 1;
  background: none;
  border: 0;
  color: var(--fg);
  font: inherit;
  padding: 0;
  min-width: 0;
}
.terminal__input:focus { outline: none; }


/* ==========================================================================
   12. Theme toggle
   ========================================================================== */

.theme-toggle {
  position: fixed;
  top: clamp(1rem, 3vh, 1.6rem);
  right: clamp(1rem, 4vw, 2rem);
  z-index: 50;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--fg);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.theme-toggle:hover { border-color: var(--fg); transform: scale(1.06); }

/* filled circle in light mode (offering dark), ring in dark mode */
.theme-toggle__icon {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--fg);
  transition: box-shadow var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
[data-theme="dark"] .theme-toggle__icon {
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--fg);
}


/* ==========================================================================
   13. Narrow screens
   ========================================================================== */

@media (max-width: 640px) {
  .job__head { grid-template-columns: 1fr; gap: 0.3rem; }
  .job__years { padding-top: 0; }
  .job__roles { margin-left: 0; padding-left: 1rem; }
  .job__roles .job__head { grid-template-columns: 1fr; }
  .hero__links { gap: 0 1.1rem; }
  /* One value moves the whole board — the type is derived from it. The cap
     is well under what a 28-column board can occupy here, because the grid
     shrinks the tracks to the space available while the font follows this
     number; too generous a cap and the glyphs outgrow their cells on the
     narrowest phones. */
  .flapboard__cells { --flap-w: 0.72rem; }
  .flap { height: 1.5rem; }
}


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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .boat { display: none; }
  .book:hover .book__spine { transform: translateY(-6px); }
  /* the accordion falls back to the browser's instant toggle */
  .section__body { transition: none; height: auto !important; }
}
