@layer chrome {

/* ============================================================
   TY MABEE - FIELD JOURNAL · fixed chrome
   things that sit over the page rather than in it
   ============================================================ */

/* ============================================================
   PAPER GRAIN
   ------------------------------------------------------------
   Was a fixed full-viewport <svg> running a live feTurbulence
   filter - the single most expensive thing on the page, since a
   filtered fixed layer is re-evaluated by the compositor on every
   scroll frame, and mix-blend-mode forces everything beneath it
   to be blended rather than fast-pathed.

   Now it is a 128x128 tile used as a background-image: rasterised
   once, cached, and repeated. stitchTiles="stitch" hides the seams.
   Identical output, a fraction of the cost.
   ============================================================ */
.grain {
  position: fixed; inset: 0;
  pointer-events: none; z-index: var(--z-grain);
  background-image: var(--noise-grain);
  background-repeat: repeat;
  background-size: 128px 128px;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
}

/* Modal dialogs render in the top layer, which is above every
   z-index - including the grain. Without this a dialog would look
   conspicuously clean against a grainy page. Each dialog carries
   its own grain instead. */
dialog::before {
  content: ""; position: absolute; inset: 0;
  pointer-events: none; z-index: 2;
  background-image: var(--noise-grain);
  background-repeat: repeat;
  background-size: 128px 128px;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  border-radius: inherit;
}

/* ============================================================
   PENCIL CURSOR
   ============================================================ */
.cursor {
  position: fixed; left: 0; top: 0; z-index: var(--z-cursor);
  pointer-events: none;
  transform: translate(-50%, -20%);
  transition: opacity 0.25s ease;
  opacity: 0;
}
body.cursor-active .cursor { opacity: 1; }
body.cursor-active,
body.cursor-active * { cursor: none !important; }

/* Two exceptions to hiding the system cursor. Text fields need a
   caret to be usable at all, and anyone who has put the pencil
   down (a catalogue command, persisted) gets their own cursor
   back - some people rely on it. */
body.cursor-active :is(input, textarea, [contenteditable]) { cursor: text !important; }
body.pencil-down .cursor { display: none; }
body.pencil-down,
body.pencil-down * { cursor: auto !important; }
body.pencil-down :is(a, button, [data-hover]) { cursor: pointer !important; }
body.pencil-down .drag { cursor: grab !important; }

.cursor svg {
  display: block; transform-origin: 50% 90%;
  transition: transform 0.15s var(--ease-out);
}
.cursor-pencil { transform-origin: 16px 29px; }
.cursor.is-hover svg { transform: rotate(-18deg) scale(1.15); }
.cursor.is-down svg { transform: rotate(-30deg) scale(0.92); }

/* the pencil artwork was hard-coded hex; these follow the theme */
.cursor .c-barrel  { fill: var(--amber); }
.cursor .c-ferrule { fill: var(--brown); }
.cursor .c-wood    { fill: var(--paper-deep); }
.cursor .c-tip     { fill: var(--ink); }

.cursor-label {
  position: absolute; left: 26px; top: 4px;
  font-family: var(--font-hand); font-size: 1rem; color: var(--green);
  white-space: nowrap; opacity: 0; transform: rotate(-4deg) translateY(4px);
  transition: opacity 0.2s ease, transform 0.2s var(--ease-spring);
}
.cursor.has-label .cursor-label { opacity: 1; transform: rotate(-4deg) translateY(0); }

/* graphite trail specks */
.speck {
  position: fixed; z-index: var(--z-speck); pointer-events: none;
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--ink-soft); opacity: 0.35;
  animation: speck-fade 0.7s ease-out forwards;
}

/* ============================================================
   THE SPINE
   ------------------------------------------------------------
   A saddle stitch down the left edge that sews itself shut as
   you read - the scroll progress indicator, as journal hardware.

   The thread is a repeating-linear-gradient rather than an SVG
   path: it stays crisp at any viewport height with no rebuild on
   resize, and the dash period can be aligned to the punched holes
   so the thread visibly disappears into each one.

   Progress is revealed by clipping a wrapper, not by scaling the
   thread - scaling would stretch the stitches.
   ============================================================ */
.spine {
  position: fixed; left: 0; top: 0; bottom: 0; width: 20px;
  z-index: var(--z-chrome);
  pointer-events: none;
}

/* punched holes, every 28px */
.spine-holes {
  position: absolute; inset: 0;
  background-image: radial-gradient(circle 2.6px at 10px 14px,
                    var(--rule-edge) 98%, transparent 100%);
  background-size: 100% 28px;
  background-repeat: repeat-y;
}

/* Clips from the top; the thread inside stays full height so the
   stitch spacing never compresses. The trailing mask is the needle
   still being pulled through. */
