/* ==================================================================
   DESIGN TOKENS
   ================================================================== */
:root {
  --bg: #fafaf9;      /* page background — near white */
  --bg-2: #f0efe9;    /* slightly deeper panel background */
  --line: #ddd9cf;    /* hairlines, borders */
  --ink: #17170f;     /* main text color — near black */
  /* THE INK AGAIN, AS THREE NUMBERS, and it exists for one reason:
     a handful of rules spend the ink at an alpha — a hatched
     placeholder, the dotted leader in a fragrance's row, the rank
     down the side of a house — and `rgba()` cannot take a hex.
     Written out by hand they were the one thing on a house page
     that did NOT follow a page turning its tokens over, which is
     how Ataraxia's rank came out black-on-dark-gray. Keep the two
     the same colour. */
  --ink-rgb: 23, 23, 15;

  /* THE SQUARED GROUND. The contact sheet page is ruled into 46px
     squares, and since 2026-09-22 so is the fragrance reader that
     opens on top of it — a picture on its way back recedes into ONE
     OF THESE SQUARES, so the two have to be the same grid or it
     lands on nothing. One number, read by both. */
  --grid-cell: 46px;
  --muted: #6d6c62;   /* secondary text */
  --brass: #9c6f35;   /* the one accent color, deepened for contrast on white */

  --sans: "Archivo", -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", monospace;
  /* The third face, and the only place it is spent is the notation on
     works/theory-03.html. It is a serif the machine already has rather
     than a fourth thing fetched over the network: maths is set in a
     serif everywhere else, and the point of it here is only that it is
     not the writing and not one of the site's readings. */
  --math: Georgia, "Iowan Old Style", "Times New Roman", Times, serif;

  /* The squared paper and the static are drawn on canvases now, by
     paper.js — their sizes and strengths are the tuning block at the
     top of that file. What stays here is the tone they add up to:
     --bg darkened by the wash, which the node markers and labels use
     to cut a clean hole in the paper behind them. */
  --paper-tone: #e4e4e3;
}

/* ==================================================================
   RESET
   ================================================================== */
* { box-sizing: border-box; }
html { height: 100%; margin: 0; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
}
body.snap-page { height: 100%; overflow: hidden; }

h1, h2, h3 { font-family: var(--sans); font-weight: 600; margin: 0; }
/* A heading or a label that is there for a screen reader and a search
   engine but not drawn: a page whose look has no title on it still says
   what it is (the search page, the test page). */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
a { color: var(--ink); }
dl { margin: 0; }
img { max-width: 100%; display: block; }

/* ==================================================================
   THE PAPER (landing page only)
   Squared paper with grain moving over it, sitting behind
   everything. It's keyed to --paper-in, which thread.js drives from
   the scroll position, so it arrives with the node map rather than
   being switched on. At full strength it should still be something
   you notice only if you look for it.
   ================================================================== */
.paper {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
/* All three opacities are set frame by frame in paper.js, so the
   paper can come in later and more slowly than the map does. */
.paper-wash { position: absolute; inset: 0; background: #000; opacity: 0; }
.paper-grid, .paper-noise {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
}

/* ==================================================================
   THE THREAD (landing page only)
   One line down the whole page, drawn by thread.js. It sits above
   the slides so it stays visible where it crosses the node map's
   canvas — that crossing is the point of it.
   ================================================================== */
.thread {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 2;
  pointer-events: none;
  overflow: visible;
}
.thread-line {
  fill: none;
  stroke: var(--ink);
  stroke-opacity: 0.26;
  stroke-width: 1;
  shape-rendering: crispEdges;
}
/* The wandering leg is a curve once it starts drifting, and its
   dashes become dots — both want round ends and smooth rendering. */
.thread-wander {
  stroke-width: 1.5;
  stroke-linecap: round;
  shape-rendering: auto;
}
/* The line that reforms on the way back up, from the sphere to the top
   of the screen. The collapse clears the page to white rather than
   darkening it, so this keeps the same ink as the rest of the thread —
   just drawn a little heavier, since for a moment it is the only thing
   on the page. */
.thread-reform {
  stroke-width: 1.5;
  shape-rendering: auto;
}

/* Present from the moment the page loads — this only stops it
   appearing at the same instant as the text above it. */
@media (prefers-reduced-motion: no-preference) {
  .thread-line { animation: thread-in 1.1s ease both 0.2s; }
}
@keyframes thread-in { from { opacity: 0; } to { opacity: 1; } }

/* ==================================================================
   SCROLL SNAP (landing page only)
   ================================================================== */
#scroll-container {
  position: relative;
  z-index: 1;
  height: 100vh;
  height: 100svh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scroll-behavior: auto; /* custom easing is handled in JS instead */
}
.slide {
  height: 100vh;
  height: 100svh;
  width: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  align-items: center;
  padding: 0 64px;
  position: relative;
}

/* ==================================================================
   SITE MENU (top-left "Menu" / "Close")
   The trigger sits on the normal light page, but the overlay itself
   stays black on purpose, regardless of the site's light theme —
   opening it dims the page behind it rather than just covering it.
   ================================================================== */

/* One shared easing curve for the whole menu, so every part of the
   transition moves with the same character. It starts almost
   imperceptibly and settles rather than stopping. */
:root { --menu-ease: cubic-bezier(0.32, 0.08, 0.24, 1); }

.menu-trigger {
  position: fixed;
  top: 22px;
  left: 22px;
  z-index: 30;
  background: none;
  border: none;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.02em;
  cursor: pointer;
  padding: 4px 0;
  transition: color 0.6s var(--menu-ease), opacity 0.2s ease;
}
.menu-trigger:hover { color: var(--brass); }
.menu-trigger.label-swap { opacity: 0; }
body.menu-open .menu-trigger { color: #f0efe8; }
body.menu-open .menu-trigger:hover { color: var(--brass); }

/* ON A PHONE THE WORD IS GIVEN A GROUND OF ITS OWN.
   The trigger is fixed to the top left of the window and the page
   travels underneath it, so on a narrow screen it ends up printed
   through whatever happens to be scrolling past — the owner sent a
   photograph of "Menu" standing inside a paragraph, unreadable. A box
   no bigger than the word, blurring what is behind it, gives the word
   back its own ground without putting a bar across the top of every
   page.

   IT IS THE PAGE'S OWN GROUND, not a colour picked here: `--chrome-ground`
   is the paper on a light page and near-black on the five drawn on a
   dark one, so the box never shows as a patch of the wrong colour. The
   word itself does not move — the padding is taken back out of `left`
   — because nothing above 700px may change and nothing below it should
   jump either.

   AND IT GOES WHEN THE MENU IS OPEN: the word stands on the overlay's
   own black there and is lettered light, so a pale box under it would
   hide it rather than help. */
:root { --chrome-ground: rgba(250, 250, 249, 0.76); }
/* The three house pages' readings are the same problem in the opposite
   corner: fixed to the foot of the window with the writing travelling
   under them, they were printed through a paragraph on a phone. Same
   box, same ground. ADAR's page is dark, so it takes the dark one
   through the list below. */
@media (max-width: 700px) {
  .pine-readout,
  .adar-readout,
  .human-readout {
    padding: 5px 9px 6px;
    border-radius: 3px;
    background: var(--chrome-ground);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
}
body.theories-page,
body.adar-page,
body.ataraxia-page,
body.essay-page,
body.find-page,
body.library-page,
body.network-page,
body.chapter-open { --chrome-ground: rgba(11, 12, 15, 0.62); }

@media (max-width: 700px) {
  .menu-trigger {
    left: 14px;
    padding: 4px 8px 5px;
    border-radius: 3px;
    background: var(--chrome-ground);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
  body.menu-open .menu-trigger {
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

.menu-overlay {
  position: fixed;
  inset: 0;
  background: #0a0a0a;
  z-index: 25;
  display: flex;
  align-items: center;
  padding: 0 64px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.85s var(--menu-ease);
}
.menu-overlay.open { opacity: 1; pointer-events: auto; }

/* The list stands in a <nav> (2026-09-27). A flex box, so the list's
   own margins stay inside it rather than running out through the nav —
   it stands exactly where it stood when it was the overlay's own child. */
.menu-nav { display: flex; }
.menu-list { list-style: none; margin: 0; padding: 0; }
.menu-list li {
  margin: 6px 0;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s var(--menu-ease), transform 0.7s var(--menu-ease);
}
/* Opening: the lines arrive one after another, just behind the
   backdrop. Closing: the .open class goes, so the delays go with it
   and they leave together — quicker than they came, which is what
   keeps closing from feeling sluggish. */
.menu-overlay.open .menu-list li { opacity: 1; transform: translateY(0); }
.menu-overlay.open .menu-list li:nth-child(1) { transition-delay: 0.14s; }
.menu-overlay.open .menu-list li:nth-child(2) { transition-delay: 0.20s; }
.menu-overlay.open .menu-list li:nth-child(3) { transition-delay: 0.26s; }
.menu-overlay.open .menu-list li:nth-child(4) { transition-delay: 0.32s; }
.menu-overlay.open .menu-list li:nth-child(5) { transition-delay: 0.38s; }
.menu-overlay.open .menu-list li:nth-child(6) { transition-delay: 0.44s; }
.menu-overlay.open .menu-list li:nth-child(7) { transition-delay: 0.50s; }
.menu-overlay.open .menu-list li:nth-child(n + 8) { transition-delay: 0.56s; }

.menu-list a {
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(28px, 5vw, 48px);
  text-decoration: none;
  color: #f0efe8;
  transition: color 0.35s var(--menu-ease);
}
.menu-list a:hover, .menu-list a:focus-visible { color: var(--brass); }
.menu-list .menu-current { color: var(--brass); }

/* The rest of the page gently dims while the menu is open, so the
   overlay reads as coming forward rather than just appearing.
   The transition sits on the base selector, not on .menu-open — when
   it lived on the open state only, closing the menu snapped the page
   back to full brightness with no fade at all.

   THE FOUR THINGS LEFT OUT USED TO BE TWO MORE THAN THIS, and the two
   that were missing are the NOTES WINDOW and its SCRIM. That rule is a
   child selector with four `:not()` on it, which outranks a plain
   `.note-panel` — and because the window is built on the BODY (it has
   to be; a `position: fixed` thing inside a transformed ancestor is
   fixed to that ancestor instead of to the window), it was a direct
   child of body and this caught it.

   SO THE WINDOW NEVER HAD THE TRANSITION IT WAS WRITTEN WITH. Its own
   rule says `opacity 300ms, transform 300ms`; what it actually got was
   this one — 850ms, opacity ONLY, so it never slid or scaled at all,
   it just faded. And the script, waiting the 260ms it thought the
   close took, hid the window and the scrim 180ms into an 850ms fade:
   both went from 0.606 opacity to nothing in a single frame, and the
   scrim is a wash over the whole page. That is the owner's "it
   glitches slightly when you click away".

   The window is over the menu overlay anyway (z-index 91 against 25),
   so there was never anything for the dimming to do to it. THE
   FOOTNOTE POP-UP on the perfume primer (`.primer-tip`) is built on the
   body for the same reason and fell into the same trap — it faded in
   over 0.85s and was read see-through — so it is excluded too. Both
   selectors carry the same exclusions, and have to: the transition
   living on one and not the other is the fault the paragraph above
   this one is about. */
body > *:not(.menu-trigger):not(.menu-overlay):not(.cursor-ring):not(.cursor-dot):not(.note-panel):not(.note-scrim):not(.primer-tip) {
  transition: opacity 0.85s var(--menu-ease);
}
body.menu-open > *:not(.menu-trigger):not(.menu-overlay):not(.cursor-ring):not(.cursor-dot):not(.note-panel):not(.note-scrim):not(.primer-tip) {
  opacity: 0.1;
}

@media (prefers-reduced-motion: reduce) {
  .menu-list li { transform: none; }
  .menu-overlay, body > *:not(.menu-trigger):not(.menu-overlay) {
    transition-duration: 0.01s;
  }
  /* :nth-child(n) matches everything — it's here only to outweigh the
     stagger delays above, which are more specific than a plain li. */
  .menu-overlay.open .menu-list li:nth-child(n) {
    transition-duration: 0.01s;
    transition-delay: 0s;
    transform: none;
  }
}

/* ==================================================================
   TITLE SLIDE — centered
   ================================================================== */
.title-slide { justify-content: center; }
.title-content { text-align: center; }
.title-content h1 {
  font-size: clamp(32px, 5.6vw, 68px);
  line-height: 1.1;
  font-style: italic;
  font-weight: 500;   /* italics read heavier, so this drops a step to compensate */
  letter-spacing: -0.005em;
}
.title-sub { font-size: 17px; color: var(--muted); margin: 18px 0 0; }

.title-block {
  position: absolute;
  bottom: 28px;
  right: 28px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  text-align: right;
}
.scroll-cue {
  position: absolute;
  bottom: 28px;
  left: 28px;
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 12px;
  cursor: pointer;
}
.scroll-cue:hover { color: var(--ink); }

@media (prefers-reduced-motion: no-preference) {
  .title-content, .title-block { animation: rise 0.9s ease both; }
  .title-block { animation-delay: 0.15s; }
}
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/* ==================================================================
   MIDDLE SLIDE — a single centered, italic line
   ================================================================== */
.intro-slide { justify-content: center; text-align: center; }
.intro-content { max-width: 680px; margin: 0 auto; }
.intro-lede {
  font-family: var(--sans);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(24px, 3.4vw, 38px);
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}

/* ==================================================================
   NODE MAP SLIDE — 3D scene built by node-scene.js
   ================================================================== */
/* The scene is full-bleed: it fills the whole slide, edge to edge, so
   the branches run off the sides of the screen rather than sitting in
   a box in the middle. .slide.node-slide is two classes deep on
   purpose — it has to outrank the plain .slide padding, including the
   one set inside the mobile media query at the bottom of this file. */
.slide.node-slide { padding: 0; gap: 0; align-items: stretch; }

.node-scene-wrap {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* pan-y, not none: vertical swipes still scroll the page on a phone,
     horizontal drags rotate the scene. */
  touch-action: pan-y;
  cursor: grab;
}
.node-scene-wrap.grabbing { cursor: grabbing; }
.node-scene-wrap canvas { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }

.node-labels { position: absolute; inset: 0; pointer-events: none; }
.node3d-label {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
  text-decoration: none;
  color: var(--ink);
  pointer-events: auto;
  padding: 6px;
}
/* A registration mark rather than a dot: hollow square, centre
   point, on a ring of page colour so it keeps its edge wherever it
   crosses the grid or a branch. */
.node3d-mark {
  position: relative;
  width: 14px;
  height: 14px;
  border: 1.5px solid var(--ink);
  flex: none;
  box-shadow: 0 0 0 4px var(--paper-tone);
  transition: transform 0.3s ease;
}
.node3d-mark::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 4px;
  height: 4px;
  background: var(--ink);
  transition: width 0.3s ease, height 0.3s ease;
}
.node3d-text {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink);
  /* the label has to stay readable over grid, grain and branches */
  text-shadow: 0 0 7px var(--paper-tone), 0 0 14px var(--paper-tone), 0 1px 0 var(--paper-tone);
  transition: letter-spacing 0.3s ease;
}
.node3d-sub {
  position: absolute;
  left: 29px;
  top: 24px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.45;
  color: var(--muted);
  opacity: 0;
  transition: opacity 0.35s ease;
  white-space: normal;
  width: 210px;
}

/* Hover and focus emphasise by weight, not by colour: the mark
   fills in, the text tightens and goes bold, and out in the scene
   the branch behind it thickens to match. */
.node3d-label:hover .node3d-sub, .node3d-label:focus-visible .node3d-sub { opacity: 1; }
.node3d-label:hover .node3d-mark, .node3d-label:focus-visible .node3d-mark { transform: scale(1.1); }
.node3d-label:hover .node3d-mark::after, .node3d-label:focus-visible .node3d-mark::after { width: 8px; height: 8px; }
.node3d-label:hover .node3d-text, .node3d-label:focus-visible .node3d-text { font-weight: 700; letter-spacing: 0.14em; }

/* Nodes drifting near the right edge flip their text to the other
   side of the mark so the sub-line doesn't run off screen. The mark
   itself stays exactly on the node either way. */
.node3d-label.flip { flex-direction: row-reverse; }
.node3d-label.flip .node3d-sub { left: auto; right: 29px; text-align: right; }

/* ==================================================================
   NODE CLICK PREVIEW (trial: Scent descriptions)
   The rest of the page defocuses behind it — same dark tone the
   menu overlay uses, for the same "coming forward" reason.
   ================================================================== */
body.preview-open #scroll-container,
body.preview-open .paper {
  filter: blur(2px);
  transition: filter 1.4s var(--menu-ease);
}
body.preview-open #scroll-container { opacity: 0.85; pointer-events: none; transition: filter 1.4s var(--menu-ease), opacity 1.4s var(--menu-ease); }
/* The grain gets a much heavier blur than everything else behind the
   window, and paper.js stops repainting it at the same time. Both are
   for the same reason: grain that is still churning away frame by
   frame keeps catching the eye through the defocus, and it reads as
   the page shaking rather than as depth of field. Blurred this far and
   held still, it becomes a texture instead of a movement. The blur is
   on top of the one .paper already carries. */
body.preview-open .paper-noise {
  filter: blur(6px);
  transition: filter 1.4s var(--menu-ease);
}
body.preview-open .menu-trigger { opacity: 0.15; pointer-events: none; transition: opacity 1.4s var(--menu-ease); }

.node-preview-backdrop { position: fixed; inset: 0; z-index: 50; background: transparent; cursor: pointer; }

/* The arm out to the window is the node's own branch, traced from the
   3D curve every frame by node-scene.js and carried on past the node.
   It's a filled shape rather than a stroked line so it can taper —
   almost nothing where it leaves the centre, full weight where it
   docks — with a gradient fading the same way, so it reads as coming
   forward out of the background. Both the shape and its fill are set
   from the script; what's here is only where it sits and what the
   fittings at the window end look like.

   z-index 65 puts it over the window (60), which is what lets the end
   mark straddle the window's edge. The ribbon itself stops short of
   the edge, so nothing is ever drawn across the panel. */
/* width/height are required here, not just inset: 0 — an <svg> is a
   replaced element, and without them it falls back to the CSS default
   replaced-element size (300x150) instead of filling the viewport, which
   silently clipped the whole arm to a corner box. */
.node-preview-arm-svg { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 65; pointer-events: none; overflow: visible; }
.node-preview-arm-path { stroke: none; }

/* the notch cut into the window's dark edge */
.node-preview-dock-bar { stroke: #dcd8ce; stroke-width: 2; }
/* the short run from the end mark into that notch */
.node-preview-dock-stub { stroke: var(--ink); stroke-width: 1.5; }
/* and the same registration mark the nodes use, to end on */
.node-preview-dock-mark { stroke: var(--ink); stroke-width: 1.5; fill: var(--paper-tone); }
.node-preview-dock-dot { fill: var(--ink); stroke: none; }

.node-preview-modal {
  position: fixed;
  width: min(480px, 88vw);
  background: #0a0a0a;
  color: #f0efe8;
  padding: 32px;
  z-index: 60;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: left 0.6s var(--menu-ease), top 0.6s var(--menu-ease), transform 0.6s var(--menu-ease), opacity 0.4s ease;
}
.node-preview-media {
  aspect-ratio: 4 / 3;
  border: 1px solid #333;
  background-color: #141414;
  background-image: repeating-linear-gradient(-45deg, #2a2a2a 0px, #2a2a2a 1px, transparent 1px, transparent 14px);
  margin-bottom: 22px;
}
.node-preview-desc { font-family: var(--sans); font-size: 14px; line-height: 1.6; color: #c9c6bb; margin: 0 0 26px; }
/* WHAT STATE A PAGE IS IN, when it has something to say about it —
   set apart from the line about the page rather than folded into
   it, in the mono the rest of the site's readings are set in. */
.node-preview-note {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  line-height: 1.6;
  color: #8b877e;
  margin: -14px 0 26px;
  padding-left: 12px;
  border-left: 1px solid rgba(139, 135, 126, 0.5);
}
/* The node's own name, lifted out of the map and set above its window a
   beat after the window lands. It sits outside the dark panel, on the
   paper, in the map's own lettering — so it reads as the label having
   moved rather than as a heading the window came with. It arrives by
   settling: wide letter-spacing closing to the spacing the labels in
   the map are set at, and a hairline drawing out under it. */
.node-preview-title {
  position: absolute;
  left: 0;
  bottom: 100%;
  margin: 0 0 18px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.36em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  opacity: 0;
  transform: translateY(7px);
  /* the paper behind it carries grid and grain, as under the map's labels */
  text-shadow: 0 0 7px var(--paper-tone), 0 0 14px var(--paper-tone), 0 1px 0 var(--paper-tone);
  transition: opacity 0.9s var(--menu-ease), letter-spacing 1.2s var(--menu-ease),
              transform 0.9s var(--menu-ease);
}
.node-preview-title::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -9px;
  height: 1px;
  width: 0;
  background: var(--ink);
  opacity: 0.45;
  transition: width 1.2s var(--menu-ease);
}
.node-preview-title.lifted { opacity: 1; letter-spacing: 0.11em; transform: translateY(0); }
.node-preview-title.lifted::after { width: 100%; }
/* Was a bare word in the corner. It's the same registration mark the
   nodes use, with the word beside it, so the way out of the window is
   built from the same vocabulary as the way in. */
.node-preview-close {
  position: absolute;
  top: 18px;
  right: 18px;
  display: flex;
  align-items: center;
  gap: 9px;
  background: none;
  border: none;
  padding: 6px;
  color: #8f8c81;
  cursor: pointer;
  transition: color 0.3s ease;
}
.node-preview-close:hover, .node-preview-close:focus-visible { color: #f0efe8; }
.node-preview-close-mark {
  position: relative;
  width: 12px;
  height: 12px;
  border: 1.4px solid currentColor;
  flex: none;
}
.node-preview-close-mark::before, .node-preview-close-mark::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 7px;
  height: 1.4px;
  background: currentColor;
}
.node-preview-close-mark::before { transform: rotate(45deg); }
.node-preview-close-mark::after { transform: rotate(-45deg); }
.node-preview-close-word {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
/* Brass on black read as an illuminated exit sign — the one place the
   accent was doing something the rest of the site doesn't. Monochrome,
   with a rule that extends on hover instead of a glow. */
.node-preview-button {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
  width: fit-content;
  border: 1px solid #57544c;
  background: transparent;
  color: #e8e6e0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 13px 22px;
  transition: background 0.35s ease, color 0.35s ease, border-color 0.35s ease;
}
.node-preview-button::after {
  content: "";
  width: 16px;
  height: 1px;
  background: currentColor;
  transition: width 0.35s ease;
}
.node-preview-button:hover, .node-preview-button:focus-visible {
  background: #e8e6e0;
  color: #16160f;
  border-color: #e8e6e0;
}
.node-preview-button:hover::after { width: 26px; }

/* ==================================================================
   THE CURSOR
   Built by nav.js, so it appears on every page.
   nav.js checks what's actually under the pointer each move and
   toggles .on-dark — more reliable across browsers than relying on
   mix-blend-mode, which didn't composite correctly against the
   WebGL canvas and the dark surfaces here. Any future full-bleed
   dark panel should get the .dark-surface class to be seen.
   ================================================================== */
html.has-cursor, html.has-cursor * { cursor: none !important; }
.cursor-ring, .cursor-dot {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 2147483000;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease, width 0.22s ease, height 0.22s ease, border-color 0.2s ease, background 0.2s ease;
}
html.cursor-awake .cursor-ring, html.cursor-awake .cursor-dot { opacity: 1; }
.cursor-ring {
  width: 18px;
  height: 18px;
  border: 1px solid var(--ink);
  background: transparent;
}
.cursor-ring.near { width: 11px; height: 11px; }
.cursor-ring.on-dark { border-color: #f0efe8; }
.cursor-dot {
  width: 3px;
  height: 3px;
  background: var(--ink);
}
.cursor-dot.near { width: 5px; height: 5px; }
.cursor-dot.on-dark { background: #f0efe8; }

/* Plain-link fallback, shown only if the 3D library fails to load */
.node-fallback-list { display: flex; flex-direction: column; gap: 1px; background: var(--line); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); width: min(640px, 92vw); }
.node-fallback-list a { display: flex; flex-direction: column; gap: 4px; background: var(--bg); padding: 18px 16px; text-decoration: none; color: var(--ink); }
.node-fallback-list a:hover { background: var(--bg-2); color: var(--brass); }
.node-fallback-list a span { font-family: var(--mono); font-size: 12px; color: var(--muted); }

/* ==================================================================
   CATEGORY PAGES
   ================================================================== */
.page-content { max-width: 900px; margin: 0 auto; padding: 140px 64px 120px; }
.category-lede { font-size: clamp(22px, 2.8vw, 28px); line-height: 1.45; max-width: 640px; margin: 24px 0 64px; }

.work-list { display: flex; flex-direction: column; gap: 1px; background: var(--line); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.work-row { display: flex; align-items: center; gap: 32px; background: var(--bg); padding: 24px 8px; text-decoration: none; color: var(--ink); }
.work-row:hover { background: var(--bg-2); }
.work-row:hover .work-row-title { color: var(--brass); }
.work-thumb { width: 96px; height: 72px; flex: none; border: 1px solid var(--line); background-image: repeating-linear-gradient(-45deg, var(--line) 0px, var(--line) 1px, transparent 1px, transparent 10px); }
.work-row-title { font-family: var(--sans); font-weight: 500; font-size: 19px; transition: color 0.2s ease; }
.work-row-meta { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-top: 6px; }

/* ==================================================================
   CONTACT SHEET CATEGORY PAGE
   The Houses view. Without JavaScript it is a plain grid of square
   pictures with their captions — a working page. contact-sheet.js puts
   .scripted on the sheet and takes over: the houses are HUNG from a
   picture rail, high and low in turn, each with a numbered label (see
   THE HANG below). Everything below is how it looks; the timing and the
   arithmetic are in that file.
   ================================================================== */
/* The page grows a lot taller the moment the sheet lands, and on a
   browser with ordinary scrollbars that makes one appear — which takes
   15px off the width and shifts everything centred on the page sideways
   at exactly the moment the flick stops, so the whole thing looks like
   it twitched. Keeping room for the scrollbar from the start means
   nothing moves. :has() so it only applies to pages like this one; the
   landing page scrolls inside its own element and must not get a gutter
   it would never use. */
html:has(.sheet-page) { scrollbar-gutter: stable; }
/* THE SHEET'S GROUND. White, with the squared plan of the site's other
   technical pages laid very faintly over it — the drawing board rather
   than blank paper.

   It went dark for one round, when the owner asked for the page to be
   modelled more interestingly than blocks on white, and they asked for
   it flipped back the round after: "make it white... otherwise the page
   itself looks really good. Keep the format and the locations." So
   everything else about it — the traces, the ties, the scatter, the
   flick — is exactly as the dark version was; only the tone turned over.

   The tokens are set FOR THIS PAGE ONLY rather than a second set of
   rules being written for everything on it: every rule below already
   draws in --ink, --line, --bg and --muted, so these four are the whole
   of the page's colour and flipping them turns it over at once. That is
   what made turning it back a four-line change. */
.sheet-page {
  --bg: #ffffff;
  --bg-2: #f4f3ef;
  --line: #ddd9cf;
  --ink: #17170f;
  --muted: #6d6c62;
  background:
    linear-gradient(to right, rgba(var(--ink-rgb), 0.035) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--ink-rgb), 0.035) 1px, transparent 1px),
    var(--bg);
  background-size:
    var(--grid-cell) var(--grid-cell),
    var(--grid-cell) var(--grid-cell),
    auto;
  color: var(--ink);
}

/* There is no title on this page any more. The heading stays in the
   markup for anything reading the page rather than looking at it — a
   page with no heading at all is a page nothing can announce. */
.sheet-head h1 {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* THE TWO WAYS OF LOOKING AT THE CATEGORY — Houses, which is the
   contact sheet itself, and Individual fragrances, which is the index
   of every fragrance written up anywhere on this site. The buttons are
   fixed across the top between the Menu on the left and the Search on
   the right, so they stand where they stand whatever the view below
   them is doing, and they arrive once the page has finished drawing
   itself.

   They sit INSIDE .sheet-head rather than loose in the page, and have
   to: every direct child of <body> is given an opacity transition by
   the rule that dims the page behind the menu, and that rule outranks
   anything written here — so a nav loose in the page could not be
   hidden without being seen fading away first. Inside the head block
   they are their own. */
/* SPLIT BY THE AXIS. The Houses view's axis runs to the very top of the
   window, and the two words stand either side of it — each anchored to
   the middle of the window by its inner edge, `--split` away from it, so
   the line passes exactly between them however long either word is. A
   short rule on each word's inner side points at it. The owner asked for
   the two "more split apart and a little more emphasized"; they were
   one centred row at 11px, and the axis ran up through the F. The box
   is the width of the window and lets every press through it: only the
   two buttons take one. */
.sheet-filters {
  --split: 30px;
  position: fixed;
  top: 20px;
  left: 0;
  right: 0;
  z-index: 30;
  height: 27px;
  pointer-events: none;
}
.sheet-filter {
  position: absolute;
  top: 4px;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  pointer-events: auto;
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 17px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}
.sheet-filter:first-child { right: calc(50% + var(--split)); }
.sheet-filter:last-child { left: calc(50% + var(--split)); }
/* The rule on the inner side, reaching towards the axis. */
.sheet-filter::before {
  content: "";
  position: absolute;
  top: 50%;
  width: 14px;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
  transition: opacity 0.4s var(--menu-ease);
}
.sheet-filter:first-child::before { left: calc(100% + 4px); }
.sheet-filter:last-child::before { right: calc(100% + 8px); }
/* On the Fragrances view there is no axis for them to point at, so they
   go; the words stay exactly where they are. */
body.view-fragrances .sheet-filter::before { opacity: 0; }
/* Which one you are looking at is said with a rule under it rather
   than with colour — the same answer the chamber's menu gives, for the
   same reason: this page spends nothing on accent. */
.sheet-filter::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0.18em;
  bottom: -4px;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--menu-ease);
}
.sheet-filter:hover { color: var(--ink); }
.sheet-filter.is-on { color: var(--ink); }
.sheet-filter.is-on::after { transform: scaleX(1); }
/* They are not there while the sheet is drawing itself, and must not
   be seen leaving either. */
body.sheet-scripted .sheet-filters { opacity: 0; transition: none; }
body.sheet-named .sheet-filters { opacity: 1; transition: opacity 1.1s var(--menu-ease) 0.3s; }

/* THE VIEWS. Only ever one of them is on the page: the one being left
   fades away FIRST and the other arrives after it, so the page never
   shows two different things at once. views.js does the ordering; what
   is here is only how each one goes. */
.view { transition: opacity 0.34s var(--menu-ease); }
.view.leaving { opacity: 0; }
.view[hidden] { display: none; }

/* THE SWIPE between the two views, once both have been opened. The two
   are stood on top of one another for the length of it and travel
   sideways together; views.js holds the box's height while they are out
   of the flow, and puts everything back afterwards.

   `overflow: hidden` is on the box and not on the page: the view coming
   in starts a full width off to one side, and without it that would be
   a horizontal scrollbar for half a second. */
.views { position: relative; }
/* PINNED TO THE WINDOW, not to the page. views.js sets each one's `top`
   from where it actually appears at the moment the swipe starts — see
   the long note there for why the page's own coordinates will not do.
   `z-index: 1` keeps them under the chrome, which sits at 30. */
.views.swiping .view.sliding {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 1;
}
/* Fixed children escape the box's overflow, so the clipping has to
   happen at the root. `clip` rather than `hidden` on purpose: `hidden`
   would make the root a scroll container for the half-second this is on,
   and the scroll is being set at the end of it. */
html.view-swiping { overflow-x: clip; }
/* The travel is put on only once both are in their starting places, so
   that neither of them transitions into position from wherever it
   happened to be. */
.views.swiping .view.travelling {
  transition: transform 0.52s var(--menu-ease), opacity 0.52s var(--menu-ease);
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .views.swiping .view.travelling { transition: none; }
}
/* The sheet's own Search belongs to the sheet. The index has a search
   of its own above its table, and two search fields on one page is one
   too many. */
body.view-fragrances .sheet-search { display: none; }

/* Search, top right, mirroring the Menu button top left. */
.sheet-search {
  position: fixed;
  top: 22px;
  right: 22px;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 8px;
}
.sheet-search-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.02em;
  cursor: pointer;
  padding: 4px 0;
  transition: color 0.6s var(--menu-ease);
}
.sheet-search-trigger:hover { color: var(--brass); }
/* The same registration mark the map's nodes and the preview's dock
   are marked with, so the way in to searching is built out of the
   site's own vocabulary. */
.sheet-search-mark {
  position: relative;
  width: 11px;
  height: 11px;
  border: 1.5px solid currentColor;
  flex: none;
}
.sheet-search-mark::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 3px;
  height: 3px;
  background: currentColor;
}
.sheet-search-field {
  width: 0;
  opacity: 0;
  padding: 5px 0;
  border: none;
  border-bottom: 1px solid var(--line);
  background: none;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink);
  transition: width 0.5s var(--menu-ease), opacity 0.4s var(--menu-ease);
}
.sheet-search-field:focus { outline: none; border-bottom-color: var(--ink); }
.sheet-search.open .sheet-search-field { width: min(230px, 42vw); opacity: 1; }
.sheet-search.open .sheet-search-word { color: var(--brass); }

/* ON A PHONE THE REST OF THE CHROME GETS A GROUND TOO. The Menu, these
   two buttons and the search all stand fixed across the top of the
   window while the sheet travels underneath them — and on a narrow
   screen the sheet is a tall single file of pictures, so what travels
   underneath them is a picture rather than white paper, and the
   lettering was printed straight through it. The same box and the same
   `--chrome-ground` the Menu has; see .menu-trigger.

   The padding is taken back out of the position either side, so the
   lettering itself stands exactly where it did. */
@media (max-width: 700px) {
  .sheet-filter,
  .sheet-search {
    padding: 4px 9px 5px;
    border-radius: 3px;
    background: var(--chrome-ground);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
  /* Each word on a ground of its own rather than one box round both, so
     the axis still runs up between them; smaller, to leave the Menu and
     the search their room, and the search is its mark alone. */
  .sheet-filters { --split: 9px; }
  .sheet-filter { top: 2px; font-size: 11px; line-height: 15px; letter-spacing: 0.12em; }
  .sheet-filter::before { display: none; }
  .sheet-filter::after { left: 9px; right: 9px; bottom: 2px; }
  .sheet-search-word { display: none; }
  .sheet-search { right: 13px; }
  /* AND THE CHROME HAS TO BE ON TOP OF THE SHEET, which it was not: a
     picture carries a z-index worked out from how far back it stands
     (1000 at the front of the volume), and the chrome stands at 30 —
     so the nearest pictures were drawn straight OVER the buttons, box
     and all, and "Houses" was printed inside a photograph. On a wide
     window the sheet is wide enough that nothing passes under them and
     nothing here applies. */
  body.sheet-page .menu-trigger,
  .sheet-filters,
  .sheet-search { z-index: 1100; }
}

.sheet {
  position: relative;
  width: min(1240px, calc(100% - 80px));
  /* Hanging a little below the chrome rather than starting right under
     it: the Menu, the two buttons and the Search are fixed across the
     top and stay where they are — the map is what sits lower. */
  margin: clamp(18px, 3.4vh, 46px) auto 0;
  padding-bottom: 90px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 46px 24px;
}
/* Taken over by the script: every house is placed by hand on the
   helix, and the sheet is the whole window, top to bottom — pulled up
   under the chrome by a negative margin the script works out, so the
   axis and the houses reach the window's own edges. It does not scroll,
   it is travelled along (see contact-sheet.js). */
.sheet.scripted {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  /* A drag travels along the helix, so a finger on it must not scroll
     the page instead. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

/* THE AXIS. With the script running, the houses stand on a helix round
   a central axis, all of it drawn in particles on one canvas that stands
   between the houses turned away behind the axis and the ones in front
   of it. Everything below that is written under `.scripted` is the
   axis; without it the page is the plain grid. */
.sheet-field { position: absolute; left: 0; top: 0; z-index: 2; pointer-events: none; }

/* A HOUSE'S NUMBER ON THE AXIS, where it stands; a button that brings it
   to the front. */
.sheet-axis-no {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
  padding: 1px 5px;
  border: 1px solid var(--line);
  background: var(--bg);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.3s, border-color 0.3s;
}
.sheet-axis-no:hover,
.sheet-axis-no.is-on { color: var(--ink); border-color: var(--ink); }

/* THE WAY ROUND: before, where you are, after — at the side on a wide
   window, across the foot on a phone. */
.sheet-nav {
  position: absolute;
  right: clamp(16px, 3vw, 44px);
  /* The middle of the room below the chrome, where the helix is laid
     out, rather than of the sheet, which reaches the window's top now. */
  top: var(--room-middle, 50%);
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  transform: translateY(-50%);
}
.sheet-nav-step {
  width: 34px;
  height: 34px;
  border: 1px solid var(--ink);
  background: var(--bg);
  color: var(--ink);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.25s, color 0.25s;
}
.sheet-nav-step:hover { background: var(--ink); color: var(--bg); }
/* HELD STILL as the house changes: the reading is a box of one size, and
   a name is one line cut short rather than a second line — "Qimu &
   Musicians" used to wrap, which pushed the button under it down, and a
   shorter name narrowed the column. */
.sheet-nav-at {
  margin: 0;
  width: 128px;
  height: 34px;
  text-align: center;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--ink);
}
.sheet-nav-at b { display: block; font-weight: 500; }
.sheet-nav-at span {
  display: block;
  margin-top: 3px;
  font-size: 10px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (max-width: 700px) {
  .sheet-nav {
    top: auto;
    bottom: 20px;
    right: 50%;
    flex-direction: row;
    gap: 14px;
    padding: 6px 10px;
    background: var(--chrome-ground);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    transform: translateX(50%);
  }
}

.sheet-frame {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border: 2px solid var(--ink);
  background-color: var(--bg-2);
  /* Hatching, as everywhere else on this site that a picture is still
     to come. */
  background-image: repeating-linear-gradient(
    var(--hatch-angle, -45deg),
    var(--line) 0px, var(--line) 1px,
    transparent 1px, transparent var(--hatch-gap, 12px)
  );
  text-decoration: none;
  color: var(--ink);
}
.sheet.scripted .sheet-frame {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 3;
  aspect-ratio: auto;
  box-sizing: border-box;
  /* Placed, sized and turned by the script alone — `transform` is
     written every frame the helix moves, so nothing here may transition
     it, nor its opacity, which is how far round the axis it stands. */
  transform-origin: 50% 50%;
  border-width: 1.5px;
  padding: 6px;
  background-color: var(--bg);
  box-shadow: 0 14px 30px -16px rgba(var(--ink-rgb), 0.5), 0 2px 4px rgba(var(--ink-rgb), 0.08);
  opacity: var(--shown, 1);
  transition: filter 0.6s var(--menu-ease), box-shadow 0.5s var(--menu-ease);
  will-change: transform;
}
/* Turned away behind the axis: softer and greyer, as well as smaller. */
.sheet.scripted .sheet-frame.behind { filter: blur(1.3px) grayscale(0.5); }
.sheet.scripted .sheet-frame.front {
  box-shadow: 0 26px 48px -20px rgba(var(--ink-rgb), 0.6), 0 3px 6px rgba(var(--ink-rgb), 0.1);
}
.sheet.scripted .sheet-frame:not(:has(img)) { padding: 0; }
.sheet-frame:has(img) { background-image: none; background-color: transparent; }
.sheet-frame img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.56s cubic-bezier(0.5, 0, 0.2, 1); }

/* THE LABEL, under every picture, as a museum labels its wall: the
   number large, and the name under it. The number is the owner's. */
.sheet-number {
  position: absolute;
  top: 6px;
  left: 6px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: var(--bg);
  padding: 1px 4px;
}
.sheet-frame .sheet-caption {
  position: absolute;
  left: 0;
  top: calc(100% + 8px);
  max-width: 150%;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.35;
  color: var(--muted);
  background: var(--bg);
  padding: 2px 5px 2px 0;
}
.sheet.scripted .sheet-frame .sheet-caption {
  top: calc(100% + 16px);
  left: 34px;
  right: -2px;
  max-width: none;
  padding: 0;
  background: none;
  color: var(--ink);
}
.sheet.scripted .sheet-frame > .sheet-number {
  top: calc(100% + 10px);
  left: -2px;
  font-family: var(--sans);
  font-size: 24px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
  background: none;
  padding: 0;
}
.sheet.scripted .sheet-frame .sheet-name {
  display: block;
  font-size: 11px;
  letter-spacing: 0.04em;
  /* A long name at the end of the rail, where a picture has least room,
     goes on to a second line rather than being cut short. */
  white-space: normal;
  border-top: 1px solid rgba(var(--ink-rgb), 0.35);
  padding-top: 3px;
}
/* A CAPTION IS THE NAME, AND THE LINE ABOUT IT ONLY WHEN POINTED AT. */
.sheet-frame .sheet-name { display: block; }
.sheet-frame .sheet-say {
  position: absolute;
  left: 0;
  top: calc(100% + 3px);
  width: max-content;
  max-width: 220px;
  color: var(--ink);
  background: var(--bg);
  padding: 2px 5px 2px 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s var(--menu-ease);
}
.sheet.scripted .sheet-frame .sheet-say {
  left: 0;
  top: calc(100% + 6px);
  max-width: min(320px, 80vw);
  white-space: normal;
  padding: 4px 8px;
  border: 1px solid rgba(var(--ink-rgb), 0.2);
}
@media (max-width: 700px) {
  .sheet-frame .sheet-say,
  .sheet.scripted .sheet-frame .sheet-say {
    width: auto;
    max-width: 100%;
    left: 0;
    right: auto;
    white-space: normal;
  }
}
.sheet-frame:hover .sheet-say,
.sheet-frame:focus-within .sheet-say,
.sheet-frame.hot .sheet-say { opacity: 1; }

/* RESTING ON A HOUSE: its motifs come up across the whole page, BEHIND
   the houses, and nothing is blurred or dimmed — the owner asked for the
   effects not to blur anything and to read as part of the page rather
   than as something hovering. The house itself only takes a deeper
   shadow. (For two rounds everything else went out of focus.) */
.sheet.scripted .sheet-frame.hot {
  box-shadow: 0 26px 44px -18px rgba(var(--ink-rgb), 0.55), 0 3px 6px rgba(var(--ink-rgb), 0.1);
}

/* The canvas the motifs are drawn on: under EVERYTHING on the page —
   the houses (1 to 4) and the particles (2) alike, on the squared ground
   itself — and never in the way of a press. */
.sheet-motifs {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* PRESSING A HOUSE: the page steps back and the picture comes forward
   — then the house is opened, and eases in on its own side. */
body.sheet-leaving .sheet-head,
body.sheet-leaving .menu-trigger,
body.sheet-leaving .sheet-where,
body.sheet-leaving .sheet.scripted .sheet-frame:not(.chosen),
body.sheet-leaving .sheet.scripted .sheet-field,
body.sheet-leaving .sheet.scripted .sheet-axis-no,
body.sheet-leaving .sheet.scripted .sheet-nav,
body.sheet-leaving .sheet-motifs {
  opacity: 0 !important;
  filter: blur(6px);
  transition: opacity 0.5s var(--menu-ease), filter 0.5s var(--menu-ease) !important;
}
body.sheet-leaving .sheet.scripted .sheet-frame.chosen {
  z-index: 8 !important;
  opacity: 0 !important;
  transition: opacity 0.45s ease 0.1s !important;
}
body.sheet-leaving .sheet.scripted .sheet-frame.chosen img { transform: scale(1.08); }

/* Searching: everything that doesn't match steps back. */
.sheet-frame.dimmed { opacity: 0.16 !important; }
.sheet.searching .sheet-field { opacity: 0.35; }

/* With the script running the page opens white, so the name, the lede
   and the three buttons are not there to begin with — and must not be
   seen leaving either, hence transition: none on the way out. They
   arrive once the sheet has landed, the name the same way a node's
   name arrives above its window on the landing page: wide letters
   closing up, a hairline drawing out. */
body.sheet-scripted .sheet-head h1 {
  opacity: 0;
  letter-spacing: 0.08em;
  transform: translateY(7px);
  transition: none;
}
body.sheet-scripted .sheet-head h1::after { width: 0; transition: none; }
body.sheet-scripted .sheet-head .category-lede { opacity: 0; transition: none; }

/* What this page is, written small beside the Menu: a rule, then the
   name of the category in the same mono the rest of the chrome is set
   in. On the contact sheet it is the only place the category is named
   now that the title has gone, and it is meant to be read second —
   after the Menu, and only if you are looking for it. */
.page-where,
.sheet-where {
  position: fixed;
  top: 22px;
  left: 76px;
  z-index: 30;
  margin: 0;
  padding: 4px 0 4px 15px;
  border-left: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 15px;
  letter-spacing: 0.1em;
  color: var(--muted);
  pointer-events: none;
}
body.sheet-scripted .sheet-where { opacity: 0; transition: none; }
/* On a narrow window the buttons in the middle of the chrome reach
   back to the Menu, and the name was printed underneath them. It is
   the second thing on the page to read, so it is the first to go. */
@media (max-width: 720px) {
  .page-where, .sheet-where { display: none; }
}

body.sheet-named .sheet-head h1 {
  opacity: 1;
  letter-spacing: -0.01em;
  transform: translateY(0);
  transition: opacity 0.9s var(--menu-ease), letter-spacing 1.2s var(--menu-ease),
              transform 0.9s var(--menu-ease);
}
body.sheet-named .sheet-head h1::after {
  width: 100%;
  transition: width 1.2s var(--menu-ease) 0.2s;
}
body.sheet-named .sheet-head .category-lede {
  opacity: 1;
  transition: opacity 1.1s var(--menu-ease) 0.3s;
}
body.sheet-named .sheet-where {
  opacity: 1;
  transition: opacity 1.1s var(--menu-ease) 0.15s;
}

/* ==================================================================
   THE SHEET'S STAGE
   The contact sheet is wrapped rather than left loose in the page, and
   it has to be: every direct child of <body> is given a transition by
   the rule that dims the page behind the menu, and that rule outranks
   anything written for the sheet — its fades would be that rule's, not
   its own. Inside a wrapper they are its own.

   This page once carried a SECOND view of the same category behind two
   buttons — the REGISTER, a page ruled edge to edge with tracks and a
   glitch, in favorites.js. The owner asked for it and the two buttons
   gone; they are gone outright rather than switched off, and there is
   no `.gallery`, no `.views`, no `view-favorites` and no favorites.js
   in the site any more.
   ================================================================== */
/* HELD BACK UNTIL THE SCRIPT HAS TAKEN OVER. Both of the pages that
   replace their own contents (this one and the theories drawing) carry
   a line in their <head> that marks the document while their script is
   on its way, and each script clears it the moment it has laid itself
   out. Without it the browser paints the no-script version first and
   then has it swept away — the page blinking its whole contents at you
   before it begins. `visibility` rather than `display` or `opacity`:
   the sheet measures its own captions on the first layout, and
   something with no layout box measures nothing. */
/* NOTHING IS SHOWN until the script has laid the sheet out — not the
   pictures, not the chrome, not the Menu. The page opens on ONE
   picture in the middle of a white page, and the owner asked for that
   quarter of a second to be exactly that and nothing else. Whatever
   the browser painted before the script ran is the plain grid this
   page falls back to, and it is never meant to be seen on the way in.
   All of it comes back on `window.load` if the script never arrives. */
html.js-coming .views,
html.js-coming .sheet-stage,
html.js-coming .sheet-search,
html.js-coming .sheet-filters,
html.js-coming .sheet-where,
html.js-coming body.sheet-page .menu-trigger { visibility: hidden; }

/* And with the script running, the chrome waits for the sheet to
   finish drawing itself: the page puts itself together, and then hands
   you the controls. They are faded rather than hidden so that anything
   reaching for them — a keyboard, a test — still finds them where they
   are. */
body.sheet-scripted .sheet-search,
body.sheet-scripted .menu-trigger { opacity: 0; transition: none; }
body.sheet-named .sheet-search,
body.sheet-named .menu-trigger {
  opacity: 1;
  transition: opacity 1.1s var(--menu-ease) 0.25s;
}

.sheet-stage { position: relative; padding-top: clamp(64px, 9vh, 96px); }

/* ==================================================================
   THE CHAMBER — categories/favorites.html
   Four injectors, one at each corner of the window, firing streams of
   square particles inward on white; they settle into a tilted ring,
   and the word FAVORITES stands in the middle of it. Pressing the word
   throws the ring out to the borders and opens the menu out of it.
   Two canvases: everything further than the middle of the chamber is
   drawn under the writing, everything nearer over it, so the near half
   of the ring passes in front of the word. Everything here is written
   under `body.chambered`, which chamber.js adds; without it the page
   is the plain list of favourites, and stays that way.
   ================================================================== */
/* The favourites on this page are `.gallery-entry` blocks, the same
   ones the contact sheet's removed Favorites view used, and that view
   is where their plain-list styling used to live. Without the script
   this page IS that list, so it keeps it — written for this page only,
   since the works templates give the same class a quite different job.
*/
body.favorites-page .gallery-entry {
  display: block;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  font-family: var(--sans);
  color: var(--ink);
  text-decoration: none;
}
body.chambered { background: var(--bg); }
body.chambered .page-content { padding: 0; margin: 0; max-width: none; }
/* The list is still in the page for anything reading rather than
   looking — it is the menu's own index — but it is not drawn.

   `.gallery-chapters` BELONGS IN THIS LIST and was left out of it when
   the chapter descriptions were added. It is source, the same as
   everything else here: chamber.js reads what a chapter IS out of it
   and writes it onto that chapter's own page. Left drawn, it sat under
   the chamber where you could not quite see it — until the menu was
   opened, which takes the whole page to a tenth of its opacity, and
   there it was: three paragraphs of placeholder writing across the
   drawing. That is the stray text the owner photographed.

   `.gallery-writings` is here for exactly the same reason: it is what
   a favourite says when its card is opened, and it is source. */
body.chambered .work-list,
body.chambered .gallery-chapters,
body.chambered .gallery-writings,
body.chambered .page-content > h1,
body.chambered .page-content > .category-lede {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Before chamber.js has taken over — see the note in the page's head. */
html.js-coming body.favorites-page .page-content { visibility: hidden; }

/* THE STEP BETWEEN THE TWO STATES — one curve and one length, and
   everything that moves when the menu opens is eased on them: the
   lettering here, and the orbit itself in chamber.js, which solves
   this same bezier rather than easing on one of its own. They used to
   be two different curves over the same 1.7s — the drawing on a
   symmetrical S and the writing on the site's `--menu-ease`, which
   sets off quicker and has a longer tail — so the two arrived at
   noticeably different moments and the step read as several things
   happening at once rather than as one. Change either number here and
   change OPEN_MS / STEP_EASE in chamber.js with it. */
.chamber {
  --chamber-step: cubic-bezier(0.45, 0, 0.45, 1);
  --chamber-step-ms: 2.2s;
  position: fixed;
  inset: 0;
  z-index: 0;
  background: var(--bg);
}
.chamber-field, .chamber-front {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
/* The near half of the chamber is drawn over the writing, so it must
   never be the thing you click. */
.chamber-front { z-index: 3; pointer-events: none; }

/* What stands in the middle of the ring: the word alone to begin with,
   and the menu it opens into. Both are centred on the same spot, so
   the menu reads as the word opening out rather than as a panel
   arriving from somewhere else. */
.chamber-plate {
  position: fixed;
  left: 50%;
  top: 50%;
  /* THE WORD IS WHAT STANDS IN THE MIDDLE, and `--menu-lift` is how
     far the plate rises so that the word and the menu together are
     centred instead. It is set by chamber.js from the menu's own
     measured height, and it travels on the step's curve like
     everything else, so the word RISES to its place over the whole
     step rather than being put there.

     It used to be put there. The menu was a second thing in this same
     stack, so the frame it went on to the page the box grew by the
     whole height of it and the word was shoved 143 pixels up the
     window in that one frame — before any of the easing had begun.
     The owner reported it: the word blinks to a position above itself
     and only then is the animation played. Nothing that arrives may
     change the size of this box; that is what `position: absolute` on
     the panel below is for. */
  transform: translate(-50%, calc(-50% - var(--menu-lift, 0px)));
  transition: transform var(--chamber-step-ms) var(--chamber-step);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: min(94vmin, calc(100% - 28px));
  /* It used to carry a `max-height` as well, which was what kept a
     long menu inside a short window. The menu is no longer in here to
     be held, so that guard moved on to the panel itself. */
  /* The plate is only somewhere to stand: it is wider than the menu
     it holds, so anything it caught either side of the menu would be
     a click that looked like it landed on the page and did nothing. */
  pointer-events: none;
}
.chamber-word, .chamber-panel { pointer-events: auto; }

/* THE WORD. Technical, in the mono the rest of the site keeps for its
   readings, bracketed by the same hollow registration square the site
   marks a point with.

   IT IS SET WIDER THAN THE RING IS, and that is the whole reason it is
   this size. The ring is centred on the word, so a smaller word would
   sit in the hole of the ellipse and nothing would ever cross it. Set
   a little wider, the ring's left and right rims fall ACROSS the ends
   of the lettering — and because one of those rims is nearer than the
   middle of the chamber and the other further, one crosses in front of
   the word and the other passes behind it. That is what gives the word
   a place in the volume instead of making it a caption printed over a
   picture of one. Sized against `vmin` because the ring is too (it is
   drawn against the smaller side of the window), so the two keep their
   proportion at every window size. */
.chamber-word {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.18em;
  padding: 0.3em 0.62em 0.36em;
  border: 0;
  background: none;
  cursor: pointer;
  white-space: nowrap;
  font-family: var(--mono);
  /* Against `vmin` because the orbit is, and capped against `vw` as
     well so the lettering cannot run off the sides of a narrow window
     — on a phone the orbit is drawn against the short side and the
     word against the long one, and only the cap keeps the two apart.

     It is set SMALLER than it was, and `RING` in chamber.js came down
     with it: the word still has to be a little wider than the orbit or
     the orbit's rims cannot cross the ends of the lettering, so the
     two sizes are one decision and neither moves alone. */
  font-size: clamp(17px, min(6vmin, 5.4vw), 68px);
  letter-spacing: 0.55em;
  text-transform: uppercase;
  color: var(--ink);
  /* ONE THING MOVES, AND IT MOVES ONCE. The whole step from the word
     to the menu's heading is the lettering getting smaller, and every
     other measurement on it is written in `em` so it simply comes with
     — the letter-spacing, the padding, the crop marks, the gap either
     side of the marks. Those used to be animated in their own right,
     each with its own duration, and several of them in pixels, so they
     arrived at slightly different moments and the word appeared to
     settle in stages. One property, one duration, and it is smooth
     because there is nothing left to fall out of step with.

     That duration and that curve are the orbit's own step out
     (OPEN_MS and STEP_EASE in chamber.js), so the lettering and the
     particles set off together, travel together and arrive together.
     They used to share only the duration. */
  transition: font-size var(--chamber-step-ms) var(--chamber-step);
}
.chamber-word-line {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.52em;
}
/* Hovering it does not tint it: what the hand does to the word is
   close the crop marks round it, which is the gesture that says it is
   a control. Nothing on this page answers in colour any more. */
.chamber-word:hover .chamber-reg, .chamber-word:focus-visible .chamber-reg {
  background: var(--ink);
}

/* THE CROP MARKS. Four corner brackets round the word, the way a
   drawing brackets the thing it is about — and the first half of
   saying "this is a control": closed, the word is the only thing on
   the page, so it has to read as something you press rather than as a
   heading somebody centred. */
.chamber-crop {
  position: absolute;
  width: 0.34em;
  height: 0.34em;
  border: 0 solid var(--line);
  /* The brackets are sized in `em`, so they come down with the
     lettering on their own. This transition is only for the growth on
     hover, and it is kept SHORT for a reason: a transition on a length
     written in `em` is also a transition on the lettering getting
     smaller, and at 0.4s the brackets were still closing for most of
     half a second after the word and the menu had both landed. That
     tail is the one thing you could still point at in the step. */
  transition: border-color 0.35s var(--menu-ease), width 0.15s var(--menu-ease),
              height 0.15s var(--menu-ease);
}
.chamber-crop.tl { top: 0; left: 0; border-top-width: 1px; border-left-width: 1px; }
.chamber-crop.tr { top: 0; right: 0; border-top-width: 1px; border-right-width: 1px; }
.chamber-crop.bl { bottom: 0; left: 0; border-bottom-width: 1px; border-left-width: 1px; }
.chamber-crop.br { bottom: 0; right: 0; border-bottom-width: 1px; border-right-width: 1px; }
/* Pressed-looking on the way in: the brackets run out towards each
   other so the four of them read as one frame closing round the word,
   which is as near as this page comes to saying "button" without
   drawing a box permanently round the only thing on it. */
.chamber-word:hover .chamber-crop, .chamber-word:focus-visible .chamber-crop {
  border-color: var(--ink);
  width: 0.9em;
  height: 0.55em;
}
.chamber-word:active .chamber-crop { width: 1.3em; height: 0.7em; }

/* THE CUE. The other half: a small boxed label under the lettering
   saying what pressing it does, and which way it will go. It is set in
   pixels rather than with the word, so it stays a control's size
   however large the lettering is drawn. */
.chamber-cue {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 0.12em;
  padding: 6px 14px;
  border: 1px solid var(--line);
  background: var(--bg);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--muted);
  transition: border-color 0.35s var(--menu-ease), color 0.35s var(--menu-ease),
              background 0.35s var(--menu-ease);
}
.chamber-cue-name { margin-right: -0.26em; }
.chamber-cue-mark {
  width: 5px;
  height: 5px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.55s var(--menu-ease);
}
.chamber-word:hover .chamber-cue, .chamber-word:focus-visible .chamber-cue {
  border-color: var(--ink);
  color: var(--ink);
  background: var(--bg-2);
}
.chamber-word:active .chamber-cue {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}
.chamber-plate.open .chamber-cue-mark { transform: translateY(1px) rotate(225deg); }
.chamber-reg {
  width: 0.34em;
  height: 0.34em;
  border: 1px solid var(--ink);
  flex: none;
  transition: background 0.35s var(--menu-ease),
              border-color var(--chamber-step-ms) var(--chamber-step);
}
/* The trailing letter-space would otherwise push the lettering off
   centre against the two marks. */
.chamber-word-name { margin-right: -0.55em; }
/* Open, the word is the menu's own heading. Only the size is set: the
   spacing, the padding, the marks and the brackets are all in `em` and
   come down with it, which is what makes the step one movement rather
   than five. */
.chamber-plate.open .chamber-word { font-size: 13px; }
.chamber-plate.open .chamber-reg { border-color: var(--muted); }

.chamber-panel {
  /* OUT OF THE FLOW, hung under the word rather than stacked below it
     — so that putting it on the page moves nothing at all, and what
     centres the two of them together is the plate's own `--menu-lift`
     easing up on the step's curve. In the flow it took its full height
     the instant it appeared and threw the word up the window in one
     frame; see the note on `.chamber-plate`.

     Centred on `translate` rather than on `transform`, because its own
     arrival and leaving animate the other one and the two would
     otherwise cancel each other out. */
  position: absolute;
  top: 100%;
  left: 50%;
  translate: -50% 0;
  width: min(460px, 100%);
  margin-top: 10px;
  padding: 18px 22px 22px;
  border: 1px solid var(--line);
  background: var(--bg);
  /* A FIXED LENGTH, whatever is in it. The owner asked for the table
     to stay the same size as more favourites are added, and two things
     come with that: the menu is a panel of a known size rather than a
     list that grows down the window, and — since the orbit is fitted
     to this box and the plate is lifted by half of it — neither the
     orbit nor where the word stands changes when a favourite is
     written. The rows scroll inside it instead.

     Out of the flow it is not held inside the plate's height, so on a
     short window it takes what there is rather than running off the
     bottom. */
  display: flex;
  flex-direction: column;
  height: 360px;
  /* `svh` and not `vh`: on a phone the viewport height CHANGES as the
     address bar slides away, and this box is what the orbit is fitted
     to and what the plate is lifted by half of — so a height written
     in `vh` moves the word and the menu every time the page is
     touched. `svh` is the small viewport, which does not move. */
  max-height: calc(100vh - 180px);
  max-height: calc(100svh - 180px);
  /* It comes up over the same moment the orbit takes to widen, and
     waits out the first third of it: the word has to have begun coming
     down to a heading before the menu appears under it, or the two
     read as two separate things happening at once.

     It fades and rises a few pixels, and does nothing else. It used to
     be squashed flat and stretched out (`scaleY(0.72)`), which carries
     every line of writing in it with it: the lettering was drawn at
     the wrong height and then corrected, which is the opposite of
     minimal however smoothly it is eased. */
  animation: chamber-open 1.45s var(--chamber-step) 0.75s both;
}
/* A DISPLAY OF ITS OWN OUTRANKS `[hidden]`. The panel is a flex column
   now, so the browser's own `[hidden] { display: none }` no longer
   holds it off the page — it stood open from the moment the page
   loaded until this was written back. Everything else here that is
   hidden by the attribute carries the same line. */
.chamber-panel[hidden] { display: none; }
@keyframes chamber-open {
  from { opacity: 0; translate: -50% -8px; }
  to { opacity: 1; translate: -50% 0; }
}
/* HANDED BACK SETTLED. On the way out of a chapter the chamber is given
   back with its menu open, under a black that is still solid; the step
   that opens it is put straight at its end (`giveBack` in chamber.js),
   and so are the word, the plate and the menu here — or they were seen
   shrinking and sliding into place after the black had cleared, which
   read as the writing jumping. `handing` lasts two frames; `settled`
   until the menu is next shut, because taking a finished animation's
   duration back mid-way would put it back where it was at that time. */
.chamber.handing { --chamber-step-ms: 0.01s; }
.chamber-plate.settled .chamber-panel { animation-duration: 0.01s; animation-delay: 0s; }
/* AND IT LEAVES ON THE SAME STEP IT ARRIVED ON. It used to be taken
   off the page in the frame the word was pressed, leaving the orbit to
   spend the next two seconds coming back in on its own — the one half
   of this page's only movement that was a cut rather than a movement.
   It goes back into the word instead, over the first part of the step,
   and chamber.js takes it off the page at the end of THAT rather than
   at the end of the step — `SHUT_MS` there is this duration, and must
   stay it. Nothing in it answers the hand on the way out. */
.chamber-plate.shutting .chamber-panel {
  pointer-events: none;
  animation: chamber-shut 1.25s var(--chamber-step) both;
}
@keyframes chamber-shut {
  from { opacity: 1; translate: -50% 0; }
  to { opacity: 0; translate: -50% -8px; }
}

/* SHUTTING BECAUSE A CHAPTER WAS PRESSED IS NOT THE SAME AS SHUTTING.
   Closing the menu is a step back to the word, and rising into it is
   right for that. Pressing a CHAPTER is the start of the burst, and the
   owner said the menu leaving looked like neither — "just goes down and
   fades away", against a movement that is everything closing on the
   middle.

   So on a burst the rows are drawn INTO it. Each one collapses towards
   the middle of the panel and shrinks as it goes, staggered from the
   outside in — the row nearest the middle is the last to be taken —
   with the panel's rule and head going the same way. It is the
   implosion, said in writing.

   `--n` is the row's place from the top and `--of` how many there are,
   both set by chamber.js; `--mid` is how far this row is from the
   middle, so the stagger is symmetrical about it rather than running
   top to bottom. */
/* THE PANEL IS DRAWN INTO THE MIDDLE, AND DISTORTED ON THE WAY THERE.
   It travels to the point the particles are closing on (`--suck-x`,
   `--suck-y`, measured in chamber.js), while its rows collapse inwards
   inside it. Two movements, one direction: the writing goes where the
   drawing goes.

   IT IS A DISTORTION AND NOT A SHRINK, which is what the owner asked
   for after the last round — "the table disappears in a not so smooth
   fashion, i think it needs to be distorted into the center". Three
   things do that, and none of them on its own is enough:

     IT IS TIPPED    into a vanishing point (`perspective` with a
                     rotate3d), so the near edge of it grows as the far
                     edge goes — the shape changes rather than only its
                     size.
     IT IS DRAWN OUT along the way it is going before it is let go:
                     scaleX runs a little OVER one while scaleY is
                     already under it, which is what a thing being
                     pulled through a point looks like.
     IT IS BLURRED   and the blur is on its own curve, late and hard, so
                     it reads as speed rather than as a fade.

   AND IT TAKES AS LONG AS THE WIND DOES. It used to be over in 0.92s
   against a wind of 2.3, so the menu had been gone for a second and a
   half before anything met in the middle — which is the other half of
   why it did not read as being pulled in by it. */
.chamber-plate.drawn-in .chamber-panel {
  pointer-events: none;
  animation: chamber-suck 2.1s cubic-bezier(0.62, 0, 0.66, 0.2) both;
  transform-origin: 50% 50%;
  /* The perspective is the FIRST function of the element's own
     transform, not the `perspective` property: that property sets the
     depth children are seen at, and what has to be tipped here is this
     element itself. */
  will-change: transform, filter, opacity;
}
@keyframes chamber-suck {
  0% {
    opacity: 1;
    translate: -50% 0;
    transform: perspective(900px);
    filter: blur(0px);
  }
  22% {
    opacity: 0.94;
    translate: -50% 0;
    transform: perspective(900px)
               translate3d(calc(var(--suck-x, 0px) * 0.06),
                           calc(var(--suck-y, 0px) * 0.06), 0)
               rotate3d(1, 0.3, 0, 7deg) scale3d(1.06, 0.9, 1);
    filter: blur(0.6px);
  }
  62% {
    opacity: 0.5;
    translate: -50% 0;
    transform: perspective(900px)
               translate3d(calc(var(--suck-x, 0px) * 0.48),
                           calc(var(--suck-y, 0px) * 0.48), 0)
               rotate3d(1, 0.3, 0, 22deg) scale3d(0.74, 0.34, 1);
    filter: blur(3px);
  }
  100% {
    opacity: 0;
    translate: -50% 0;
    transform: perspective(900px)
               translate3d(var(--suck-x, 0px), var(--suck-y, 0px), 0)
               rotate3d(1, 0.3, 0, 42deg) scale3d(0.1, 0.03, 1);
    filter: blur(9px);
  }
}
/* THE ROWS COLLAPSE INSIDE IT, outside in — the row nearest the middle
   is the last to be taken — and they are squeezed sideways as well as
   up, so the whole table narrows to a line rather than merely sliding
   together. */
.chamber-plate.drawn-in .chamber-row {
  animation: chamber-drawn-in 1.2s cubic-bezier(0.6, 0, 0.72, 0.3) both;
  animation-delay: calc((1 - var(--mid, 0)) * 0.55s);
}
.chamber-plate.drawn-in .chamber-head {
  animation: chamber-drawn-in 1.2s cubic-bezier(0.6, 0, 0.72, 0.3) both;
}
@keyframes chamber-drawn-in {
  from {
    opacity: 1;
    transform: translateY(0) scale(1, 1);
    letter-spacing: normal;
  }
  to {
    opacity: 0;
    transform: translateY(calc(var(--pull, 0px) * -1)) scale(0.58, 0.3);
    letter-spacing: -0.09em;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chamber-plate.drawn-in .chamber-panel,
  .chamber-plate.drawn-in .chamber-row,
  .chamber-plate.drawn-in .chamber-head { animation-duration: 0.01s; }
}
/* WITH ANIMATION TURNED OFF there is nothing to watch the step
   happening: chamber.js settles the drawing into whichever state the
   page is now in and leaves it there, and this is the writing's half
   of the same thing — the word, the plate and the menu take their new
   places at once rather than travelling to them. */
@media (prefers-reduced-motion: reduce) {
  .chamber { --chamber-step-ms: 0.01s; }
  .chamber-panel,
  .chamber-plate.shutting .chamber-panel {
    animation-duration: 0.01s;
    animation-delay: 0s;
  }
}

.chamber-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.chamber-spec {
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--muted);
}

/* The rows are what scrolls, not the panel: the heading and the way
   back stay where they are at the top of it. `min-height: 0` because a
   flex child will not shrink below its content without being told it
   may. */
.chamber-column {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.chamber-level[hidden] { display: none; }
.chamber-level { animation: chamber-level 0.4s var(--menu-ease) both; }
@keyframes chamber-level {
  from { opacity: 0; transform: translateX(8px); }
  to { opacity: 1; transform: none; }
}

/* ONE COLUMN, and no strip across the top: the chapters stand in it
   first, and opening one puts its favourites in the same column in
   their place. A row is a stack — its number and reading above, its
   name under them — whether it is a chapter or a favourite, so the two
   levels read as one thing going deeper rather than as two designs. */
.chamber-row {
  position: relative;
  display: block;
  width: 100%;
  padding: 15px 22px 15px 0;
  border: none;
  background: none;
  text-align: left;
  text-decoration: none;
  color: var(--ink);
  cursor: pointer;
}
/* THE RULE UNDER A ROW IS WHAT ANSWERS THE HAND, and it answers by
   GETTING SHORTER. The rows are ruled off from each other edge to
   edge; point at one and its own rule draws back from the right,
   press it and it draws back further still. It is drawn as a layer of
   its own rather than as the row's `border-bottom`, because a border
   cannot be shortened without making the row itself narrower — and
   `transform` is the one thing that can be animated here without the
   browser laying the menu out again on every frame.

   This replaced an indent, where the whole row stepped sideways under
   the pointer. Both say "this one", but a line getting shorter is the
   drawing's own language and moves nothing that anyone is reading. */
.chamber-row::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--line);
  transform-origin: left center;
  transition: transform 0.5s var(--menu-ease), background 0.5s var(--menu-ease);
}
.chamber-row:hover::after, .chamber-row:focus-visible::after {
  transform: scaleX(0.3);
  background: var(--ink);
}
.chamber-row:active::after { transform: scaleX(0.13); }
.chamber-file {
  display: flex;
  gap: 12px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.chamber-no { color: var(--ink); }
.chamber-name {
  display: block;
  margin-top: 5px;
  font-family: var(--sans);
  font-size: clamp(16px, 1.5vw, 19px);
  letter-spacing: -0.01em;
}
.chamber-go {
  position: absolute;
  right: 0;
  bottom: 15px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--muted);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity 0.3s var(--menu-ease), transform 0.3s var(--menu-ease);
}
.chamber-row:hover .chamber-go,
.chamber-row:focus-visible .chamber-go { opacity: 1; transform: none; color: var(--ink); }

@media (max-width: 720px) {
  .chamber-plate { width: calc(100% - 44px); }
  .chamber-panel { padding: 16px 16px 18px; }
  .chamber-head { flex-wrap: wrap; gap: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .chamber-panel, .chamber-level { animation-duration: 0.01s; }
  .chamber-word, .chamber-row::after { transition-duration: 0.01s; }
}

/* ==================================================================
   THE STRUCTURE — categories/theories.html
   A technical drawing in three dimensions that you scroll *into*: a
   frame of ribs and rails running away into the depth, a ruled spine
   along the floor, and a fine swarm of particles in the air. It is
   drawn on a fixed canvas; the page itself is made tall enough to
   hold the road, and the scroll position is the travel. Everything
   here is written under `body.structured`, which structure.js adds —
   without it the page is the plain list of rows every other category
   uses, and stays that way.
   ================================================================== */
/* A little brighter since the night of 2026-09-25 ("Make the theories
   page a little brighter"): it was #0a0b0e. */
body.structured { background: #15171d; }
/* Before structure.js has taken over: the ground is already the
   drawing's, so what follows is the drawing arriving rather than one
   page being swapped for another. */
html.js-coming body.theories-page { background: #15171d; }
html.js-coming body.theories-page .page-content { visibility: hidden; }
html.js-coming body.theories-page .menu-trigger { color: #dce6f7; }
body.structured .page-content { padding: 0; margin: 0; max-width: none; }
/* The list is still in the page for anything reading rather than
   looking — it is the structure's own index — but it is not drawn.
   Pinned to the corner with its margins taken off: left where it was
   laid out it adds to the height of a page whose height is the road. */
body.structured .work-list,
body.structured .page-content > h1,
body.structured .page-content > .category-lede {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The Menu is written on the drawing now, so it has to be light. */
body.structured .menu-trigger { color: #dce6f7; }

/* What you scroll down. It has no content of its own: it is there to
   give the page a height, so that the scrollbar, the arrow keys, Page
   Down and a finger on a phone all drive the travel without a line of
   code catching them. */
.structure-road { width: 100%; }

.structure {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: #15171d;
}
.structure-field { display: block; width: 100%; height: 100%; }

.structure-readout {
  position: fixed;
  top: 22px;
  right: 26px;
  margin: 0;
  padding: 4px 0;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 15px;
  letter-spacing: 0.12em;
  color: rgba(226, 232, 242, 0.6);
}
body.structured .page-where { color: rgba(226, 232, 242, 0.55); border-left-color: rgba(226, 232, 242, 0.2); }

/* THE OPENING. The drawing sets itself up when the page loads — the
   rails shoot out, the ribs come up out of the depth, the rule writes
   itself along the floor — and the chrome arrives with it rather than
   standing there first over an empty screen. structure.js adds `lit`
   when the setting up has finished. */
.structure:not(.lit) .structure-readout,
.structure:not(.lit) .page-where,
.structure:not(.lit) .structure-cue { opacity: 0; }
.structure .structure-readout,
.structure .page-where { transition: opacity 0.9s var(--menu-ease); }

/* Said once, at the top of the road, and gone as soon as you have
   started down it. */
.structure-cue {
  position: fixed;
  left: 50%;
  bottom: 34px;
  transform: translateX(-50%);
  margin: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  color: rgba(226, 232, 242, 0.42);
  transition: opacity 0.7s var(--menu-ease);
  pointer-events: none;
  z-index: 2;
}
.structure-cue.gone { opacity: 0; }
.structure-cue-mark {
  width: 9px;
  height: 9px;
  border: 1px solid rgba(226, 232, 242, 0.42);
}

/* THE WHEEL. The rule down the middle is drawn on the canvas; this is
   the piece of the screen that answers the hand, laid over the near
   end of it where the rule is widest and easiest to catch. Dragging
   it writes the page's own scroll position, so it and the scrollbar
   can never disagree about where you are.

   `touch-action: none` is what stops a finger dragging it from also
   scrolling the page underneath — which would travel twice as far as
   the hand asked for. */
.structure-spine {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: min(340px, 36vw);
  height: 42vh;
  border: none;
  background: linear-gradient(to top, rgba(205, 218, 235, 0.05), rgba(205, 218, 235, 0));
  opacity: 0;
  transition: opacity 0.4s var(--menu-ease);
  touch-action: none;
  pointer-events: auto;
  z-index: 1;
}
.structure-spine:hover, .structure-spine.held, .structure-spine:focus-visible { opacity: 1; }
.structure-spine:focus-visible { outline: 1px solid rgba(142, 180, 226, 0.5); outline-offset: -2px; }

/* A station is the thing you click: the link is laid over the
   assembly rather than beside it, and structure.js sizes it to
   whatever that assembly is doing on the screen this frame — so
   nothing here may give one a size or a transform of its own. */
.structure-marks { position: fixed; inset: 0; pointer-events: none; z-index: 2; }
.structure-stop {
  position: absolute;
  display: block;
  text-decoration: none;
  pointer-events: auto;
  border: 1px solid transparent;
  transition: border-color 0.3s var(--menu-ease);
}
.structure-stop.gone { display: none; }
.structure-stop:hover, .structure-stop:focus-visible { border-color: rgba(142, 180, 226, 0.4); }
.structure-say {
  position: absolute;
  left: 0;
  top: 100%;
  margin-top: 8px;
  display: block;
  white-space: nowrap;
}
/* KEEPING A STATION'S NAME ON THE WINDOW, and only on a phone.
   `--say-shift` is how far the lettering has to slide back along not to
   run off the side; structure.js works it out and writes it every
   frame, and it is only ever negative — the name is pulled back off the
   edge, never pushed out to it.

   BOTH RULES ARE HELD BELOW 700px, and the two go together. Each line
   being its own width is what lets structure.js ask how wide the NAME
   is without being told how wide the line under it is (that one is only
   ever read on a hover and is much the longest of the three) — and
   without it the shift would be measured against the wrong number. So
   above 700px there is no shift, no transform and no change to any box
   on the drawing: it is the page it always was. */
@media (max-width: 700px) {
  .structure-say > span { width: max-content; max-width: 100%; }
  .structure-say { transform: translateX(var(--say-shift, 0px)); }
}
.structure-no {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  color: rgba(142, 180, 226, 0.9);
}
.structure-name {
  display: block;
  margin-top: 3px;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 16px;
  letter-spacing: -0.01em;
  color: #eef1f6;
}
.structure-meta {
  display: block;
  margin-top: 3px;
  font-family: var(--mono);
  font-size: 10px;
  color: rgba(226, 232, 242, 0.42);
  opacity: 0;
  transition: opacity 0.3s var(--menu-ease);
}
/* The line under the name is only worth reading once you are among
   it, so it waits until you are close and until you point at it. */
.structure-stop.close:hover .structure-meta,
.structure-stop.close:focus-visible .structure-meta { opacity: 1; }
.structure-stop:hover .structure-name,
.structure-stop:focus-visible .structure-name { color: #9ecbff; }

/* THE CARD. Clicking a station lays it out on the window and writes
   this beside it — the preview of the theory. It is INSIDE the
   station's own link, so the card is more of the station rather than a
   second thing to aim at, and the click that follows opens the piece.

   It waits for the figure to finish coming forward before it writes
   itself in: the station is set out first, and only then is there
   something beside it to read. */
.structure-card {
  position: absolute;
  left: calc(100% + 54px);
  top: 50%;
  width: min(360px, 30vw);
  padding: 18px 20px 0;
  border-left: 1px solid rgba(142, 180, 226, 0.55);
  background: rgba(10, 11, 14, 0.66);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-50%) translateX(-12px);
  transition: opacity 0.35s var(--menu-ease), transform 0.35s var(--menu-ease),
              visibility 0s linear 0.35s;
}
/* The leader from the figure to the card, the way the drawing joins
   anything to the note about it. */
.structure-card::before {
  content: "";
  position: absolute;
  right: 100%;
  top: 30px;
  width: 54px;
  height: 1px;
  background: rgba(142, 180, 226, 0.4);
}
.structure-stop.open .structure-card {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) translateX(0);
  transition: opacity 0.5s var(--menu-ease) 0.3s, transform 0.5s var(--menu-ease) 0.3s,
              visibility 0s;
}
.structure-card > span { display: block; }
.structure-card-kicker {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  color: rgba(142, 180, 226, 0.9);
}
.structure-card-name {
  margin-top: 10px;
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(21px, 2.1vw, 30px);
  letter-spacing: -0.015em;
  line-height: 1.1;
  color: #eef1f6;
  white-space: normal;
}
.structure-card-meta {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.6;
  color: rgba(226, 232, 242, 0.5);
  white-space: normal;
}
.structure-card-note {
  margin-top: 12px;
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.6;
  color: rgba(226, 232, 242, 0.75);
  white-space: normal;
}
.structure-card-spec {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid rgba(226, 232, 242, 0.14);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: rgba(150, 160, 176, 0.8);
}
.structure-card-open {
  margin-top: 14px;
  padding-bottom: 18px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(142, 180, 226, 0.9);
}
.structure-stop.open:hover .structure-card-open,
.structure-stop.open:focus-visible .structure-card-open { color: #9ecbff; }
/* A SECOND WAY IN, for a piece that carries something of its own worth
   going straight to: stood directly under "OPEN →", the one on top of
   the other, and set the same way — bare, not boxed. It was boxed and
   over at the right-hand end of the card until the owner asked for the
   two "on top of one another" and the calculator's "not in a box". */
.structure-card-calc {
  margin: 0 0 18px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(142, 180, 226, 0.9);
  cursor: pointer;
  transition: color 0.25s var(--menu-ease);
}
.structure-card-calc[hidden] { display: none; }
.structure-card-calc:hover, .structure-card-calc:focus-visible { color: #9ecbff; }
/* The card's own bottom padding belongs to whichever of the two is
   last: with the calculator under it, "OPEN →" gives its up. */
.structure-card-open:has(+ .structure-card-calc:not([hidden])) { padding-bottom: 10px; }

/* One name, one place: the station's name is in the card while it is
   open, so the copy under the figure goes — and the names of the
   stations it is standing in front of go with it. */
.structure-stop.open .structure-say { opacity: 0; }
.structure-marks.holding .structure-stop:not(.open) .structure-say { opacity: 0; }
.structure-say { transition: opacity 0.35s var(--menu-ease); }

@media (max-width: 860px) {
  /* The two numbers stay: they are how you know where you are. */
  .structure-readout { top: 16px; right: 16px; font-size: 10px; letter-spacing: 0.08em; }
  .structure-spine { width: min(260px, 60vw); }
}

/* And on a phone the line under a station's name wraps rather than
   running off the side: it is much the longest of the three and there
   is no room for it on one line there. Held to 700px with everything
   else, though it would be worth having at 860 too — nothing above
   700px may change. */
@media (max-width: 700px) {
  .structure-meta { white-space: normal; max-width: 62vw; }
}

/* Where there is no room beside the figure the card goes under it, and
   structure.js lays the figure higher up the window to make that room.
   The two have to agree: this is the same condition. */
@media (max-width: 860px), (max-aspect-ratio: 95/100) {
  .structure-card {
    left: 50%;
    top: calc(100% + 44px);
    width: min(420px, 84vw);
    padding: 16px 18px 0;
    transform: translate(-50%, 10px);
  }
  .structure-stop.open .structure-card { transform: translate(-50%, 0); }
  .structure-card::before {
    right: auto;
    left: -1px;
    top: -44px;
    width: 1px;
    height: 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Held still rather than switched off, like the rest of the drawing:
     the card is simply there as soon as the station is open. */
  .structure-card,
  .structure-stop.open .structure-card { transition-duration: 0.01s; transition-delay: 0s; }
  .structure-say { transition-duration: 0.01s; }
}

/* ==================================================================
   GALLERY WORK PAGE
   ================================================================== */
.work-header { max-width: 640px; margin: 0 auto; padding: 140px 64px 80px; }
.work-header .work-kicker { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-bottom: 12px; }
.work-header h1 { font-size: clamp(30px, 4.5vw, 48px); }
.work-header .work-intro { color: var(--muted); margin-top: 20px; max-width: 56ch; }

.gallery-entry { max-width: 900px; margin: 0 auto; padding: 48px 64px; display: flex; gap: 48px; align-items: flex-start; border-top: 1px solid var(--line); }
.gallery-entry .entry-number { font-family: var(--mono); font-size: 12px; color: var(--brass); flex: none; width: 32px; padding-top: 4px; }
.gallery-entry .entry-media { flex: 1 1 55%; aspect-ratio: 4 / 3; border: 1px solid var(--line); background-color: var(--bg-2); background-image: repeating-linear-gradient(-45deg, var(--line) 0px, var(--line) 1px, transparent 1px, transparent 14px); position: relative; }
.gallery-entry .entry-media::after { content: "your image here"; position: absolute; bottom: 12px; left: 12px; font-family: var(--mono); font-size: 11px; color: var(--muted); background: var(--bg); padding: 3px 7px; border: 1px solid var(--line); }
.gallery-entry .entry-media:has(img) { background-image: none; }
.gallery-entry .entry-media:has(img)::after { display: none; }
.gallery-entry .entry-media img { width: 100%; height: 100%; object-fit: cover; }
.gallery-entry .entry-text { flex: 1 1 45%; color: var(--muted); max-width: 48ch; }

/* ==================================================================
   ARTICLE WORK PAGE
   ================================================================== */
.article-body { max-width: 640px; margin: 0 auto; padding: 0 64px 120px; }
.article-body h2 { font-size: 21px; margin: 48px 0 16px; }
.article-body p { margin: 0 0 18px; color: var(--muted); }
.article-body p + h2 { margin-top: 56px; }

.infobox { float: right; width: 260px; margin: 0 0 24px 32px; border: 1px solid var(--line); background: var(--bg-2); padding: 16px; }
.infobox .infobox-media { aspect-ratio: 4/3; border: 1px solid var(--line); background-image: repeating-linear-gradient(-45deg, var(--line) 0px, var(--line) 1px, transparent 1px, transparent 10px); margin-bottom: 12px; }
.infobox dl { font-family: var(--mono); font-size: 12px; }
.infobox > dl > div { display: flex; gap: 8px; padding: 6px 0; border-top: 1px solid var(--line); }
.infobox dt { color: var(--muted); min-width: 74px; }
.infobox dd { margin: 0; }

/* ==================================================================
   TEST NODE PAGES (sandbox)
   ================================================================== */
.test-tag { display: inline-block; font-family: var(--mono); font-size: 12px; color: var(--brass); border: 1px solid var(--line); padding: 3px 8px; margin-bottom: 20px; }

/* ==================================================================
   CONTACT
   ================================================================== */
/* One sentence, and nothing else: "Get in touch, send a carrier pigeon."
   The address and the links came off at the owner's word. */
.contact-page h1 { max-width: 16ch; }

/* THE DETAILS BEHIND A CAPTCHA (contact.js): the characters in specks in
   a hairline box, a box to type them into under it, and the details once
   they are typed. Set in the site's mono and ruled as its readings are. */
.contact-lock { margin-top: clamp(22px, 4vh, 40px); max-width: 420px; }
/* THE OWNER'S "or just send an email:" — the sentence over it carried on,
   so it is set in the reading's own face, a size down from the heading
   and in its ink, rather than as one of the mono labels. */
.contact-or {
  font-size: clamp(20px, 2.2vw, 26px);
  line-height: 1.3;
  color: var(--ink);
  margin: 0 0 22px;
}
.contact-check[hidden], .contact-details[hidden] { display: none; }
.contact-captcha { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; }
.contact-captcha-canvas {
  display: block;
  width: 300px;
  height: 96px;
  max-width: 100%;
  border: 1px solid var(--line);
  background: var(--bg);
}
.contact-captcha-new,
.contact-captcha-go {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  background: none;
  border: 1px solid var(--line);
  padding: 9px 12px;
  cursor: pointer;
  transition: border-color 0.2s var(--menu-ease), background-color 0.2s var(--menu-ease);
}
.contact-captcha-new { border-color: transparent; padding-left: 0; padding-right: 0; color: var(--muted); }
.contact-captcha-new:hover { color: var(--ink); }
.contact-captcha-go:hover, .contact-captcha-go:focus-visible { border-color: var(--ink); }
.contact-captcha-label {
  display: block;
  margin: 22px 0 8px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.contact-captcha-row { display: flex; gap: 12px; align-items: stretch; }
.contact-captcha-field {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 18px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 6px 0;
  outline: none;
}
.contact-captcha-field:focus { border-bottom-color: var(--ink); }
.contact-captcha-say { min-height: 1.6em; margin: 12px 0 0; font-family: var(--mono); font-size: 11px; color: var(--muted); }
.contact-details {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px 22px;
  margin: 0;
  padding: 18px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  animation: contact-open 0.5s var(--menu-ease) both;
}
.contact-details dt {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  align-self: baseline;
}
.contact-details dd { margin: 0; font-size: 18px; color: var(--ink); align-self: baseline; }
.contact-details a { color: var(--ink); text-underline-offset: 3px; }
@keyframes contact-open { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* Without the script, the line saying so; with it, not. */
.contact-lock.is-scripted .contact-lock-nojs { display: none; }
.contact-lock-nojs { font-size: 15px; color: var(--muted); margin: 0; }
@media (prefers-reduced-motion: reduce) { .contact-details { animation: none; } }

/* ==================================================================
   FOCUS STATES
   ================================================================== */
a:focus-visible, button:focus-visible { outline: 1px solid var(--brass); outline-offset: 3px; }

/* ==================================================================
   RESPONSIVE
   ================================================================== */
@media (max-width: 860px) {
  .menu-overlay { padding: 0 24px; }
  .slide { padding: 0 24px; flex-direction: column !important; justify-content: center; gap: 20px; }
  .title-block { right: 16px; bottom: 16px; }
  .scroll-cue { left: 16px; bottom: 16px; }
  .page-content, .work-header, .article-body { padding-left: 24px; padding-right: 24px; }
  .gallery-entry { flex-direction: column; padding: 32px 24px; gap: 20px; }
  .gallery-entry .entry-text { max-width: none; }
  .infobox { float: none; width: 100%; margin: 0 0 32px; }
  .node3d-text { font-size: 14px; }
  .node3d-sub { width: 160px; }
}


/* ==================================================================
   PINEWARD — works/pineward.html
   A long piece: an introduction and fifty-two parts, each of which is
   a picture and a few paragraphs. Fifty-two of anything listed down a
   page is a wall, so a part shows only its number, a small picture and
   its title until it is opened.

   The parts are grouped into four STRATA of thirteen — Canopy,
   Understorey, Trunk, Roots — a section through a forest read from the
   light down into the ground. Down the side runs the TRUNK: one tick
   per part, inked in as you pass it, with the reading in the corner
   counting them. pineward.js draws the canopy, adds the trunk and the
   reading, and brings the parts up as they are reached; without it the
   page is all of its writing and nothing is hidden.
   ================================================================== */
/* THE GROUND OF THIS PAGE IS PINE GREEN, very faintly.

   The owner asked for "some background elements in pine green colours".
   What is here is three things, and all three are the page's ground
   rather than anything drawn over the reading:

     THE WASH    the paper turned a hair towards the green, so white is
                 not quite white. At 2% it is below the point where you
                 would call it a colour, and above the point where you
                 would call it white.
     THE LIGHT   two very soft pools of green, one high on each side,
                 where the wood stands. They sit under the margins and
                 not under the column, so the writing is still on paper.
     THE FLOOR   the green gathering towards the foot of the page, which
                 is the Roots stratum — the page reads down from the
                 light into the ground, and its ground goes with it.

   All of it is `--pine-green` at single-figure percentages. It must
   stay that faint: this page's accent is worth something because it is
   spent in so few places, and a green page would spend it everywhere.
   The canopy canvas draws over this and is unaffected. */
.pineward-page {
  background-color: #eef1ea;
  background-image:
    radial-gradient(64% 46% at 6% 2%, rgba(26, 74, 44, 0.20) 0%, transparent 72%),
    radial-gradient(58% 40% at 96% 10%, rgba(26, 74, 44, 0.17) 0%, transparent 70%),
    radial-gradient(70% 34% at 50% 100%, rgba(26, 74, 44, 0.16) 0%, transparent 76%),
    linear-gradient(to bottom, rgba(26, 74, 44, 0.03) 0%, rgba(26, 74, 44, 0.10) 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}
/* WRITTEN AS PLAIN rgba(), NOT color-mix(). It was mixed for one round,
   and that broke the cursor everywhere on this page: a background
   written with color-mix() computes to `color(srgb 0.96 0.97 0.96)`,
   whose components run 0 to 1 — and nav.js read them as 0 to 255, saw a
   white page as nearly black, and drew the cursor WHITE ON WHITE. The
   parser handles both forms now (see `colourOf` there), but there is
   no reason for this page to be the one that tests it. The numbers are
   --pine-green's own (#1a4a2c) written out. */

/* THE CANOPY, standing behind the title and fading out before the
   writing starts. It is drawn by the script; there is nothing in it
   without one. */
/* THE WOOD stands behind the whole piece rather than behind its title,
   so the canvas is FIXED to the window and the drawing is carried in
   the page's own coordinates: a page six thousand pixels long does not
   need a canvas six thousand pixels tall. What is quiet across the
   middle of it, where the writing stands, is worked out in pineward.js
   rather than masked here — a mask would take the trees out of the
   page's foot as well. */
/* THE HOUSE'S GREEN. The owner asked for dark green accents on this page
   — the scale, the marks that say where you are, and the wood's answer to
   the hand. It is scoped to this page, so no other page on the site
   spends it, and pineward.js keeps its own copy of the same value for
   the canvas (GREEN). Change one and change the other. */
/* THE HOUSE'S TWO COLOURS. --pine-green is the accent, spent nowhere
   else on the site; --pine-bark is the brown of a trunk, and the wood
   in the margins is drawn between the two — bark at the trunk, green
   out at the needles. pineward.js keeps the same pair as GREEN and
   BARK for the canvas: change one and change the other. */
.pineward-page { --pine-green: #1a4a2c; --pine-bark: #4a3422; }

.pine-canopy {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.pine-head {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(150px, 26vh, 260px) 64px clamp(40px, 7vh, 80px);
}
.pine-kicker {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 18px;
}
.pine-head h1 {
  font-size: clamp(34px, 6.4vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0;
}
/* The second half of the title is the quiet half: it is what the house
   smells like, and the house is what the piece is called. */
.pine-head h1 em {
  display: block;
  margin-top: 0.18em;
  font-size: 0.44em;
  font-style: italic;
  letter-spacing: -0.01em;
  color: var(--muted);
}
/* THE INTRODUCTION, and every stratum's own heading: the same block,
   a number in the mono the site keeps for readings and then the name
   under a hairline. */
.pine-intro,
.pine-stratum-head {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(40px, 7vh, 86px) 64px clamp(20px, 3vh, 34px);
}
.pine-section-mark,
.pine-stratum-no {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  color: var(--muted);
  margin: 0 0 10px;
}
.pine-intro h2,
.pine-stratum-head h2 {
  font-size: clamp(22px, 3vw, 34px);
  letter-spacing: -0.01em;
  margin: 0;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.pine-stratum-count {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--muted);
  margin: 8px 0 0;
}
.pine-text { max-width: 62ch; }
.pine-text p { margin: 0 0 16px; color: var(--muted); }
.pine-intro .pine-text { margin-top: 22px; }

/* THE PARTS. Closed, one is a number, a small picture and a title on a
   ruled line — fifty-two of those read as an index rather than as a
   wall. Open, the picture is printed at size with the writing beside
   it. */
.pine-parts {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 0 64px clamp(30px, 6vh, 70px);
}
.pine-part {
  border-bottom: 1px solid var(--line);
}
.pine-part > summary {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 14px 0;
  cursor: pointer;
  list-style: none;
  /* The marker a browser draws for a <summary> is the one thing on
     this page that would not be the site's own drawing. */
}
.pine-part > summary::-webkit-details-marker { display: none; }
.pine-part > summary::marker { content: ""; }
.pine-no {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  flex: none;
  width: 28px;
}
/* The small picture. Hatched while there is none, like every other
   place on this site where a picture is still to come. */
.pine-thumb {
  flex: none;
  width: 52px;
  height: 52px;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(
    -45deg, var(--line) 0px, var(--line) 1px, transparent 1px, transparent 9px
  );
  overflow: hidden;
}
.pine-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pine-title {
  flex: 0 1 auto;
  font-size: 17px;
  color: var(--ink);
}
/* The run of dots between a part's title and its cue: the same thing
   the contact sheet does between two pictures, in a line of type. It
   is a pseudo-element rather than another span in the markup because
   there are fifty-two of these, and it is ordered between the two
   rather than after them because that is where a leader goes. */
.pine-part > summary::after {
  content: "";
  order: 1;
  flex: 1 1 auto;
  height: 3px;
  margin: 0 4px;
  background-image: radial-gradient(circle at 1.5px 50%, var(--line) 1.1px, transparent 1.3px);
  background-size: 7px 3px;
  background-repeat: repeat-x;
}
.pine-cue { order: 2; }
.pine-cue {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  flex: none;
  padding: 3px 8px;
  border: 1px solid var(--line);
}
.pine-part > summary:hover .pine-cue,
.pine-part > summary:focus-visible .pine-cue { color: var(--ink); border-color: var(--ink); }
.pine-part[open] > summary .pine-no,
.pine-part[open] > summary .pine-title { color: var(--ink); }

.pine-body {
  display: flex;
  gap: 32px;
  align-items: flex-start;
  padding: 6px 0 30px 48px;
}
.pine-plate {
  flex: 1 1 46%;
  margin: 0;
}
.pine-plate::before {
  content: "";
  display: block;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(
    -45deg, var(--line) 0px, var(--line) 1px, transparent 1px, transparent 14px
  );
}
.pine-plate:has(img)::before { display: none; }
.pine-plate img { width: 100%; display: block; }
.pine-plate figcaption {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  margin-top: 10px;
}
.pine-body .pine-text { flex: 1 1 54%; }

/* THE TRUNK: one tick per part down the side of the page, inked in as
   it is passed, with the reading in the corner counting them. Both are
   the script's; neither carries anything to read. */
.pine-trunk {
  position: fixed;
  left: clamp(14px, 2.4vw, 30px);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 9px;
  z-index: 3;
  pointer-events: none;
}
/* THE SCALE. A hairline the length of the piece with one tick per part
   beside it, and the passed part of the rule inked in behind them in the
   house's green — so how far down the piece you have come is read as a
   bar filling rather than as ticks counted.

   The fir off the bottle stood here for one round, as the whole progress
   bar; the owner asked for it scrapped. There is no .pine-tree here any
   more. */
.pine-trunk-line {
  position: relative;
  width: 2px;
  height: min(52vh, 440px);
  background: var(--line);
  display: block;
  overflow: hidden;
}
/* Grown from the top down, which is the way the piece is read. A scale
   transform rather than a height, so filling it costs nothing. */
.pine-trunk-fill {
  position: absolute;
  inset: 0;
  background: var(--pine-green);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.55s var(--menu-ease);
}
.pine-ticks {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: min(52vh, 440px);
  margin-left: -4px;
}
.pine-tick {
  width: 4px;
  height: 1px;
  background: var(--line);
  transition: width 0.5s var(--menu-ease), background 0.5s var(--menu-ease);
}
.pine-tick.passed { width: 10px; background: var(--pine-green); }
.pine-readout {
  position: fixed;
  right: clamp(14px, 2.4vw, 34px);
  bottom: clamp(14px, 2.4vw, 30px);
  margin: 0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--muted);
  z-index: 3;
  pointer-events: none;
  text-align: right;
}
/* WHERE THE GREEN IS SPENT, and it is deliberately not spent anywhere
   else on this page: the count of what has been read, the number of the
   stratum you are in, and the cue on the part under the pointer. Three
   marks that all say "where you are" — the same thing the scale says. */
.pine-readout-no { color: var(--pine-green); font-weight: 500; }
.pine-stratum-no { color: var(--pine-green); }
.pine-part[open] > summary .pine-no { color: var(--pine-green); }
.pine-part > summary:hover .pine-cue,
.pine-part[open] > summary .pine-cue {
  color: var(--pine-green);
  border-color: var(--pine-green);
}
.pine-readout-where { display: block; margin-top: 4px; text-transform: uppercase; }

/* Coming up as they are reached. Only ever a part of the page that has
   not been read yet: `arrived` is put on and never taken off, so
   nothing is ever animated twice or animated on the way back up. */
.pine-ready .pine-part,
.pine-ready .pine-rises {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 620ms var(--menu-ease), transform 620ms var(--menu-ease);
}
.pine-ready .pine-part.arrived,
.pine-ready .pine-rises.arrived {
  opacity: 1;
  transform: none;
}

/* The foot of a house: where you came from on the left, where you can
   go next on the right. Both are the same small mono as the rest of
   the chrome; the difference between them is only which way they
   point. */
/* ==================================================================
   THE PINEWARD GALLERY — works/pineward.html only

   The pictures the owner took for that page, at the foot of it: small
   squares in a strip that cycles on its own, and a viewer that opens
   any of them over a darkened page. pineward-gallery.js.

   It stands above the wood (z-index 1) like everything else that is
   read on this page — the canvas behind is decoration and must never
   come over the reading.
================================================================== */
.pine-gallery {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 56px 64px 10px;
}
.pine-gallery-head {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--pine-green);
  margin: 0 0 22px;
}
/* THE STRIP. The arrows stand outside the window the pictures travel
   in, so a picture is never half under one. */
.pine-strip {
  display: flex;
  align-items: center;
  gap: 14px;
}
.pine-reel {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}
.pine-run {
  display: flex;
  gap: 14px;
  will-change: transform;
}
/* SMALL, AND SQUARE. The picture files are cut square already, so
   nothing here has to crop: the box is square and the picture fills
   it. `flex: 0 0 auto` is what keeps them their own size rather than
   being shared out across the window. */
.pine-shot {
  flex: 0 0 auto;
  display: block;
  width: 150px;
  height: 150px;
  border: 1px solid var(--line);
  background: var(--bg-2);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.3s var(--menu-ease), transform 0.3s var(--menu-ease);
}
.pine-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 0.3s var(--menu-ease);
}
.pine-shot:hover, .pine-shot:focus-visible {
  border-color: var(--pine-green);
  transform: translateY(-2px);
}
.pine-shot:hover img { opacity: 0.86; }

/* THE ARROWS, on the page and in the viewer both — the owner asked for
   the strip to carry the same pair the viewer does. Drawn as this
   site draws everything: a hairline square with a mark in it. */
.pine-arrow, .pine-viewer-arrow, .pine-viewer-shut {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.25s var(--menu-ease), border-color 0.25s var(--menu-ease),
              opacity 0.25s var(--menu-ease);
}
.pine-arrow:hover, .pine-arrow:focus-visible {
  color: var(--pine-green);
  border-color: var(--pine-green);
}
/* An arrow at the end of the strip is drawn back rather than taken
   away: the strip still rounds the ends, so it is not off — it is the
   reading that says which way there is more. */
.pine-arrow:disabled { opacity: 0.35; cursor: default; }

.pine-gallery-say {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 18px 0 0;
}

/* ==================================================================
   THE VIEWER — the whole page darkened, one picture standing in the
   middle of it, its number in the top right corner of that picture,
   and an arrow either side.
================================================================== */
html.pine-viewing, html.pine-viewing body { overflow: hidden; }
.pine-viewer {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 3vw, 34px);
  padding: clamp(16px, 5vh, 54px) clamp(12px, 4vw, 48px);
  background: rgba(12, 12, 8, 0.93);
  opacity: 0;
  transition: opacity 0.26s var(--menu-ease);
}
.pine-viewer.here { opacity: 1; }
.pine-viewer[hidden] { display: none; }

/* THE PLATE IS A FIXED WINDOW, and the pictures are fitted inside it.
   It used to be sized to whatever picture was in it, which meant every
   change of picture was also a change of the box's shape — the arrows
   either side jumped, and the whole thing resized under the movement.
   That was half of what the owner called choppy. The window is the same
   size whatever is standing in it now, and a portrait picture simply
   stands in the middle of it. */
.pine-viewer-plate {
  position: relative;
  margin: 0;
  flex: 0 1 auto;
  width: min(1100px, 100%);
  height: calc(100vh - clamp(32px, 10vh, 108px));
  overflow: hidden;
}
/* TWO LAYERS, ONE ON TOP OF THE OTHER. One goes out as the next comes
   in, both in the same movement — see `show()` in pineward-gallery.js.
   A single layer had to empty itself between the two, and the gap is
   what you could see. */
.pine-viewer-img {
  position: absolute;
  inset: 0;
  margin: auto;
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  background: #14140e;
  opacity: 0;
  will-change: transform, opacity;
}
.pine-viewer-img:not([src]) { display: none; }
/* Where each layer stands: on show, waiting to come in from one side,
   or on its way out to the other. The eased curve is the same one the
   strip travels on, so the two movements on this page are one
   movement. */
.pine-viewer-img.is-on {
  opacity: 1;
  transform: none;
  transition: transform 0.42s cubic-bezier(0.22, 0.61, 0.24, 1),
              opacity 0.26s var(--menu-ease);
}
.pine-viewer-img.from-on { transform: translateX(46px); opacity: 0; transition: none; }
.pine-viewer-img.from-back { transform: translateX(-46px); opacity: 0; transition: none; }
.pine-viewer-img.to-back {
  transform: translateX(-46px);
  opacity: 0;
  transition: transform 0.42s cubic-bezier(0.22, 0.61, 0.24, 1),
              opacity 0.3s var(--menu-ease);
}
.pine-viewer-img.to-on {
  transform: translateX(46px);
  opacity: 0;
  transition: transform 0.42s cubic-bezier(0.22, 0.61, 0.24, 1),
              opacity 0.3s var(--menu-ease);
}
/* THE NUMBER, in the top right corner OF THE PICTURE — not of the
   window. It says where this one stands in the strip and nothing
   else. */
.pine-viewer-no {
  position: absolute;
  z-index: 2;
  top: 10px;
  right: 10px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  color: #e8e8e0;
  background: rgba(12, 12, 8, 0.72);
  padding: 4px 8px;
}
.pine-viewer-arrow, .pine-viewer-shut {
  border-color: rgba(232, 232, 224, 0.3);
  background: rgba(12, 12, 8, 0.6);
  color: #e8e8e0;
}
.pine-viewer-arrow:hover, .pine-viewer-arrow:focus-visible,
.pine-viewer-shut:hover, .pine-viewer-shut:focus-visible {
  border-color: #e8e8e0;
  background: rgba(232, 232, 224, 0.12);
}
.pine-viewer-shut {
  position: absolute;
  top: clamp(12px, 3vh, 26px);
  right: clamp(12px, 3vw, 26px);
}

@media (max-width: 720px) {
  .pine-gallery { padding: 44px 22px 10px; }
  .pine-shot { width: 108px; height: 108px; }
  .pine-viewer { gap: 8px; padding: 14px 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .pine-run { transition: none !important; }
  .pine-shot, .pine-shot img, .pine-viewer { transition: none; }
  .pine-shot:hover, .pine-shot:focus-visible { transform: none; }
  .pine-viewer-img { transition: none !important; transform: none !important; }
}

.pine-foot {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 40px 64px 90px;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.pine-on {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-decoration: none;
  text-align: right;
}
.pine-on:hover { color: var(--ink); }
.pine-back {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-decoration: none;
}
.pine-back:hover { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .pine-ready .pine-part,
  .pine-ready .pine-rises { opacity: 1; transform: none; transition: none; }
  .pine-tick { transition: none; }
}

@media (max-width: 860px) {
  .pine-head,
  .pine-intro,
  .pine-stratum-head,
  .pine-parts,
  .pine-foot { padding-left: 24px; padding-right: 24px; }
  .pine-body { flex-direction: column; padding-left: 0; gap: 20px; }
  /* The trunk is a scale for a page being read across a room; on a
     phone the page is the whole of the room. */
  .pine-trunk { display: none; }
}

/* ==================================================================
   THE ESSAY PAGES — works/theory-*.html, works/resins-in-perfumery.html
   A page for a long piece of writing, in the theories drawing's
   language rather than the contact sheet's: near-white on gray-black,
   a swarm of particles standing in the air behind the writing, corner
   sights, and a RULE down the left — one tick per section, filled in
   as far as you have read, with the section you are in named under it.

   essay.js draws the field and builds the rule. Without it the page is
   all of its writing on the same ground; nothing here hides anything.
   ================================================================== */
.essay-page {
  background: #0a0b0e;
  color: #e8ecf3;
}
.essay-page .menu-trigger { color: #dce6f7; }
.essay-field {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.essay-head {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(150px, 26vh, 260px) 64px clamp(30px, 5vh, 60px);
}
.essay-kicker {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8d94a3;
  margin: 0 0 18px;
}
.essay-head h1 {
  font-size: clamp(32px, 5.6vw, 66px);
  line-height: 1.04;
  letter-spacing: -0.02em;
  margin: 0;
  color: #f2f5fa;
}
.essay-head h1 em {
  display: block;
  margin-top: 0.2em;
  font-size: 0.42em;
  font-style: italic;
  letter-spacing: -0.01em;
  color: #9aa2b2;
}
.essay-standfirst {
  margin: 26px 0 0;
  max-width: 54ch;
  color: #aab2c1;
  font-size: 17px;
}
/* The readings at the head of the piece — when it was written, how
   long it runs, what it is about. Ruled like an instrument's plate. */
.essay-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0 42px;
  margin: 34px 0 0;
  padding-top: 14px;
  border-top: 1px solid rgba(150, 160, 176, 0.35);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.essay-facts dt { color: #767e8d; margin: 0 0 4px; }
.essay-facts dd { color: #cdd4df; margin: 0; }

.essay-body {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 0 64px clamp(60px, 10vh, 130px);
}
/* The picture. It is a plate on a dark ground, so it is ruled rather
   than floated: nothing here has a shadow. */
.essay-plate {
  margin: 0 0 clamp(40px, 7vh, 80px);
  border: 1px solid rgba(150, 160, 176, 0.4);
  background: #101218;
  background-image: repeating-linear-gradient(-45deg, rgba(150,160,176,0.18) 0px, rgba(150,160,176,0.18) 1px, transparent 1px, transparent 16px);
  padding: 0;
}
.essay-plate img { display: block; width: 100%; height: auto; }
.essay-plate:not(:has(img)) { aspect-ratio: 16 / 9; position: relative; }
.essay-plate figcaption {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: #767e8d;
  padding: 10px 14px;
  border-top: 1px solid rgba(150, 160, 176, 0.25);
  background: #0a0b0e;
}
.essay-plate:not(:has(img)) figcaption { position: absolute; left: 0; right: 0; bottom: 0; }

.essay-section { max-width: 66ch; padding-top: clamp(30px, 5vh, 56px); }
.essay-section h2 {
  font-size: clamp(20px, 2.6vw, 30px);
  letter-spacing: -0.01em;
  margin: 0 0 18px;
  color: #f2f5fa;
}
/* The number beside a heading — the piece's own section marks, which
   are what the rule down the side is ticking off. */
.essay-section h2 .essay-no {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  color: #8eb4e2;
  margin-bottom: 10px;
}
.essay-section p { margin: 0 0 18px; color: #aab2c1; }
.essay-section p.essay-lead { color: #dbe1ea; font-size: 18px; }
.essay-section ul { margin: 0 0 18px; padding-left: 20px; color: #aab2c1; }
.essay-section li { margin: 0 0 8px; }
/* A short run of readings inside the writing — notes, a pyramid, a
   list of what is in something. Mono, ruled, and quiet. */
/* A SECTION THAT IS NOT WRITTEN YET — the dashed box the house pages
   use, in the essay pages' own colours. An unfinished piece should
   read as unfinished rather than as thin. */
.essay-waiting {
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.06em;
  line-height: 1.85;
  color: #767e8d;
  border: 1px dashed #2f3542;
  padding: 14px 16px;
}
.essay-note {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.8;
  color: #cdd4df;
  border-left: 1px solid #8eb4e2;
  padding: 4px 0 4px 16px;
  margin: 0 0 20px;
}
.essay-section a { color: #cfe0f5; text-decoration: none; border-bottom: 1px solid rgba(142, 180, 226, 0.6); }
.essay-section a:hover { color: #fff; border-bottom-color: #8eb4e2; }
/* A word the owner marked to be underlined — Dupes, Designers and Niches'
   "projects too well". A hairline under it, clear of the letters. */
.essay-section u { text-decoration-thickness: 1px; text-underline-offset: 4px; }

/* THE RULE. It stands clear of the writing on the left, and is taken
   off the page below 1080px, where there is no margin to stand in. */
.essay-rule {
  position: fixed;
  left: 30px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 180px;
}
.essay-rule-line {
  position: relative;
  width: 1px;
  height: 34vh;
  background: rgba(150, 160, 176, 0.35);
  margin-left: 3px;
}
.essay-rule-run {
  position: absolute;
  left: -1px;
  top: 0;
  width: 3px;
  background: #8eb4e2;
  height: 0;
}
.essay-marks { list-style: none; margin: 0; padding: 0; }
.essay-mark {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  text-decoration: none;
  color: #767e8d;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.essay-mark-tick {
  width: 9px;
  height: 1px;
  background: rgba(150, 160, 176, 0.6);
  flex: none;
  transition: width 0.25s var(--menu-ease, ease), background-color 0.25s ease;
}
.essay-mark-name {
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 0.25s ease, transform 0.25s ease;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 150px;
}
.essay-rule:hover .essay-mark-name,
.essay-mark:focus-visible .essay-mark-name,
.essay-mark.here .essay-mark-name { opacity: 1; transform: none; }
.essay-mark.here { color: #dbe1ea; }
.essay-mark.here .essay-mark-tick { width: 16px; background: #8eb4e2; }
.essay-mark:hover { color: #e8ecf3; }
.essay-here {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #8d94a3;
  margin: 6px 0 0;
  max-width: 150px;
}
.essay-read {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: #e8ecf3;
  margin: 0;
}

.essay-foot {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 0 64px 80px;
  border-top: 1px solid rgba(150, 160, 176, 0.25);
  padding-top: 26px;
}
.essay-back {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: #8d94a3;
  text-decoration: none;
}
.essay-back:hover { color: #e8ecf3; }

@media (max-width: 1080px) {
  .essay-rule { display: none; }
}
@media (max-width: 860px) {
  .essay-head,
  .essay-body,
  .essay-foot { padding-left: 24px; padding-right: 24px; }
}

/* ==================================================================
   ADAR — works/adar.html
   The house nobody has heard of, and the second piece in Scent
   descriptions. It is laid out like Pineward — an introduction and
   then the fragrances, each compacted to a number, a small picture and
   a title until it is opened — but its ground is a VOID rather than a
   wood: near-white on near-black, a hole standing off to one side of
   the window with soundings ringing out from it, and specks falling
   round its rim. Nothing on it is ever tinted.

   The fragrances are grouped into the house's own trilogies. Down the
   side runs the SOUNDING: one tick per fragrance, inked in as it is
   passed, with the reading in the corner counting them and naming the
   group. adar.js draws the void, adds the sounding and brings the
   parts up; without it the page is all of its writing.
   ================================================================== */
.adar-page {
  background: #07070a;
  color: #e8e8ee;
}
.adar-page .menu-trigger { color: #e8e8ee; }
.adar-void {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.adar-head {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(150px, 26vh, 260px) 64px clamp(40px, 7vh, 80px);
}
.adar-kicker {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8a8a96;
  margin: 0 0 18px;
}
.adar-head h1 {
  font-size: clamp(34px, 6.4vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0;
  color: #f4f4f8;
}
.adar-head h1 em {
  display: block;
  margin-top: 0.18em;
  font-size: 0.4em;
  font-style: italic;
  letter-spacing: -0.01em;
  color: #9a9aa6;
}
.adar-intro,
.adar-group-head {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(40px, 7vh, 86px) 64px clamp(20px, 3vh, 34px);
}
.adar-section-mark,
.adar-group-no {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  color: #8a8a96;
  margin: 0 0 10px;
}
.adar-intro h2,
.adar-group-head h2 {
  font-size: clamp(22px, 3vw, 34px);
  letter-spacing: -0.01em;
  margin: 0;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(232, 232, 238, 0.18);
  color: #f4f4f8;
}
.adar-group-line {
  margin: 14px 0 0;
  color: #9a9aa6;
  font-style: italic;
  max-width: 46ch;
}
.adar-group-count {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: #8a8a96;
  margin: 8px 0 0;
}
.adar-text { max-width: 62ch; }
.adar-text p { margin: 0 0 16px; color: #a8a8b4; }
.adar-intro .adar-text { margin-top: 22px; }
/* The ADAR Effect note at the end of the introduction: the owner's
   own coinage, set apart from the placeholder writing above it. */
.adar-text p.adar-effect {
  color: #dcdce4;
  border-left: 1px solid rgba(232, 232, 238, 0.35);
  padding-left: 18px;
}

.adar-parts {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 0 64px clamp(30px, 6vh, 70px);
}
.adar-part { border-bottom: 1px solid rgba(232, 232, 238, 0.14); }
.adar-part > summary {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 14px 0;
  cursor: pointer;
  list-style: none;
}
.adar-part > summary::-webkit-details-marker { display: none; }
.adar-no {
  font-family: var(--mono);
  font-size: 12px;
  color: #8a8a96;
  flex: none;
  width: 26px;
}
.adar-thumb {
  flex: none;
  width: 56px;
  height: 56px;
  border: 1px solid rgba(232, 232, 238, 0.18);
  background-color: #101014;
  background-image: repeating-linear-gradient(-45deg, rgba(232,232,238,0.1) 0px, rgba(232,232,238,0.1) 1px, transparent 1px, transparent 12px);
}
/* THE SMALL SQUARE IS ALWAYS A SQUARE, whatever shape the photograph
   is: it is cropped to fill one (`cover`) rather than squashed into
   one, and `--focus` on the element says which part of the picture to
   keep — the subject, which in a tall photograph of a bottle is
   usually above the middle. Set it per picture in the markup. */
.adar-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
  display: block;
}
.adar-title { font-size: 18px; color: #f4f4f8; }
/* What a fragrance is also called — its Greek, or the way the owner
   writes it when the title is a symbol. */
.adar-sub { font-family: var(--mono); font-size: 12px; color: #8a8a96; margin-left: 8px; }
/* The run of dots between a title and its cue — the index line of a
   book, and what keeps a long column of names readable. */
.adar-part > summary::after {
  content: "";
  flex: 1;
  height: 1px;
  background-image: radial-gradient(circle, rgba(232,232,238,0.32) 0.5px, transparent 0.6px);
  background-size: 6px 1px;
  background-repeat: repeat-x;
  order: 3;
}
.adar-cue {
  order: 4;
  flex: none;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #8a8a96;
  border: 1px solid rgba(232, 232, 238, 0.2);
  padding: 3px 9px;
}
.adar-part[open] > summary .adar-cue,
.adar-part > summary:hover .adar-cue { color: #f4f4f8; border-color: rgba(232, 232, 238, 0.5); }

.adar-body {
  display: flex;
  gap: 36px;
  align-items: flex-start;
  padding: 6px 0 34px 46px;
}
.adar-plates { flex: none; width: 320px; display: flex; flex-direction: column; gap: 22px; }
.adar-plate {
  flex: none;
  width: 320px;
  margin: 0;
}
.adar-plates .adar-plate { width: 100%; }
/* OPEN, A PICTURE KEEPS ITS OWN SHAPE. Only the small square in the
   summary is cropped; here a tall photograph is printed tall. The
   placeholder under it is square, because a hatched box has no shape
   of its own to keep. */
.adar-plate > div {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  border: 1px solid rgba(232, 232, 238, 0.18);
  background-color: #101014;
  background-image: repeating-linear-gradient(-45deg, rgba(232,232,238,0.1) 0px, rgba(232,232,238,0.1) 1px, transparent 1px, transparent 16px);
}
.adar-plate img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid rgba(232, 232, 238, 0.18);
  background-color: #101014;
}
/* A picture and its placeholder are never both on the page. adar.js
   takes an <img> off the page if the file is not there, which puts the
   placeholder back — so a house whose photographs have not been added
   yet looks unfinished rather than broken. */
.adar-plate:has(img) > div { display: none; }
.adar-plate-more figcaption { display: none; }
.adar-plate figcaption {
  font-family: var(--mono);
  font-size: 11px;
  color: #8a8a96;
  margin-top: 10px;
}
/* A STAGE of a fragrance — top, mid, base, a sidenote. The owner
   writes in those, so the page sets them as what they are: a small
   mono label with a rule beside it, not a heading. */
.adar-stage {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #8a8a96;
  margin: 0 0 12px;
  font-weight: 400;
}
.adar-stage::after {
  content: "";
  flex: 1;
  height: 1px;
  background: rgba(232, 232, 238, 0.14);
}
.adar-stage + p { margin-top: 0; }
.adar-part .adar-text p + .adar-stage { margin-top: 26px; }

/* THE SOUNDING down the side, and the reading in the corner. */
.adar-sounding {
  position: fixed;
  left: 28px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: 2;
  pointer-events: none;
}
/* The scale, and the part of it you have read. The line is the whole
   of the page; the fill grows down it from the very top, so it starts
   moving on the first pixel of scroll rather than waiting for the first
   fragrance. Pineward's trunk does the same thing in green. */
.adar-sounding-line {
  position: relative;
  width: 1px;
  height: 34vh;
  background: rgba(232, 232, 238, 0.16);
}
.adar-sounding-fill {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 100%;
  background: rgba(232, 232, 238, 0.5);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.25s var(--menu-ease);
}
@media (prefers-reduced-motion: reduce) {
  .adar-sounding-fill { transition: none; }
}
.adar-ticks { display: flex; flex-direction: column; gap: 6px; }
.adar-tick {
  width: 8px;
  height: 1px;
  background: rgba(232, 232, 238, 0.25);
  transition: width 0.3s var(--menu-ease), background-color 0.3s ease;
}
.adar-tick.passed { width: 16px; background: #e8e8ee; }
.adar-readout {
  position: fixed;
  right: 28px;
  bottom: 24px;
  margin: 0;
  text-align: right;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: #8a8a96;
  z-index: 2;
  pointer-events: none;
}
.adar-readout-no { color: #f4f4f8; }
.adar-readout-where {
  display: block;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The parts come up as they are reached. Nothing is hidden until the
   script says it is watching, so a blocked script leaves every one of
   them on the page. */
.adar-ready .adar-part,
.adar-ready .adar-rises {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.62s var(--menu-ease), transform 0.62s var(--menu-ease);
}
.adar-ready .adar-part.arrived,
.adar-ready .adar-rises.arrived { opacity: 1; transform: none; }

.adar-foot {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 40px 64px 80px;
  border-top: 1px solid rgba(232, 232, 238, 0.14);
  display: flex;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.adar-on {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: #8a8a96;
  text-decoration: none;
  text-align: right;
}
.adar-on:hover { color: #e8e8ee; }
.adar-back {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: #8a8a96;
  text-decoration: none;
}
.adar-back:hover { color: #e8e8ee; }

@media (prefers-reduced-motion: reduce) {
  .adar-ready .adar-part,
  .adar-ready .adar-rises { opacity: 1; transform: none; transition: none; }
  .adar-tick { transition: none; }
}

@media (max-width: 1080px) {
  .adar-sounding { display: none; }
}
@media (max-width: 860px) {
  .adar-head,
  .adar-intro,
  .adar-group-head,
  .adar-parts,
  .adar-foot { padding-left: 24px; padding-right: 24px; }
  .adar-body { flex-direction: column; padding-left: 0; gap: 20px; }
  .adar-plate { width: 100%; }
}

/* A class name or a snippet written inside an essay's own text. */
.essay-section code {
  font-family: var(--mono);
  font-size: 0.92em;
  color: #cdd4df;
}

/* ==================================================================
   THE INDEX PAGES — categories/researches.html, and the Individual
   fragrances view of the contact sheet page.

   A directory rather than a drawing: a few readings across the top, a
   plate or two on the right, and a long TABLE standing in the bottom
   left corner of the window. The table scrolls inside its own box
   with its headings stuck to the top of it, and every heading is a
   button that sorts by that column — index-page.js does the sorting
   and the searching; everything about the way it looks is here.

   Both places use the same block, so a change here is a change to
   both. That is deliberate: they are the same kind of page.
   ================================================================== */
.index-page {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto 1fr auto;
  align-content: start;
  gap: 0 26px;
  padding: 74px 28px 22px;
  font-size: 13px;
}
.index-head {
  grid-column: 1 / span 2;
  grid-row: 1;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 26px;
}
.index-name { margin: 0; color: var(--muted); }
.index-col { margin: 0; color: var(--muted); }
/* What the page is, written where the template's own paragraph
   stands: a long way down the second column, so the top of the page
   is mostly air. */
.index-say {
  grid-column: 2;
  margin-top: clamp(40px, 11vh, 120px);
  max-width: 42ch;
}
.index-say p { margin: 0 0 4px; }
.index-say .index-say-lede { color: var(--ink); margin-bottom: 22px; }
.index-say .index-say-head { color: var(--ink); }
.index-say .index-say-meta { color: var(--muted); }

/* The right-hand half: the one line that says what is going on, and
   then the plates under it. They are wrapped together because they
   share the two right-hand columns of the grid — side by side in the
   same cell they would be drawn on top of each other. */
.index-right {
  grid-column: 3 / span 2;
  grid-row: 1;
}
/* The plates are capped rather than left to fill the two columns they
   stand in: the whole page is meant to come out on one screen, table
   and footer and all, and on a wide window a plate that is half the
   page wide is what pushes the table off the bottom of it. */
.index-plates {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 250px));
  gap: 26px;
  align-items: start;
}
.index-line {
  margin: 0 0 22px;
  color: var(--ink);
}
.index-plate { margin: 0; }
.index-plate > div,
.index-plate img,
.index-plate canvas {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(-45deg, var(--line) 0px, var(--line) 1px, transparent 1px, transparent 14px);
  object-fit: cover;
}
/* The one plate that is drawn rather than photographed keeps the
   page's own ground, not the placeholder's hatching. */
.index-plate canvas { background-image: none; background-color: var(--bg); }
.index-plate figcaption {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  margin-top: 8px;
}

/* THE TABLE, standing in the bottom left corner. */
.index-board {
  grid-column: 1 / span 2;
  grid-row: 2;
  align-self: end;
  margin-top: clamp(24px, 4vh, 44px);
  min-width: 0;
}
.index-search {
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 7px;
  margin-bottom: 10px;
}
.index-search-mark {
  width: 9px;
  height: 9px;
  border: 1px solid var(--muted);
  flex: none;
}
.index-search-field {
  flex: 1;
  border: 0;
  background: none;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
  padding: 0;
}
.index-search-field::placeholder { color: var(--muted); }
.index-search-field:focus { outline: none; }
.index-count { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* The box the table scrolls inside. Its height is set here rather
   than left to the content, because the whole point of the thing is
   that the rest of the page stays where it is however many rows there
   are — the owner has a great many more to add. */
.index-scroll {
  max-height: min(38vh, 360px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}
.index-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.index-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  padding: 0 0 5px;
  background: var(--bg);
  border-bottom: 1px solid var(--ink);
  font-weight: 400;
}
.index-sort {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--ink);
}
.index-sort::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 3px solid transparent;
  border-right: 3px solid transparent;
  border-top: 4px solid var(--muted);
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.index-sort:hover::after { opacity: 0.45; }
.index-sort.sorted::after { opacity: 1; border-top-color: var(--ink); }
.index-sort.sorted.up::after { transform: rotate(180deg); }
.index-table td {
  padding: 3px 12px 3px 0;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 0;
}
.index-table tr:hover td { color: var(--ink); }
.index-table .index-no,
.index-table .index-date { font-family: var(--mono); font-size: 12px; }
.index-table .index-no { width: 64px; max-width: none; }
.index-table .index-date { width: 92px; max-width: none; }
/* WHAT KIND OF WORK IT IS — Research or Exploration — on the Works
   page. Set in the mono the rest of the readings on this page use, and
   in small capitals, so it reads as a classification rather than as a
   second title. */
.index-table .index-kind {
  width: 150px;
  max-width: none;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.index-table .index-of { width: 30%; }
/* THE LINK IS CLAMPED TOO, and not only the cell it is in. A cell is
   `max-width: 0` with an ellipsis so a long name is cut rather than
   allowed to widen the table; the anchor inside it is a block of its
   own, and a block inside a clipped cell simply overflows it — so on a
   narrow window the rows that HAD a page behind them printed their name
   straight through the column beside it, and the rows that did not
   ellipsised properly. The owner sent a photograph of exactly that:
   "Resins" written over "RESEARCH". */
.index-table td a {
  color: inherit;
  text-decoration: none;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.index-table tr:hover .index-what { color: var(--ink); }
/* A row with nothing to open is not a link, and says so by being
   quieter than one that is. */
.index-table tr[data-open="no"] td { color: var(--line); }
.index-table tr[data-open="no"]:hover td { color: var(--muted); }

/* ON A PHONE THE READINGS GIVE UP THEIR ROOM TO THE NAME. The three
   columns either side of it are set to a width each, and on a 390px
   screen those three take three quarters of the table between them —
   which leaves a fragrance or a research with about eighty pixels to be
   named in, and every one of them cut to two letters. They are set in
   the mono and they are short, so they can be smaller and tighter here
   without becoming unreadable; the name gets what they give up. */
@media (max-width: 700px) {
  .index-table { font-size: 12px; }
  .index-table td { padding-right: 8px; }
  .index-table .index-no { width: 42px; font-size: 11px; }
  .index-table .index-date { width: 76px; font-size: 11px; }
  .index-table .index-kind { width: 94px; font-size: 10px; letter-spacing: 0.04em; }
  .index-table .index-of { width: 26%; }
  .index-sort { font-size: 10px; letter-spacing: 0.06em; }
}

@media (max-width: 900px) {
  .index-page {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto auto;
    padding: 74px 22px 22px;
  }
  .index-head,
  .index-right,
  .index-board { grid-column: 1; }
  .index-head { grid-row: 1; grid-template-columns: 1fr; }
  .index-say { grid-column: 1; margin-top: 26px; }
  .index-right { grid-row: 2; margin-top: 30px; }
  .index-board { grid-row: 3; margin-top: 40px; }
  .index-scroll { max-height: 56vh; }
}

/* ==================================================================
   EXPLORATIONS & RESEARCHES — categories/researches.html, laid out
   again for itself on 2026-09-26, at the owner's word: the paragraph
   "under the title RE, not under introduction" (the "Information"
   heading went), "Delete the right side of the page, and move the
   table upwards, so that it takes up abour 3/5ths of the page on the
   left", and on the right "something extravagant with the particles
   that reacts ot the thing being hovered on the left".

   So the page is TWO COLUMNS, three fifths and two: on the left the name,
   what the page is, and the table directly under them, filling the
   window to its foot and scrolling in its own box; on the right THE
   FIELD (`.re-field`, drawn by explorations.js), the full height of the
   window, bracketed at its corners and captioned at its foot.

   ALL OF IT UNDER `.researches-page`: `.index-page` is shared with the
   Fragrances view's old index on Scent descriptions, which keeps its own
   layout.
   ================================================================== */
.researches-page .index-page {
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  grid-template-rows: auto minmax(0, 1fr);
  gap: 0 clamp(28px, 4.5vw, 72px);
  height: 100vh;
  height: 100svh;
  min-height: 560px;
  padding: 74px 28px 28px;
}
.researches-page .index-head {
  grid-column: 1;
  grid-row: 1;
  display: block;
}
.researches-page .index-name {
  color: var(--ink);
  font-size: clamp(28px, 2.9vw, 42px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.02;
}
.researches-page .index-say {
  margin-top: 18px;
  max-width: 60ch;
}
.researches-page .index-say .index-say-lede { margin-bottom: 0; }
.researches-page .index-board {
  grid-column: 1;
  grid-row: 2;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* A little lower than it stood — the owner, 2026-09-26: "in RE, lower
     the left side table a little". It was 5vh (45px at 900 high). */
  margin-top: clamp(44px, 9.5vh, 100px);
}
.researches-page .index-scroll {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
}

/* THE FIELD. Its corners bracketed, as the site brackets a drawing. */
.re-field {
  --tick: rgba(var(--ink-rgb), 0.45);
  grid-column: 2;
  grid-row: 1 / span 2;
  position: relative;
  margin: 0;
  min-height: 0;
  background:
    linear-gradient(var(--tick), var(--tick)) 0 0 / 14px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 0 0 / 1px 14px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 0 / 14px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 0 / 1px 14px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 0 100% / 14px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 0 100% / 1px 14px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 100% / 14px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 100% / 1px 14px no-repeat;
}
.re-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
/* THE CAPTION: "display the name (in tiny script) of what is actually
   being shown" — small, in the mono, centred at the field's foot between
   THE ARROWS, "small and subtle near the bottom", which go on to the next
   form or back. The name keeps one width, so the arrows never move. */
.re-caption {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--muted);
  white-space: nowrap;
}
.re-caption-no { color: rgba(var(--ink-rgb), 0.45); }
.re-caption-name {
  width: 23ch;
  color: rgba(var(--ink-rgb), 0.82);
  overflow: hidden;
  text-overflow: ellipsis;
}
.re-caption-name.is-new { animation: re-name 560ms ease both; }
@keyframes re-name { from { opacity: 0; } to { opacity: 1; } }
.re-step {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  /* A LITTLE MORE OBVIOUS (2026-09-27): a hairline square round each,
     the chevron larger and firmer — they were a faint chevron alone. */
  border: 1px solid rgba(var(--ink-rgb), 0.3);
  background: none;
  color: rgba(var(--ink-rgb), 0.66);
  cursor: pointer;
  transition: color 200ms ease, border-color 200ms ease, background-color 200ms ease;
}
.re-step:hover, .re-step:focus-visible { color: var(--ink); border-color: var(--ink); background: rgba(var(--ink-rgb), 0.04); }
.re-step svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 1.5; }
.re-step[hidden] { display: none; }
.re-field:not(.is-drawn) .re-caption { visibility: hidden; }
/* How far through the form's hold, or its turning into the next, the
   field stands: a hairline across the field's foot over the caption,
   nearly the whole of its width, filled as it goes. It stood between the
   arrows for a round, and the owner asked for it "longer (as it was
   before)" (2026-09-27). */
.re-run {
  position: absolute;
  left: 18px;
  right: 18px;
  bottom: 46px;
  height: 1px;
  background: rgba(var(--ink-rgb), 0.12);
  pointer-events: none;
}
.re-field:not(.is-drawn) .re-run { visibility: hidden; }
.re-run i {
  display: block;
  height: 100%;
  background: rgba(var(--ink-rgb), 0.6);
  transform-origin: 0 50%;
  transform: scaleX(var(--run, 0));
}

/* BELOW 900px ONE COLUMN: the name and what the page is, the field as a
   band under them, and the table under that. */
@media (max-width: 900px) {
  .researches-page .index-page {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    height: auto;
    min-height: 100vh;
    padding: 74px 22px 22px;
  }
  .researches-page .index-head { grid-row: 1; }
  .re-field { grid-column: 1; grid-row: 2; height: clamp(260px, 44vh, 380px); margin-top: 26px; }
  .researches-page .index-board { grid-row: 3; margin-top: 30px; }
  .researches-page .index-scroll { flex: none; max-height: 56vh; }
}
@media (max-width: 700px) {
  .re-caption { bottom: 8px; }
  .re-run { bottom: 42px; }
}

/* ==================================================================
   THE TEST PAGE — works/test-page.html
   The owner's picture of a dense red network, on its own dark ground:
   the page's five tokens turned over (as the Note Library's are), a soft
   shadow under the network where the picture has one, THE NETWORK drawn by
   network.js across the whole window, and its TAGS — small black labels
   in the mono, two of them yellow — set beside their nodes by the script.
   ================================================================== */
.network-page {
  --bg: #1f1f20;
  --bg-2: #282829;
  --line: #3a3a3b;
  --ink: #ece8e2;
  --ink-rgb: 236, 232, 226;
  --muted: #8f8b86;
  background:
    radial-gradient(ellipse 46% 26% at 50% 78%, rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0) 70%),
    var(--bg);
  color: var(--ink);
  overflow: hidden;
}
.net-stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
}
.net-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}
.net-labels {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  pointer-events: none;
}
.net-label {
  position: absolute;
  left: 0;
  top: 0;
  padding: 3px 6px 3px;
  background: #0c0c0c;
  color: #e9e5df;
  font-family: var(--mono);
  font-size: 9px;
  line-height: 1.25;
  letter-spacing: 0.08em;
  white-space: nowrap;
  opacity: 0;
}
.net-label span {
  display: block;
  color: #8f8b86;
  font-size: 8px;
}
.net-label.is-marked {
  padding: 6px 9px;
  background: #f2b418;
  color: #141414;
  font-size: 11px;
  font-weight: 500;
}
.net-hint {
  position: absolute;
  left: 22px;
  bottom: 18px;
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--muted);
  white-space: nowrap;
  pointer-events: none;
  transition: opacity 600ms ease;
}
.net-stage.is-turned .net-hint,
.net-stage.is-bare .net-hint { opacity: 0; }
/* The hint is longer since the five systems (2026-09-27): kept clear of
   the map on the right, and on a phone stood under the Menu instead. */
.net-hint { max-width: calc(100% - 270px); white-space: normal; line-height: 1.6; }
@media (max-width: 700px) {
  .net-hint { top: 72px; bottom: auto; left: 16px; max-width: calc(100% - 32px); }
}
.net-fallback {
  position: absolute;
  left: 28px;
  bottom: 28px;
  margin: 0;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
}
.net-fallback[hidden] { display: none; }
/* 2026-09-27 — FIVE SYSTEMS, ONE SELECTED NODE, AND THE WAY BETWEEN THEM.
   What network.js adds to the page: THE CARD a selected node says itself
   in, beside it; the small label a node or a bridge under the pointer says
   its name in; and THE MAP at the foot, the five systems as a quincunx,
   saying which one you are at, its dots a way there too. In the mono, on
   black, ruled in the nodes' red. */
.net-card {
  position: absolute;
  left: 0;
  top: 0;
  min-width: 190px;
  padding: 12px 14px 10px;
  background: rgba(12, 12, 12, 0.92);
  border: 1px solid rgba(255, 90, 99, 0.55);
  border-left: 3px solid #ff3a44;
  color: #ece8e2;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  pointer-events: none;
}
.net-card[hidden] { display: none; }
.net-card-code { margin: 0; font-size: 12px; font-weight: 500; color: #fff; }
.net-card-role { margin: 3px 0 9px; color: #ff6a72; letter-spacing: 0.14em; }
.net-card dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 3px 14px; }
.net-card dl div { display: contents; }
.net-card dt { color: #8f8b86; text-transform: uppercase; }
.net-card dd { margin: 0; text-align: right; }
.net-hover,
.net-bridge {
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
  padding: 3px 7px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 160ms ease;
}
.net-hover { background: #0c0c0c; color: #e9e5df; border: 1px solid rgba(255, 90, 99, 0.4); }
.net-bridge { background: #ff3a44; color: #140608; font-weight: 500; }
.net-hover.is-on,
.net-bridge.is-on { opacity: 1; }
.net-map {
  position: absolute;
  right: 22px;
  bottom: 20px;
  width: 188px;
  padding: 10px 10px 12px;
  background: rgba(20, 20, 21, 0.86);
  border: 1px solid rgba(255, 90, 99, 0.2);
  font-family: var(--mono);
  color: var(--muted);
}
.net-map-say {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 8px;
  margin: 0 0 8px;
  font-size: 10px;
  letter-spacing: 0.08em;
}
.net-map-no { color: #ff6a72; }
.net-map-name { color: var(--ink); }
.net-map-count { flex-basis: 100%; font-size: 9px; }
.net-map-plan {
  position: relative;
  height: 96px;
  border: 1px solid rgba(255, 90, 99, 0.28);
}
.net-map-plan svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.net-map-plan line { stroke: rgba(255, 90, 99, 0.45); stroke-width: 0.8; vector-effect: non-scaling-stroke; }
.net-map-dot {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  padding: 0;
  border: 1px solid #ff5a63;
  background: #1f1f20;
  transform: rotate(45deg);
  cursor: pointer;
  transition: background-color 200ms ease, box-shadow 200ms ease;
}
.net-map-dot:hover,
.net-map-dot:focus-visible { background: rgba(255, 90, 99, 0.45); }
.net-map-dot.is-here { background: #ff3a44; box-shadow: 0 0 10px 2px rgba(255, 58, 68, 0.6); }
@media (max-width: 700px) {
  .net-map { right: 12px; bottom: 12px; width: 160px; }
  .net-map-plan { height: 78px; }
}

/* ==================================================================
   THE SEARCH PAGE — search.html
   The one place that looks over the whole site. Dark ground, one long
   field ruled across it, and the answers as ruled rows — number, name,
   what kind of thing it is, and the trail that says where it lives.

   The ground itself is NOT here: find-ground.js draws the squared plan
   and the two columns of black specks on a canvas, because both of them
   answer what is being typed and a stylesheet cannot. What is here is
   the tone they are drawn against and every word on top of them.

   The page is named beside the Menu (`.page-where`, the same as every
   other page), not on the page proper — there is no heading.
   ================================================================== */
.find-page {
  /* This page's own palette. Scoped to the page so that changing it
     changes nothing anywhere else on the site. */
  --find-ground: #191c21;
  --find-ink: #e6e8ee;
  --find-muted: #8f949e;
  --find-rule: rgba(226, 228, 234, 0.16);
  --find-rule-strong: rgba(226, 228, 234, 0.34);
  background: var(--find-ground);
  color: var(--find-ink);
  min-height: 100vh;
}
.find-ground {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
/* The chrome has to be light on this ground, the way it is on the other
   dark pages. */
.find-page .menu-trigger { color: #dce6f7; }
.find-page .page-where {
  color: var(--find-muted);
  border-left-color: var(--find-rule-strong);
}

.find {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  min-height: 100svh;
  max-width: 1080px;
  margin: 0 auto;
  padding: 88px 28px 56px;
  display: flex;
  flex-direction: column;
}
/* THE FIELD. One line ruled across the page, with a mark at its left
   and a count at its right — the same three-part reading the index
   pages use above their tables. */
.find-form {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: clamp(64px, 16vh, 150px);
  padding-bottom: 12px;
  border-bottom: 1px solid var(--find-rule-strong);
}

/* FILTERING BY KIND. A row of words under the field, each with the
   number of this search's answers it would give. They are words rather
   than boxes: the page is a field with one line ruled across it, and a
   row of buttons would be the heaviest thing on it. */
.find-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  margin-top: 16px;
}
.find-filter {
  border: 0;
  background: none;
  padding: 3px 0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--find-muted);
  cursor: pointer;
  position: relative;
  transition: color 0.3s var(--menu-ease), opacity 0.3s var(--menu-ease);
}
/* The rule under the one that is on is drawn from nothing rather than
   switched on, the same way everything else on this site arrives. */
.find-filter::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--find-ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s var(--menu-ease);
}
.find-filter:hover:not(:disabled) { color: var(--find-ink); }
.find-filter.is-on { color: var(--find-ink); }
.find-filter.is-on::after { transform: scaleX(1); }
.find-filter:disabled { opacity: 0.3; cursor: default; }
/* The tally. Only there once there is a search for it to count — before
   that the button is one word. */
.find-filter-no {
  margin-left: 6px;
  font-size: 10px;
  opacity: 0.65;
}
.find-filter-no:empty { display: none; }
.find-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.find-mark {
  width: 11px;
  height: 11px;
  border: 1px solid var(--find-ink);
  flex: none;
}
.find-field {
  flex: 1;
  border: 0;
  background: none;
  padding: 0;
  font-family: var(--sans);
  font-size: clamp(22px, 3.4vw, 38px);
  letter-spacing: -0.01em;
  color: var(--find-ink);
  min-width: 0;
}
.find-field::placeholder { color: rgba(226, 228, 234, 0.3); }
.find-field:focus { outline: none; }
.find-count {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--find-muted);
  flex: none;
}

.find-reading-note {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--find-muted);
  margin: 12px 0 0;
  opacity: 0;
  transition: opacity 0.3s ease;
}
body.find-reading .find-reading-note { opacity: 1; }

.find-out { flex: 1; margin-top: 26px; }
.find-saying {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--find-muted);
  margin: 0 0 10px;
}

/* A ROW. It arrives — the rows come in one after another rather than
   all at once, which is the same thing the map does on the contact
   sheet and the reason a search here reads as being answered rather
   than as a list appearing. */
.find-row {
  display: grid;
  /* The trail is the longest thing on a row — *Scent descriptions ·
     Houses · Pineward · Trunk* — so it gets the wider of the two
     flexible columns, or it is cut off mid-word. */
  grid-template-columns: 52px minmax(0, 1fr) 92px minmax(0, 1.5fr);
  gap: 0 18px;
  align-items: baseline;
  padding: 9px 0;
  border-bottom: 1px solid var(--find-rule);
  text-decoration: none;
  color: var(--find-ink);
  animation: find-in 0.45s var(--menu-ease) both;
  animation-delay: calc(var(--n, 0) * 26ms);
}
@keyframes find-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
.find-no { font-family: var(--mono); font-size: 12px; color: var(--find-muted); }
.find-what { font-size: 17px; color: var(--find-ink); }
.find-kind {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--find-muted);
}
.find-where {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--find-muted);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.find-row:hover { background: rgba(226, 228, 234, 0.06); }
.find-row:hover .find-what,
.find-row:hover .find-where { color: #fff; }

.find-nothing {
  margin: 28px 0 0;
  font-size: 19px;
  color: var(--find-ink);
}
.find-nothing span {
  display: block;
  margin-top: 8px;
  font-size: 14px;
  color: var(--find-muted);
  max-width: 48ch;
}

/* ==================================================================
   A CHAPTER'S OWN PAGE — categories/favorites.html

   Opening a chapter in the chamber implodes the particles into the
   middle of the window and throws them out again, and this is what is
   standing there when they have gone: the chapter named, what it is,
   and its favourites as cards.

   BLACK, WITH SILVER. The chamber itself is the one page on this site
   that spends no accent at all, and this keeps that promise — there is
   no brass anywhere below. What it has instead is silver: a cool grey
   that catches, used for the hairlines, the corner marks on the cards
   and the lettering that is doing the work. `--silver-dim` is the same
   colour with the shine taken off, for everything that is only there to
   be read past.

   It stands INSIDE the chamber, not loose in the page — see the note
   where chamber.js builds it.
   ================================================================== */
.chapter-page {
  --silver: #c8ccd4;
  --silver-dim: #7b8089;
  --silver-line: rgba(200, 204, 212, 0.26);
  position: fixed;
  inset: 0;
  z-index: 4;
  overflow-y: auto;
  /* The browser's own habit of holding what is on screen still while
     something above it changes height is turned off here: a favourite's
     drawer carries the page itself (`reveal` in chamber.js), worked out
     on where everything will be, and the browser nudging it as well sent
     the card pressed off the top. */
  overflow-anchor: none;
  /* THE SCROLLBAR'S ROOM IS KEPT WHETHER THERE IS ONE OR NOT. On a
     screen that shows scrollbars (most without a trackpad), Chapter 1 is
     taller than the window and Chapter 2 is not, and a drawer can make a
     short chapter tall: each time the bar came or went the whole sheet
     moved sideways by half its width. Measured, 7px between the two
     chapters. */
  scrollbar-gutter: stable;
  background: #000;
  color: var(--silver);
  /* NOTHING CUTS THIS PAGE OPEN ANY MORE. For several rounds the
     explosion was two things: a drawing travelling out, and a black
     ellipse cut open behind it from the same point, written from
     chamber.js as a polygon frame by frame because a CSS `ellipse()`
     cannot be turned and that one was.

     The owner asked for the black part of the explosion to go, and for
     the drawing's own panels to darken to this page's colour in its
     place. So they do, on the canvas above this page, and this page is
     simply laid underneath once they have the window covered — by
     which time both are the same black and there is nothing to see in
     the swap. There is no clip-path here, and no `clipTo` in
     chamber.js. */
  /* AND IT FADES ON THE WAY OUT. Pressing "← Favourites" used to take
     the black off the window on the frame it was pressed, and what was
     underneath was a white page with no chrome on it — the menu and the
     particles both faded out by the burst — which then faded back in
     afterwards. So leaving a chapter was a flash of an empty white
     page. The chamber is given back to itself first, under a black that
     is still solid, and only then does the black clear. 620ms, and
     `LEAVE_CLEAR` in chamber.js is the same number. */
  transition: opacity 620ms var(--menu-ease);
}
.chapter-page.clearing { opacity: 0; pointer-events: none; }
.chapter-page[hidden] { display: none; }

/* THE DRAWING BEHIND A CHAPTER'S PAGE, if that chapter asks for one —
   `data-ground` in the page's own markup. It stands outside the sheet
   so the writing can fade for a turn, or for the way out, without the
   drawing behind it flickering with it. */
.chapter-ground {
  /* FIXED, not absolute. This page scrolls inside itself, and an
     absolutely placed canvas in here scrolls with the writing — so the
     sun would be left behind at the top of a long chapter. Fixed means
     the window, because `.chapter-page` carries no transform. */
  position: fixed;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  /* It comes up with the rest of the page, once the drawing that
     brought you here has gone — never underneath it. */
  opacity: 0;
  transition: opacity 1.1s var(--menu-ease);
}
.chapter-page.here .chapter-ground { opacity: 1; }
/* `hidden` is an attribute and the browser's own rule for it is in the
   user-agent stylesheet, which ANY author rule outranks — including
   the `display: block` two lines above. */
.chapter-ground[hidden] { display: none; }
/* THE MORPH: one chapter's drawing flown into the next, on a canvas of
   its own laid over the ground, while the arrows turn the page. Only
   there for the length of the flight (chamber.js, `morph`). */
.chapter-morph {
  position: fixed;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}
.chapter-morph[hidden] { display: none; }
/* THE HANDOVER, in two states. `laid` is the page standing UNDER the
   mesh — it has to be there before the drawing goes, or the chamber's
   white shows through for a frame — with nothing written on it yet.
   `here` is the drawing gone, and everything on the sheet comes in on
   that.

   It used to arrive underneath. The page was laid four hundred-odd
   milliseconds before the drawing was taken off and its cards started
   their entrance there, so by the time you could see anything the
   heading was simply present and the cards were half way in: the end
   of the burst read as a cut to a page already part built. Now the
   drawing goes first and the page writes itself in after it. */
.chapter-page.laid .chapter-sheet > *,
/* AND THE NAME AND ITS ARROWS BY THEMSELVES. They stand inside
   `.chapter-title` now rather than being children of the sheet, and an
   ancestor at zero opacity does not make a descendant's OWN opacity
   zero — which is what `the chapter page's writing waits for the
   drawing to be taken off` reads, and it failed the moment the name
   gained a wrapper. Held back here as well, so nothing on the sheet has
   a strength of its own while the drawing is still over it. */
.chapter-page.laid .chapter-title > *,
.chapter-page.laid .chapter-card { opacity: 0; }
.chapter-page.here .chapter-sheet > *:not(.chapter-cards) {
  animation: chapter-in 0.55s var(--menu-ease) both;
  animation-delay: calc(var(--i, 0) * 55ms);
}
@keyframes chapter-in {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}

.chapter-sheet {
  position: relative;
  z-index: 1;
  max-width: 1040px;
  margin: 0 auto;
  padding: clamp(84px, 14vh, 150px) 28px 80px;
  /* THE WRITING GOES BEFORE THE PAGE IS REWRITTEN UNDER IT — stepping
     to another chapter with the arrows, and leaving the chapter
     altogether. Both are timed against this in chamber.js (`TURN_MS`
     and `LEAVE_WRITING`), with a little slack either side. */
  transition: opacity 300ms var(--menu-ease);
}
.chapter-page.turning .chapter-sheet,
.chapter-page.going .chapter-sheet { opacity: 0; }

.chapter-back {
  border: 0;
  background: none;
  padding: 0;
  margin: 0 0 clamp(36px, 8vh, 76px);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--silver-dim);
  cursor: pointer;
  transition: color 0.3s var(--menu-ease);
}
.chapter-back:hover { color: var(--silver); }

.chapter-kicker {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--silver-dim);
  margin: 0 0 14px;
}
.chapter-name {
  font-size: clamp(38px, 7vw, 82px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0;
  /* The one flourish: the lettering catches the light across itself,
     which is the whole of what "silver" means on a screen. */
  background: linear-gradient(100deg, #6f747d 0%, #eef1f6 42%, #9aa0aa 62%, #dfe3ea 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* THE ARROWS EITHER SIDE OF THE NAME. The owner asked for "an arrow
   near the right side and left of the word chapter, that you can cycle
   through the page in order to acceess chapter 2 and 3 and 1 again" —
   so they step one chapter along and wrap round, and they are taken off
   the page altogether when there is only one chapter to be on. */
.chapter-title {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2.4vw, 26px);
  min-width: 0;
}
.chapter-title .chapter-name { min-width: 0; }
.chapter-step {
  flex: none;
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 17px;
  line-height: 1;
  color: var(--silver-dim);
  background: none;
  border: 1px solid var(--silver-line);
  cursor: pointer;
  transition: color 0.3s var(--menu-ease), border-color 0.3s var(--menu-ease),
    transform 0.3s var(--menu-ease);
}
.chapter-step:hover { color: var(--silver); border-color: rgba(200, 204, 212, 0.5); }
.chapter-step-back:hover { transform: translateX(-3px); }
.chapter-step-on:hover { transform: translateX(3px); }
.chapter-step[hidden] { display: none; }

.chapter-spec {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--silver-dim);
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--silver-line);
}
.chapter-note {
  max-width: 56ch;
  margin-top: clamp(26px, 5vh, 48px);
}
.chapter-note p {
  margin: 0 0 14px;
  font-size: 17px;
  line-height: 1.62;
  color: #aeb3bc;
}
.chapter-note[hidden] { display: none; }
/* A NOTE ABOUT THE CHAPTER rather than part of what it is — set apart
   from the writing above it by a rule and by the site's mono. The
   owner's own: "The fragrances are not ranked nor ordered in any way."
   It is written in the page's markup inside that chapter's block, so
   only the chapters that want one have one. */
.chapter-note .gallery-aside {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  line-height: 1.8;
  color: var(--silver-dim);
  border-left: 1px solid var(--silver-line);
  padding: 2px 0 2px 16px;
  margin: 22px 0 0;
}

/* THE WAVE — the home page's own centre, spread over the whole window.

   IT IS DISTORTION, NOT COLOUR, and that is the owner's own correction:
   "i wanted it to have that effect of causing distortion like the
   central node of the main page, not colour". For two rounds each shell
   was a soft grey fill at a fraction of an opacity, which is a thing
   painted over the page rather than a thing the page is seen THROUGH.
   Nothing here has a colour any more. What each shell has is a
   `backdrop-filter`, so what passes over the window bends and smears
   what is behind it the way the map's translucent shells bend what is
   behind them.

   AND IT IS GEOMETRIC, which is the other half of the note — "more
   geometrical, and not so circular smooth... more mechanical". Every
   edge here is a POLYGON of twelve sides, not a circle: the shells and
   the hairline that leads them. (The cut the chapter page was once
   opened with was a polygon too; it is gone — the mesh's own panels
   turn the window over now.) At this size twelve sides read as
   facets rather than as a circle, which is the difference between a
   ripple and a machine.

   IT STANDS OUTSIDE THE PAGE IT OPENS, and over the whole window. For
   a round it was the first thing inside .chapter-page — and that page is
   cut open from nothing, so whatever the shells did in their first
   moments was clipped away with everything else and the hub could only
   ever arrive at the same instant as the black. The owner asked for it
   "just before the black explosion", which means it has to be able to go
   out over the chamber's own white first.

   IT PASSES OVER THE PAGE, NOT UNDER IT — z-index 5, above the page's
   4. That is what gives the distortion anything to work on. A
   backdrop-filter bends what is BEHIND it, and what is behind a wave
   sitting under the page is a white window with almost nothing left on
   it: the particles have met in the middle and the chrome has gone, so
   the filter bit on nothing and the wave was invisible for the half of
   its travel that mattered. Above the page it passes over the black
   coming open and over the silver on it, and there the bend reads. It
   is `pointer-events: none`, so nothing on the page is harder to press
   for it being there. */
.chapter-wave {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  overflow: hidden;
}
.chapter-wave[hidden] { display: none; }
/* ALL OF IT LIES IN THE RING'S PLANE. `--wave-x` and `--wave-y` are
   where the middle of the orbit lands on the window, `--wave-turn` the
   angle its long axis is standing at, and `--wave-flat` how flat it is
   drawn — all four measured off the orbit itself and written here by
   `chamber.js` at the moment the wave goes out. `rotate() scale()
   scaleY()` in that order turns the twelve-sided figure into that same
   ellipse, facets and all. */
.chapter-wave span {
  position: absolute;
  left: var(--wave-x, 50%);
  top: var(--wave-y, 50%);
  transform: translate(-50%, -50%) rotate(var(--wave-turn, 0rad))
             scale(0.02) scaleY(var(--wave-flat, 1));
  /* A twelve-sided ring: the outer figure with the inner one cut back
     out of it, so the filter only bites in the band between them. */
  clip-path: polygon(evenodd,
    100% 50%, 86.6% 75%, 50% 93.3%, 13.4% 75%, 0% 50%, 13.4% 25%,
    50% 6.7%, 86.6% 25%,
    88% 50%, 77% 70.6%, 50% 85.6%, 23% 70.6%, 12% 50%, 23% 29.4%,
    50% 14.4%, 77% 29.4%);
}
/* The inner shell: nearer the core, and the heavier bite of the two. */
.chapter-shell-in {
  width: 116vmax;
  height: 116vmax;
  /* Nearly all blur, and only a touch of contrast. An earlier go had
     brightness in it as well, which on a near-white ground came out as
     a pale WASH travelling over the page — a colour by another name,
     and the one thing the owner asked to be rid of. Blur bends; it does
     not paint. */
  /* PURE BLUR. Contrast was in here, and on a near-white ground it
     pushed the page to white inside the band — a pale wash travelling
     over the window, which is a colour however it is made. Blur alone
     shows nothing where there is nothing, and bends what there is. */
  backdrop-filter: blur(13px);
  -webkit-backdrop-filter: blur(13px);
  /* Its length is the mesh's own, handed over by chamber.js as
     `--wave-ms`, so the two are never out of step. */
  animation: chapter-shell var(--wave-ms, 2450ms) cubic-bezier(0.14, 0.62, 0.22, 1) both;
}
/* The outer shell: 5.15/2.37 of the inner, as on the map, and a far
   lighter touch — it is the edge of the same thing, further out. */
.chapter-shell-out {
  /* 5.15/2.37 of the inner, as on the map. It was 326vmax against the
     inner's 150, which is three windows across — and a backdrop-filter
     costs the whole of its own box however little of it is on screen.
     The pair keeps its ratio at a size that crosses the window and
     stops. This is most of what the owner meant by "make it smoother":
     the first frames of the burst were spent compositing two enormous
     blurred layers. */
  width: 252vmax;
  height: 252vmax;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  animation: chapter-shell var(--wave-ms, 2450ms) cubic-bezier(0.14, 0.62, 0.22, 1) both;
}
@keyframes chapter-shell {
  0% {
    transform: translate(-50%, -50%) rotate(var(--wave-turn, 0rad))
               scale(0.02) scaleY(var(--wave-flat, 1));
    opacity: 0;
  }
  8% { opacity: 1; }
  70% { opacity: 0.8; }
  100% {
    transform: translate(-50%, -50%) rotate(var(--wave-turn, 0rad))
               scale(1) scaleY(var(--wave-flat, 1));
    opacity: 0;
  }
}
/* THE WHOLE OF THE WAVE — a canvas, and everything on it is drawn by
   `drawMesh` in chamber.js.

   It was a single hairline figure made out of a bordered box for a
   round, then a train of faceted rings travelling out with a black
   ellipse cut open behind them. It is a LATTICE now: rings crossed by
   spokes standing in the orbit's plane, with a front travelling
   outward through it, and the cells between them — the PANELS —
   darkening behind that front until they are the chapter page's own
   black. There is nothing behind it: the drawing is what turns the
   window over.

   A canvas rather than boxes because there are four hundred panels and
   as many crossings on the window at once, and because the spokes run
   BETWEEN the rings, which no arrangement of boxes can do. */
/* The shells' own box. It exists so that restarting their animations —
   which is done by replacing its contents — cannot touch the canvas
   beside it. */
.chapter-shells { position: absolute; inset: 0; }
.chapter-rings {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}
/* A browser with no backdrop-filter gets the hairline figure and
   nothing painted over the page, which is the right thing to fall back
   to: the wave is quieter, and it is still the same shape. */

/* THE CARDS. One per favourite, ruled in silver with a mark at two
   corners — the same registration mark the rest of the site uses,
   opened out to the size of a card.

   A CARD OPENS WHERE IT STANDS NOW. It used to be a link straight to
   the piece; the owner asked for it to open in place — "the other
   favorite fragrances will go down and the square in which Des Cendres
   is will expand revealing the window of the fragrance". So the grid
   item is a SHELL holding a button and the body it opens, the open one
   takes the whole width of the grid (which is what sends the ones after
   it down a row), and what it opens into carries the two ways on from
   it. The card itself cannot be a link any more: a link inside a link
   is not a thing the browser will build. */
.chapter-cards {
  margin-top: clamp(40px, 8vh, 76px);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  /* ROWS ARE SPACED BY EACH CARD'S OWN MARGIN, not by a gap: a gap is
     laid out the moment a row exists, so a drawer opening from no
     height would still have jumped the rows after it down by one. */
  column-gap: 18px;
  row-gap: 0;
}
/* A chapter can have nothing in it — Chapter 2 is named, written up and
   empty — and `hidden` alone would lose to the `display: grid` above. */
.chapter-cards[hidden] { display: none; }
/* WHAT STANDS AT THE END OF THE LIST — a `.gallery-after` in the
   chapter's own block: the owner's word on the ones that nearly made
   it. Quieter than the writing above the cards, under a short rule. */
.chapter-after { max-width: 62ch; margin-top: clamp(34px, 6vh, 56px); }
.chapter-after[hidden] { display: none; }
.chapter-after::before {
  content: "";
  display: block;
  width: 36px;
  height: 1px;
  margin-bottom: 20px;
  background: rgba(200, 204, 212, 0.4);
}
.chapter-after p { margin: 0; font-size: 15.5px; line-height: 1.7; color: var(--silver-dim); }
.chapter-after a { color: var(--silver); text-underline-offset: 3px; text-decoration-color: rgba(200, 204, 212, 0.4); }
.chapter-after a:hover { text-decoration-color: currentColor; }

/* THE SHELL is the grid item: the card, and — while it is shut — what
   it says opened, which travels into the drawer under its row when it
   is pressed and back when it is shut. Its own margin spaces the rows
   (see `.chapter-cards`), and its `::after` is the short stem let down
   from an open card to its drawer. */
.chapter-card-shell {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin-bottom: 18px;
}
/* THE LINE FROM AN OPEN CARD INTO ITS DRAWER: one hairline from the
   middle of the card's foot down to the drawer's head, so which card
   the drawer belongs to is never a question. */
.chapter-card-shell::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  width: 1px;
  height: 18px;
  background: var(--silver);
  opacity: 0;
  transform: scaleY(0);
  transform-origin: top;
  transition: opacity 0.3s var(--menu-ease), transform 0.42s var(--menu-ease);
  pointer-events: none;
}
.chapter-card-shell.is-open::after { opacity: 0.55; transform: none; }

.chapter-card {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "no   no"
    "name name"
    "house go";
  gap: 18px 14px;
  flex: 1 1 auto;
  width: 100%;
  min-height: 190px;
  padding: 20px;
  margin: 0;
  font: inherit;
  text-align: left;
  text-decoration: none;
  color: var(--silver);
  border: 1px solid var(--silver-line);
  background: linear-gradient(160deg, rgba(200, 204, 212, 0.05), rgba(200, 204, 212, 0) 60%);
  cursor: pointer;
  transition: border-color 0.35s var(--menu-ease), background 0.35s var(--menu-ease),
    min-height 0.35s var(--menu-ease), padding 0.35s var(--menu-ease);
  /* They arrive one after another, the way every list on this site
     does — the explosion puts the page there, and the cards settle
     onto it. */
  opacity: 0;
}
/* A card comes in with the rest of the sheet, once the drawing is off
   it — never underneath it. `--n` is set on the shell and inherited. */
.chapter-page.here .chapter-card {
  animation: chapter-card-in 0.6s var(--menu-ease) both;
  animation-delay: calc(0.18s + var(--n, 0) * 70ms);
}
@keyframes chapter-card-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}
/* ARRIVED, the animation is retired and its end state written down.
   A finished animation that holds its fill keeps the card on a layer of
   its own, and a card opened out of the middle of a row onto a row of
   its own was left half painted on it — Haxan opened with the left of
   its head blank, name and all. `chamber.js` adds this on the
   animation's own end. */
.chapter-page.here .chapter-card.is-in { animation: none; opacity: 1; }
/* The corner marks: two hairlines at the top left and the bottom right,
   drawn in and not on — they run out to meet the card's own rule as the
   hand comes on to it. */
.chapter-card::before,
.chapter-card::after {
  content: "";
  position: absolute;
  width: 13px;
  height: 13px;
  border: 1px solid var(--silver);
  opacity: 0.55;
  transition: width 0.35s var(--menu-ease), height 0.35s var(--menu-ease),
    opacity 0.35s var(--menu-ease);
}
.chapter-card::before { left: 7px; top: 7px; border-right: 0; border-bottom: 0; }
.chapter-card::after { right: 7px; bottom: 7px; border-left: 0; border-top: 0; }
.chapter-card:hover {
  border-color: rgba(200, 204, 212, 0.5);
  background: linear-gradient(160deg, rgba(200, 204, 212, 0.1), rgba(200, 204, 212, 0) 60%);
}
.chapter-card:hover::before,
.chapter-card:hover::after { width: 24px; height: 24px; opacity: 1; }

/* OPEN, the card stays the card it was, where it was — lit, its corner
   marks run out and its sign turned to a minus — and what it opens
   into stands in the drawer under its row. (It turned into a bar across
   the whole width for a round, which left the cards before it in its
   row standing alone beside a hole.) */
.chapter-card-shell.is-open .chapter-card {
  border-color: rgba(200, 204, 212, 0.62);
  background: linear-gradient(160deg, rgba(200, 204, 212, 0.12), rgba(200, 204, 212, 0.02) 70%);
}
.chapter-card-shell.is-open .chapter-card::before,
.chapter-card-shell.is-open .chapter-card::after { width: 24px; height: 24px; opacity: 1; }
.chapter-card-shell.is-open .chapter-card-go { color: var(--silver); }

.chapter-card-no {
  grid-area: no;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--silver-dim);
}
.chapter-card-name {
  grid-area: name;
  font-size: 20px;
  line-height: 1.22;
  letter-spacing: -0.01em;
}
/* THE HOUSE THE PERFUME COMES FROM, where the date used to stand. The
   owner asked for the dates off the favourites and for the house in
   their place; there are no dates anywhere on this page now. */
.chapter-card-house {
  grid-area: house;
  align-self: end;
  font-size: 13px;
  letter-spacing: 0.01em;
  color: var(--silver-dim);
}
/* THE SIGN, where "OPEN ↓" and "CLOSE ↑" stood in capitals until
   2026-09-26: a small circle with a cross of hairlines in it, whose
   upright turns down flat as the card opens — a plus becoming a minus. */
.chapter-card-go {
  grid-area: go;
  align-self: end;
  justify-self: end;
  color: var(--silver-dim);
  transition: color 0.35s var(--menu-ease);
}
.chapter-card-sign {
  position: relative;
  display: block;
  width: 20px;
  height: 20px;
  border: 1px solid currentColor;
  border-radius: 50%;
  opacity: 0.8;
}
.chapter-card-sign::before,
.chapter-card-sign::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 1px;
  background: currentColor;
  transform: translate(-50%, -50%);
  transition: transform 0.42s var(--menu-ease);
}
.chapter-card-sign::after { transform: translate(-50%, -50%) rotate(90deg); }
.chapter-card-shell.is-open .chapter-card-sign::after { transform: translate(-50%, -50%) rotate(0deg); }
.chapter-card:hover .chapter-card-go { color: var(--silver); }

/* THE DRAWER — what a card opens into, under the row it stands in, the
   whole width of the grid (chamber.js, `openDrawer`). Opened on a
   measured height rather than on `auto`, which is not a height a
   browser will ease to, and its space under it growing with it, so the
   rows after it travel down rather than jump. A hairline across its
   head, which the line from the open card comes down to, and a ground
   a shade lighter than the page's; no box round it. */
.fav-drawer {
  grid-column: 1 / -1;
  position: relative;
  overflow: hidden;
  margin-bottom: 0;
  border-top: 1px solid rgba(200, 204, 212, 0.34);
  background: linear-gradient(180deg, rgba(200, 204, 212, 0.06), rgba(200, 204, 212, 0.015) 70%);
  transition: height 420ms var(--menu-ease), margin-bottom 420ms var(--menu-ease);
}
.fav-drawer.is-shown { margin-bottom: 28px; }
/* What it carries comes in a beat after it starts to open, goes first
   when it shuts, and fades over when another card of the same row is
   pressed. */
.fav-drawer-in { opacity: 0; transition: opacity 180ms var(--menu-ease); }
.fav-drawer.is-shown .fav-drawer-in { opacity: 1; transition: opacity 380ms var(--menu-ease) 120ms; }
.fav-drawer.is-shown.is-swapping .fav-drawer-in { opacity: 0; transition: opacity 160ms var(--menu-ease); }
/* Its close: a small circle with a cross in it, the card's own sign
   turned a quarter. */
.fav-drawer-close {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid rgba(200, 204, 212, 0.4);
  border-radius: 50%;
  background: none;
  color: var(--silver);
  cursor: pointer;
  transition: border-color 0.3s var(--menu-ease), transform 0.42s var(--menu-ease);
}
.fav-drawer-close::before,
.fav-drawer-close::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 11px;
  height: 1px;
  background: currentColor;
  transform: translate(-50%, -50%) rotate(45deg);
}
.fav-drawer-close::after { transform: translate(-50%, -50%) rotate(-45deg); }
.fav-drawer-close:hover,
.fav-drawer-close:focus-visible { border-color: var(--silver); transform: rotate(90deg); }
.chapter-card-body[hidden] { display: none; }

/* WHAT AN OPENED FAVOURITE IS, since 2026-09-26 — "less techy, more
   minimalist and geometric. Make it somehow react with the sun too."
   Two things side by side and nothing else: the writing, in the page's
   own face, and the picture in an upright frame on the side facing the
   sun, which the sun itself answers (sun.js, "the answer"). No
   capitals, no mono, no boxes; one hairline over the two ways on. */
.fav-open {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(40px, 6vw, 88px);
  /* Room enough at the head for the drawer's close to stand clear of
     the sun's marks round the picture. */
  padding: 72px clamp(40px, 4.4vw, 64px) clamp(38px, 4vw, 54px) clamp(24px, 3.4vw, 44px);
}
.fav-open-bare { grid-template-columns: minmax(0, 1fr); }
.fav-read { max-width: 60ch; min-width: 0; }

/* THE NAME AND THE HOUSE head the writing, in the page's own face — the
   drawer is the width of the grid, so it says whose it is itself. */
.fav-name {
  margin: 0;
  padding-right: 44px;
  font-size: clamp(26px, 2.6vw, 34px);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--silver);
}
.fav-house {
  margin: 8px 0 28px;
  font-size: 14px;
  letter-spacing: 0.01em;
  color: var(--silver-dim);
}
/* THE WRITING: the owner's own, whole paragraphs as they wrote them. */
.fav-writing p { margin: 0 0 16px; font-size: 17px; line-height: 1.72; color: #c4c8cf; }
.fav-writing p:last-child { margin-bottom: 0; }
/* THE COMMENTARY, if a block marks one — set apart rather than run on. */
.fav-writing .gallery-word {
  border-left: 1px solid var(--silver-line);
  padding-left: 16px;
  color: #9ba0a9;
}

/* THE TWO WAYS ON: "Notes" opens the same window every other page opens,
   in this page's colours, and "Read the whole entry" follows the
   favourite's own href — in that order since 2026-09-26, at the owner's
   word. Words, a small circle and a hairline arrow — not buttons in
   capitals. */
.fav-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 34px;
  margin: 30px 0 0;
  padding: 20px 0 0;
  border-top: 1px solid var(--silver-line);
}
.fav-link {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
  font: inherit;
  font-size: 14.5px;
  letter-spacing: 0.01em;
  color: var(--silver);
  text-decoration: none;
  background: none;
  border: 0;
  cursor: pointer;
}
.fav-link-say {
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s var(--menu-ease);
}
.fav-link:hover .fav-link-say,
.fav-link:focus-visible .fav-link-say { border-bottom-color: currentColor; }
/* The arrow: a hairline with a chevron at its end, which runs out a
   little further as the hand comes on to it. */
.fav-link-mark {
  position: relative;
  display: block;
  width: 26px;
  height: 1px;
  background: currentColor;
  transition: width 0.35s var(--menu-ease);
}
.fav-link-mark::after {
  content: "";
  position: absolute;
  right: 0;
  top: 50%;
  width: 6px;
  height: 6px;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: translateY(-50%) rotate(45deg);
}
.fav-link:hover .fav-link-mark { width: 38px; }
/* The notes' circle, filled as the hand comes on to it. */
.fav-link-ring {
  display: block;
  width: 11px;
  height: 11px;
  border: 1px solid currentColor;
  border-radius: 50%;
  transition: background-color 0.3s var(--menu-ease);
}
.fav-link:hover .fav-link-ring { background: currentColor; }

/* THE PICTURE, IN AN UPRIGHT FRAME, on the side of the card that faces
   the sun. The picture fills it edge to edge — nothing behind it, no
   mat — and chamber.js places it so the bottle stands in the middle
   (`seat`); until then, or if it cannot be, it covers the frame from its
   middle. On a clean white or black ground it may be drawn back until
   the whole bottle fits, and the frame takes that colour (inline, from
   `seat`), so no edge shows. One hairline round it; the sun draws the
   rest (sun.js). It was
   a circle for one round, the whole picture over a blurred copy of itself
   — "it looks tacky" — and nothing of that is here any more. */
.fav-print {
  margin: 0;
  display: grid;
  justify-items: center;
  gap: 34px;
}
.fav-print-face {
  position: relative;
  display: block;
  width: clamp(200px, 19vw, 250px);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: #0d0e11;
  outline: 1px solid rgba(200, 204, 212, 0.22);
  outline-offset: 0;
}
.fav-print-shot {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}
.fav-credit {
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--silver-dim);
}
.fav-credit a { color: inherit; text-underline-offset: 3px; }
.fav-credit a:hover { color: var(--silver); }

/* THE NOTES WINDOW, IN THIS PAGE'S COLOURS.

   It is the SAME window as everywhere else — `.note-panel`, its bar,
   its close, and a body written by the one renderer in notes.js — and
   what is different is five tokens, redefined on the window itself.
   Every rule the window already draws in reads them, so this turns the
   whole of it over at once and touches nothing anywhere else. That is
   the same trick `.find-page`, `.sheet-page` and `.ataraxia-page` are
   built on, and `--ink-rgb` is here for the same reason it exists at
   all: `rgba()` cannot take a hex, so the handful of rules that spend
   the ink at an alpha would otherwise stay black on black. */
.fav-note {
  --bg: #08090b;
  --bg-2: #101217;
  --line: rgba(200, 204, 212, 0.26);
  --ink: #e6e9ef;
  --ink-rgb: 230, 233, 239;
  --muted: #8b9099;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.66);
}
.fav-note-scrim { background: rgba(0, 0, 0, 0.62); }

/* EVERYTHING GOES AS THE WINDING STARTS. The owner asked for all of it
   to fade, "including the favorites text", and on the same frame the
   particles begin to close rather than a beat before or after — the
   whole point of the second round on this was that it be one movement.

   What goes: the word and its cue and crop marks, the menu, and this
   page's own search. What stays is the particles, which are the
   movement.

   THE FRONT CANVAS IS NOT FADED HERE, and must not be. It carries the
   drawing's labels, but it also carries every particle nearer than the
   middle of the chamber — half the disc — so fading it took half the
   ring away on the way in. The labels are faded in `chamber.js`
   instead, where the particles can be told apart from the chrome. */
/* AND THE PLATE KEEPS ITS OWN TRANSITION. `transition` is one
   property: writing a new one here REPLACES the `transform` transition
   the plate carries, and the plate's transform is what `--menu-lift`
   rides on — how far it stands above the middle so the word and the
   menu are centred together. Opening a chapter closes the menu, which
   writes a new lift on the same frame this class lands; with the
   transform transition gone the plate SNAPPED down the whole height of
   the menu in one frame, in full view, before any of the burst had
   begun. That is the little jump the owner reported on pressing a
   chapter. Both transitions are named here, so the plate eases down
   while it fades. */
.favorites-page.bursting .chamber-plate,
.favorites-page.bursting .page-find {
  opacity: 0;
  pointer-events: none;
  transition: opacity 1.25s var(--menu-ease),
              transform var(--chamber-step-ms) var(--chamber-step);
}
/* And the particles themselves, once the wave has gone out past them. */
.chamber.burst-wave .chamber-field {
  opacity: 0;
  transition: opacity 0.4s var(--menu-ease);
}
.chapter-open .chamber-plate,
.chapter-open .page-find { opacity: 0; pointer-events: none; }
.chapter-open .menu-trigger { color: #c8ccd4; }

@media (prefers-reduced-motion: reduce) {
  .chapter-page { transition: none; clip-path: none; }
  .chapter-page.here .chapter-card,
  .chapter-page.here .chapter-sheet > *:not(.chapter-cards) { animation: none; }
  .chapter-page.here .chapter-card,
  .chapter-page.here .chapter-sheet > * { opacity: 1; }
  .chapter-wave { display: none; }
  .favorites-page.bursting .chamber-plate,
  .favorites-page.bursting .page-find,
  .chamber.burst-wave .chamber-field { transition: none; }
  /* A card still opens and a chapter can still be stepped to; neither
     is eased. The heights are written straight on by chamber.js. */
  .chapter-sheet,
  .chapter-card,
  .fav-drawer,
  .fav-drawer-in,
  .chapter-card-shell::after,
  .chapter-step { transition: none; }
}

@media (max-width: 720px) {
  .chapter-sheet { padding: 76px 18px 60px; }
  .chapter-cards { grid-template-columns: 1fr; }
  /* The arrows fall under the name rather than squeezing it: at a
     phone's width there is no room either side of a heading this size.
     Nothing above 700px moves. */
  .chapter-title { flex-wrap: wrap; gap: 14px; }
  .chapter-title .chapter-name { flex: 1 0 100%; order: -1; }
  .chapter-step { width: 44px; height: 44px; }
  /* The picture goes above the writing on a phone, centred: beside is
     not a thing a phone has room for. */
  .fav-open { grid-template-columns: 1fr; gap: 30px; padding: 64px 18px 32px; }
  .fav-print { order: -1; }
  .fav-print-face { width: min(220px, 60vw); }
  .fav-drawer-close { top: 14px; right: 14px; }
}

/* ==================================================================
   THE FRAGRANCES VIEW — categories/scent-descriptions.html only

   The index is shared with Researches, which keeps the four-column
   drawing-office layout it was written for. The owner asked for THIS
   view to be reworked: minimalist, table-led, and of a piece with the
   rest of the site. So it is laid out again here under the class
   views.js already puts on the body, and nothing below leaves this
   view — Researches is untouched by all of it.

   The shape: one centred column. A quiet band naming what you are
   looking at, the field ruled across under it, and then the table,
   which is the whole point of the view and is given the room to say so.
   ================================================================== */
/* THE FRAGRANCES VIEW LAYS ITSELF OUT, and it is asked of the VIEW
   rather than of the body. It was `body.view-fragrances` for a
   round, and that class is toggled the moment a swipe starts — so
   going from this view to the sheet took its layout away while it
   was still on screen travelling off, and for the length of the
   swipe it was drawn in the Researches layout instead: readings
   across the top, a plates column on the right and a plate the
   size of the window. That is what the owner photographed. A
   view's own layout belongs to the view, not to the page it is
   standing on. */
.view[data-view="fragrances"] .index-page {
  /* A column exactly one screen tall, with the table taking whatever is
     left over. The whole point of the board is that the page around it
     does not grow as the list does — there are a great many more
     fragrances to come — so the scroll belongs to the table and the
     page itself never has one. */
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100svh;
  max-width: 1080px;
  margin: 0 auto;
  padding: 84px 28px 28px;
}
/* The naming band: what this is, on the left, and the one line about it
   on the right — read in that order and then done with. */
.view[data-view="fragrances"] .index-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 26px;
  margin-bottom: clamp(30px, 6vh, 64px);
}
.view[data-view="fragrances"] .index-name {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.view[data-view="fragrances"] .index-col {
  font-size: clamp(20px, 2.6vw, 30px);
  line-height: 1.2;
  color: var(--ink);
  flex-basis: 100%;
  letter-spacing: -0.01em;
}
.view[data-view="fragrances"] .index-say { display: contents; }
.view[data-view="fragrances"] .index-say-lede {
  max-width: 52ch;
  color: var(--muted);
  font-size: 13px;
}
/* The sorting note belongs on the table, not in the preamble — the
   headings say what they do by being pressed. */
.view[data-view="fragrances"] .index-say-head,
.view[data-view="fragrances"] .index-say-meta { display: none; }

/* The plates and their line are the drawing-office furniture of the
   Researches layout. This view is the table, so they stand down. */
.view[data-view="fragrances"] .index-right { display: none; }

/* THE BOARD. Full width, and the table given its head: the box it
   scrolls inside is the tall part of the page rather than a third of
   it, because a list of every fragrance on the site is what somebody
   came to this view for. */
.view[data-view="fragrances"] .index-board {
  margin-top: 0;
  width: 100%;
  /* min-height:0 is what lets this shrink inside the flex column; without
     it the board keeps its content's height and pushes the page taller. */
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.view[data-view="fragrances"] .index-search {
  gap: 14px;
  padding-bottom: 12px;
  margin-bottom: 0;
  border-bottom-color: var(--ink);
}
.view[data-view="fragrances"] .index-search-field {
  font-family: var(--sans);
  font-size: clamp(17px, 2vw, 22px);
}
.view[data-view="fragrances"] .index-search-mark { width: 11px; height: 11px; border-color: var(--ink); }
.view[data-view="fragrances"] .index-count { font-size: 12px; }
.view[data-view="fragrances"] .index-scroll {
  max-height: none;
  flex: 1;
  min-height: 0;
  margin-top: 4px;
}
.view[data-view="fragrances"] .index-table { font-size: 14px; }
.view[data-view="fragrances"] .index-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  /* The heading row stays put while the table runs under it, so what a
     column is never scrolls away. It needs its own ground to do that,
     or the rows show through. */
  background: var(--bg);
}
.view[data-view="fragrances"] .index-table td { padding: 11px 0; }
.view[data-view="fragrances"] .index-table tr:hover td { color: var(--ink); }
@media (max-width: 820px) {
  /* Narrow enough that one screen is not worth holding to: the page may
     grow and the table comes out in full. */
  .view[data-view="fragrances"] .index-page { height: auto; padding: 82px 18px 44px; }
  .view[data-view="fragrances"] .index-scroll { flex: none; overflow: visible; }
}

/* WHAT A SEARCH OPENS. A part reached from a result is opened and
   brought to the middle of the window, and says so for a moment — a
   page that silently scrolls somewhere leaves you wondering whether it
   did anything. */
.pine-part.found > summary,
.human-part.found > summary,
.adar-part.found > summary { animation: found-mark 2.4s var(--menu-ease); }
@keyframes found-mark {
  0%, 100% { background: transparent; }
  12%, 60% { background: rgba(156, 111, 53, 0.13); }
}

@media (max-width: 820px) {
  .find-row { grid-template-columns: 40px minmax(0, 1fr); }
  .find-kind, .find-where { grid-column: 2; text-align: left; }
}
@media (prefers-reduced-motion: reduce) {
  .find-row { animation: none; }
  .pine-part.found > summary,
  .adar-part.found > summary { animation: none; }
}

/* WHAT A PAGE'S OWN SEARCH SAYS. Under the field, the few things on
   this page that answer to what was typed, each with the trail that
   says where it lives — a fragrance is an answer you can act on rather
   than a word. Nothing is shown until something is typed. */
.sheet-found {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: min(360px, 82vw);
  background: var(--bg);
  border: 1px solid var(--line);
  display: none;
}
.sheet-search.has-found .sheet-found { display: block; }
.sheet-found-row {
  display: block;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
}
.sheet-found-row:last-child { border-bottom: 0; }
.sheet-found-row:hover { background: var(--bg-2); }
.sheet-found-what { display: block; font-size: 14px; color: var(--ink); }
.sheet-found-where {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-top: 3px;
}
.sheet-found-none {
  margin: 0;
  padding: 11px 12px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.5;
  color: var(--muted);
}
/* And a table that has been searched down to nothing says so. */
.index-board.index-nothing .index-scroll::after {
  content: "Nothing here by that name — press enter to search the site.";
  display: block;
  padding: 12px 0;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
}

/* THE INDEX ARRIVING. index-page.js puts `index-arriving` on the page
   when the view is switched to; everything about how it comes in is
   here. The rows are staggered for the first eighteen of them — long
   enough to read as being laid out one after another, short enough
   that the table is never waiting on itself. */
.index-arriving .index-head,
.index-arriving .index-right,
.index-arriving .index-search { animation: index-in 0.7s var(--menu-ease) both; }
.index-arriving .index-right { animation-delay: 0.08s; }
.index-arriving .index-search { animation-delay: 0.16s; }
.index-arriving .index-table thead th { animation: index-in 0.6s var(--menu-ease) 0.2s both; }
.index-arriving .index-table tbody tr:nth-child(-n+18) {
  animation: index-row 0.5s var(--menu-ease) both;
}
.index-arriving .index-table tbody tr:nth-child(1) { animation-delay: 0.26s; }
.index-arriving .index-table tbody tr:nth-child(2) { animation-delay: 0.30s; }
.index-arriving .index-table tbody tr:nth-child(3) { animation-delay: 0.34s; }
.index-arriving .index-table tbody tr:nth-child(4) { animation-delay: 0.38s; }
.index-arriving .index-table tbody tr:nth-child(5) { animation-delay: 0.42s; }
.index-arriving .index-table tbody tr:nth-child(6) { animation-delay: 0.46s; }
.index-arriving .index-table tbody tr:nth-child(7) { animation-delay: 0.50s; }
.index-arriving .index-table tbody tr:nth-child(8) { animation-delay: 0.54s; }
.index-arriving .index-table tbody tr:nth-child(9) { animation-delay: 0.58s; }
.index-arriving .index-table tbody tr:nth-child(10) { animation-delay: 0.62s; }
.index-arriving .index-table tbody tr:nth-child(11) { animation-delay: 0.66s; }
.index-arriving .index-table tbody tr:nth-child(12) { animation-delay: 0.70s; }
.index-arriving .index-table tbody tr:nth-child(13) { animation-delay: 0.74s; }
.index-arriving .index-table tbody tr:nth-child(14) { animation-delay: 0.78s; }
.index-arriving .index-table tbody tr:nth-child(15) { animation-delay: 0.82s; }
.index-arriving .index-table tbody tr:nth-child(16) { animation-delay: 0.86s; }
.index-arriving .index-table tbody tr:nth-child(17) { animation-delay: 0.90s; }
.index-arriving .index-table tbody tr:nth-child(18) { animation-delay: 0.94s; }
@keyframes index-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}
@keyframes index-row {
  from { opacity: 0; transform: translateY(7px); }
  to { opacity: 1; transform: none; }
}

/* A browser's own clear button on a search field is the one thing on
   this site drawn by something other than this stylesheet. */
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }

@media (prefers-reduced-motion: reduce) {
  .index-arriving .index-head,
  .index-arriving .index-right,
  .index-arriving .index-search,
  .index-arriving .index-table thead th,
  .index-arriving .index-table tbody tr { animation: none; }
}

/* A PAGE'S OWN SEARCH, on the two pages that had none: a mark in the
   top right that opens into a field, looking over that page only.
   page-search.js builds it; this is how it looks. It takes its colour
   from the ground it stands on — the theories drawing is near-black,
   the chamber is white — through `dark-surface`, which those pages
   already carry for the cursor's sake. */
.page-find {
  position: fixed;
  top: 22px;
  right: 22px;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 8px;
}
.page-find-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 0;
  background: none;
  padding: 4px 0;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 15px;
  letter-spacing: 0.1em;
  color: var(--muted);
}
.page-find-trigger:hover { color: var(--ink); }
.page-find-mark {
  width: 11px;
  height: 11px;
  border: 1px solid currentColor;
  position: relative;
}
.page-find-mark::after {
  content: "";
  position: absolute;
  inset: 3px;
  background: currentColor;
}
.page-find-field {
  width: 0;
  opacity: 0;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  padding: 2px 0;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
  transition: width 0.4s var(--menu-ease), opacity 0.3s var(--menu-ease);
}
.page-find-field:focus { outline: none; }
.page-find.open .page-find-field { width: min(230px, 42vw); opacity: 1; }
.page-find-out {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: min(340px, 82vw);
  background: var(--bg);
  border: 1px solid var(--line);
  display: none;
}
.page-find.has-found .page-find-out { display: block; }
.page-find-row {
  display: block;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
}
.page-find-row:last-child { border-bottom: 0; }
.page-find-row:hover { background: var(--bg-2); }
.page-find-what { display: block; font-size: 14px; color: var(--ink); }
.page-find-where {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-top: 3px;
}
.page-find-none {
  margin: 0;
  padding: 11px 12px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.5;
  color: var(--muted);
}
/* On the theories drawing, which is near-black. */
body.theories-page .page-find-trigger { color: #8d94a3; }
body.theories-page .page-find-trigger:hover { color: #dce6f7; }
body.theories-page .page-find-field { color: #e8ecf3; border-bottom-color: rgba(150, 160, 176, 0.5); }
body.theories-page .page-find-out { background: #0f1116; border-color: rgba(150, 160, 176, 0.4); }
body.theories-page .page-find-row { border-bottom-color: rgba(150, 160, 176, 0.25); }
body.theories-page .page-find-row:hover { background: #151922; }
body.theories-page .page-find-what { color: #e8ecf3; }
body.theories-page .page-find-where,
body.theories-page .page-find-none { color: #8d94a3; }
/* It is not there while a drawing is still setting itself up. */
body.theories-page .page-find { opacity: 0; transition: opacity 0.9s var(--menu-ease); }
body.theories-page.lit .page-find { opacity: 1; }

@media (max-width: 640px) {
  .page-find-word { display: none; }
}

/* ==================================================================
   PHOTOGRAPHY — categories/other-2.html only

   The second "Other" page is the photographs now, and it is laid out
   the way a photographer lays out work: sets rather than one long run,
   a grid that a wide frame breaks, frame numbers down the margin where
   a roll carries them, and a caption that says what a photographer
   writes on the back of a print.

   It spends no accent and draws nothing on a canvas: what carries it is
   the hairline, the mono reading and the registration mark the rest of
   the site is made of.
================================================================== */
.photo-page {
  max-width: 1180px;
  margin: 0 auto;
  padding: clamp(84px, 13vh, 150px) clamp(22px, 5vw, 64px) 0;
}

/* --- the head ---------------------------------------------------- */
.photo-head {
  max-width: 720px;
  margin-bottom: clamp(56px, 10vh, 104px);
}
.photo-kicker {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 18px;
}
.photo-page h1 {
  font-size: clamp(42px, 7vw, 82px);
  line-height: 0.98;
  letter-spacing: -0.02em;
  margin: 0 0 26px;
}
.photo-lede {
  font-size: clamp(15px, 1.5vw, 18px);
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 40px;
  max-width: 54ch;
}
/* THE READINGS. The same row of small facts the index pages carry,
   which is what a portfolio puts under its own name. */
.photo-readings {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 0;
  border-top: 1px solid var(--line);
}
.photo-readings > div {
  padding: 14px 28px 14px 0;
  margin-right: 28px;
  border-right: 1px solid var(--line);
}
.photo-readings > div:last-child { border-right: 0; margin-right: 0; }
.photo-readings dt {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 6px;
}
.photo-readings dd {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink);
  margin: 0;
}

/* --- a set ------------------------------------------------------- */
.photo-set { margin-bottom: clamp(62px, 11vh, 118px); }
.photo-set-head {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 0 24px;
  align-items: start;
  padding-bottom: 22px;
  margin-bottom: 30px;
  border-bottom: 1px solid var(--line);
}
/* The number stands in the margin and the writing in the column beside
   it, both said outright: left to flow, the third child comes back
   round to column one and the set's line is set in the margin at
   96px wide. */
.photo-set-no {
  grid-column: 1;
  grid-row: 1 / span 2;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 6px 0 0;
}
.photo-set-head h2 { grid-column: 2; }
.photo-set-say { grid-column: 2; }
.photo-set-head h2 {
  font-size: clamp(20px, 2.4vw, 28px);
  line-height: 1.15;
  margin: 0 0 10px;
}
.photo-set-say {
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  margin: 0;
  max-width: 62ch;
}

/* --- the sheet --------------------------------------------------- */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(238px, 1fr));
  gap: clamp(22px, 3vw, 40px);
}
/* THE ONE YOU HAVE CHOSEN OUT OF THE SHEET. It takes two columns and
   stands wider than tall, which is the frame ringed on a contact sheet
   and then printed. */
.photo-frame.photo-wide { grid-column: span 2; }
@media (max-width: 620px) {
  .photo-frame.photo-wide { grid-column: span 1; }
}

.photo-frame { margin: 0; }
.photo-plate {
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
  /* Hatched while there is no picture, the way every plate on this
     site waits for its file. */
  background-image: repeating-linear-gradient(
    -45deg,
    var(--line) 0px, var(--line) 1px,
    transparent 1px, transparent 11px
  );
  overflow: hidden;
  text-decoration: none;
  transition: border-color 0.35s var(--menu-ease);
}
.photo-wide .photo-plate { aspect-ratio: 16 / 10; }
.photo-plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.7s var(--menu-ease), opacity 0.35s var(--menu-ease);
}
/* A plate with a picture in it has no hatch showing, so the hatch is
   covered rather than switched off. */
.photo-plate:hover { border-color: var(--ink); }
.photo-plate:hover img { transform: scale(1.02); opacity: 0.92; }

/* THE REGISTRATION MARK in the corner of an empty plate — the same
   hollow square the rest of the site marks things with, and what says
   this is a frame waiting rather than a frame that failed to load. */
.photo-mark {
  position: absolute;
  left: 12px;
  top: 12px;
  width: 9px;
  height: 9px;
  border: 1px solid var(--muted);
}
.photo-plate img ~ .photo-mark { display: none; }

/* --- what is written under a frame ------------------------------- */
.photo-frame figcaption {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 2px 8px;
  padding-top: 12px;
}
.photo-no {
  grid-row: span 2;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding-top: 1px;
}
.photo-say { font-size: 14px; line-height: 1.35; color: var(--ink); }
.photo-meta {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* --- the foot ---------------------------------------------------- */
.photo-foot {
  border-top: 1px solid var(--line);
  padding: 26px 0 96px;
}
.photo-foot p {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 0;
}

/* --- arriving ---------------------------------------------------- */
/* Set by photography.js, and only by it: without the script nothing
   carries `photo-waiting` and every frame is simply there. */
.photo-waiting .photo-frame {
  opacity: 0;
  transform: translateY(16px);
}
.photo-waiting .photo-frame.here {
  opacity: 1;
  transform: none;
  transition: opacity 0.7s var(--menu-ease), transform 0.7s var(--menu-ease);
  transition-delay: calc(var(--n, 0) * 70ms);
}

/* ==================================================================
   THE PHOTOGRAPHY VIEWER — one plate over the darkened page, its
   reading under it, an arrow either side.
================================================================== */
html.photo-viewing, html.photo-viewing body { overflow: hidden; }
.photo-viewer {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 3vw, 34px);
  padding: clamp(16px, 5vh, 54px) clamp(12px, 4vw, 48px);
  background: rgba(12, 12, 8, 0.94);
  opacity: 0;
  transition: opacity 0.26s var(--menu-ease);
}
.photo-viewer.here { opacity: 1; }
.photo-viewer[hidden] { display: none; }

.photo-viewer-plate {
  position: relative;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
  max-width: min(1180px, 100%);
  max-height: 100%;
}
.photo-viewer-img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - clamp(86px, 18vh, 168px));
  width: auto;
  height: auto;
  object-fit: contain;
  border: 1px solid rgba(232, 232, 224, 0.22);
  background: #14140e;
}
.photo-viewer-read {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  align-items: baseline;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: rgba(232, 232, 224, 0.68);
}
.photo-viewer-say { color: #e8e8e0; letter-spacing: 0.02em; font-size: 12px; }

.photo-viewer-plate { transition: transform 0.15s ease-in, opacity 0.15s ease-in; }
.photo-viewer-plate.leaving-on { transform: translateX(-36px); opacity: 0; }
.photo-viewer-plate.leaving-back { transform: translateX(36px); opacity: 0; }
/* Its own keyframes. It shared Pineward's for a round, and Pineward's
   went when that viewer was rebuilt to cross-slide two layers — a
   shared name between two pages' scripts is exactly the coupling this
   site does not have anywhere else. */
.photo-viewer-plate.going-on,
.photo-viewer-plate.going-back {
  animation: photo-swap 0.24s cubic-bezier(0.18, 0.7, 0.26, 1) both;
}
.photo-viewer-plate.going-back { animation-name: photo-swap-back; }
@keyframes photo-swap {
  from { transform: translateX(42px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
@keyframes photo-swap-back {
  from { transform: translateX(-42px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

.photo-viewer-arrow, .photo-viewer-shut {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid rgba(232, 232, 224, 0.3);
  background: rgba(12, 12, 8, 0.6);
  color: #e8e8e0;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.25s var(--menu-ease), background 0.25s var(--menu-ease);
}
.photo-viewer-arrow:hover, .photo-viewer-arrow:focus-visible,
.photo-viewer-shut:hover, .photo-viewer-shut:focus-visible {
  border-color: #e8e8e0;
  background: rgba(232, 232, 224, 0.12);
}
.photo-viewer-shut {
  position: absolute;
  top: clamp(12px, 3vh, 26px);
  right: clamp(12px, 3vw, 26px);
}

@media (prefers-reduced-motion: reduce) {
  .photo-waiting .photo-frame { opacity: 1; transform: none; }
  .photo-plate, .photo-plate img, .photo-viewer { transition: none; }
  .photo-plate:hover img { transform: none; }
  .photo-viewer-plate { transition: none; animation: none !important; }
  .photo-viewer-plate.leaving-on, .photo-viewer-plate.leaving-back {
    transform: none;
    opacity: 1;
  }
}

/* ==================================================================
   NOT SMELLED YET — the list at the foot of Pineward.

   Names only. The owner asked for "JUST TEXT (NO IMAGES OR ANYTHING)",
   and that is also the honest thing: a fragrance that has not been
   smelled has no writing and no picture, so giving it the full part's
   furniture would be furniture around nothing. It carries the strata's
   own head so it reads as one more group, which is what it is.
================================================================== */
.pine-waiting {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 0 64px;
}
.pine-waiting-list {
  list-style: none;
  margin: 0;
  padding: 0;
  columns: 2;
  column-gap: 48px;
}
.pine-waiting-list li {
  break-inside: avoid;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
}
.pine-waiting-name { font-size: 15px; color: var(--muted); }
.pine-waiting-say {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.03em;
  color: var(--pine-green);
}
@media (max-width: 720px) {
  .pine-waiting { padding: 0 22px; }
  .pine-waiting-list { columns: 1; }
}

/* ==================================================================
   READING: JUSTIFIED, LARGER, AND LESS DENSE

   The owner asked for three things together — "the text generally
   should be justified", the reading pages "bigger by 1 or 2", and "less
   compact... I dont want it to look so dense". They are one change,
   because doing any one of them alone makes the other two worse: a
   justified column at 16px with tight leading is the densest thing on
   a page, and a bigger font in a narrow column justifies badly.

   JUSTIFIED TEXT NEEDS HYPHENATION. Without it a justified column opens
   rivers of white space wherever a long word will not fit — which is
   worse than the ragged edge it replaced. Every rule here that sets
   `text-align: justify` sets `hyphens: auto` with it; keep them
   together.

   WHAT IS NOT JUSTIFIED: anything set in the mono face (readings,
   captions, trails, table cells), anything one line long, and the
   landing page's own sentence. Justifying a line of six words only
   stretches it.
================================================================== */
.essay-section p,
.pine-text p,
.pine-intro p,
.adar-part .adar-body p,
.human-part .human-body p,
.human-intro .human-text p,
.chapter-note p,
.index-say-lede,
.photo-set-say,
.photo-lede,
.category-lede {
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
}

/* THE ESSAY PAGES — the theories and the researches, which is what the
   owner meant by "theories, researches and explorations". */
.essay-body { font-size: 17.5px; line-height: 1.78; }
/* THE COLUMN RUNS TO THE PLATE'S OWN EDGE. It was capped at 66ch, which
   on this page came out 225px narrower than the picture above it — the
   owner asked for the two to line up, and they were right that it read
   as a mistake rather than as a measure. The body's own max-width is
   the measure now. */
.essay-section { max-width: none; padding-top: clamp(38px, 6vh, 68px); }
.essay-section p { margin: 0 0 26px; font-size: 17.5px; line-height: 1.78; }
.essay-section p.essay-lead { font-size: 19.5px; line-height: 1.7; }
.essay-section ul { margin: 0 0 26px; line-height: 1.78; font-size: 17.5px; }
.essay-section ul li { margin-bottom: 9px; }
.essay-section h2 { margin-bottom: 20px; }

/* THE INDEX PAGES — their lede and their table, which are the only
   reading on them. */
.index-say-lede { font-size: 16.5px; line-height: 1.7; }
.index-table td { font-size: 15px; }
.index-table .index-what { line-height: 1.5; }

/* PINEWARD AND ADAR — the writing inside a part. */
.pine-text p, .adar-part .adar-body p { font-size: 16.5px; line-height: 1.75; margin: 0 0 20px; }

@media (max-width: 720px) {
  /* A justified column this narrow is all rivers, whatever the
     hyphenation does. */
  .essay-section p, .pine-text p, .adar-part .adar-body p,
  .index-say-lede, .photo-set-say, .photo-lede, .category-lede {
    text-align: left;
  }
  .essay-body, .essay-section p { font-size: 16.5px; }
}

/* ==================================================================
   WHAT A THEORY IS WRITTEN WITH — the blocks the owner's own theories
   needed that the essay template did not have: a triangle, a key, a
   run of worked cases, an ordered list and footnotes.
================================================================== */

/* THE TRIANGLE. Drawn as inline SVG rather than as a picture, so it is
   the page's own ink: it takes the page's colours, it stays sharp at
   any size, and it is in the markup where it can be read. The three
   poles are the three characteristics, and a fragrance is a dot placed
   in the body of it. */
.tri-figure {
  margin: 34px 0 30px;
  padding: 0;
  display: flex;
  justify-content: center;
}
.tri {
  width: 100%;
  max-width: 460px;
  height: auto;
  overflow: visible;
}
.tri-edge {
  fill: rgba(150, 160, 176, 0.05);
  stroke: rgba(170, 178, 193, 0.55);
  stroke-width: 1.25;
  stroke-linejoin: round;
}
.tri-pole {
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  fill: #aab2c1;
}
.tri-pole-top { text-anchor: middle; }
.tri-pole-left { text-anchor: start; }
.tri-pole-right { text-anchor: end; }
.tri-mark circle { fill: var(--brass, #b98a3e); }
.tri-mark text {
  font-family: var(--mono);
  font-size: 16px;
  letter-spacing: 0.06em;
  fill: #dbe1ea;
}

/* A KEY — a term and what it means, which is how the sweat theory
   explains its own lists. */
.essay-key { margin: 0 0 26px; }
.essay-key > div {
  display: grid;
  grid-template-columns: minmax(0, 210px) minmax(0, 1fr);
  gap: 6px 24px;
  padding: 13px 0;
  border-top: 1px solid rgba(150, 160, 176, 0.22);
}
.essay-key dt {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #dbe1ea;
}
.essay-key dd { margin: 0; color: #aab2c1; font-size: 16.5px; line-height: 1.7; }

/* THE WORKED CASES — one fragrance, read against the framework. */
.essay-cases {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 18px;
  margin: 6px 0 26px;
}
.essay-case {
  border: 1px solid rgba(150, 160, 176, 0.24);
  padding: 18px 20px 16px;
}
.essay-case-name {
  margin: 0 0 10px !important;
  font-size: 15.5px !important;
  color: #dbe1ea;
  text-align: left !important;
}
.essay-case ol {
  margin: 0;
  padding-left: 20px;
  color: #aab2c1;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.75;
}

.essay-section ol.essay-lists,
.essay-section ol.essay-order,
.essay-section ol.essay-footnotes {
  margin: 0 0 26px;
  padding-left: 24px;
  color: #aab2c1;
  font-size: 17.5px;
  line-height: 1.78;
}
.essay-section ol.essay-lists li,
.essay-section ol.essay-order li { margin-bottom: 9px; }
.essay-section ol.essay-order {
  font-family: var(--mono);
  font-size: 14px;
  letter-spacing: 0.04em;
  columns: 2;
  column-gap: 40px;
}
.essay-section ol.essay-order li { break-inside: avoid; margin-bottom: 7px; }

/* FOOTNOTES, and the marks that lead to them. A mark is a link down
   and the note carries a link back, so the reading can be followed
   both ways without losing your place. */
.essay-fn {
  font-family: var(--mono);
  font-size: 11px;
  vertical-align: super;
  line-height: 0;
  margin-left: 2px;
  padding: 0 2px;
  color: var(--brass, #b98a3e);
  text-decoration: none;
}
.essay-fn:hover, .essay-fn:focus-visible { text-decoration: underline; }
.essay-section ol.essay-footnotes { font-size: 15.5px; line-height: 1.7; }
.essay-section ol.essay-footnotes li { margin-bottom: 14px; }
.essay-fn-back { color: var(--brass, #b98a3e); text-decoration: none; margin-left: 6px; }

/* ON TO THE NEXT THEORY, opposite the way back — the owner asked for
   one at the foot of every theory page. The foot was a single link, so
   it is a row of two now. */
.essay-foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  flex-wrap: wrap;
}
.essay-on {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: #aab2c1;
  text-decoration: none;
  text-align: right;
  transition: color 0.3s var(--menu-ease);
}
.essay-on:hover, .essay-on:focus-visible { color: #dbe1ea; }

@media (max-width: 720px) {
  .essay-key > div { grid-template-columns: minmax(0, 1fr); }
  .essay-section ol.essay-order { columns: 1; }
  .tri-pole { font-size: 19px; }
}

/* ==================================================================
   THE SWEAT THEORY'S COMBINATION TABLE — works/theory-02.html

   Eighteen rows, one per combination of the framework's three lists,
   with a blank column the owner fills in as they find a fragrance that
   sits there. The blank is the point of it: a row left empty says that
   combination is still waiting, and drawing it as a RULE rather than as
   nothing is what makes that read as waiting rather than as broken.
================================================================== */
.sweat-table {
  width: 100%;
  border-collapse: collapse;
  margin: 8px 0 26px;
  font-size: 15px;
}
.sweat-caption {
  caption-side: top;
  text-align: left;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #8f98a8;
  padding-bottom: 14px;
}
.sweat-table th {
  text-align: left;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #8f98a8;
  padding: 0 14px 10px 0;
  border-bottom: 1px solid rgba(150, 160, 176, 0.4);
  white-space: nowrap;
}
.sweat-list-no {
  color: var(--brass, #b98a3e);
  margin-right: 6px;
}
.sweat-table td {
  padding: 11px 14px 11px 0;
  border-bottom: 1px solid rgba(150, 160, 176, 0.16);
  color: #aab2c1;
  vertical-align: baseline;
}
.sweat-table tr:hover td { color: #dbe1ea; }
/* The code is the framework's own shorthand, so it is set in the mono
   the rest of the site keeps for a reading. */
.sweat-code {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.18em;
  color: #dbe1ea;
  width: 72px;
}
/* THE COLUMN THAT IS WAITING. Empty, it draws a rule to write on;
   filled, it is just the name. */
.sweat-who { width: 34%; color: #dbe1ea; }
.sweat-who:empty::after {
  content: "";
  display: block;
  width: 100%;
  max-width: 220px;
  border-bottom: 1px dashed rgba(150, 160, 176, 0.42);
  height: 0;
  transform: translateY(-4px);
}

@media (max-width: 720px) {
  /* The three spelled-out columns are what a narrow window has to lose:
     the code carries the same information, and the page has just
     explained how to read it. */
  .sweat-table th:nth-child(2), .sweat-table th:nth-child(3),
  .sweat-table th:nth-child(4), .sweat-table td:nth-child(2),
  .sweat-table td:nth-child(3), .sweat-table td:nth-child(4) {
    display: none;
  }
  .sweat-who { width: auto; }
}

/* ==================================================================
   THE NOTE DISSEMINATION FRAMEWORK — works/theory-03.html

   That piece argues in DIAGRAMS: a circle standing for the zone of
   indistinguishability, and an arrow for every note, lying on a radius
   through it. All of them are inline SVG in the page itself, so they
   are drawn in the page's own ink — they take the essay's colours,
   they scale with it, and they are all still there with the script
   switched off.

   THE ONE PLACE ON THIS SITE SET IN A SERIF is the maths on that page.
   The owner asked for the notation to be in a different face from the
   writing, and the site only has two: Archivo for the reading and IBM
   Plex Mono for its readings and numbers. Setting a variable in the
   mono would make it look like one of those readings, so the maths
   takes a serif instead — which is what maths is set in anyway, and
   needs no third font fetched to say so.
================================================================== */
.zone-figure {
  margin: 34px 0 30px;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.zone {
  width: 100%;
  max-width: 300px;
  height: auto;
  overflow: visible;
}
.zone-figure-wide .zone { max-width: 760px; }
/* A GRAPH ON EITHER SCALE (theory-03's Example 3): both drawings are in
   the figure and one is `hidden`. The browser's own [hidden] rule is
   written for HTML elements only, so an <svg> needs its own. The button
   that changes them is the calculator's, at the figure's top right. */
.zone-figure svg[hidden] { display: none; }
.zone-switch-head {
  width: 100%;
  max-width: 760px;
  display: flex;
  justify-content: flex-end;
}
.zone-switch-head .calc-scale { margin: 0; }
.zone-figure figcaption,
.zone-row-figure figcaption {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: #8d94a3;
  text-align: center;
  max-width: 560px;
  line-height: 1.6;
}
.zone-figure-no {
  color: #dbe1ea;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* THE PARTS OF A DIAGRAM. The ring is the zone; an arrow is a note;
   the head says which way that note is going. */
.zone-ring {
  fill: rgba(150, 160, 176, 0.05);
  stroke: rgba(170, 178, 193, 0.55);
  stroke-width: 1.25;
}
.zone-arrow {
  stroke: rgba(190, 198, 212, 0.75);
  stroke-width: 1.15;
  stroke-linecap: round;
}
.zone-head { fill: rgba(190, 198, 212, 0.9); }
/* The leader that points from a legend diagram at the words. */
.zone-lead {
  stroke: rgba(150, 160, 176, 0.5);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}
/* THE CONNECTOR between two diagrams is deliberately NOT the arrow a
   note is drawn with — an open chevron on a plain line, so a reader
   never takes the passage of time for another note. */
.zone-link {
  stroke: rgba(185, 138, 62, 0.75);
  stroke-width: 1.1;
}
.zone-link-head {
  fill: none;
  stroke: rgba(185, 138, 62, 0.9);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.zone-name {
  font-family: var(--mono);
  font-size: 19px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-anchor: middle;
  fill: #aab2c1;
}

/* THE LEGEND — a small diagram with its leader running into the words
   beside it, so the line lands on what it is naming. */
.zone-legend-figure { margin: 30px 0; padding: 0; }
.zone-legend {
  display: flex;
  align-items: center;
  gap: 0;
  margin-bottom: 20px;
}
.zone-legend .zone-small { max-width: 190px; flex: none; }
.zone-legend-say {
  margin: 0;
  color: #aab2c1;
  font-size: 16.5px;
  line-height: 1.66;
}
.zone-legend-say strong { color: #dbe1ea; }

/* A ROW OF THREE DIAGRAMS, each carrying its own working underneath —
   the complication examples. */
.zone-row-figure {
  margin: 34px 0 30px;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.zone-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 26px;
  width: 100%;
}
.zone-cell { display: flex; flex-direction: column; align-items: center; }
.zone-cell .zone { max-width: 200px; }
.zone-cell-name {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #dbe1ea;
  margin: 10px 0 12px;
  text-align: center;
}
.zone-cell .math-block { width: 100%; }
/* A third of the column is not enough room for a formula on one line,
   so inside a cell the maths wraps and is set a shade smaller. */
.zone-cell .math-line { white-space: normal; font-size: 15px; }
.zone-cell .math-aside { font-size: 12px; }
.zone-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 30px;
  margin: 0 0 26px;
}
/* The name over each of a pair of worked answers — "For the 5-note
   Xerjoff". In the writing's own face, at a medium weight, with a
   hairline of the page's blue under it as wide as the name: the head of
   the answer under it, quieter than the page's own headings. The owner,
   2026-09-27: "change the xerjoff font again so it suits the rest of the
   page" — it was bold and white (2026-09-26), heavier than anything
   round it but the section headings, and before that the mono in spaced
   capitals, which read as one of the site's readings. */
.zone-pair-name {
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0;
  color: #dbe1ea;
  display: inline-block;
  padding-bottom: 5px;
  border-bottom: 1px solid rgba(142, 180, 226, 0.55);
  margin: 0 0 8px;
}

/* THE MATHS. Serif, because it is not the writing and it is not one of
   the site's readings either. A worked answer is a first line and then
   the lines that carry on under it, indented to stand under its own
   equals sign. */
/* A WORKED ANSWER IS A GRID, so that every `=` in it stands in the
   same column. It was a first line and then continuation lines pushed
   right by a guessed 3.2em, which lines nothing up the moment a label
   is longer than the guess — and on the three-across examples the
   longest label (IBR with a "Dry Down" subscript) wrapped its first
   line and threw the rest of the block out with it. The owner asked
   for the equations neat and organised.

   Three cells a row: what is left of the equals, the equals itself,
   and what is right of it. A continuation line has an empty first
   cell, which is what puts it under the one above. `display: contents`
   on the row is what lets the <p> hold three cells that belong to the
   block's own grid rather than to a grid of its own. */
.math-block {
  margin: 0 0 22px;
  display: grid;
  grid-template-columns: max-content max-content minmax(0, 1fr);
  column-gap: 0.38em;
  row-gap: 3px;
  align-items: baseline;
  justify-content: start;
}
.math-line { display: contents; }
.mlhs { text-align: right; white-space: nowrap; }
.meq { text-align: center; }
.mrhs { text-align: left; }
/* THE ABSOLUTE BARS, spent only where a reading is put against a
   threshold. The bars are drawn a shade quieter than what they hold,
   so IBR still reads as the thing being said. */
.mabs { color: #9aa3b1; padding: 0 0.06em; }

/* A VERTICAL FRACTION — a numerator over a rule over a denominator,
   which is what a fraction looks like everywhere but in a line of
   code. The owner asked for all of them to be written this way rather
   than with a slash.

   `inline-grid` rather than two stacked blocks: it keeps the pair one
   thing on the line, it sizes itself to whichever of the two is wider,
   and the rule is the numerator's own bottom border so it is always
   exactly as wide as the fraction. `vertical-align: middle` puts the
   rule about where the slash used to be. */
.frac {
  display: inline-grid;
  grid-template-rows: auto auto;
  justify-items: center;
  vertical-align: middle;
  margin: 0 0.22em;
  line-height: 1.16;
}
.frac-n {
  border-bottom: 1px solid currentColor;
  padding: 0 0.34em 0.12em;
}
.frac-d { padding: 0.14em 0.34em 0; }
/* A fraction is taller than a line of type, so a row that has one in
   it is centred rather than sat on its own baseline — otherwise the
   numerator takes the baseline and the whole fraction hangs below it. */
.math-block { align-items: center; }
/* .essay-section p sets the reading's paragraph spacing and outranks a
   plain class, so every line of a worked answer would stand a
   paragraph apart. The working is one block, not five paragraphs. */
/* And the reading is JUSTIFIED, which maths must not be: a formula
   stretched to the measure puts gaps between its own symbols. */
.essay-section .math-line { hyphens: none; }
.essay-section .mlhs,
.essay-section .meq,
.essay-section .mrhs { text-align: inherit; hyphens: none; }
.essay-section .mlhs { text-align: right; }
.essay-section .meq { text-align: center; }
.essay-section .mrhs { text-align: left; }
.essay-section .math-standalone { margin: 22px 0; text-align: center; hyphens: none; }
.essay-section .essay-claim { text-align: center; hyphens: none; }
.essay-section .zone-legend-say,
.essay-section .zone-cell-name,
.essay-section .zone-pair-name { margin-bottom: 0; }
.essay-section .zone-cell-name { margin: 10px 0 12px; }
.essay-section .zone-pair-name { margin: 0 0 14px; }
.math-line {
  font-family: var(--math);
  font-size: 16.5px;
  line-height: 1.5;
  color: #cfd6e1;
  margin: 0 0 2px;
  white-space: nowrap;
}
.math-standalone {
  font-family: var(--math);
  font-size: 18px;
  color: #dbe1ea;
  text-align: center;
  margin: 22px 0;
}
.mv { font-style: italic; }
/* A RECURRING DIGIT, with the bar over it. Drawn as an overline rather
   than with a combining character, which not every face carries. */
.mrec { text-decoration: overline; text-decoration-thickness: 1px; }
.math-aside {
  font-family: var(--sans);
  font-style: normal;
  font-size: 13px;
  color: #8d94a3;
  white-space: normal;
}
.essay-section .math-line sub { font-size: 0.68em; }

/* THE CLAIM the whole first half of the piece is built towards. */
.essay-claim {
  font-weight: 600;
  text-align: center;
  color: #e8ecf3;
  font-size: 19px;
  line-height: 1.6;
  margin: 30px auto 30px;
  max-width: 34em;
}
/* A SUBHEADING inside a section. The rule down the left is built from
   sections, so these are not ticks — they are the piece's own smaller
   joints. */
.essay-sub {
  font-family: var(--mono);
  font-size: 14.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8eb4e2;
  margin: 34px 0 14px;
  font-weight: 500;
}
/* The owner's own note to themselves, left in the page on purpose. */
.essay-check {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--brass, #b98a3e);
  white-space: nowrap;
}
/* A LINE THE PIECE COINS A NAME IN — the owner's "will be further
   referred to as". Set apart from the reasoning round it, because
   everything after it uses the name. */
.essay-coin {
  border-left: 2px solid rgba(142, 180, 226, 0.5);
  padding-left: 18px;
  color: #dbe1ea;
}
.essay-section p.essay-coin { text-align: left; hyphens: none; }
.essay-assumptions > li { margin-bottom: 26px; }
.essay-assumptions p { margin-bottom: 14px; }

/* THE GRAPH — the modified IBR across the three stages. */
.graph { max-width: 640px; }
.graph-axis { stroke: rgba(170, 178, 193, 0.7); stroke-width: 1.25; }
.graph-grid { stroke: rgba(150, 160, 176, 0.16); stroke-width: 1; }
/* The nine lines inside each decade of a log scale. They have to be
   there — without them a log axis reads as an odd linear one — and
   faint enough not to be read as readings. */
.graph-grid-fine { stroke: rgba(150, 160, 176, 0.07); }
.graph-hold {
  stroke: rgba(185, 138, 62, 0.65);
  stroke-width: 1;
  stroke-dasharray: 5 5;
}
.graph-hold-name {
  font-family: var(--math);
  font-size: 15px;
  fill: var(--brass, #b98a3e);
  text-anchor: end;
}
.graph-tick {
  font-family: var(--math);
  font-size: 15px;
  fill: #8d94a3;
  text-anchor: end;
}
.graph-mid { text-anchor: middle; }
.graph-run { fill: none; stroke: #8eb4e2; stroke-width: 1.6; }
.graph-dot { fill: #8eb4e2; }
.graph-read {
  font-family: var(--math);
  font-size: 15px;
  fill: #dbe1ea;
}
.graph-read-end { text-anchor: end; }
.graph-axis-name {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  fill: #aab2c1;
}

@media (max-width: 720px) {
  /* Three diagrams abreast do not survive a phone: they go one under
     the other, each still carrying its own working. */
  .zone-row { grid-template-columns: minmax(0, 1fr); gap: 34px; }
  .zone-cell .zone { max-width: 240px; }
  .zone-pair { grid-template-columns: minmax(0, 1fr); }
  .zone-legend { flex-direction: column; align-items: flex-start; gap: 6px; }
  .zone-legend .zone-small { max-width: 150px; }
  .math-line { white-space: normal; }
  .zone-name { font-size: 24px; }
}

/* ==================================================================
   ALMOST HUMAN — works/almost-human.html

   The third house in Scent descriptions. Pineward is paper turned
   faintly green and ADAR is black; this one is the site's OWN paper,
   untinted, and it spends no accent colour anywhere — what it spends
   instead is density. The figures down the margins are drawn on a
   canvas by almost-human.js and nothing here knows about them beyond
   leaving them room.

   The shape of the page is Pineward's and ADAR's: a head, an
   introduction, and then the fragrances, each compacted to a number, a
   small square and a title until it is opened. Where those two differ
   is the ground and the scale, and this file is those two things.
================================================================== */
.human-page {
  background: var(--bg);
  color: var(--ink);
}
.human-field {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.human-head {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(150px, 26vh, 260px) 64px clamp(40px, 7vh, 80px);
}
.human-kicker {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 18px;
}
.human-head h1 {
  font-size: clamp(34px, 6.4vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--ink);
}
.human-head h1 em {
  display: block;
  margin-top: 0.18em;
  font-size: 0.4em;
  font-style: italic;
  letter-spacing: -0.01em;
  color: var(--muted);
}
.human-standfirst {
  margin: 28px 0 0;
  max-width: 54ch;
  color: var(--muted);
  font-size: 17px;
}
/* A CLEARING FOR THE CROWD, and the only thing on this page that is
   here to be empty.

   The owner asked for the figures not to be put over anything. The room
   a page leaves is its margins, and this one's writing is a 940px column
   with 64px of padding either side — so below about eleven hundred
   across there is no margin left to stand a person in, and the crowd
   would have nowhere to be. The page makes one band of room on purpose,
   before the introduction, and almost-human.js stands them in it.

   ABOVE THAT IT IS WORTH NOTHING: the margins are there, the crowd is in
   them, and an empty band would only push the writing down.

   AND THE BAND ONLY APPEARS ON A PHONE, not everywhere the margins have
   run out. By the measure above it would be worth having anywhere under
   about eleven hundred across — but the standing rule here is that
   NOTHING ABOVE 700px MAY CHANGE, and a band that pushes the writing
   down a third of a screen is a change. Between the two the crowd
   simply has fewer clearings to stand in, and stands over nothing
   either way, which is what was actually asked for.

   The house's mark used to stand here, at the head of the piece. It is
   shown by the hand now and nowhere else — see THE HOUSE'S MARK, IN THE
   GLITCH in almost-human.js. */
.human-gap { height: 0; }
@media (max-width: 700px) {
  .human-gap { height: clamp(240px, 40vh, 360px); }
}
/* A list inside a fragrance's writing. One of the five is written with
   one; the rest are prose. */
.human-list {
  margin: 14px 0 0;
  padding-left: 20px;
  list-style: none;
}
.human-list li {
  position: relative;
  margin: 7px 0 0;
  line-height: 1.7;
}
.human-list li::before {
  content: "";
  position: absolute;
  left: -20px;
  top: 0.78em;
  width: 9px;
  height: 1px;
  background: var(--line);
}
/* A NUMBERED one, where the owner numbered the points (Flamenco EDP's
   maceration and temperature): the number in the mono, where the rule
   stands in the other. */
ol.human-list { counter-reset: human-step; }
ol.human-list li { counter-increment: human-step; color: #45443c; }
ol.human-list li::before {
  content: counter(human-step) ".";
  top: 0;
  width: auto;
  height: auto;
  background: none;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.7rem;
  color: var(--muted);
}

.human-intro {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(40px, 7vh, 86px) 64px clamp(20px, 3vh, 34px);
}
.human-section-mark {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  color: var(--muted);
  margin: 0 0 10px;
}
.human-intro h2 {
  font-size: clamp(22px, 3vw, 34px);
  letter-spacing: -0.01em;
  margin: 0;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}
.human-text { max-width: 62ch; }
.human-text p { margin: 0 0 16px; color: #45443c; }
.human-intro .human-text { margin-top: 22px; }
/* A note the owner has written and means, set apart from the
   placeholder writing round it — ADAR's ADAR Effect paragraph does the
   same job on that page. */
.human-text p.human-note {
  color: var(--ink);
  border-left: 1px solid var(--line);
  padding-left: 18px;
}
/* What is still waiting to be written, and says so. It is drawn as a
   placeholder rather than as writing, so an unfinished house reads as
   unfinished instead of as thin. */
.human-text p.human-waiting {
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.06em;
  color: var(--muted);
  border: 1px dashed var(--line);
  padding: 14px 16px;
}

.human-parts {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 0 64px clamp(30px, 6vh, 70px);
}
.human-part { border-bottom: 1px solid var(--line); }
.human-part > summary {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 14px 0;
  cursor: pointer;
  list-style: none;
}
.human-part > summary::-webkit-details-marker { display: none; }
.human-no {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  flex: none;
  width: 26px;
}
/* The small square is always a square, whatever shape the photograph
   is: cropped to fill one rather than squashed into one, with
   `--focus` saying which part of it to keep. ADAR's thumb explains the
   reasoning at length. */
.human-thumb {
  flex: none;
  width: 56px;
  height: 56px;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(-45deg, rgba(var(--ink-rgb),0.09) 0px, rgba(var(--ink-rgb),0.09) 1px, transparent 1px, transparent 12px);
}
.human-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
  display: block;
}
.human-title { font-size: 18px; color: var(--ink); }
.human-part > summary::after {
  content: "";
  flex: 1;
  height: 1px;
  background-image: radial-gradient(circle, rgba(var(--ink-rgb),0.3) 0.5px, transparent 0.6px);
  background-size: 6px 1px;
  background-repeat: repeat-x;
  order: 3;
}
.human-cue {
  order: 4;
  flex: none;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  padding: 3px 9px;
}
.human-part[open] > summary .human-cue,
.human-part > summary:hover .human-cue { color: var(--ink); border-color: #b6b1a4; }

.human-body {
  display: flex;
  gap: 36px;
  align-items: flex-start;
  padding: 6px 0 34px 46px;
}
.human-plate {
  flex: none;
  width: 320px;
  margin: 0;
}
.human-plate > div {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(-45deg, rgba(var(--ink-rgb),0.09) 0px, rgba(var(--ink-rgb),0.09) 1px, transparent 1px, transparent 16px);
}
.human-plate img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
}
/* A picture and its placeholder are never both on the page:
   almost-human.js takes an <img> off if the file is not there, which
   puts the placeholder back. */
.human-plate:has(img) > div { display: none; }
/* MORE THAN ONE PICTURE: the rest stand in a row under the first.
   A <span>, so the rule above cannot take it for the placeholder. */
.human-plate-more {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 10px;
}
.human-plate .human-plate-more img { aspect-ratio: 2 / 3; object-fit: cover; }
.human-plate figcaption {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  margin-top: 10px;
}
/* WHERE A PICTURE CAME FROM, under its own name — the individual
   fragrances' pictures come from eight different places, so each is
   credited under itself rather than in one line at the foot. Smaller
   than the name and quieter: a credit is looked for, not read. */
.human-plate-credit,
.frag-plate-credit {
  display: block;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.human-plate-credit a,
.frag-plate-credit a { color: inherit; text-underline-offset: 2px; }
.human-plate-credit a:hover,
.frag-plate-credit a:hover { color: var(--ink); }
.frag-plate-credit { margin-top: 10px; }
/* A STAGE of a fragrance — top, mid, base, a sidenote. The owner
   writes in those on ADAR and this house takes the same label, so the
   two read as one person's notes. */
.human-stage {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 12px;
  font-weight: 400;
}
.human-stage::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
.human-stage + p { margin-top: 0; }
.human-part .human-text p + .human-stage { margin-top: 26px; }

/* THE RANK down the side, and the reading in the corner. Pineward's
   trunk and ADAR's sounding by a third name: the LINE is the whole of
   the page and the fill grows down it from the very first pixel of
   scroll, while the TICKS count the fragrances you have been past.
   Two readings, said separately, on all three houses. */
.human-rank {
  position: fixed;
  left: 28px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: 2;
  pointer-events: none;
}
.human-rank-line {
  position: relative;
  width: 1px;
  height: 34vh;
  background: rgba(var(--ink-rgb), 0.16);
}
.human-rank-fill {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 100%;
  background: rgba(var(--ink-rgb), 0.55);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.25s var(--menu-ease);
}
.human-ticks { display: flex; flex-direction: column; gap: 6px; }
.human-tick {
  width: 8px;
  height: 1px;
  background: rgba(var(--ink-rgb), 0.25);
  transition: width 0.3s var(--menu-ease), background-color 0.3s ease;
}
.human-tick.passed { width: 16px; background: var(--ink); }
.human-readout {
  position: fixed;
  right: 28px;
  bottom: 24px;
  margin: 0;
  text-align: right;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted);
  z-index: 2;
  pointer-events: none;
}
.human-readout-no { color: var(--ink); }
.human-readout-where {
  display: block;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The parts come up as they are reached. Nothing is hidden until the
   script says it is watching, so a blocked script leaves every one of
   them on the page. */
.human-ready .human-part,
.human-ready .human-rises {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.62s var(--menu-ease), transform 0.62s var(--menu-ease);
}
.human-ready .human-part.arrived,
.human-ready .human-rises.arrived { opacity: 1; transform: none; }

.human-foot {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: 40px 64px 80px;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.human-back,
.human-on {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-decoration: none;
}
.human-on { text-align: right; }
.human-back:hover, .human-on:hover { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .human-rank-fill { transition: none; }
  .human-tick { transition: none; }
  .human-ready .human-part,
  .human-ready .human-rises { opacity: 1; transform: none; transition: none; }
}

@media (max-width: 1080px) {
  .human-rank { display: none; }
}
@media (max-width: 860px) {
  .human-head,
  .human-intro,
  .human-parts,
  .human-foot { padding-left: 24px; padding-right: 24px; }
  .human-body { flex-direction: column; padding-left: 0; gap: 20px; }
  .human-plate { width: 100%; }
}

/* ==================================================================
   THE HOUSE SHAPE, AND THE THREE THINGS ONLY THE NEWER HOUSES NEED

   Everything above is written for Almost Human and is the shape every
   house uses now — Ataraxia, Grande Parfums and Les Abstraits all
   carry `human-page` and the `human-*` classes, and house.js gives
   them the behaviour. The prefix is where those names were born, not
   what they mean; renaming them would touch three working pages and
   their tests to no end.

   What follows is only what those three added.
================================================================== */

/* A FRAGRANCE THAT HAS NO NAME YET. Set apart from a written title
   rather than dressed up as one, so a list of them reads as work still
   to come rather than as a house full of things called Untitled. */
.human-untitled {
  font-style: italic;
  color: var(--muted);
}

/* THE STANDOUT MARK — the owner's own, asked for by hand on Grande
   Parfums' Vintage Memoir: "make a handdrawn star next to this one".
   It is an SVG path with every point nudged off true and every edge
   bowed, so it is a star somebody drew rather than one worked out; it
   is drawn as a STROKE and left unfilled for the same reason. It sits
   after the title and before the cue, and takes no room in the row's
   own layout. */
.human-star {
  display: inline-flex;
  align-items: center;
  margin-left: 9px;
  flex: 0 0 auto;
}
.human-star svg {
  width: 17px;
  height: 17px;
  overflow: visible;
}
.human-star svg path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.35;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* NOT SMELLED YET — the names at the foot of a house, with nothing
   behind them. Pineward has had this since it was written; Grande
   Parfums is the second house to need it, so the rules are here rather
   than a second copy under another prefix. `.pine-waiting` stays as it
   is: it spends the pine green, which is that page's alone. */
.house-waiting {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 8px auto 0;
  padding: 0 64px 20px;
}
.house-waiting-head {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin: 0 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.house-waiting-head h2 {
  margin: 0;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.house-waiting-head .human-section-mark { margin: 0; }
.house-waiting-count {
  margin: 0 0 0 auto;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.house-waiting-list {
  list-style: none;
  margin: 0;
  padding: 0;
  columns: 2;
  column-gap: 48px;
}
.house-waiting-list li {
  break-inside: avoid;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
}
.house-waiting-name { font-size: 15px; color: var(--muted); }

@media (max-width: 860px) {
  .house-waiting { padding-left: 24px; padding-right: 24px; }
}
@media (max-width: 720px) {
  .house-waiting-list { columns: 1; }
}

/* A QUOTATION inside a fragrance's writing — Les Abstraits' La Douleur
   Exquise quotes Fragrantica on where its materials come from. Set in
   from the writing with a rule, in the page's muted ink, so it reads as
   somebody else's words; the line under it says whose. */
/* A WORD THAT CARRIES ITS DEFINITION, shown when it is pointed at or
   reached by the keyboard — the owner's "give the definition if hovered",
   for "exclusion zone" in Tombstone's Evergrow. Underlined in dots, the
   way a word with more to say is marked, and the definition stands in a
   small box above it. */
.human-define {
  position: relative;
  border-bottom: 1px dotted currentColor;
  cursor: help;
}
.human-define::after {
  content: attr(data-define);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  width: min(300px, 76vw);
  transform: translate(-50%, 4px);
  padding: 9px 11px;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.5;
  text-align: left;
  color: var(--bg);
  background: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--menu-ease), transform 0.22s var(--menu-ease);
  z-index: 5;
}
.human-define:hover::after,
.human-define:focus::after { opacity: 1; transform: translate(-50%, 0); }
.human-define:focus { outline: none; }

/* A SPOILER (house.js): a dropdown that opens onto its paragraphs
   blurred under "Are you sure?" — Yes clears them, No shuts it. Drawn in
   the page's own tokens, so it follows a dark house over. Without the
   script `.is-asking` is never set, and it opens onto them plainly. */
.human-spoiler {
  margin: 18px 0 22px;
  border: 1px dashed rgba(var(--ink-rgb), 0.38);
}
.human-spoiler > summary {
  cursor: pointer;
  list-style: none;
  padding: 9px 14px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
}
.human-spoiler > summary::-webkit-details-marker { display: none; }
.human-spoiler > summary::before { content: "\25B8\00a0\00a0"; }
.human-spoiler[open] > summary::before { content: "\25BE\00a0\00a0"; }
.human-spoiler-body { position: relative; padding: 2px 16px 12px; }
.human-spoiler-text { transition: filter 0.6s ease; }
.human-spoiler.is-asking:not(.is-sure) .human-spoiler-text {
  filter: blur(7px);
  -webkit-user-select: none;
  user-select: none;
}
.human-spoiler-ask {
  position: absolute;
  inset: 0;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  gap: 12px;
}
.human-spoiler-ask[hidden] { display: none; }
.human-text .human-spoiler-ask p {
  flex-basis: 100%;
  margin: 0;
  text-align: center;
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--ink);
}
.human-spoiler-ask button {
  padding: 6px 18px;
  border: 1px solid var(--ink);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.25s, color 0.25s;
}
.human-spoiler-ask button:hover { background: var(--ink); color: var(--bg); }

.human-quote {
  margin: 4px 0 20px;
  padding: 2px 0 2px 18px;
  border-left: 1px solid var(--line);
}
.human-text .human-quote p { color: var(--muted); font-size: 14.5px; margin: 0 0 10px; }
.human-text .human-quote p.human-quote-from {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  margin: 0;
}
.abstraits-page .human-text a { color: var(--ink); text-underline-offset: 3px; }
.abstraits-page .human-text a:hover { color: var(--muted); }

/* A PICTURE IN THE MIDDLE OF THE WRITING — the drawing of the road in
   Des Cendres, which the owner asked for inside the paragraph that
   describes it. The whole width of the writing, and plain. */
.human-scene { margin: 6px 0 22px; }
.human-scene img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  background: var(--bg-2);
}
.human-scene figcaption {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  margin-top: 8px;
}

/* THE LAST WORD at the foot of a house, after the fragrances and before
   the credit: Les Abstraits ends by sending the reader to Antoine Lie's
   own paragraph. The introduction's box, without its heading. */
.human-after {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(24px, 4vh, 44px) 64px clamp(20px, 3vh, 34px);
}
@media (max-width: 860px) {
  .human-after { padding-left: 24px; padding-right: 24px; }
}

/* THE HOUSE A FRAGRANCE CAME FROM, on the Individual fragrances page
   and nowhere else — a house page never needs it, because there they
   are all the same house. It sits after the title and before the cue,
   and takes no room in the row's own layout. */
.human-house {
  margin-left: 14px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
@media (max-width: 720px) {
  .human-house { display: none; }
}

/* ==================================================================
   ATARAXIA — the same house shape, on dark gray

   The fourth house is the only one of the three that turns the page
   over, and it does it the way the contact sheet and the search page
   do: by REDEFINING THE FIVE TOKENS on its own body class rather than
   by writing a second set of rules for everything on it. Every rule in
   the block above already draws in `--bg`, `--bg-2`, `--line`, `--ink`
   and `--muted`, so setting them here turns the whole page over at
   once and touches no other page at all.

   DARK GRAY, NOT BLACK, which is what the owner asked for and is also
   what keeps it from being ADAR next door — that page is #07070a and
   means it. The bands of glowing particles ataraxia.js draws are white
   and additive, so the ground has to be dark enough for them to read
   as light and light enough not to be a hole.

   WHAT IS LEFT AFTER THE TOKENS is three rules. Each one is a place
   the shared block names a colour outright rather than a token, and
   each is here rather than fixed in place above because the value it
   names is right on the three pages that are on paper.
   ================================================================== */
/* SLIGHTLY DARKER since 2026-09-24, at the owner's word: #212328 was
   the ground for its first rounds. Still gray and not ADAR's black. */
.ataraxia-page {
  --bg: #1b1d21;
  --bg-2: #24262b;
  --line: #373a42;
  --ink: #edeef2;
  --muted: #9a9daa;
  --ink-rgb: 237, 238, 242;
}
/* The reading, which is set a touch softer than the headings on every
   house page. On paper that is a warm near-black; here it has to come
   from the other side. */
.ataraxia-page .human-text p { color: #c4c7d1; }
/* The cue's border when a part is open or pointed at. */
.ataraxia-page .human-part[open] > summary .human-cue,
.ataraxia-page .human-part > summary:hover .human-cue { border-color: #6b6f7b; }

/* ==================================================================
   THE FRAGRANCE READER — one fragrance, in place

   Pressing a fragrance in the Fragrances table used to LEAVE THE PAGE.
   The owner asked for it to open here instead, one at a time: the
   table fades away, the page goes blank, and that fragrance comes up
   on it with its picture, its writing and its notes.

   THE GRID IS THE PAGE'S OWN. The contact sheet is ruled into 46px
   squares (`--grid-cell`, set on :root and spent in `.sheet-page`), and
   the reader is ruled into the same ones — the SAME declaration, so
   they cannot drift apart. It is the reader's ground and it is also
   what a picture goes home to on the way back: it recedes into one of
   these squares, so the two grids being the same grid is not a
   nicety.

   IT BRIEFLY HAD A GRID OF ITS OWN, at about 90px, drawn as a lattice
   of elements. That was written before anyone noticed the page was
   already ruled, and it looked like a second grid over the first —
   which is exactly what it was.
   ================================================================== */
.frag-reader {
  position: fixed;
  inset: 0;
  z-index: 20;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(to right, rgba(var(--ink-rgb), 0.035) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--ink-rgb), 0.035) 1px, transparent 1px),
    var(--bg);
  background-size:
    var(--grid-cell) var(--grid-cell),
    var(--grid-cell) var(--grid-cell),
    auto;
  /* The grid is painted against the WINDOW rather than scrolling with
     the reading, so the squares a picture goes home to are the squares
     you can see. */
  background-attachment: fixed;
  opacity: 0;
  /* SLOWER AND SMOOTHER, which the owner asked for in as many words.
     The arrival is longer than the departure on purpose: coming to a
     fragrance should feel like settling onto it, and leaving should
     not feel like waiting. */
  transition: opacity 700ms var(--menu-ease);
}
.frag-reader.is-here { opacity: 1; }
.frag-reader[hidden] { display: none; }
/* The page fading out from under it, which is the whole of the
   "everything goes blank" the owner asked for. */
.index-page.is-going {
  opacity: 0;
  transition: opacity 560ms var(--menu-ease);
  pointer-events: none;
}
/* AND IT REALLY GOES, which it did not for one round.

   `hidden` is an ATTRIBUTE, and the browser's own rule for it —
   `[hidden] { display: none }` — lives in the user-agent stylesheet,
   which ANY author rule outranks. `.index-page` sets a `display` of
   its own, so setting `hidden` on it did nothing at all: the table
   stayed exactly where it was, and the instant the script took
   `is-going` off it — in the same breath, believing it hidden — it
   SNAPPED BACK to full strength and sat there while the reader faded
   in over it. That is the owner's "there is a lag where the thing in
   the back appears and it looks choppy".

   AND ONE `[hidden]` RULE WAS NOT ENOUGH, which is worth writing down
   because the first fix looked right and changed nothing. This view
   sets the page's display AGAIN, through
   `.view[data-view="fragrances"] .index-page` — three class-level
   parts against a plain `.index-page[hidden]`'s two, so the plain one
   loses and the page went on being a flex box. Both are written here,
   and the longer one is what actually does the work.

   THE LESSON GENERALISES: anything on this site given a `display` of
   its own needs an `[hidden]` rule that outranks EVERY rule giving it
   one, not just the first. */
.index-page[hidden],
.view[data-view="fragrances"] .index-page[hidden] { display: none; }
/* Nothing scrolls behind a reader standing over the whole window. */
body.frag-open { overflow: hidden; }

.frag-in {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(80px, 14vh, 150px) 64px clamp(60px, 10vh, 110px);
  transition: opacity 460ms var(--menu-ease);
}
/* THE WAY BACK IS A FADE — "Just make it fade away smoothly"
   (2026-09-27): the whole reader, writing, pictures and ground at once,
   eased at both ends, over the list coming back behind it. (It was the
   writing going first and the pictures flying into the grid.) */
.frag-reader.is-leaving { transition: opacity 620ms cubic-bezier(0.45, 0, 0.25, 1); }

.frag-kicker {
  display: flex;
  gap: 14px;
  margin: 0 0 14px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.frag-name {
  margin: 0 0 34px;
  font-size: clamp(30px, 5vw, 56px);
  line-height: 1.04;
  letter-spacing: -0.02em;
  color: var(--ink);
}
/* THE PICTURE BESIDE THE WRITING, which is the shape a fragrance has
   on its own page. Stacked, the plate is a 940px square and the
   writing starts below the fold — the first version of this did
   exactly that and the notes were two screens down. */
.frag-body {
  display: grid;
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  gap: 48px;
  align-items: start;
}
@media (max-width: 900px) {
  .frag-body { grid-template-columns: 1fr; gap: 32px; }
  .frag-plate { max-width: 360px; }
}
.frag-plate { margin: 0; }
.frag-plate > div {
  width: 100%;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--line);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(-45deg, rgba(var(--ink-rgb),0.09) 0px, rgba(var(--ink-rgb),0.09) 1px, transparent 1px, transparent 16px);
}
.frag-plate img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
}
.frag-plate:has(img) > div { display: none; }
/* MORE THAN ONE PICTURE — Haxan carries three. The first stands full
   width and the rest stand in a row under it, the way they do on the
   page the writing is fetched from. It is a <span> and not a <div> so
   the rule above, which takes the placeholder away once a picture is
   there, cannot take this away with it. */
.frag-plate-more {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 10px;
}
.frag-plate-more img { aspect-ratio: 2 / 3; object-fit: cover; height: auto; }

.frag-text { max-width: 62ch; }
.frag-side { min-width: 0; }
.frag-text p { margin: 0 0 16px; color: #45443c; }
.frag-text p.human-waiting,
.frag-waiting {
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.06em;
  color: var(--muted);
  border: 1px dashed var(--line);
  padding: 14px 16px;
}
.frag-notes { max-width: 62ch; margin-top: 26px; }
.frag-notes-head {
  margin: 0 0 4px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}

/* THE ARROW AT THE FOOT, which is where the owner asked for it: "when
   you finish reading, there should be an arrow at the end". */
.frag-back {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-top: 54px;
  padding: 11px 18px 12px;
  background: none;
  border: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.3s ease, gap 0.3s var(--menu-ease);
}
.frag-back:hover { border-color: var(--ink); gap: 18px; }
.frag-arrow { font-size: 14px; }

@media (max-width: 700px) {
  .frag-in { padding: 96px 20px 70px; }
}
/* With motion turned off there is no blanking and no fading: the
   reader is simply there, and simply gone again. */
@media (prefers-reduced-motion: reduce) {
  .frag-reader,
  .frag-in,
  .index-page.is-going { transition: none; }
  .frag-reader { opacity: 1; }
}

/* ==================================================================
   VIEW NOTES — the pyramid, in a window over the page

   It opened BESIDE the writing at first, as a third column in the
   part's own row, and the owner asked for a window instead. That was
   the simpler thing as well as the asked-for one: standing in the row,
   the panel had to be paid for out of somebody's width — a part is a
   picture and a column of writing inside 940px, and a 252px panel left
   the writing about 110px across until the plate was told to give way.

   A WINDOW COSTS THE LAYOUT NOTHING, so all of that is gone: no
   `notes-on`, no narrowed plate, no separate arrangement for a phone.
   One window, sized against whatever it is opened on.

   `notes.js` builds the button and the window and puts the window on
   the BODY — a fixed thing inside an ancestor carrying a transform is
   positioned against that ancestor, and a part's box is given one
   while it opens. Without the script there is neither, and the page is
   all of its writing exactly as before.
================================================================== */

/* THE BUTTON. "in a different font and emphasized" — so it is the
   site's mono rather than its reading face, ruled, letterspaced and
   set apart from the paragraph above it. It is a real <button>, so it
   takes the keyboard and the global focus ring without anything here.
   AS WIDE AS THE WRITING, and plainer to see, since the night of
   2026-09-25: "make the notes button in all fragrances more visible, and
   make it wider, so that it stretches from where it is to the cursor" —
   it was a small box the width of its words in a hairline the colour of
   the page's rules. A second one (Almost Human's landscape) stands
   under the first. */
.note-open {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  margin: 28px 0 0;
  padding: 13px 16px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: left;
  color: var(--ink);
  background: rgba(var(--ink-rgb), 0.035);
  border: 1px solid rgba(var(--ink-rgb), 0.5);
  cursor: pointer;
  transition: border-color 220ms ease, color 220ms ease, background-color 220ms ease;
}
.note-open + .note-open { margin-top: 10px; }
.note-open:hover { border-color: var(--ink); background: rgba(var(--ink-rgb), 0.07); }
/* The registration mark this site uses for anything that opens: a
   hollow square, filled in while it is open. */
.note-open-mark {
  flex: none;
  width: 8px;
  height: 8px;
  border: 1px solid currentColor;
  transition: background 220ms ease;
}
.note-open.is-on .note-open-mark { background: currentColor; }

/* THE SCRIM. Its own layer under the window, and the thing that makes
   pressing the page behind close it. */
.note-scrim {
  position: fixed;
  inset: 0;
  background: rgba(26, 25, 22, 0.42);
  opacity: 0;
  transition: opacity 300ms ease;
  z-index: 90;
}
.note-scrim.is-on { opacity: 1; }
.note-scrim[hidden] { display: none; }

/* The page behind a window does not scroll under it. */
body.note-holding { overflow: hidden; }

/* THE WINDOW. Centred on the viewport rather than anchored to
   anything on the page, because it belongs to the fragrance rather
   than to a place in it. It is sized against the window it is
   standing in — `min()` rather than a media query — so a phone and a
   desktop get the same window at the size each has room for. */
.note-panel {
  position: fixed;
  left: 50%;
  top: 50%;
  width: min(430px, calc(100vw - 36px));
  max-height: min(74vh, 640px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--bg);
  border: 1px solid var(--line);
  box-shadow: 0 24px 64px rgba(26, 25, 22, 0.22);
  padding: 0 26px 26px;
  z-index: 91;
  /* Up a hair and a shade small to begin with, so it arrives rather
     than appearing. */
  transform: translate(-50%, -48%) scale(0.985);
  opacity: 0;
  transition: opacity 300ms ease, transform 300ms cubic-bezier(0.42, 0.02, 0.24, 1);
}
.note-panel.is-up {
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
}
.note-panel[hidden] { display: none; }

/* THE BAR ACROSS THE TOP, which stays put while the notes scroll under
   it — a long flat list can be forty notes deep. */
.note-bar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 22px 0 14px;
  margin-bottom: 4px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.note-head {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  min-width: 0;
}
.note-head-say {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
/* THE FRAGRANCE'S OWN NAME. A window standing over the page has left
   its fragrance behind, so it says which one it belongs to — which
   the panel beside the writing never had to. */
.note-head-of {
  font-size: 15px;
  color: var(--ink);
}
.note-shut {
  flex: none;
  margin-left: auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  line-height: 1;
  color: var(--muted);
  background: none;
  border: 0;
  cursor: pointer;
  transition: color 200ms ease;
}
.note-shut:hover { color: var(--ink); }

.note-pyramid { margin: 0; }
.note-row {
  display: grid;
  grid-template-columns: 46px 1fr;
  gap: 0 12px;
  padding: 11px 0;
  border-top: 1px solid var(--line);
}
.note-row:first-child { border-top: 0; }
/* LANDSCAPE is a longer word than TOP, MID, BASE or NOTES, and the
   label column is sized for those. Only the Almost Human window uses
   this, and it carries one row, so nothing has to line up with
   anything. */
.note-pyramid-wide .note-row { grid-template-columns: 78px 1fr; }
.note-row dt {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 2px;
}
.note-row dd {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
}
/* THE LINE THAT SAYS THE SOURCE GAVE NO DIVISION. It stands above the
   list rather than under it, because it changes what the list means.*/
.note-undivided,
.note-aside {
  margin: 12px 0 10px;
  font-size: 12px;
  line-height: 1.6;
  font-style: italic;
  color: var(--muted);
}
.note-aside { margin: 14px 0 0; font-style: normal; }

/* WHICH VERSION THESE NOTES BELONG TO, and the owner asked for it to be
   said plainly: several Pineward fragrances have been reformulated and
   the note list changes underneath the name, so a list with no year on
   it is a list you cannot check. It stands at the very top of the
   window, above everything, because it qualifies all of it. */
.note-version {
  margin: 14px 0 0;
  padding: 9px 11px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg-2);
  border-left: 2px solid var(--ink);
}
.note-version span {
  display: block;
  margin-bottom: 3px;
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.note-version strong { font-weight: 500; }

/* THE HEADING OVER ONE OF TWO LISTS. Only Haxan has two — the
   perfumer's own account above, Fragrantica's reading of the same
   fragrance below — so this is what tells them apart. */
.note-half {
  margin: 16px 0 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
}
/* And the second list is set off from the first by a rule rather than
   by space alone, because the two are the same shape and would
   otherwise read as one long list with a stray heading in it. */
.note-also {
  margin-top: 22px;
  padding-top: 4px;
  border-top: 1px solid var(--line);
}

.note-waiting {
  margin: 14px 0 0;
  padding: 13px 14px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.05em;
  line-height: 1.7;
  color: var(--muted);
  border: 1px dashed var(--line);
}
.note-source {
  margin: 16px 0 0;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.05em;
  line-height: 1.7;
  color: var(--muted);
  border-top: 1px solid var(--line);
  padding-top: 12px;
}
.note-source span {
  display: block;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  margin-bottom: 3px;
}
.note-source { position: relative; }
.note-cite { color: var(--muted); text-underline-offset: 3px; }
a.note-cite:hover { color: var(--ink); }

/* A WORD OF WARNING ON A SOURCE. Only Fragrantica carries one: the
   house's own page is the first source for every fragrance here and
   Fragrantica is the fallback, so where it is named it is named
   because the house publishes nothing.

   IT STANDS ABOVE THE LINE RATHER THAN BELOW IT, and that is not a
   preference: the window scrolls inside itself, the source is the last
   thing in it, and a tooltip hung underneath would be clipped off the
   bottom every time. Above, it sits over the notes, inside the window.

   Shown on hover AND on keyboard focus — a caution only a mouse can
   reach is not a caution. Screen readers are told it either way, by
   the `aria-describedby` the script writes. */
.note-warn {
  position: absolute;
  left: 0;
  bottom: 100%;
  margin-bottom: 9px;
  width: max-content;
  max-width: 100%;
  padding: 9px 11px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.03em;
  line-height: 1.55;
  text-transform: none;
  color: var(--bg);
  background: var(--ink);
  opacity: 0;
  visibility: hidden;
  transform: translateY(3px);
  transition: opacity 180ms ease, transform 180ms ease, visibility 180ms;
  pointer-events: none;
  z-index: 2;
}
/* The little point down at the line it belongs to. */
.note-warn::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 14px;
  border: 5px solid transparent;
  border-top-color: var(--ink);
}
.note-cite:hover ~ .note-warn,
.note-cite:focus ~ .note-warn,
.note-cite:focus-visible ~ .note-warn {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.adar-page .note-warn { color: #07070a; background: #dcdce4; }
.adar-page .note-warn::after { border-top-color: #dcdce4; }
.adar-page .note-cite { color: #a8a8b4; }
.adar-page a.note-cite:hover { color: #dcdce4; }

@media (prefers-reduced-motion: reduce) {
  .note-warn { transition: none; }
}

/* ADAR IS THE ONE HOUSE ON A DARK GROUND, and its writing's colours
   are written rather than taken from the tokens, so the window's are
   too. The window is on the BODY, which on that page IS `.adar-page`,
   so these still reach it. */
.adar-page .note-panel {
  background: #07070a;
  border-color: rgba(232, 232, 238, 0.28);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.66);
}
.adar-page .note-bar {
  background: #07070a;
  border-bottom-color: rgba(232, 232, 238, 0.28);
}
.adar-page .note-head-of { color: #dcdce4; }
.adar-page .note-row { border-top-color: rgba(232, 232, 238, 0.18); }
.adar-page .note-row dd { color: #dcdce4; }
.adar-page .note-source { border-top-color: rgba(232, 232, 238, 0.18); }
.adar-page .note-waiting { border-color: rgba(232, 232, 238, 0.28); }
.adar-page .note-version {
  color: #dcdce4;
  background: rgba(232, 232, 238, 0.07);
  border-left-color: #dcdce4;
}
.adar-page .note-half { color: #dcdce4; }
.adar-page .note-also { border-top-color: rgba(232, 232, 238, 0.18); }
.adar-page .note-scrim { background: rgba(0, 0, 0, 0.62); }
.adar-page .note-shut { color: #a8a8b4; }
.adar-page .note-shut:hover { color: #dcdce4; }
.adar-page .note-open { color: #dcdce4; border-color: rgba(232, 232, 238, 0.5); background: rgba(232, 232, 238, 0.04); }
.adar-page .note-open:hover { border-color: #dcdce4; }

@media (max-width: 560px) {
  /* A little more of the screen, and a little less padding, because at
     this width every millimetre is the notes. */
  .note-panel {
    width: calc(100vw - 24px);
    max-height: 80vh;
    padding: 0 18px 20px;
  }
  .note-bar { padding-top: 18px; }
}

@media (prefers-reduced-motion: reduce) {
  .note-panel,
  .note-scrim { transition: none; }
  .note-panel { transform: translate(-50%, -50%) scale(1); }
}


/* ==================================================================
   THE NOTE DISSEMINATION CALCULATOR — works/theory-03.html

   It stands in the SAME page as the theory rather than in one of its
   own: the owner asked for the stars behind it to "remain where they
   are and on screen", and a second page would throw that field away
   and roll a different one. So the reading fades out, the calculator
   fades in, and essay.js goes on drawing behind both without being
   told anything.

   Everything on it is built by calculator.js, which is why there is
   nothing of it in the markup — with the script blocked the button
   that opens it does nothing and the theory is unharmed.
================================================================== */
/* THE WAY IN — a button the width of the reading, because the owner
   asked for a BIG screen-wide one. */
.calc-enter {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
  margin: 34px 0 10px;
  padding: clamp(22px, 4vh, 38px) clamp(20px, 4vw, 40px);
  background: rgba(142, 180, 226, 0.06);
  border: 1px solid rgba(142, 180, 226, 0.4);
  color: #e8ecf3;
  cursor: pointer;
  text-align: left;
  transition: background-color 0.3s var(--menu-ease), border-color 0.3s var(--menu-ease);
}
.calc-enter:hover, .calc-enter:focus-visible {
  background: rgba(142, 180, 226, 0.13);
  border-color: rgba(142, 180, 226, 0.75);
}
.calc-enter-word {
  font-size: clamp(28px, 5vw, 54px);
  line-height: 1;
  letter-spacing: -0.02em;
  font-weight: 600;
}
.calc-enter-cue {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8eb4e2;
  flex: none;
}

/* THE CHANGE-OVER. Two fades that never both run: the reading goes,
   then the calculator comes. The field is not in either of them. */
.essay-page.calc-going .essay-head,
.essay-page.calc-going .essay-body,
.essay-page.calc-going .essay-foot {
  opacity: 0;
  transition: opacity 0.4s var(--menu-ease);
  pointer-events: none;
}
/* HIDING THE READING IS A CLASS AND NOT THE `hidden` ATTRIBUTE.
   `[hidden]` is a user-agent rule with almost no weight, and every one
   of these three carries a `display` of its own further up this file —
   so setting `hidden` on them did nothing at all and the essay's foot
   and its rule stayed on the window over the calculator. */
.essay-page.calc-on .essay-head,
.essay-page.calc-on .essay-body,
.essay-page.calc-on .essay-foot,
.essay-page.calc-on .essay-rule { display: none; }

.calc {
  position: relative;
  z-index: 1;
  max-width: 940px;
  margin: 0 auto;
  padding: clamp(110px, 18vh, 190px) 64px 90px;
  animation: calc-in 0.5s var(--menu-ease) both;
}
.calc[hidden] { display: none; }
.calc.calc-going-out { animation: calc-out 0.32s var(--menu-ease) both; }
@keyframes calc-in { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes calc-out { from { opacity: 1; } to { opacity: 0; } }

/* Focused by the script when the calculator opens, so that a reader
   using the keyboard lands on it rather than back at the top of a page
   that has changed under them. It is not a control, so it does not
   wear the site's focus ring. */
.calc-head h2:focus { outline: none; }
/* An h2: the page's one h1 is the piece's own title (2026-09-27). */
.calc-head h2 {
  font-size: clamp(28px, 4.6vw, 52px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 26px;
  color: #f1f4f8;
}
.calc-lede {
  max-width: 62ch;
  color: #aab2c1;
  font-size: 17px;
  line-height: 1.7;
  margin: 0 0 16px;
}
.calc-review {
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(142, 180, 226, 0.6);
  color: #8eb4e2;
  font: inherit;
  padding: 0;
  cursor: pointer;
}
.calc-review:hover, .calc-review:focus-visible { color: #dbe1ea; }

/* THE THREE MODELS. */
.calc-models {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: 34px 0 10px;
  align-items: flex-start;
}
.calc-model-wrap { display: flex; flex-direction: column; gap: 8px; max-width: 320px; }
.calc-model {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  padding: 13px 20px;
  background: none;
  border: 1px solid rgba(150, 160, 176, 0.45);
  color: #aab2c1;
  cursor: pointer;
  transition: color 0.25s var(--menu-ease), border-color 0.25s var(--menu-ease),
              background-color 0.25s var(--menu-ease);
}
.calc-model:hover, .calc-model:focus-visible { color: #e8ecf3; border-color: #8eb4e2; }
.calc-model.chosen {
  color: #0d1016;
  background: #8eb4e2;
  border-color: #8eb4e2;
}
.calc-model.chosen .mv { color: inherit; }
/* The owner's own caveat on the second variation, which has to be
   read BEFORE the rest of it arrives. */
.calc-caveat {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: #8d94a3;
  max-width: 34ch;
}

.calc-body[hidden] { display: none; }
.calc-part { margin: 46px 0 0; }
.calc-part h2 {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8eb4e2;
  font-weight: 500;
  margin: 0 0 18px;
}
/* THE RESET stands on the line of the heading of the thing it clears,
   over at the other end of it, rather than under the fields: there it
   would read as another control to fill in. The heading keeps its own
   bottom margin, so the row needs none. */
.calc-part-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
}
.calc-reset {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 7px 14px;
  margin: 0 0 18px;
  background: none;
  border: 1px solid rgba(150, 160, 176, 0.45);
  color: #aab2c1;
  cursor: pointer;
  transition: color 0.25s var(--menu-ease), border-color 0.25s var(--menu-ease),
              opacity 0.25s var(--menu-ease);
}
.calc-reset:hover, .calc-reset:focus-visible,
.calc-scale:hover, .calc-scale:focus-visible { color: #e8ecf3; border-color: #8eb4e2; }
/* THE LOG SCALE TOGGLE is the reset's twin, with an on state: filled
   means you are looking at the logarithmic version. The same filled
   blue the models and the sign use for "this is the one that is on". */
.calc-scale {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 7px 14px;
  margin: 0 0 18px;
  background: none;
  border: 1px solid rgba(150, 160, 176, 0.45);
  color: #aab2c1;
  cursor: pointer;
  transition: color 0.25s var(--menu-ease), border-color 0.25s var(--menu-ease),
              background-color 0.25s var(--menu-ease);
}
.calc-scale.chosen {
  color: #0d1016;
  background: #8eb4e2;
  border-color: #8eb4e2;
}
.calc-scale.chosen:hover, .calc-scale.chosen:focus-visible { color: #0d1016; }
/* NOTHING TO CLEAR. It stays in the page at the same size rather than
   going away, so the heading's line does not jump about as you type. */
.calc-reset:disabled {
  opacity: 0.32;
  cursor: default;
  color: #aab2c1;
  border-color: rgba(150, 160, 176, 0.45);
}
/* IT ARRIVES FROM THE TOP DOWN, one part behind the last — the owner
   asked for the page to fade in that way once a model is chosen. */
.calc-arriving { animation: calc-part-in 0.5s var(--menu-ease) both; animation-delay: calc(var(--n, 0) * 0.09s); }
@keyframes calc-part-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* THE BIG EQUATION, and the line under it that says what a term is. */
.calc-equation-part { text-align: center; }
.calc-equation {
  font-family: var(--math);
  font-size: clamp(30px, 5.4vw, 58px);
  color: #f1f4f8;
  margin: 0 0 20px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.12em;
}
.calc-eq-op { color: #8d94a3; padding: 0 0.06em; }
/* THE SIGN, with its two halves apart. It is a plus-or-minus, but the
   single `±` character welds its bar to the underside of the plus in
   the face this page is set in, and the owner asked for them separate.
   So it is a `+` and a `−` set one over the other in the same face,
   with the space between them ours to choose. `line-height` is what
   sets that space: both glyphs sit the same height above their own
   baseline, so the gap between them IS the line height. Anything near
   1 reads as two signs stacked; the value below is a plus-or-minus
   with room in it. */
.calc-pm {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  line-height: 0.66;
  vertical-align: -0.08em;
}
.calc-pm-half { display: block; }
/* A TERM SAYS WHAT IT IS WHEN POINTED AT, and the mark that says so is
   an underline UNDER THE LETTERING rather than a border under the box:
   a numerator already has a border under it — the fraction's own rule —
   and two lines stacked a few pixels apart read as a mistake. */
.calc-term {
  font: inherit;
  font-style: italic;
  background: none;
  border: 0;
  color: inherit;
  padding: 0 0.05em;
  cursor: help;
  line-height: 1;
  text-decoration: underline dotted rgba(142, 180, 226, 0.5);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.16em;
}
.calc-term:hover, .calc-term:focus-visible {
  color: #8eb4e2;
  text-decoration-color: #8eb4e2;
}
.calc-say {
  min-height: 3.4em;
  max-width: 58ch;
  margin: 0 auto;
  color: #8d94a3;
  font-size: 15.5px;
  line-height: 1.66;
  transition: color 0.2s ease;
}
.calc-say-on { color: #dbe1ea; }
.calc-note {
  max-width: 62ch;
  color: #aab2c1;
  font-size: 15.5px;
  line-height: 1.7;
  margin: 30px 0 0;
  border-left: 1px solid var(--line, rgba(150,160,176,0.3));
  border-left-color: rgba(150, 160, 176, 0.3);
  padding-left: 18px;
}

/* THE INPUTS, and the diagram that answers them. */
/* THE COUNT AND THE SIGN, on lines of their own. They were a row, and
   a row read as one control with two halves; they are two different
   questions. */
.calc-common {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: flex-start;
  margin: 0 0 28px;
  padding-bottom: 22px;
  border-bottom: 1px solid rgba(150, 160, 176, 0.18);
}
/* ONE STAGE: its numbers on the left, its diagram beside them, and its
   answer under the numbers. Laid out by AREA rather than by counting
   rows, so the diagram can stand level with the middle of the numbers
   however many of them a model asks for — var. 2 has three where var. 1
   has two. */
.calc-stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 250px);
  grid-template-areas: "fields zone" "out zone";
  grid-template-rows: auto auto;
  gap: 18px 34px;
  align-content: start;
  padding: 26px 0;
  border-bottom: 1px solid rgba(150, 160, 176, 0.14);
}
.calc-stage-fields { grid-area: fields; display: flex; flex-direction: column; gap: 12px; }
.calc-stage-zone { grid-area: zone; align-self: center; }
.calc-stage-zone .zone { max-width: 220px; }
.calc-zone-figure { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.calc-stage-out { grid-area: out; align-self: start; }
.calc-field {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--math);
  font-size: 17px;
  color: #dbe1ea;
}
.calc-field-name { min-width: 7.5em; text-align: right; }
.calc-field-unit { color: #8d94a3; }
.calc-input {
  width: 8em;
  background: rgba(150, 160, 176, 0.08);
  border: 0;
  border-bottom: 1px solid rgba(150, 160, 176, 0.5);
  color: #f1f4f8;
  font-family: var(--math);
  font-size: 17px;
  padding: 5px 8px;
}
.calc-input:focus { outline: none; border-bottom-color: #8eb4e2; background: rgba(142, 180, 226, 0.1); }
/* NO STEPPERS. The little up/down arrows a browser puts on a number
   field are the wrong control here: IC and BC are two halves of one
   hundred and move together, so stepping one of them a percent at a
   time reads as though they were independent. Typing is the way in. */
.calc-input::-webkit-outer-spin-button,
.calc-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.calc-input { -moz-appearance: textfield; appearance: textfield; }
.calc-sign { display: flex; align-items: center; gap: 10px; }
.calc-sign .calc-field-name { font-family: var(--math); font-size: 17px; color: #dbe1ea; min-width: 7.5em; }
.calc-sign-pick {
  font-family: var(--math);
  font-size: 19px;
  width: 2.2em;
  padding: 4px 0;
  background: none;
  border: 1px solid rgba(150, 160, 176, 0.45);
  color: #aab2c1;
  cursor: pointer;
}
.calc-sign-pick.chosen { background: #8eb4e2; border-color: #8eb4e2; color: #0d1016; }

/* THE ANSWER, and the working behind it — which the owner asked to be
   shown on hover rather than always. */
.calc-result {
  font-family: var(--math);
  font-size: 21px;
  color: #f1f4f8;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.4em;
}
.calc-result-eq { color: #8d94a3; }
.calc-result-value { color: #8eb4e2; font-weight: 600; }
.calc-steps {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 0.35s var(--menu-ease), opacity 0.3s var(--menu-ease);
}
.calc-stage-out:hover .calc-steps,
.calc-stage-out:focus-within .calc-steps { max-height: 300px; opacity: 1; }
.calc-steps .math-block { margin: 12px 0 0; font-size: 15px; }
.calc-steps .math-line { font-size: 15px; }

/* THE TABLE of the three readings against the thresholds. */
.calc-table-wrap { overflow-x: auto; }
.calc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 15px;
}
.calc-table th {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #8d94a3;
  font-weight: 400;
  text-align: left;
  padding: 0 16px 10px 0;
  border-bottom: 1px solid rgba(150, 160, 176, 0.3);
  white-space: nowrap;
}
.calc-table td {
  padding: 13px 16px 13px 0;
  border-bottom: 1px solid rgba(150, 160, 176, 0.14);
  color: #aab2c1;
  vertical-align: top;
}
.calc-table td:nth-child(1), .calc-table td:nth-child(2), .calc-table td:nth-child(3) {
  font-family: var(--math);
  color: #dbe1ea;
  white-space: nowrap;
}
.calc-table td:nth-child(2) { color: #8eb4e2; }

.calc-graph { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.calc-graph-note {
  margin: 0;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: #8d94a3;
  text-align: center;
  max-width: 46ch;
}
.calc-graph .graph { max-width: 640px; }
.calc-verdict {
  font-size: 18px;
  line-height: 1.7;
  color: #aab2c1;
  max-width: 62ch;
  margin: 0;
}
.calc-verdict strong { color: #8eb4e2; }

.calc-foot { margin: 70px 0 0; padding-top: 26px; border-top: 1px solid rgba(150, 160, 176, 0.2); }
.calc-back {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  background: none;
  border: 0;
  color: #aab2c1;
  cursor: pointer;
  padding: 0;
}
.calc-back:hover, .calc-back:focus-visible { color: #e8ecf3; }

/* THE WINDOW that reviews complication 3. */
.calc-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(8, 10, 14, 0.82);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  animation: calc-out 0.25s var(--menu-ease) reverse both;
}
.calc-modal[hidden] { display: none; }
.calc-modal-sheet {
  max-width: 62ch;
  max-height: 80vh;
  overflow-y: auto;
  background: #11141a;
  border: 1px solid rgba(150, 160, 176, 0.35);
  padding: clamp(26px, 5vw, 46px);
}
.calc-modal-sheet h3 {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8eb4e2;
  font-weight: 500;
  margin: 0 0 18px;
}
.calc-modal-sheet p { color: #aab2c1; font-size: 16px; line-height: 1.72; margin: 0 0 16px; }
.calc-modal-sheet i { font-family: var(--math); }
.calc-modal-shut {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  background: none;
  border: 1px solid rgba(150, 160, 176, 0.45);
  color: #aab2c1;
  padding: 9px 18px;
  margin-top: 10px;
  cursor: pointer;
}
.calc-modal-shut:hover, .calc-modal-shut:focus-visible { color: #e8ecf3; border-color: #8eb4e2; }

@media (prefers-reduced-motion: reduce) {
  .calc, .calc-arriving, .calc-modal { animation: none; }
  .calc-steps { transition: none; }
  .essay-page.calc-going .essay-head,
  .essay-page.calc-going .essay-body,
  .essay-page.calc-going .essay-foot { transition: none; }
}

@media (max-width: 860px) {
  .calc { padding-left: 24px; padding-right: 24px; }
  .calc-stage {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "fields" "zone" "out";
  }
  .calc-stage-zone { justify-self: center; }
  .calc-field-name { min-width: 6em; }
}

/* THE PICTURE ON A STATION'S CARD. A theory that carries `data-plate`
   on its row shows it above the writing when the station is set out —
   see the note in structure.js. It is the one picture on this drawing,
   so it is ruled like everything else rather than floated. */
.structure-card-plate {
  display: block;
  margin: 0 0 16px;
  border: 1px solid rgba(150, 160, 176, 0.28);
  background: #0b0e13;
  overflow: hidden;
}
.structure-card-plate[hidden] { display: none; }
.structure-card-plate img {
  display: block;
  width: 100%;
  height: auto;
}

/* The multiplication sign between two fractions wants air round it —
   a bare `×` between two stacked pairs reads as part of the lower one. */
.mop { color: #8d94a3; padding: 0 0.28em; }

/* ==================================================================
   WHERE THE PICTURES CAME FROM

   One quiet line at the foot of a house, above the way on to the next,
   naming the source its photographs were actually taken from. The owner
   asked for pictures to be credited wherever they are used.

   It is set in the site's mono and kept well under the writing's
   weight: a credit is a thing you go looking for, not a thing that
   competes with the fragrance above it. It sits inside the house's own
   measure so it lines up with the writing rather than with the window.
   ================================================================== */
.house-credit {
  /* THE SAME MEASURE AND THE SAME GUTTER AS THE FOOT BELOW IT — 940px
     wide with 64px of side padding inside that, which is what
     `.pine-foot`, `.adar-foot` and `.human-foot` all use. Written
     without the side padding it was 940px of TEXT against the foot's
     812px, so the credit hung out past the writing on a wide window and
     ran hard into both edges of a phone. */
  max-width: 940px;
  margin: clamp(40px, 7vh, 72px) auto 0;
  /* THE SAME SPACE BELOW THE WORDS AS ABOVE THEM. It had 16px above and
     none below, so the words sat on the foot's rule underneath — the
     owner's "have the text in the middle". */
  padding: 18px 64px 18px;
  border-top: 1px solid var(--line);
  /* AND THE WORDS IN A COLUMN OF THEIR OWN beside the label, so a second
     line starts under the first rather than back under "PICTURES". */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 14px;
  align-items: baseline;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.85;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.house-credit-say {
  display: inline-block;
  margin-right: 0;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
}
.house-credit strong { font-weight: 500; color: var(--ink); }
.house-credit a { color: var(--ink); text-underline-offset: 3px; }
.house-credit a:hover { color: var(--muted); }

/* ADAR'S PAGE IS BLACK, AND SETS NONE OF THE FIVE TOKENS the credit is
   drawn in — it paints its own colours straight onto its rules — so the
   credit came out in the paper pages' ink on black, and its words could
   barely be read (the owner, 2026-09-26). In ADAR's own greys here. */
.adar-page .house-credit { color: #a8a8b4; border-top-color: rgba(232, 232, 238, 0.14); }
.adar-page .house-credit-say,
.adar-page .house-credit strong,
.adar-page .house-credit a { color: #e8e8ee; }
.adar-page .house-credit a:hover { color: #a8a8b4; }

/* THE WAY BACK A HOUSE, beside the way on — "the option to go back a
   house as well as forward a house" (2026-09-26). The two stand together
   on the right of every house's foot, the one before and the one after,
   the ninth and the first wrapping round to each other; the way back to
   Scent descriptions keeps the left. Set in each foot's own mono. */
.house-steps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 10px 18px;
  margin-left: auto;
}
/* THE PARTITION between the two — "put a 'll' partition between the go
   back and go forward between the houses buttons" (2026-09-27): two
   upright hairlines, in the foot's quiet colour, standing between them. */
.house-steps-bar {
  flex: none;
  width: 5px;
  height: 13px;
  border-left: 1px solid currentColor;
  border-right: 1px solid currentColor;
  color: var(--muted);
  opacity: 0.8;
}
.adar-foot .house-steps-bar { color: #8a8a96; }
.house-prev {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-decoration: none;
}
.house-prev:hover { color: var(--ink); }
.adar-foot .house-prev { color: #8a8a96; }
.adar-foot .house-prev:hover { color: #e8e8ee; }
@media (max-width: 700px) {
  /* On a phone the two go under the way back to the category, one to a
     side, rather than both squeezed to the right. */
  .house-steps { width: 100%; justify-content: space-between; margin-left: 0; }
  /* With the partition between them, the two stand side by side, each
     taking its half and wrapping its own words, rather than one under
     the other — where the partition would have nothing to part. */
  .house-steps { flex-wrap: nowrap; }
  .house-steps > a { flex: 1 1 0; min-width: 0; line-height: 1.6; }
  .house-steps > a:last-child { text-align: right; }
}

/* (The picture on an opened favourite was a floated rectangle here,
   `.fav-shot`, until 2026-09-26; it is a framed print now, `.fav-print`,
   with the rest of the opened favourite's rules.) */
@media (max-width: 720px) {
  /* The same 24px the three house feet drop to at this width. */
  .house-credit { padding-left: 24px; padding-right: 24px; }
  /* A long source name in the mono has nowhere to break on a narrow
     screen — "Profumix Luxury Perfumes" ran off the edge — so it is
     allowed to break inside a word here rather than overflow. */
  .house-credit { overflow-wrap: anywhere; }
}

/* ==================================================================
   TALE PARFUMS — the house, drawn by hand

   The owner's brief was "simple and very 'drawn by hand' ... almost
   childish", after the four drawings on the house's labels. tale.js
   draws the doodles in the margins; EVERYTHING ELSE THAT LOOKS DRAWN
   IS HERE, and needs no script:

     THE PICTURES Uneven corners — `border-radius` given eight
                  different radii, `--drawn` — tilted, and held to the
                  page with a strip of TAPE.
     THE CIRCLES  A loop drawn round each number.

   AND NO MORE THAN THAT, which is the owner's second word on it: "not
   make it as hand drawn as you did... SOME additions of
   handdrawn-ness... keep the drawings and decorations... make the
   lines straight and keep the images as they are." So every RULE on
   the page is straight — it was a drawn wave for one round — and the
   boxes (the cue, View notes, the not-yet-written box) have straight
   edges. The doodles, the circles and the pictures are what stayed.

   AND IN THE SITE'S OWN FACE. It was set in two handwriting faces —
   Gochi Hand for the headings, Patrick Hand for the reading — for two
   rounds, and the owner's third word on it was "make the font the same
   as normal please. the other stuff keep." So every line of type on the
   page is the house shape's own, exactly as on Les Abstraits, and
   nothing here sets a face, a size or a spacing.

   IT COMES IN piece by piece rather than all at once — see THE
   ENTRANCE below.

   The page's colour is the five tokens, redefined on its own body class
   the way Ataraxia's are, and it is warm paper rather than white — the
   paper somebody would draw on.
   ================================================================== */
.tale-page {
  --bg: #fbf8f0;
  --bg-2: #f3eee2;
  --line: #d8d0bf;
  --ink: #1f1d18;
  --muted: #6c6556;
  --ink-rgb: 31, 29, 24;
  --drawn: 255px 15px 225px 15px / 15px 225px 15px 255px;
  --loop: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 34'%3E%3Cpath d='M30 4C18 1 5 6 4 17S15 32 25 31S41 23 40 14S31 3 22 5' fill='none' stroke='%231f1d18' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* THE DOODLES' LAYER — the whole height of the page, under everything,
   answering nothing (the boil reads the pointer off the window). */
.tale-doodles {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.tale-doodle { position: absolute; overflow: visible; }
/* DRAWN IN, stroke by stroke. Every stroke is one unit long
   (`pathLength`), so the dash that hides it and the clock that draws it
   are the same for a long stem and a short lash; each starts a beat
   after the one before. */
.tale-to-draw path { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.tale-to-draw .tale-tint { opacity: 0; }
.tale-to-draw.tale-drawn path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset var(--draw, 1100ms) cubic-bezier(0.5, 0.1, 0.3, 1) calc(var(--n, 0) * 70ms);
}
.tale-to-draw.tale-drawn .tale-tint {
  opacity: 0.95;
  transition: opacity 700ms ease var(--draw, 1100ms);
}

/* THE HEAD: the lily, the house's mark, stood to the right of the name
   with a few stars round it. */
.tale-page .human-head { position: relative; }
.tale-head-doodle {
  position: absolute;
  right: 56px;
  top: clamp(96px, 17vh, 170px);
  width: 190px;
  height: 190px;
  pointer-events: none;
}
.tale-head-doodle .tale-doodle { position: absolute; }
.tale-mark { left: 0; top: 0; width: 100%; height: 100%; transform: rotate(-6deg); }
.tale-by-1 { left: -38px; top: 34px; transform: rotate(-12deg); }
.tale-by-2 { left: 166px; top: 150px; }
.tale-by-3 { left: 150px; top: -14px; width: 22px; height: 22px; transform: rotate(14deg); }

.tale-page .human-head h1 {
  display: inline-block;
  padding-bottom: 14px;
  /* ONE STRAIGHT STROKE under the name. It was a drawn double wave for
     a round; the owner asked for the lines straight. */
  border-bottom: 3px solid var(--ink);
}
.tale-page .human-section-mark {
  color: var(--ink);
  display: inline-block;
  padding: 7px 11px;
  background: var(--loop) no-repeat center / 100% 100%;
}
.tale-page .human-intro h2 {
  padding-bottom: 12px;
  border-bottom: 1.5px solid rgba(var(--ink-rgb), 0.55);
}
.tale-page .human-text p.human-waiting {
  border: 1.5px dashed #b9b0a0;
}

/* THE FRAGRANCES: each row ruled off with the house's own straight line,
   its number circled, its drawing pinned on crooked. The rules were
   drawn waves for a round, and the owner asked for them straight. */
.tale-page .human-no {
  color: var(--ink);
  width: 40px;
  text-align: center;
  padding: 7px 0;
  background: var(--loop) no-repeat center / 100% 100%;
}
.tale-page .human-thumb {
  width: 68px;
  height: 68px;
  border: 2px solid var(--ink);
  border-radius: var(--drawn);
  background: #fff;
  overflow: hidden;
  transform: rotate(-3deg);
}
.tale-page .human-part:nth-child(even) .human-thumb { transform: rotate(2.5deg); }
.tale-page .human-cue {
  color: var(--ink);
  border: 1.5px solid var(--ink);
  padding: 2px 13px 3px;
  transition: transform 0.25s var(--menu-ease), background-color 0.25s ease;
}
.tale-page .human-part > summary:hover .human-cue { transform: rotate(-3deg); border-color: var(--ink); }
.tale-page .human-part[open] > summary .human-cue { background: rgba(239, 179, 146, 0.4); border-color: var(--ink); }

/* THE PICTURES, pinned to the page with a strip of tape, crooked. */
.tale-page .human-plate { position: relative; }
.tale-page .human-plate > img {
  border: 2px solid var(--ink);
  border-radius: var(--drawn);
  background: #fff;
  transform: rotate(-1.4deg);
}
.tale-page .human-plate::before {
  content: "";
  position: absolute;
  top: -12px;
  left: 50%;
  width: 96px;
  height: 26px;
  background: rgba(226, 212, 168, 0.72);
  transform: translateX(-50%) rotate(-5deg);
  z-index: 1;
}
.tale-page .human-plate-more img {
  border: 2px solid var(--ink);
  border-radius: var(--drawn);
  background: #fff;
  transform: rotate(2deg);
}
.tale-page .human-plate-more img + img { transform: rotate(-1.6deg); }
.tale-page .note-open { border: 1.5px solid var(--ink); }

/* THE ENTRANCE. Everything is held back while `tale-coming` is on the
   page (set in its own <head>), and tale.js brings the pieces in one
   after another once the handwriting has arrived: a fade and a short
   rise each. The fixed chrome only fades — it is placed with a
   transform of its own, which a rise would undo. */
html.tale-coming .tale-page .human-head > *,
html.tale-coming .tale-page .human-intro,
html.tale-coming .tale-page .human-parts,
html.tale-coming .tale-page .house-credit,
html.tale-coming .tale-page .human-foot,
html.tale-coming .tale-page .tale-doodles,
html.tale-coming .tale-page .human-rank,
html.tale-coming .tale-page .human-readout { opacity: 0 !important; }
/* `!important` ON THE OPACITY ONLY, and it has to be: house.js brings
   the introduction and the parts up with rules of its own
   (`.human-ready .human-rises.arrived`) that outrank anything written
   here, and without it the introduction showed through ahead of the
   name. The transform is left to lose — house.js's rise is its own. */
.tale-page .tale-enter { opacity: 0 !important; transform: translateY(16px); }
.tale-page .tale-enter-fade { opacity: 0 !important; }
.tale-page .tale-enter.tale-entered,
.tale-page .tale-enter-fade.tale-entered {
  opacity: 1 !important;
  transform: none;
  transition: opacity 0.9s var(--menu-ease), transform 1s var(--menu-ease);
}
/* The lily at the head keeps its own place; it only fades. */
.tale-page .tale-head-doodle.tale-enter { transform: none; }

@media (max-width: 860px) {
  .tale-head-doodle { right: 20px; top: 64px; width: 104px; height: 104px; }
  .tale-by-1 { left: -26px; top: 18px; width: 22px; height: 22px; }
  .tale-by-2 { left: 92px; top: 84px; width: 22px; height: 22px; }
  .tale-by-3 { left: 84px; top: -8px; width: 16px; height: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .tale-page .tale-enter,
  .tale-page .tale-enter-fade { opacity: 1; transform: none; transition: none; }
  .tale-to-draw path { stroke-dasharray: none; stroke-dashoffset: 0; }
  .tale-to-draw .tale-tint { opacity: 0.95; }
  .tale-to-draw.tale-drawn path,
  .tale-to-draw.tale-drawn .tale-tint { transition: none; }
}

/* ==================================================================
   MY PERSONAL INTRODUCTION TO PERFUME — an essay page of its own

   works/my-personal-introduction-to-perfume.html. The essay shell —
   the dark ground, the rule down the left — with its own ground and its
   own accent: THE MIST (primer.js) rising behind the writing, drops of
   perfume turning to vapour as they climb, and the warm GOLD of perfume
   in a bottle in place of the essays' steel blue. Everything below is
   scoped to `.primer-page`, so no other essay changes.

   THE DIAGRAMS are inline SVG drawn in four classes — `pd-ink` for a
   line, `pd-soft` for glass, `pd-gold-fill` for the liquid, `pd-label`
   and `pd-small` for words — so the whole set is recoloured here.
   ================================================================== */
.primer-page { --gold: #e0b672; --gold-rgb: 224, 182, 114; }
.primer-mist {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}
.primer-page .essay-head h1 { font-size: clamp(34px, 6vw, 72px); }
.primer-page .essay-head h1 em { color: var(--gold); font-size: 0.5em; }
.primer-page .essay-facts { border-top-color: rgba(var(--gold-rgb), 0.35); }
.primer-page .essay-section h2 .essay-no { color: var(--gold); }
.primer-page .essay-rule-run,
.primer-page .essay-mark.here .essay-mark-tick { background: var(--gold); }
.primer-page .essay-section a { color: #f1dcb4; border-bottom-color: rgba(var(--gold-rgb), 0.6); }
.primer-page .essay-section a:hover { color: #fff; border-bottom-color: var(--gold); }
.primer-page .essay-section strong { color: #eef1f6; font-weight: 600; }

/* A FIGURE in the writing: the drawing, then a line saying what it is. */
.primer-figure { margin: 8px 0 30px; }
.primer-figure svg { display: block; width: 100%; height: auto; overflow: visible; }
.primer-figure figcaption {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  line-height: 1.7;
  color: #8d94a3;
}
.primer-mix { max-width: 560px; }
.primer-pyramid { max-width: 380px; margin-left: auto; margin-right: auto; }
.primer-bleed { max-width: 560px; }
.primer-perform { max-width: 640px; }

.primer-page .pd-ink { fill: none; stroke: #cfd6e2; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.primer-page .pd-soft path { fill: rgba(207, 214, 226, 0.04); stroke: #9aa2b2; stroke-width: 1.3; stroke-linejoin: round; }
.primer-page .pd-gold-fill { fill: rgba(var(--gold-rgb), 0.78); }
.primer-page .pd-pale-fill { fill: rgba(207, 214, 226, 0.16); }
.primer-page .pd-mix-fill { fill: rgba(var(--gold-rgb), 0.42); }
.primer-page .pd-label { fill: #dbe1ea; font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; }
.primer-page .pd-small { fill: #8d94a3; font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em; }
.primer-page .pd-axis { fill: none; stroke: rgba(150, 160, 176, 0.45); stroke-width: 1; }

/* THE PYRAMID: three tiers, ruled; the second one lit in gold, getting
   deeper towards the base the way a perfume gets heavier. */
.primer-page .pd-tier { fill: rgba(207, 214, 226, 0.03); stroke: #9aa2b2; stroke-width: 1.3; stroke-linejoin: round; }
.primer-page .pd-tier-1.pd-on { fill: rgba(var(--gold-rgb), 0.18); }
.primer-page .pd-tier-2.pd-on { fill: rgba(var(--gold-rgb), 0.32); }
.primer-page .pd-tier-3.pd-on { fill: rgba(var(--gold-rgb), 0.5); }
.primer-page .pd-tier.pd-on { stroke: var(--gold); }
.primer-page .pd-tier-name { fill: #8d94a3; font-family: var(--mono); font-size: 13px; letter-spacing: 0.18em; text-transform: uppercase; }
.primer-page .pd-tier-note { fill: #f5f1e8; font-family: var(--sans); font-size: 19px; font-weight: 500; }

/* THE BLEED: three waves, each rising as the one before it fades. */
.primer-page .pd-wave { fill: none; stroke-width: 2; stroke-linecap: round; }
.primer-page .pd-wave-1 { stroke: rgba(var(--gold-rgb), 0.55); }
.primer-page .pd-wave-2 { stroke: rgba(var(--gold-rgb), 0.8); }
.primer-page .pd-wave-3 { stroke: var(--gold); }

/* PERFORMANCE: a sphere round the wearer, a life on the skin, a trail. */
.primer-page .pd-ring circle { fill: none; stroke: rgba(var(--gold-rgb), 0.55); stroke-width: 1.2; stroke-dasharray: 3 5; }
.primer-page .pd-ring circle:first-child { stroke-dasharray: none; fill: rgba(var(--gold-rgb), 0.08); }
.primer-page .pd-person circle { fill: none; stroke: #dbe1ea; stroke-width: 1.6; }
.primer-page .pd-person path { fill: none; stroke: #dbe1ea; stroke-width: 1.6; stroke-linecap: round; }
.primer-page .pd-life { fill: rgba(var(--gold-rgb), 0.35); stroke: var(--gold); stroke-width: 1.3; }

/* THE CONCENTRATIONS: one bar per name, placed on a 0–40% scale. */
.primer-bars { max-width: 620px; }
.primer-bar {
  display: grid;
  grid-template-columns: 190px 1fr 64px;
  align-items: center;
  gap: 14px;
  padding: 7px 0;
  border-bottom: 1px solid rgba(150, 160, 176, 0.14);
}
.primer-bar-name { color: #dbe1ea; font-size: 15px; }
.primer-bar-track { position: relative; height: 10px; background: rgba(150, 160, 176, 0.1); }
.primer-bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  /* On a 0–30% scale, the top of the owner's table. */
  left: calc(var(--from) / 30 * 100%);
  width: calc((var(--to) - var(--from)) / 30 * 100%);
  background: linear-gradient(90deg, rgba(var(--gold-rgb), 0.55), var(--gold));
}
.primer-bar-say { font-family: var(--mono); font-size: 12px; color: var(--gold); text-align: right; }
.primer-scale {
  display: flex;
  justify-content: space-between;
  margin: 6px 78px 0 204px !important;
  font-family: var(--mono);
  font-size: 10px !important;
  color: #767e8d !important;
  text-align: left !important;
}

/* THE ACCORDS: notes on the left, several to a row each, and the accord
   they make standing tall on the right across all of them. */
.primer-table-wrap { overflow-x: auto; margin: 6px 0 30px; }
.primer-accords { width: 100%; max-width: 520px; border-collapse: collapse; font-size: 15px; }
.primer-accords thead th {
  text-align: left;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  padding: 0 14px 10px;
  border-bottom: 1px solid rgba(var(--gold-rgb), 0.5);
}
.primer-accords td { color: #aab2c1; padding: 5px 14px; }
.primer-accords tbody th {
  text-align: left;
  vertical-align: middle;
  font-weight: 500;
  color: #f1e3c8;
  padding: 0 14px;
  border-left: 1px solid rgba(var(--gold-rgb), 0.45);
  background: rgba(var(--gold-rgb), 0.06);
}
.primer-accords tr.primer-group td,
.primer-accords tr.primer-group th { border-top: 1px solid rgba(150, 160, 176, 0.22); }
.primer-accords tr.primer-group td { padding-top: 10px; }

/* THE DROPDOWN the owner asked for, on a real <details>. */
.primer-more {
  margin: 10px 0 20px;
  border: 1px solid rgba(var(--gold-rgb), 0.35);
  background: rgba(var(--gold-rgb), 0.04);
}
.primer-more summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  cursor: pointer;
  list-style: none;
  color: #eef1f6;
  font-weight: 500;
}
.primer-more summary::-webkit-details-marker { display: none; }
.primer-more-cue {
  flex: none;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
}
.primer-more-cue::after { content: " +"; }
.primer-more[open] .primer-more-cue::after { content: " −"; }
.primer-more summary:hover { background: rgba(var(--gold-rgb), 0.07); }
.primer-more-body { padding: 2px 18px 6px; }

/* A NUMBERED LIST and a MYTH'S heading. */
.primer-list { margin: 0 0 20px; padding-left: 22px; color: #aab2c1; }
.primer-list li { margin: 0 0 12px; padding-left: 4px; }
.primer-list li::marker { color: var(--gold); font-family: var(--mono); font-size: 13px; }
.primer-myth {
  margin: 26px 0 10px;
  font-size: 17px;
  font-weight: 600;
  color: #f1e3c8;
}
.primer-myth::before { content: "— "; color: var(--gold); }

/* THE LAST WORD, and the motto in the middle of the page, set large in
   italics as asked: "middle of the page, somehow italics and
   emphasized". */
/* The block runs the whole width of the column so the motto can stand
   in its middle; the paragraph above it keeps the writing's measure. */
.primer-close { padding-top: clamp(40px, 7vh, 80px); }
.primer-close p { color: #cdd4df; margin: 0 0 18px; max-width: 66ch; }
.primer-close strong { color: #fff; font-weight: 600; }
.primer-page .primer-motto {
  max-width: none;
  margin: clamp(50px, 10vh, 110px) 0 clamp(50px, 10vh, 110px);
  text-align: center !important;
  font-size: clamp(38px, 7vw, 84px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--gold);
}
.primer-motto em { font-style: italic; font-weight: 500; }
.primer-motto::before,
.primer-motto::after {
  content: "";
  display: block;
  width: 64px;
  height: 1px;
  margin: 0 auto 26px;
  background: rgba(var(--gold-rgb), 0.6);
}
.primer-motto::after { margin: 26px auto 0; }

/* THE FOOTNOTES: a raised number in the writing, the notes at the foot,
   and — with primer.js — each note shown beside its number too. */
.primer-fn { font-size: 0.72em; line-height: 0; margin-left: 2px; }
.primer-page .primer-fn a { font-family: var(--mono); color: var(--gold); border: 0; padding: 0 2px; }
.primer-endnotes {
  max-width: 66ch;
  margin-top: clamp(20px, 4vh, 40px);
  padding-top: 26px;
  border-top: 1px solid rgba(150, 160, 176, 0.25);
}
/* Footnotes and Citations are sections like any other (so they are on
   the rule), headed the same way; only their lists are set smaller. */
.primer-endnotes h2 + ol, .primer-endnotes h2 + ul { margin-bottom: 10px; }
.primer-endnotes .primer-footnotes, .primer-endnotes .primer-sources { padding-left: 20px; color: #8d94a3; font-size: 14px; line-height: 1.7; }
.primer-footnotes li, .primer-sources li { margin: 0 0 10px; }
.primer-footnotes li:target { color: #e8ecf3; }
.primer-endnotes a { color: #cdd4df; text-decoration: none; border-bottom: 1px solid rgba(var(--gold-rgb), 0.5); overflow-wrap: anywhere; }
.primer-endnotes a:hover { color: #fff; }
.primer-back { margin-left: 4px; }
.primer-tip {
  position: fixed;
  z-index: 50;
  padding: 12px 14px;
  background: #15171d;
  border: 1px solid rgba(var(--gold-rgb), 0.55);
  color: #cdd4df;
  font-size: 13.5px;
  line-height: 1.6;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.primer-tip.is-on { opacity: 1; transform: none; }
.primer-tip[hidden] { display: none; }
.primer-tip-no {
  display: block;
  margin-bottom: 6px;
  font-family: var(--mono);
  font-size: 11px;
  color: #e0b672;
}

@media (max-width: 700px) {
  .primer-bar { grid-template-columns: 1fr 58px; gap: 4px 10px; }
  .primer-bar-name { grid-column: 1 / -1; font-size: 14px; }
  .primer-scale { margin: 6px 68px 0 0 !important; }
}
@media (prefers-reduced-motion: reduce) {
  .primer-tip { transition: none; }
}

/* ==================================================================
   THE WAY IN — every house
   A house used to simply be there the moment it was opened, which the
   owner described as it "just kinda blinks on the screen and then
   thats it". It eases in instead: the page's ground is there at once —
   the body's background is the window's, and fading the body does not
   fade it, so ADAR and Ataraxia never flash white on the way — and the
   page's contents come up over it, the head rising a little as it does.

   On every house and on the individual fragrances, and whichever way
   the page was reached. Tale is left out on purpose: it has a way in of
   its own, piece by piece (`tale-coming`, tale.js), and the two on top
   of each other would be one too many.

   Opacity and a short rise on the head only — never a transform on the
   body or on anything holding the page's fixed chrome, which a
   transform would take off the window and carry with it.
   ================================================================== */
@keyframes house-arrive { from { opacity: 0; } to { opacity: 1; } }
@keyframes house-rise { from { transform: translateY(14px); } to { transform: none; } }
body.pineward-page,
body.adar-page,
body.human-page:not(.tale-page) {
  animation: house-arrive 0.95s cubic-bezier(0.3, 0, 0.2, 1) both;
}
/* (The head stands inside the page's <main> since 2026-09-27, so these
   are not written as the body's own children any more.) */
body.pineward-page .pine-head,
body.adar-page .adar-head,
body.human-page:not(.tale-page) .human-head {
  animation: house-rise 1.1s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  body.pineward-page,
  body.adar-page,
  body.human-page:not(.tale-page),
  body.pineward-page .pine-head,
  body.adar-page .adar-head,
  body.human-page:not(.tale-page) .human-head { animation: none; }
}

/* ==================================================================
   THE FRAGRANCES, A WHOLE-PAGE TABLE — fragrance-line.js (2026-09-25,
   night)

   The Fragrances view of Scent descriptions, in three columns: THE
   ASIDE on the left (the name, the count, the readings, the scale and
   the mark), THE ITEMS in the middle — nearly the whole page, scrolling
   in their own box — and THE OPTIONS on the right, which show the items
   as a LIST, as small BOXES or as CARDS. Nothing is drawn behind any of
   it: the line and the pyramid it stood on were taken out, at the
   owner's "the thing in the back is downright ugly". The page's squared
   paper is the ground.

   The old table is still in the page and still what an item presses; it
   is only hidden while this is up (`.view.table-on`), and a copy of it
   stands in archive/. Without the script the page is the old view.
   ================================================================== */
.view.table-on .index-page,
.view.table-on .index-page[hidden] { display: none !important; }
.frag-stage {
  --cols: 64px minmax(0, 1fr) 30% 112px;
  position: fixed;
  inset: 0;
  z-index: 1;
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr) 64px;
  column-gap: clamp(28px, 3.4vw, 56px);
  padding: 92px 30px 0 40px;
  transition: opacity 0.45s var(--menu-ease);
}
body.frag-open .frag-stage { opacity: 0; pointer-events: none; }

/* THE ASIDE: space, and a few things to look at. */
.frag-aside {
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding-bottom: 30px;
  opacity: 0;
  transition: opacity 0.6s var(--menu-ease);
}
.frag-stage.here .frag-aside { opacity: 1; }
.frag-aside-name {
  margin: 0;
  font-size: clamp(28px, 2.6vw, 38px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
}
.frag-aside-kicker {
  margin: 0 0 6px;
  font-size: clamp(14px, 1.15vw, 17px);
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1;
  color: var(--muted);
}
.frag-aside-count {
  margin: 38px 0 0;
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.frag-aside-big {
  font-family: var(--mono);
  font-size: 64px;
  line-height: 0.9;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.frag-aside-unit,
.frag-readings dt,
.frag-readings dd,
.frag-scale-end,
.frag-options-say,
.frag-mode-word,
.frag-sort,
.frag-sort-say {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.frag-aside-unit { color: var(--muted); }
.frag-readings {
  margin: 24px 0 0;
  padding: 12px 0 0;
  border-top: 1px solid rgba(var(--ink-rgb), 0.24);
  display: grid;
  row-gap: 8px;
}
.frag-readings div { display: flex; justify-content: space-between; gap: 12px; }
.frag-readings dt, .frag-readings dd { margin: 0; letter-spacing: 0.1em; }
.frag-readings dt { color: var(--muted); }
.frag-readings dd { color: var(--ink); font-variant-numeric: tabular-nums; }
/* THE SCALE: a hairline with a tick for every fragrance shown, filled as
   far as the items have been seen through; the ticks of the ones in view
   are inked. The rank of the house pages, in the same plain ink. */
.frag-scale {
  flex: 1;
  min-height: 80px;
  margin: 28px 0 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  justify-items: start;
  row-gap: 8px;
}
.frag-scale-end { font-size: 10px; letter-spacing: 0.12em; color: var(--muted); }
.frag-scale-rule { position: relative; width: 12px; height: 100%; }
.frag-scale-rule::before,
.frag-scale-fill {
  content: "";
  position: absolute;
  left: 5px;
  top: 0;
  bottom: 0;
  width: 1px;
}
.frag-scale-rule::before { background: rgba(var(--ink-rgb), 0.18); }
.frag-scale-fill {
  background: var(--ink);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.25s linear;
}
.frag-scale-tick {
  position: absolute;
  left: 1px;
  width: 9px;
  height: 1px;
  margin-top: -0.5px;
  background: rgba(var(--ink-rgb), 0.26);
  transition: background-color 0.3s, width 0.3s;
}
.frag-scale-tick.in-view { background: var(--ink); width: 11px; }
.frag-scale-tick[hidden] { display: none; }
/* THE MARK: a small ring of specks, one larger for every fragrance. */
.frag-mark { display: block; flex: none; width: 176px; height: 176px; margin: 12px 0 -14px -10px; }
@media (max-height: 720px) { .frag-mark { display: none; } }
/* THE WAY BACK to the houses, at the foot of the aside — the Houses view
   carries its twin, "The fragrances →", under the way round. */
.frag-to-houses,
.sheet-to-fragrances {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0 7px;
  border: 0;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.35);
  background: none;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.25s, gap 0.3s var(--menu-ease);
}
.frag-to-houses { align-self: flex-start; margin-top: auto; }
.frag-to-houses:hover,
.sheet-to-fragrances:hover { border-bottom-color: var(--ink); gap: 16px; }
/* THE DIVIDER between the aside and the table: the line the houses' axis
   becomes when the views cross (views.js's thread lands on it). Placed by
   the script, the height of the window. */
.frag-divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: rgba(var(--ink-rgb), 0.3);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s var(--menu-ease);
}
.frag-divider[hidden] { display: none; }
.frag-stage.here .frag-divider { opacity: 1; }
.sheet-to-fragrances { margin-top: 18px; }
@media (max-width: 700px) {
  /* The two words across the top are the way over on a phone. */
  .frag-to-houses, .sheet-to-fragrances { display: none; }
}

/* THE ITEMS */
.frag-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.frag-list-head {
  display: flex;
  align-items: flex-end;
  gap: 18px;
  padding: 0 0 14px;
  opacity: 0;
  transition: opacity 0.6s var(--menu-ease);
}
.frag-stage.here .frag-list-head { opacity: 1; }
.frag-list-search {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.35);
  padding: 4px 0 7px;
}
.frag-list-mark {
  flex: none;
  width: 10px;
  height: 10px;
  border: 1px solid var(--ink);
  border-radius: 50%;
}
.frag-list-field {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: none;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
}
.frag-list-count {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--muted);
  padding-bottom: 8px;
}
/* The box the items scroll in, running to the window's foot and fading
   out into it. */
.frag-list-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  outline: none;
  padding-bottom: 56px;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 44px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 44px), transparent);
}
.frag-list-scroll:focus-visible { box-shadow: inset 1px 0 0 var(--ink); }
.frag-sortbar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: grid;
  grid-template-columns: var(--cols);
  align-items: end;
  background: rgba(255, 255, 255, 0.94);
  opacity: 0;
  transition: opacity 0.5s var(--menu-ease);
}
.frag-stage.here .frag-sortbar { opacity: 1; }
/* Its rule draws itself across as the view comes in. */
.frag-sortbar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.55s var(--menu-ease) 0.1s;
}
.frag-stage.here .frag-sortbar::after { transform: none; }
.frag-sort-say { display: none; color: var(--muted); }
.frag-sort {
  padding: 9px 12px 8px 0;
  border: 0;
  background: none;
  white-space: nowrap;
  text-align: left;
  color: var(--muted);
  cursor: pointer;
}
.frag-sort:hover, .frag-sort.is-sorted { color: var(--ink); }
.frag-sort.is-sorted::after { content: " ↓"; }
.frag-sort.is-sorted.is-reversed::after { content: " ↑"; }
.frag-items { list-style: none; margin: 0; padding: 0; font-size: 14px; }
.frag-item {
  position: relative;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.4s var(--menu-ease), border-color 0.2s, background-color 0.2s;
  transition-delay: calc(40ms + var(--i, 0) * 32ms), 0s, 0s;
}
/* A FRAGRANCE THE SEARCH LEAVES OUT IS OFF THE PAGE, in all three
   layouts. The plain `[hidden]` rule lost to each layout's own `display`
   (`.frag-stage.is-list .frag-item` and the rest outrank it), so the
   search marked the rows it had left out and every one of them stayed on
   the page — the owner's "make it work" (2026-09-26). The trap is in
   CLAUDE.md: a rule for `[hidden]` has to outrank every rule that gives
   the thing a display, and this one does. */
.frag-item[hidden],
.frag-stage.is-list .frag-item[hidden],
.frag-stage.is-boxes .frag-item[hidden],
.frag-stage.is-cards .frag-item[hidden] { display: none; }
.frag-stage.rows-in .frag-item { opacity: 1; }
.frag-item > span {
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.4s var(--menu-ease), transform 0.4s var(--menu-ease);
  transition-delay: calc(40ms + var(--i, 0) * 32ms);
}
.frag-stage.rows-in .frag-item > span { opacity: 1; transform: none; }
/* Changing the way they are shown: out, and back in the new shape. */
.frag-stage.switching .frag-item {
  opacity: 0 !important;
  transition: opacity 0.14s linear !important;
}
.frag-t-no, .frag-t-date {
  font-family: var(--mono);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.frag-t-name a { color: var(--ink); text-decoration: none; font-weight: 600; }
.frag-t-house { color: var(--muted); }
.frag-item:hover .frag-t-no,
.frag-item:hover .frag-t-house,
.frag-item:hover .frag-t-date { color: var(--ink); }
.frag-item:hover .frag-t-name a,
.frag-t-name a:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
.frag-t-pic { display: none; }

/* AS A LIST: a compact ruled row each, the columns under the sort bar's. */
.frag-stage.is-list .frag-item {
  display: grid;
  grid-template-columns: var(--cols);
  align-items: center;
  height: 34px;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.12);
  background: rgba(255, 255, 255, 0.55);
}
.frag-stage.is-list .frag-item:hover { background: rgba(var(--ink-rgb), 0.05); }
.frag-stage.is-list .frag-item > span {
  min-width: 0;
  padding-right: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* AS BOXES: small squares, many to a row. */
.frag-stage.is-boxes .frag-sortbar,
.frag-stage.is-cards .frag-sortbar {
  display: flex;
  align-items: baseline;
  gap: 0 18px;
  padding-top: 1px;
}
.frag-stage.is-boxes .frag-sort-say,
.frag-stage.is-cards .frag-sort-say { display: block; padding: 9px 4px 8px 0; }
.frag-stage.is-boxes .frag-sort,
.frag-stage.is-cards .frag-sort { padding-right: 0; }
.frag-stage.is-boxes .frag-items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 10px;
  padding-top: 14px;
}
/* A BOX (2026-09-26, later) is its fragrance's picture, square, with
   the number, the house, the name and the date under it on the paper —
   the picture was the box's ground for a round, blurred, with the
   writing over it, and the owner asked for "the image itself" and no
   ground at all. Until the picture comes the square is the hatching. */
.frag-stage.is-boxes .frag-item {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "pic pic" "no house" "name name" "date date";
  align-content: start;
  padding: 0 0 11px;
  overflow: hidden;
  border: 1px solid rgba(var(--ink-rgb), 0.16);
  background: #fff;
}
.frag-stage.is-boxes .frag-t-pic {
  grid-area: pic;
  position: relative;
  display: block;
  aspect-ratio: 1;
  margin-bottom: 9px;
  overflow: hidden;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.12);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(-45deg, rgba(var(--ink-rgb), 0.09) 0px, rgba(var(--ink-rgb), 0.09) 1px, transparent 1px, transparent 10px);
}
.frag-stage.is-boxes .frag-t-pic-no { font-size: 24px; left: 10px; bottom: 6px; }
.frag-stage.is-boxes .frag-item:hover,
.frag-stage.is-cards .frag-item:hover { border-color: var(--ink); }
.frag-stage.is-boxes .frag-t-no { grid-area: no; padding-left: 11px; font-size: 11.5px; }
.frag-stage.is-boxes .frag-t-house {
  grid-area: house;
  min-width: 0;
  padding: 1px 11px 0 8px;
  text-align: right;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.frag-stage.is-boxes .frag-t-name { grid-area: name; padding: 6px 11px 0; font-size: 14.5px; line-height: 1.2; }
.frag-stage.is-boxes .frag-t-date { grid-area: date; padding: 0 11px; margin-top: 5px; font-size: 10.5px; }

/* AS CARDS: larger, each with its picture — or the hatching and its
   number, until the picture is there. */
.frag-stage.is-cards .frag-items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 18px;
  padding-top: 16px;
}
.frag-stage.is-cards .frag-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "pic pic" "no date" "name name" "house house";
  align-content: start;
  padding: 0 0 15px;
  border: 1px solid rgba(var(--ink-rgb), 0.16);
  background: #fff;
}
.frag-stage.is-cards .frag-t-pic {
  grid-area: pic;
  position: relative;
  display: block;
  /* Upright, as a bottle is: most of these are photographed standing,
     and an upright frame keeps the most of each bottle in it. */
  aspect-ratio: 4 / 5;
  margin-bottom: 12px;
  overflow: hidden;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.12);
  background-color: var(--bg-2);
  background-image: repeating-linear-gradient(-45deg, rgba(var(--ink-rgb), 0.09) 0px, rgba(var(--ink-rgb), 0.09) 1px, transparent 1px, transparent 12px);
}
/* THE PICTURE ITSELF, EDGE TO EDGE (2026-09-26, later): "i dont want
   any background to be visible, just make the image itself fit into the
   box ... The point is simply for the fragrance to be visible." It
   covers its frame, and fragrance-line.js places it — larger than the
   frame, moved so the bottle is in the middle and, where the bottle is
   small in its picture, brought closer — writing the place for the
   boxes' square (`--b-*`) and the cards' upright frame (`--c-*`) at
   once. Before it is placed, and if it never can be, it simply covers
   the frame from its middle. (A blurred copy of the picture stood behind
   it for one round, with a studio shot multiplied onto white and a scene
   feathered into its blur; the owner wanted neither, and none of it is
   here any more.) */
.frag-t-pic img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}
.frag-stage.is-boxes .frag-t-pic.is-placed img {
  right: auto;
  bottom: auto;
  left: var(--b-x);
  top: var(--b-y);
  width: var(--b-w);
  height: var(--b-h);
}
.frag-stage.is-cards .frag-t-pic.is-placed img {
  right: auto;
  bottom: auto;
  left: var(--c-x);
  top: var(--c-y);
  width: var(--c-w);
  height: var(--c-h);
}
.frag-t-pic-no {
  position: absolute;
  left: 12px;
  bottom: 8px;
  font-family: var(--mono);
  font-size: 34px;
  letter-spacing: -0.03em;
  color: rgba(var(--ink-rgb), 0.5);
}
.frag-t-pic.has-picture .frag-t-pic-no { display: none; }
.frag-stage .frag-t-pic.has-picture { background-image: none; }
.frag-stage.is-cards .frag-t-no { grid-area: no; padding-left: 14px; font-size: 12px; }
.frag-stage.is-cards .frag-t-date { grid-area: date; padding-right: 14px; text-align: right; font-size: 11px; }
.frag-stage.is-cards .frag-t-name { grid-area: name; padding: 7px 14px 0; font-size: 17px; line-height: 1.2; }
.frag-stage.is-cards .frag-t-house { grid-area: house; padding: 4px 14px 0; font-size: 13px; }

/* THE OPTIONS, on the right: List, Boxes, Cards. */
.frag-options {
  align-self: start;
  display: grid;
  justify-items: center;
  row-gap: 12px;
  opacity: 0;
  transition: opacity 0.6s var(--menu-ease) 0.1s;
}
.frag-stage.here .frag-options { opacity: 1; }
.frag-options-say { margin: 0 0 2px; font-size: 10px; letter-spacing: 0.16em; color: var(--muted); }
.frag-mode {
  display: grid;
  justify-items: center;
  row-gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.frag-mode-word { font-size: 9.5px; }
.frag-mode.is-on { color: var(--ink); }
.frag-mode-icon {
  position: relative;
  display: block;
  width: 42px;
  height: 42px;
  border: 1px solid rgba(var(--ink-rgb), 0.24);
  background: rgba(255, 255, 255, 0.82);
  color: var(--ink);
  transition: border-color 0.2s, background-color 0.2s, color 0.2s;
}
.frag-mode:hover .frag-mode-icon { border-color: var(--ink); }
.frag-mode.is-on .frag-mode-icon { border-color: var(--ink); background: var(--ink); color: #fff; }
.frag-mode-icon i { position: absolute; box-sizing: border-box; display: none; }
/* three rules */
.frag-mode-list i:nth-child(-n + 3) { display: block; left: 24%; right: 24%; height: 1px; background: currentColor; }
.frag-mode-list i:nth-child(1) { top: 33%; }
.frag-mode-list i:nth-child(2) { top: 50%; }
.frag-mode-list i:nth-child(3) { top: 67%; }
/* a grid of small squares */
.frag-mode-boxes i { display: block; width: 22%; height: 22%; border: 1px solid currentColor; }
.frag-mode-boxes i:nth-child(1) { left: 26%; top: 26%; }
.frag-mode-boxes i:nth-child(2) { left: 52%; top: 26%; }
.frag-mode-boxes i:nth-child(3) { left: 26%; top: 52%; }
.frag-mode-boxes i:nth-child(4) { left: 52%; top: 52%; }
/* two tall cards */
.frag-mode-cards i:nth-child(-n + 2) { display: block; top: 22%; width: 22%; height: 56%; border: 1px solid currentColor; }
.frag-mode-cards i:nth-child(1) { left: 24%; }
.frag-mode-cards i:nth-child(2) { left: 54%; }

/* THERE AT ONCE, WHOLE: what the stage is while views.js crosses the
   houses over into it — the crossing is its arrival, so nothing in it
   arrives a second time. */
.frag-stage.instant,
.frag-stage.instant *,
.frag-stage.instant *::after { transition: none !important; }
@media (prefers-reduced-motion: reduce) {
  .frag-aside, .frag-list-head, .frag-sortbar, .frag-sortbar::after, .frag-options,
  .frag-item, .frag-item > span, .frag-scale-fill { transition: none !important; }
}
/* ON A PHONE the aside comes down to a head — the name and its line — and
   the three options stand at the right of it as icons; the items take the
   width. */
@media (max-width: 700px) {
  .frag-stage {
    --cols: 40px minmax(0, 1fr) 30% 82px;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr);
    column-gap: 12px;
    padding: 76px 16px 0;
  }
  .frag-aside { grid-column: 1; grid-row: 1; padding: 0 0 12px; }
  .frag-aside-name { font-size: 26px; }
  .frag-aside-kicker { font-size: 13px; margin-bottom: 4px; }
  .frag-aside-count, .frag-readings, .frag-scale, .frag-mark { display: none; }
  .frag-options {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    grid-auto-flow: column;
    column-gap: 6px;
    padding-bottom: 12px;
  }
  .frag-options-say, .frag-mode-word { display: none; }
  .frag-mode-icon { width: 34px; height: 34px; }
  .frag-main { grid-column: 1 / 3; grid-row: 2; }
  .frag-items { font-size: 13px; }
  .frag-t-no, .frag-t-date { font-size: 11px; }
  .frag-sort { letter-spacing: 0.08em; padding-right: 6px; }
  /* Three to a row: each box's picture fills its square, so a small
     square still shows the bottle. */
  .frag-stage.is-boxes .frag-items { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; }
  .frag-stage.is-boxes .frag-item { padding-bottom: 9px; }
  .frag-stage.is-boxes .frag-t-no { padding-left: 8px; font-size: 10.5px; }
  .frag-stage.is-boxes .frag-t-house { padding-right: 8px; font-size: 8.5px; }
  .frag-stage.is-boxes .frag-t-name { padding: 5px 8px 0; font-size: 13px; }
  .frag-stage.is-boxes .frag-t-date { padding: 0 8px; font-size: 10px; }
  .frag-stage.is-cards .frag-items { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
  .frag-stage.is-cards .frag-t-name { font-size: 15px; }
  .frag-stage.is-cards .frag-t-house { font-size: 12px; }
}

/* THE CROSSING between the two views (views.js), whenever the Fragrances
   are the whole-page table: both views pinned to the window, the one
   being left fading a little way off to one side as the other fades in —
   a press mid-way turns it round from wherever it is — and THE THREAD,
   one line the height of the window, travelling from the houses' axis to
   the table's divider and back. It replaced the pixel stretch, which the
   owner had taken out. */
.views.swiping .view.crossing {
  transition: opacity 0.38s var(--menu-ease), transform 0.38s var(--menu-ease);
}
.views-thread {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  z-index: 2;
  background: rgba(var(--ink-rgb), 0.3);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s linear;
}
.views-thread.moving {
  transition: transform 0.38s var(--menu-ease), opacity 0.38s var(--menu-ease);
}
@media (prefers-reduced-motion: reduce) {
  .views.swiping .view.crossing, .views-thread, .views-thread.moving { transition: none; }
}

/* ==================================================================
   FOUR HOUSES' PAPER — Grande Parfums, Les Abstraits, Tombstone, Qimu
   & Musicians (2026-09-25)

   The owner: "feel free to give them some colour in the background, the
   same way you have in the case of pineward (green) and tale (some
   muted orange) ... Qimu should be blue though; semi light blue."

   Each is the page's own tokens turned (Tale's way: everything on the
   page already draws in them) plus a few soft pools (Pineward's way),
   fixed to the window. All of it written as plain rgba() and hex, never
   color-mix() — see the note under Pineward's ground for why.

     GRANDE        champagne — the house has no theme, so only warmth;
     LES ABSTRAITS iris — a grey violet, a pool of it by the armoire and
                   one where the puddle lies;
     TOMBSTONE     ash — a cold stone grey, lighter above the horizon
                   and darker at the foot where the floor reflects;
     QIMU          blue, semi-light, and the only one of the four you
                   would call a colour, because the owner said so.
   ================================================================== */
body.grande-page {
  --bg: #f6f2e8; --bg-2: #eee8da; --line: #dcd3c0;
  --chrome-ground: rgba(246, 242, 232, 0.8);
  background-color: var(--bg);
  background-image:
    radial-gradient(60% 44% at 8% 6%, rgba(176, 138, 62, 0.10) 0%, transparent 72%),
    radial-gradient(56% 40% at 94% 94%, rgba(176, 138, 62, 0.10) 0%, transparent 72%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}
body.abstraits-page {
  --bg: #f3f0f5; --bg-2: #e9e4ee; --line: #d6cfde;
  --chrome-ground: rgba(243, 240, 245, 0.8);
  background-color: var(--bg);
  background-image:
    radial-gradient(40% 60% at 6% 86%, rgba(112, 94, 156, 0.13) 0%, transparent 74%),
    radial-gradient(34% 30% at 94% 100%, rgba(104, 92, 132, 0.12) 0%, transparent 72%),
    linear-gradient(to bottom, rgba(112, 94, 156, 0.02) 0%, rgba(112, 94, 156, 0.06) 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}
body.tombstone-page {
  --bg: #ecebe8; --bg-2: #e2e1dd; --line: #cfcdc7;
  --chrome-ground: rgba(236, 235, 232, 0.8);
  background-color: var(--bg);
  background-image:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0) 60%,
      rgba(70, 72, 78, 0.05) 76%, rgba(70, 72, 78, 0.12) 100%),
    radial-gradient(80% 16% at 50% 76%, rgba(120, 122, 128, 0.10) 0%, transparent 76%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}
/* THE SOUND, on Qimu & Musicians: "a button on top that allows you to
   mute and unmute. it should be a square and relatively obvious"
   (2026-09-26). A square at the top right with a speaker drawn in it —
   crossed out while the notes are silent, sounding and filled in the
   score's blue once they play — and what it is doing written beside it.
   Made by qimu.js, so without the script there is no button and nothing
   to play. It stands under the Menu's overlay (25) and over the page. */
.qimu-sound {
  position: fixed;
  top: 16px;
  right: 22px;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0;
  border: 0;
  background: none;
  color: rgb(44, 62, 99);
  cursor: pointer;
  font-family: var(--mono);
}
.qimu-sound-say {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(44, 62, 99, 0.78);
  transition: color 0.25s var(--menu-ease);
}
.qimu-sound-box {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border: 1.5px solid rgb(44, 62, 99);
  background: rgba(226, 235, 246, 0.82);
  transition: background-color 0.25s var(--menu-ease), color 0.25s var(--menu-ease), transform 0.25s var(--menu-ease);
}
.qimu-sound svg { display: block; }
.qimu-sound svg path { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.qimu-sound .qimu-sound-horn { fill: currentColor; }
.qimu-sound .qimu-sound-waves { display: none; }
.qimu-sound.is-on .qimu-sound-waves { display: inline; }
.qimu-sound.is-on .qimu-sound-cross { display: none; }
.qimu-sound.is-on .qimu-sound-box { background: rgb(44, 62, 99); color: #eef3fa; }
.qimu-sound.is-on .qimu-sound-say { color: rgb(44, 62, 99); }
.qimu-sound:hover .qimu-sound-box,
.qimu-sound:focus-visible .qimu-sound-box { transform: translateY(-1px); }
.qimu-sound:focus-visible { outline: none; }
.qimu-sound:focus-visible .qimu-sound-box { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3.5px rgb(44, 62, 99); }
@media (max-width: 700px) {
  /* On a phone the word goes, and the square stands on the chrome's own
     ground like the Menu across from it. */
  .qimu-sound { top: 10px; right: 12px; }
  .qimu-sound-say { display: none; }
}

body.qimu-page {
  --bg: #e2ebf6; --bg-2: #d6e1ef; --line: #bccbdd;
  --chrome-ground: rgba(226, 235, 246, 0.8);
  background-color: var(--bg);
  background-image:
    radial-gradient(60% 46% at 4% 4%, rgba(74, 110, 160, 0.12) 0%, transparent 72%),
    radial-gradient(56% 44% at 96% 96%, rgba(74, 110, 160, 0.10) 0%, transparent 72%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* ==================================================================
   THE NOTE LIBRARY — categories/note-library.html, note-library.js

   A library at night, read through a catalogue terminal. The page's
   markup is the catalogue, and without the script it is laid out as a
   plain list (the first block below). With it, every record is a BOOK
   standing on its shelf (everything under body.lib-built).

   THE PAGE'S COLOUR IS THE FIVE TOKENS, as on every dark page, and
   it is BLACK AND WHITE: the owner, 2026-09-26, "change the theme of
   the bage from that turqoise to black/white (to match the rest of the
   website". Its one accent was a terminal's phosphor, a turquoise spent
   on the terminal, the tabs, a book answering a search and the card;
   that is white now (`--lib-hi`), and the ground a neutral black with
   no blue in it. THE ACCORDS KEEP THEIR COLOURS ("that part can stay"),
   and nothing else on the page has one: each accord's colour (`--hue`,
   set by the script) is on its tab, its code, the band across the head
   of each of its books, and the specks of its card's mark.
   ================================================================== */
.library-page {
  --bg: #0b0b0c;
  --bg-2: #141415;
  --line: #2a2a2c;
  --ink: #ececec;
  --ink-rgb: 236, 236, 236;
  --muted: #8e8e91;
  --lib-hi: #f2f2f2;
  --lib-hi-rgb: 242, 242, 242;
  background: var(--bg);
  color: var(--ink);
  min-height: 100vh;
}
html.js-coming body.library-page .library { visibility: hidden; }

.library {
  position: relative;
  z-index: 1;
  max-width: 1240px;
  margin: 0 auto;
  padding: 108px 44px 140px;
}
.lib-kicker {
  margin: 0 0 14px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(var(--ink-rgb), 0.72);
}
.lib-title {
  margin: 0;
  font-family: var(--sans);
  font-size: clamp(44px, 7vw, 84px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 0.95;
}
.lib-lede {
  max-width: 560px;
  margin: 18px 0 0;
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
}

/* THE PLAIN CATALOGUE — what the page is without its script. */
.lib-shelf { margin: 56px 0 0; }
.lib-shelf-name { margin: 0 0 4px; font-size: 22px; font-weight: 600; }
.lib-shelf-code {
  margin-right: 8px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.12em;
  color: hsl(var(--hue, 0) 45% 58%);
}
.lib-shelf-say { margin: 0 0 18px; color: var(--muted); font-size: 14px; }
.lib-record { padding: 12px 0; border-top: 1px solid var(--line); }
.lib-name { margin: 0 0 4px; font-size: 16px; font-weight: 600; }
.lib-say { margin: 0; max-width: 680px; color: var(--muted); font-size: 14px; line-height: 1.55; }

/* ------------------------------------------------------------------
   THE READOUT, THE TERMINAL AND THE INDEX
   ------------------------------------------------------------------ */
.lib-readout {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  margin: 44px 0 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.lib-readout > div { padding: 14px 18px 16px; border-left: 1px solid var(--line); }
.lib-readout > div:first-child { border-left: none; padding-left: 0; }
.lib-readout dt {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
.lib-readout dd {
  margin: 6px 0 0;
  font-family: var(--mono);
  font-size: 34px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--ink);
}
/* A figure that is a word — the note used most — set smaller so it fits
   the column, with what it counts under it. */
.lib-readout dd.lib-readout-word {
  font-size: 24px;
  line-height: 1.1;
  letter-spacing: 0;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-top: 5px;
}
.lib-readout-word small {
  display: block;
  margin-top: 5px;
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--muted);
}

.lib-desk { margin: 34px 0 0; }
.lib-terminal {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 15px 18px;
  /* A hairline box, as every field on the site is: no phosphor, no
     scanlines, no glow (they went with the turquoise). */
  border: 1px solid rgba(var(--ink-rgb), 0.3);
  background: rgba(var(--ink-rgb), 0.025);
  font-family: var(--mono);
}
.lib-prompt {
  flex: none;
  font-size: 14px;
  color: var(--lib-hi);
}
.lib-query {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: none;
  outline: none;
  background: none;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  caret-color: var(--lib-hi);
}
.lib-query::placeholder { color: rgba(var(--ink-rgb), 0.3); }
/* The browser's own clear is hidden: the × after the count is the one. */
.lib-query::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; display: none; }
/* THE ×, at the right of the bar: the owner's "a little X to reset the
   search on the right side of the bar". A hairline square with a cross
   in it, faint and inert while there is nothing to clear. */
.lib-clear {
  flex: none;
  position: relative;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid rgba(var(--ink-rgb), 0.4);
  background: none;
  color: rgba(var(--ink-rgb), 0.85);
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s, opacity 0.2s;
}
.lib-clear span::before,
.lib-clear span::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 11px;
  height: 1px;
  background: currentColor;
  transform: translate(-50%, -50%) rotate(45deg);
}
.lib-clear span::after { transform: translate(-50%, -50%) rotate(-45deg); }
.lib-clear:hover:not(:disabled),
.lib-clear:focus-visible { border-color: var(--lib-hi); color: var(--lib-hi); outline: none; }
.lib-clear:disabled { opacity: 0.22; cursor: default; }
.lib-count {
  flex: none;
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.lib-tools {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin: 14px 0 0;
}
.lib-order { display: flex; gap: 0; border: 1px solid var(--line); }
.lib-order-by,
.lib-random,
.lib-tab,
.lib-card-step,
.lib-card-close {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: none;
  border: none;
  cursor: pointer;
  transition: color 0.25s, border-color 0.25s, background 0.25s;
}
.lib-order-by { padding: 7px 12px; text-transform: uppercase; }
.lib-order-by + .lib-order-by { border-left: 1px solid var(--line); }
.lib-order-by.is-on { color: var(--bg); background: var(--lib-hi); }
.lib-random {
  padding: 7px 12px;
  border: 1px solid var(--line);
  text-transform: uppercase;
}
.lib-random:hover, .lib-order-by:not(.is-on):hover { color: var(--ink); border-color: rgba(var(--lib-hi-rgb), 0.5); }
.lib-nothing { margin: 16px 0 0; font-family: var(--mono); font-size: 12px; color: var(--muted); }
.lib-nothing[hidden] { display: none; }
.lib-nothing a { color: var(--lib-hi); }

.lib-index {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 30px 0 0;
}
.lib-tab {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 9px 6px 8px;
  border: 1px solid var(--line);
}
.lib-tab::before {
  content: "";
  align-self: center;
  width: 6px;
  height: 6px;
  background: hsl(var(--hue, 150) 45% 58%);
  opacity: 0.8;
}
.lib-tab[data-shelf=""]::before { background: var(--lib-hi); }
.lib-tab small { font-size: 9px; color: rgba(var(--ink-rgb), 0.38); }
.lib-tab:hover { color: var(--ink); border-color: rgba(var(--ink-rgb), 0.3); }
.lib-tab.is-on {
  color: var(--ink);
  border-color: rgba(var(--lib-hi-rgb), 0.7);
}

/* ------------------------------------------------------------------
   THE PERIODIC TABLE OF NOTES. The owner, 2026-09-26: "i also want you
   to redisgn the note library. I am running out of ideas so redisgn it
   somehow and we will change it keep the colours and the search".

   AN ACCORD IS A GROUP (`.lib-shelf`): a block of elements standing down
   in columns seven deep — the seven periods — as many columns wide as it
   needs (`--cols`, set by the script), under a HEAD (`.lib-plate`) with
   its number, its code in its colour, its name and how many it holds.
   The groups stand side by side and wrap.

   A NOTE IS AN ELEMENT (`.lib-record`): a square tile faintly its
   accord's colour, with its number at the top left, how many fragrances
   use it at the top right, its SYMBOL large in the middle and its name
   at its foot.

   It was files in boxes (a case file, "movies and spy stuff") for one
   round, and books on shelves before that. None of either is left.
   ------------------------------------------------------------------ */
.library-page { --tile: 74px; --tile-gap: 4px; }
.lib-stacks { margin: 34px 0 0; }
body.lib-built .lib-stacks {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 34px 20px;
}
body.lib-built .lib-shelf,
body.lib-built .lib-record,
.lib-key,
.lib-card { --mark: hsl(var(--hue, 0) 45% 58%); --mark-rgb-a: hsla(var(--hue, 0), 45%, 58%, 0.1); }
body.lib-built .lib-grey,
body.lib-built .lib-grey .lib-record,
.lib-key.lib-grey,
.lib-card.lib-grey { --mark: hsl(0 0% 62%); --mark-rgb-a: hsla(0, 0%, 62%, 0.1); }

body.lib-built .lib-shelf {
  margin: 0;
  padding: 0;
  transition: opacity 0.35s;
}
/* A group the terminal or a tab has left out stays where it stands, dim,
   so the table keeps its shape. */
body.lib-built .lib-shelf.is-away { opacity: 0.16; }

/* THE GROUP'S HEAD. */
body.lib-built .lib-plate {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 2px 8px;
  width: calc(var(--cols, 1) * (var(--tile) + var(--tile-gap)) - var(--tile-gap));
  min-width: calc(var(--tile) * 2);
  margin: 0 0 8px;
  padding: 0 0 7px;
  border-bottom: 2px solid var(--mark);
  cursor: default;
}
.lib-group-no {
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--muted);
}
body.lib-built .lib-shelf-name {
  margin: 0;
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
}
body.lib-built .lib-shelf-code {
  display: block;
  margin: 0 0 1px;
  font-size: 15px;
  letter-spacing: 0.1em;
  color: var(--mark);
}
body.lib-built .lib-shelf-say { display: none; }
.lib-shelf-count {
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--muted);
}

/* THE ELEMENTS, seven deep and as many across as the group needs. */
body.lib-built .lib-records {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, var(--tile));
  grid-auto-columns: var(--tile);
  gap: var(--tile-gap);
}
body.lib-built .lib-record {
  position: relative;
  box-sizing: border-box;
  min-width: 0;
  width: var(--tile);
  height: var(--tile);
  margin: 0;
  padding: 0;
  border: 1px solid hsla(var(--hue, 0), 45%, 58%, 0.34);
  background: var(--mark-rgb-a);
  color: var(--ink);
  cursor: pointer;
  outline: none;
  transition: transform 0.25s cubic-bezier(0.2, 0.7, 0.2, 1), border-color 0.25s, background-color 0.25s, opacity 0.35s;
}
body.lib-built .lib-grey .lib-record { border-color: rgba(var(--ink-rgb), 0.3); }
.lib-el-no,
.lib-el-uses {
  position: absolute;
  top: 5px;
  font-family: var(--mono);
  font-size: 9px;
  line-height: 1;
  color: rgba(var(--ink-rgb), 0.62);
  font-variant-numeric: tabular-nums;
}
.lib-el-no { left: 6px; }
.lib-el-uses { right: 6px; color: var(--mark); }
.lib-el-sym {
  position: absolute;
  left: 0;
  right: 0;
  top: 19px;
  text-align: center;
  font-family: var(--sans);
  font-size: 23px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.01em;
}
body.lib-built .lib-record .lib-name {
  position: absolute;
  left: 4px;
  right: 4px;
  bottom: 6px;
  margin: 0;
  font-family: var(--sans);
  font-size: 8.5px;
  font-weight: 400;
  line-height: 1.15;
  text-align: center;
  color: rgba(var(--ink-rgb), 0.78);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
body.lib-built .lib-record .lib-say { display: none; }

body.lib-built .lib-record:hover,
body.lib-built .lib-record:focus-visible {
  transform: translateY(-3px);
  border-color: var(--mark);
  background: hsla(var(--hue, 0), 45%, 58%, 0.2);
}
body.lib-built .lib-record:focus-visible { outline: 1px solid var(--lib-hi); outline-offset: 2px; }

/* An element that answers the terminal, and one that does not. */
body.lib-built .lib-record.is-hit {
  border-color: var(--lib-hi);
  box-shadow: inset 0 0 0 1px rgba(var(--lib-hi-rgb), 0.8);
}
body.lib-built .lib-record.is-hit::before {
  content: "";
  position: absolute;
  bottom: -5px;
  left: 50%;
  width: 4px;
  height: 4px;
  margin-left: -2px;
  background: var(--lib-hi);
}
body.lib-built .lib-record.is-dim { opacity: 0.14; }

/* THE ELEMENT BEING READ: outlined in white while its card is open. */
body.lib-built .lib-record.is-out {
  border-color: var(--lib-hi);
  background: hsla(var(--hue, 0), 45%, 58%, 0.26);
  z-index: 2;
}

/* THE ARRIVAL: the table filling in number order. */
@keyframes lib-shelve { from { opacity: 0; transform: scale(0.82); } }
body.lib-arriving .lib-record { animation: lib-shelve 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) var(--in) backwards; }

/* THE KEY at the head of the page: one tile large, and what each part of
   it is, ruled out to the side. */
.lib-head.has-key {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 24px 40px;
}
.lib-head.has-key > :not(.lib-key) { grid-column: 1; }
.lib-key {
  grid-column: 2;
  grid-row: 1 / span 3;
  align-self: end;
  position: relative;
  display: grid;
  grid-template-columns: auto auto;
  align-items: stretch;
  gap: 0 14px;
  margin: 0;
  padding-right: 150px;
}
.lib-key-tile,
.lib-card-tile {
  position: relative;
  width: 150px;
  height: 150px;
  box-sizing: border-box;
  border: 1px solid var(--mark);
  background: var(--mark-rgb-a);
}
.lib-key-no,
.lib-key-uses {
  position: absolute;
  top: 10px;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  color: rgba(var(--ink-rgb), 0.7);
}
.lib-key-no { left: 12px; }
.lib-key-uses { right: 12px; color: var(--mark); }
.lib-key-sym {
  position: absolute;
  left: 0;
  right: 0;
  top: 40px;
  text-align: center;
  font-size: 50px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
}
.lib-key-name {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 14px;
  text-align: center;
  font-size: 13px;
  color: rgba(var(--ink-rgb), 0.85);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.lib-key figcaption {
  grid-column: 1;
  grid-row: 1;
  position: relative;
  width: 150px;
}
.lib-key-tile { grid-column: 2; grid-row: 1; }
.lib-key-say {
  position: absolute;
  right: 0;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
/* Each label ruled across to the part of the tile it names. */
.lib-key-say::after {
  content: "";
  position: absolute;
  top: 50%;
  left: calc(100% + 6px);
  height: 1px;
  background: rgba(var(--ink-rgb), 0.3);
}
.lib-key-say[data-at="no"] { top: 5px; }
.lib-key-say[data-at="no"]::after { width: 14px; }
/* How many fragrances use it is at the tile's right, so its label stands
   on the right and is ruled back to it. */
.lib-key-say[data-at="uses"] { top: 5px; right: auto; left: calc(100% + 14px + 150px + 22px); }
.lib-key-say[data-at="uses"]::after { left: auto; right: calc(100% + 6px); width: 16px; }
.lib-key-say[data-at="sym"] { top: 59px; }
.lib-key-say[data-at="sym"]::after { width: 46px; }
.lib-key-say[data-at="name"] { top: 125px; }
.lib-key-say[data-at="name"]::after { width: 36px; }

/* ------------------------------------------------------------------
   THE ROOM
   ------------------------------------------------------------------ */
.lib-ground {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.lib-lamp {
  position: fixed;
  inset: 0;
  z-index: 12;
  pointer-events: none;
  background: radial-gradient(300px circle at var(--lx, -999px) var(--ly, -999px), rgba(255, 255, 255, 0.07), transparent 70%);
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 0.6s;
}
.lib-lamp.is-on { opacity: 1; }

/* ------------------------------------------------------------------
   THE CARD — AN ELEMENT CARD standing beside the table. Not a dialog: the
   table stays in reach, and pressing another element turns it over to
   that one. Its tile large beside ITS ATOM (the canvas: an electron for
   every fragrance that uses the note), then its name and accord, what it
   is, its ISOTOPES (other spellings) and its COMPOUNDS (the fragrances).
   It was a case file for one round, with a stamp and an exhibit.

   STILL DRAWN AS THE REST OF THE SITE IS: a hairline edge with a
   registration tick at each corner, no glass, no glow, no blur — the
   owner found the glass version "too futuristic".
   ------------------------------------------------------------------ */
.lib-card {
  --tick: rgba(var(--ink-rgb), 0.7);
  position: fixed;
  top: 74px;
  right: 24px;
  bottom: 24px;
  z-index: 20;
  width: min(420px, calc(100vw - 36px));
  overflow-y: auto;
  padding: 18px 26px 28px;
  border: 1px solid rgba(var(--ink-rgb), 0.2);
  background:
    linear-gradient(var(--tick), var(--tick)) 0 0 / 12px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 0 0 / 1px 12px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 0 / 12px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 0 / 1px 12px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 0 100% / 12px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 0 100% / 1px 12px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 100% / 12px 1px no-repeat,
    linear-gradient(var(--tick), var(--tick)) 100% 100% / 1px 12px no-repeat,
    rgba(13, 13, 14, 0.98);
  box-shadow: 0 30px 70px -34px rgba(0, 0, 0, 0.9);
}
.lib-card[hidden] { display: none; }
.lib-card.is-in { animation: lib-card-in 0.45s cubic-bezier(0.2, 0.7, 0.2, 1); }
@keyframes lib-card-in { from { opacity: 0; transform: translateX(26px); } }
.lib-card-top {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 0 0 12px;
  border-bottom: 1px dashed rgba(var(--ink-rgb), 0.32);
}
.lib-card-kind {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--muted);
}
.lib-card-call {
  margin-right: auto;
  font-family: var(--mono);
  font-size: 14px;
  letter-spacing: 0.16em;
  color: var(--ink);
}
.lib-card-close { font-size: 22px; line-height: 1; padding: 2px 6px; }
.lib-card-close:hover { color: var(--ink); }
.lib-card-atom {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 8px 16px;
  margin: 22px 0 18px;
}
.lib-card-tile { width: 132px; height: 132px; }
.lib-card-tile .lib-key-sym { top: 34px; font-size: 44px; }
.lib-card-mark {
  display: block;
  width: 100%;
  height: 150px;
}
.lib-card-caption {
  grid-column: 1 / -1;
  margin: 0;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
.lib-card-shelf {
  margin: 6px 0 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--mark);
}
.lib-card-name {
  margin: 0;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  outline: none;
}
.lib-card-say { min-height: 3em; margin: 16px 0 0; font-size: 15px; line-height: 1.6; color: var(--ink); }
.lib-card h3 {
  margin: 22px 0 8px;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
.lib-card-aka[hidden], .lib-card-found[hidden], .lib-card-step[hidden] { display: none; }
.lib-card-aka ul { margin: 6px 0 4px; padding: 0 0 0 17px; list-style: none; font-size: 13px; line-height: 1.7; color: rgba(var(--ink-rgb), 0.8); }
.lib-card-aka li { display: inline; }
.lib-card-aka li + li::before { content: " · "; color: var(--muted); }
.lib-card-list { margin-top: 6px; }

/* THE DROPDOWNS. "Also catalogued as", and every group under "Found
   in" — the individual fragrances, the houses, and each house — is a
   <details> that opens on its own summary line: a small triangle that
   turns as it opens, the name, and how many are inside at the far end.
   The card opens with them all shut, which is the owner's "a lot less
   chaotic". */
.lib-drop > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 0;
  list-style: none;
  cursor: pointer;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.08);
  outline: none;
}
.lib-drop > summary::-webkit-details-marker { display: none; }
.lib-drop > summary::before {
  content: "";
  flex: none;
  width: 0;
  height: 0;
  border-left: 5px solid var(--muted);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 0.2s var(--menu-ease), border-color 0.2s;
}
.lib-drop[open] > summary::before { transform: rotate(90deg); }
.lib-drop > summary:hover::before,
.lib-drop > summary:focus-visible::before { border-left-color: var(--lib-hi); }
.lib-drop > summary:focus-visible { box-shadow: inset 0 -1px 0 var(--lib-hi); }
.lib-found-count {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.lib-card-aka { margin: 22px 0 0; }
.lib-card-aka > summary .lib-drop-name {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
.lib-found-section { margin: 6px 0 0; }
.lib-found-head {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
}
.lib-found-individual ul,
.lib-found-house ul { margin: 2px 0 6px; padding: 0 0 0 17px; list-style: none; }
.lib-found-individual a,
.lib-found-house a {
  display: flex;
  gap: 10px;
  padding: 4px 0;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.06);
  color: var(--ink);
  text-decoration: none;
  font-size: 14px;
}
.lib-found-individual a:hover .lib-found-name { color: var(--lib-hi); text-decoration: underline; text-underline-offset: 3px; }
.lib-found-houses .lib-found-house { margin: 0 0 0 17px; }
.lib-found-houses .lib-found-house > summary { padding: 6px 0; border-bottom-color: rgba(var(--ink-rgb), 0.05); }
.lib-found-housename {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(var(--ink-rgb), 0.78);
}
.lib-found-house a:hover .lib-found-name { color: var(--lib-hi); text-decoration: underline; text-underline-offset: 3px; }
.lib-found-no { flex: none; width: 22px; font-family: var(--mono); font-size: 11px; color: var(--muted); padding-top: 2px; }
.lib-found-name { transition: color 0.2s; }
.lib-card-steps { display: flex; justify-content: space-between; gap: 10px; margin: 26px 0 0; }
.lib-card-step { padding: 6px 0; text-align: left; }
.lib-card-step[data-step="1"] { margin-left: auto; text-align: right; }
.lib-card-step:hover { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  body.lib-arriving .lib-record,
  .lib-card.is-in { animation: none; }
}

@media (max-width: 700px) {
  .library { padding: 84px 16px 110px; }
  .lib-readout { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lib-readout > div:nth-child(3) { border-left: none; padding-left: 0; }
  .lib-readout > div:nth-child(n + 3) { border-top: 1px solid var(--line); }
  .lib-readout dd { font-size: 26px; }
  .lib-terminal { flex-wrap: wrap; padding: 12px 14px; }
  .lib-count { order: 3; width: 100%; }
  /* ON A PHONE every group is the width of the page, its elements in rows
     across it rather than seven deep, and the key is left out — the tile
     under the finger is its own key. */
  .library-page { --tile: 62px; }
  body.lib-built .lib-stacks { display: block; }
  body.lib-built .lib-shelf { margin: 0 0 30px; }
  body.lib-built .lib-plate { width: auto; }
  body.lib-built .lib-records {
    grid-auto-flow: row;
    grid-template-rows: none;
    grid-template-columns: repeat(auto-fill, minmax(var(--tile), 1fr));
    grid-auto-rows: var(--tile);
  }
  body.lib-built .lib-record { width: auto; }
  .lib-head.has-key { display: block; }
  .lib-key { display: none; }
  .lib-card {
    top: auto;
    left: 12px;
    right: 12px;
    bottom: 12px;
    width: auto;
    max-height: 70vh;
  }
  .lib-card-tile { width: 104px; height: 104px; }
  .lib-card-tile .lib-key-sym { top: 28px; font-size: 34px; }
  .lib-card-mark { height: 120px; }
}