.spine-run {
  position: absolute; left: 0; top: 0; width: 100%;
  height: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 34px), transparent);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 34px), transparent);
}
.spine-thread {
  position: absolute; top: 0; left: 8px;
  width: 4px; height: 100vh;
  opacity: 0.6;
  /* dash period 28px, matching the holes; the 9-19px gap sits over
     each hole so the thread passes through rather than across it */
  background: repeating-linear-gradient(to bottom,
    var(--brown) 0 9px, transparent 9px 19px, var(--brown) 19px 28px);
}

/* Where scroll-driven animation exists this runs off the main
   thread entirely and spine.js never touches the DOM. The JS path
   is the fallback, not the other way round. */
@supports (animation-timeline: scroll()) {
  .spine-run {
    animation: stitch linear both;
    animation-timeline: scroll(root block);
  }
}
@keyframes stitch { from { height: 0; } to { height: 100%; } }
.force-spine-fallback .spine-run { animation: none; }

/* ============================================================
   INDEX TABS
   ------------------------------------------------------------
   Filofax tabs on the fore-edge. Real <nav><a href="#id">, so they
   work with no JS and no CSS at all; the scrollspy in spine.js
   only adds aria-current and the slide-out.
   ============================================================ */
.tabs {
  position: fixed; right: 0; top: 50%; translate: 0 -50%;
  z-index: var(--z-chrome);
  pointer-events: none;
}
.tabs ol { list-style: none; display: flex; flex-direction: column; gap: 0.3rem; }
.tabs li { display: flex; justify-content: flex-end; }

.tab {
  pointer-events: auto;
  display: flex; align-items: center; gap: 0.55rem;
  writing-mode: vertical-rl;
  padding: 0.95rem 0.32rem 0.95rem 0.5rem;
  background: var(--paper-warm);
  color: var(--ink-soft);
  text-decoration: none;
  border: 1.5px solid var(--ink);
  border-right: none;
  /* wobble on the outer edge only - the inner edge is the binding */
  border-radius: 90px 0 0 100px / 16px 0 0 18px;
  box-shadow: -2px 3px 0 rgb(from var(--shade) r g b / 0.12);
  /* staggered like tabs cut at different depths */
  translate: calc(var(--i, 0) * 2px) 0;
  transition: translate 0.32s var(--ease-spring),
              background 0.3s ease, color 0.3s ease;
}
.tab-num { font-family: var(--font-mono); font-size: 0.6rem; color: var(--rust); letter-spacing: 0.1em; }
.tab-label { font-size: 0.78rem; letter-spacing: 0.02em; }

.tab:hover,
.tab:focus-visible { translate: -7px 0; color: var(--ink); }

.tab[aria-current] {
  background: var(--paper);
  color: var(--ink);
  translate: -11px 0;
  box-shadow: -3px 4px 0 rgb(from var(--shade) r g b / 0.16);
}
.tab[aria-current] .tab-label { font-weight: 600; }

/* ------------------------------------------------------------
   NARROW · the binding does not fit beside the page, so the tabs
   become a strip of ribbon markers along the bottom edge. Hiding
   them outright would leave the site with no navigation again,
   which is the thing this phase exists to fix.
   ------------------------------------------------------------ */
@media (max-width: 860px) {
  .spine { display: none; }

  .tabs {
    right: 0; left: 0; top: auto; bottom: 0; translate: none;
    background: var(--paper);
    background: linear-gradient(to top,
      rgb(from var(--paper) r g b / 0.97), rgb(from var(--paper) r g b / 0.82));
    border-top: 1.5px solid var(--rule-edge);
    padding: 0.35rem 0.5rem calc(0.35rem + env(safe-area-inset-bottom, 0px));
  }
  .tabs ol {
    flex-direction: row; gap: 0.3rem;
    overflow-x: auto; scrollbar-width: none;
    scroll-snap-type: x proximity;
  }
  .tabs ol::-webkit-scrollbar { display: none; }
  .tabs li { flex: 0 0 auto; scroll-snap-align: center; }

  .tab {
    writing-mode: horizontal-tb;
    flex-direction: column; gap: 0.1rem;
    padding: 0.4rem 0.7rem 0.5rem;
    border-right: 1.5px solid var(--ink);
    border-bottom: none;
    border-radius: 10px 12px 0 0 / 14px 16px 0 0;
    box-shadow: 2px -2px 0 rgb(from var(--shade) r g b / 0.1);
    translate: 0 calc(var(--i, 0) * 1px);
  }
  .tab:hover, .tab:focus-visible { translate: 0 -3px; }
  .tab[aria-current] { translate: 0 -5px; box-shadow: 2px -3px 0 rgb(from var(--shade) r g b / 0.16); }
  .tab-label { font-size: 0.7rem; }

  /* Keep the active marker in view as scrollspy advances. */
  .tab[aria-current] { scroll-margin-inline: 42vw; }

  /* clear the strip so the colophon is not trapped underneath */
  .colophon { padding-bottom: 5rem; }
}

}
