/* ============================================================================
   library.css — Oyang Archive
   The Library: the head menu, the reading-room index view, and the three new
   movements on the home page (the premise, the values, the shelf).

   WHY THIS FILE IS TOKEN-DRIVEN AND NOT INLINE
   ----------------------------------------------------------------------------
   The rest of the site is authored as inline style="" and re-coloured at
   runtime by theme.js, which reads each element's computed inline colour and
   tags it with a semantic class (see theme.css's header). That layer exists
   because the original markup came out of a design export. Nothing new needs
   to inherit that debt: everything below reads the palette straight from the
   tokens phases.css already publishes, so it follows both the light/dark
   choice and the 五行 phase of the year with no JavaScript at all.

     --p-*   light palette, set per html[data-phase]      (phases.css §1)
     --d-*   dark palette,  set per phase + [data-theme]  (phases.css §2)

   The fallbacks in every var() are the site's original fire-phase hexes, so
   the file still renders correctly if phase.js never runs.

   MOTION
   ----------------------------------------------------------------------------
   Premium archetype throughout: 240ms for a hover, 420ms for a thing being
   picked up, 700-900ms for an entrance, no overshoot anywhere. Every rule is
   gated on `prefers-reduced-motion: no-preference` or neutralised in the
   reduce block at the end of the file. Only transform and opacity animate.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   0. Token bridge
   One local name per role, resolved once here, so every rule below reads the
   same variable in both themes and all five phases.
   --------------------------------------------------------------------------- */
.oa-lib {
  --lib-bg:     var(--p-bg,    #f6f5f2);
  --lib-surf:   var(--p-surf,  #ffffff);
  --lib-surf2:  var(--p-surf2, #efece6);
  --lib-surf3:  var(--p-surf3, #e4dcda);
  --lib-panel:  var(--p-panel, #171413);
  --lib-t1:     var(--p-t1,    #14110f);
  --lib-t2:     var(--p-t2,    #4b4540);
  --lib-t3:     var(--p-t3,    #7a736b);
  --lib-t4:     var(--p-t4,    #9b938a);
  --lib-acc:    var(--p-acc,   #9e2b25);
  --lib-acc2:   var(--p-acc2,  #b23a33);
  --lib-wen:    var(--p-wen,   #2f4a6b);
  --lib-xiu:    var(--p-xiu,   #1c502c);
  --lib-yu:     var(--p-yu,    #4c2a56);
  --lib-bd:     var(--p-bd,    #e4e0d8);
  --lib-bds:    var(--p-bds,   #cfc8bd);

  /* shadow tinted to the ground, never pure black (theme.css house rule) */
  --lib-shadow:   32, 26, 22;
  --lib-shadow-a: .16;

  --lib-ease:  cubic-bezier(.22, 1, .36, 1);
  --lib-ease2: cubic-bezier(.32, .72, 0, 1);
  --lib-spring: cubic-bezier(.34, 1.56, .64, 1);   /* gentle overshoot, for signature entrances */
}
html[data-theme="dark"] .oa-lib {
  --lib-bg:     var(--d-bg,        #17130f);
  --lib-surf:   var(--d-surface,   #211c17);
  --lib-surf2:  var(--d-surface-2, #272119);
  --lib-surf3:  var(--d-surface-3, #2e2820);
  --lib-panel:  var(--d-panel,     #1e1a15);
  --lib-t1:     var(--d-text,      #ece6dc);
  --lib-t2:     var(--d-text-2,    #cbc3b6);
  --lib-t3:     var(--d-text-3,    #a49a8c);
  --lib-t4:     var(--d-text-4,    #857b6e);
  --lib-acc:    var(--d-accent,        #e46a60);
  --lib-acc2:   var(--d-accent-strong, #d1483f);
  --lib-wen:    var(--d-wen, #8aa8d6);
  --lib-xiu:    var(--d-xiu, #5fb27b);
  --lib-yu:     var(--d-yu,  #b98ccb);
  --lib-bd:     var(--d-border,        #35302a);
  --lib-bds:    var(--d-border-strong, #453f36);
  --lib-shadow:   0, 0, 0;
  --lib-shadow-a: .46;
}

/* Every work carries its own ink. Set per element with `style="--ink:..."`
   using one of the four semantic tokens, so the volume colours follow the
   phase along with everything else. */
.oa-lib [data-ink="wu"]  { --ink: var(--lib-acc); }
.oa-lib [data-ink="wen"] { --ink: var(--lib-wen); }
.oa-lib [data-ink="xiu"] { --ink: var(--lib-xiu); }
.oa-lib [data-ink="yu"]  { --ink: var(--lib-yu);  }
.oa-lib [data-ink="ink"] { --ink: var(--lib-t2);  }

/* ===========================================================================
   1. THE SHELF-CAPTION CASCADE KEYFRAME
   The desktop "Library" dropdown was removed (the nav link now enters the
   library directly), and all of its CSS went with it. Only this keyframe
   survives: the shelf caption (§2, .oa-shelf-caption > span) still animates
   its rows in on it.
   =========================================================================== */
@keyframes oaLibRowIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ===========================================================================
   2. THE SHELF — a real shelf, built in CSS 3-D
   Each work stands as a box: a spine facing the reader, a page-block face to
   its right, and a head face on top. The whole rank sits on one perspective,
   so the ones further along the shelf are genuinely further away rather than
   drawn smaller. Hovering pulls a volume out and tips it toward the reader,
   which is the one gesture a shelf actually has.

   No WebGL: eleven boxes of six faces is a job for CSS transforms, and a
   canvas would cost a library-sized download to do the same thing worse on a
   page that has no build step. The depth here is real 3-D, just not a scene
   graph. See the pointer-parallax note in library.js.
   =========================================================================== */
.oa-shelf-stage {
  position: relative;
  perspective: 1400px;
  perspective-origin: 50% 42%;
  padding: 34px 0 0;
}
.oa-shelf {
  display: flex; align-items: flex-end; justify-content: center;
  gap: 9px;
  transform-style: preserve-3d;
  transform: rotateX(var(--shelf-rx, 4deg)) rotateY(var(--shelf-ry, 0deg));
  transition: transform .5s var(--lib-ease);
  padding: 0 8px;
  min-height: 220px;
}
.oa-shelf.oa-shelf-live { transition: none; }   /* pointer parallax owns it */

/* one volume */
/* shared button reset for shelf objects — book spines and instrument objects */
.oa-spine, .oa-inst {
  -webkit-appearance: none; appearance: none;
  background: none; border: 0; margin: 0; padding: 0; cursor: pointer;
}
.oa-spine {
  --sp-w: 46px;
  --sp-h: 196px;
  --sp-d: 26px;                       /* the block of pages, i.e. the thickness */
  position: relative;
  width: var(--sp-w); height: var(--sp-h);
  flex: 0 0 auto;
  transform-style: preserve-3d;
  transform: translateZ(0) rotateY(0deg);
  transition: transform .42s var(--lib-ease2);
  will-change: auto;
}
.oa-spine:hover, .oa-spine:focus-visible {
  transform: translateZ(30px) translateY(-12px) rotateY(-17deg);
  z-index: 2;
  will-change: transform;
}
/* Clicked up out of the rank — held there (below hover in the file so it wins
   when the raised volume is also hovered, and it never drops back mid-read). */
.oa-spine.is-raised,
.oa-spine.is-raised:hover,
.oa-spine.is-raised:focus-visible {
  transform: translateZ(38px) translateY(-30px) rotateY(-14deg);
  z-index: 3;
  will-change: transform;
}
.oa-spine-face {
  position: absolute; inset: 0;
  border-radius: 2px 3px 3px 2px;
  backface-visibility: hidden;
  overflow: hidden;
}
/* the spine itself: cloth in the work's own ink, with the two gold rules a
   bound volume carries above and below its title */
.oa-spine-front {
  transform: translateZ(calc(var(--sp-d) / 2));
  background:
    linear-gradient(100deg,
      color-mix(in srgb, var(--ink, var(--lib-acc)) 86%, #14110f) 0%,
      var(--ink, var(--lib-acc)) 42%,
      color-mix(in srgb, var(--ink, var(--lib-acc)) 78%, #14110f) 100%);
  box-shadow:
    inset 1px 0 0 rgba(255, 255, 255, .16),
    inset -1px 0 0 rgba(20, 17, 15, .22),
    inset 0 0 0 .5px rgba(20, 17, 15, .28);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 9px; padding: 14px 4px;
}
/* In dark mode the phase accents are lifted so they read as INK on a dark
   ground. Used as a FILL behind white lettering they would be pastel and
   illegible, so the cloth is mixed back down toward the ground: the volume
   keeps its colour identity and the title keeps its contrast. */
html[data-theme="dark"] .oa-spine-front {
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--ink, var(--lib-acc)) 44%, #0c0a08) 0%,
    color-mix(in srgb, var(--ink, var(--lib-acc)) 60%, #0c0a08) 42%,
    color-mix(in srgb, var(--ink, var(--lib-acc)) 40%, #0c0a08) 100%);
  box-shadow:
    inset 1px 0 0 rgba(255, 255, 255, .10),
    inset -1px 0 0 rgba(0, 0, 0, .34),
    inset 0 0 0 .5px rgba(0, 0, 0, .4);
}
.oa-spine-rule {
  width: 60%; height: 1px; flex: 0 0 auto;
  background: rgba(255, 255, 255, .42);
}
.oa-spine-han {
  font-family: 'Noto Serif TC', serif;
  font-size: 19px; line-height: 1.14; letter-spacing: .04em;
  color: rgba(255, 255, 255, .96);
  writing-mode: vertical-rl; text-orientation: upright;
  text-shadow: 0 1px 1px rgba(20, 17, 15, .3);
  flex: 0 1 auto; overflow: hidden;
}
.oa-spine-rom {
  font-family: 'Inter', sans-serif; font-size: 8.5px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: rgba(255, 255, 255, .74);
  writing-mode: vertical-rl;
  flex: 0 0 auto; max-height: 74px; overflow: hidden;
}
/* The page block and the head, placed by the standard box-face composition:
   each face is laid out flush inside the box, rotated about its OWN centre,
   then translated in the parent's space onto the plane it belongs on.
   Written as `translate() rotate()` because CSS composes those as T·R, i.e.
   the rotation happens first in local space and the translation afterwards in
   the box's space. Reversing the order puts the face somewhere else entirely. */

/* the page block, standing at the right-hand face of the box */
.oa-spine-pages {
  width: var(--sp-d); height: 100%; top: 0; left: auto; right: 0; bottom: auto;
  transform-origin: 50% 50%;
  transform: translateX(calc(var(--sp-d) / 2)) rotateY(90deg);
  background:
    repeating-linear-gradient(90deg,
      #efe9dd 0px, #efe9dd 1px, #ddd4c4 1px, #ddd4c4 2px),
    linear-gradient(90deg, #f7f2e8, #d9d0bf);
  box-shadow: inset 0 0 0 .5px rgba(20, 17, 15, .18);
  border-radius: 0 2px 2px 0;
}
html[data-theme="dark"] .oa-spine-pages {
  background:
    repeating-linear-gradient(90deg,
      #cfc6b6 0px, #cfc6b6 1px, #b3aa9a 1px, #b3aa9a 2px),
    linear-gradient(90deg, #d8cfbf, #a9a091);
}
/* the head of the book, only ever glimpsed as the rank tips back */
.oa-spine-head {
  width: 100%; height: var(--sp-d); top: 0; left: 0; right: auto; bottom: auto;
  transform-origin: 50% 50%;
  transform: translateY(calc(var(--sp-d) / -2)) rotateX(90deg);
  background: linear-gradient(180deg, #e8e1d3, #cdc4b3);
  box-shadow: inset 0 0 0 .5px rgba(20, 17, 15, .2);
}
html[data-theme="dark"] .oa-spine-head { background: linear-gradient(180deg, #c8bfaf, #9d9485); }

/* The shadow the volume casts on the board. It lies flat, hinged on the box's
   bottom edge and folded away from the reader, so it reads as contact rather
   than as a smudge behind the spine. */
.oa-spine-cast {
  position: absolute; left: -9px; right: -16px; bottom: 0; height: 22px;
  transform-origin: 50% 100%;
  transform: rotateX(90deg);
  background: radial-gradient(58% 70% at 44% 40%,
    rgba(var(--lib-shadow), calc(var(--lib-shadow-a) + .22)), transparent 74%);
  transition: opacity .42s var(--lib-ease), transform .42s var(--lib-ease2);
  pointer-events: none;
}
.oa-spine:hover .oa-spine-cast,
.oa-spine:focus-visible .oa-spine-cast {
  opacity: .45;
  transform: rotateX(90deg) scale(1.2);
}
/* a volume still being bound stands unlettered and a little apart */
.oa-spine[aria-disabled="true"] { cursor: default; opacity: .5; margin-left: 16px; }
.oa-spine[aria-disabled="true"]:hover { transform: none; }
.oa-spine[aria-disabled="true"]:hover .oa-spine-cast { opacity: 1; transform: rotateX(90deg) translateZ(-2px); }

/* the board the rank stands on */
.oa-shelf-board {
  position: relative;
  height: 12px; margin: 0 auto;
  max-width: 100%;
  border-radius: 2px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--lib-bds) 70%, var(--lib-surf)) 0%,
    var(--lib-bds) 42%,
    color-mix(in srgb, var(--lib-bds) 62%, var(--lib-panel)) 100%);
  box-shadow: 0 12px 26px -16px rgba(var(--lib-shadow), calc(var(--lib-shadow-a) + .2));
}
.oa-shelf-board::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: rgba(255, 255, 255, .3);
}

/* the caption that names whichever volume the reader is pointing at */
.oa-shelf-caption {
  min-height: 58px;
  margin: 20px auto 0; max-width: 560px; text-align: center;
  font-family: 'Source Serif 4', serif; font-size: 15px; line-height: 1.55;
  color: var(--lib-t2);
}
.oa-shelf-caption b {
  display: block;
  font-family: 'Spectral', serif; font-weight: 700; font-size: 17.5px;
  color: var(--lib-t1); margin-bottom: 3px;
}
@media (prefers-reduced-motion: no-preference) {
  .oa-shelf-caption > span { animation: oaLibRowIn .34s var(--lib-ease) both; }
}

@media (max-width: 860px) {
  .oa-shelf-stage { perspective: 1100px; overflow-x: auto; overscroll-behavior-x: contain; }
  .oa-shelf { justify-content: flex-start; gap: 7px; padding: 0 18px; }
  .oa-spine { --sp-w: 40px; --sp-h: 168px; --sp-d: 22px; }
  .oa-spine-han { font-size: 16px; }
}
@media (max-width: 520px) {
  .oa-spine { --sp-w: 36px; --sp-h: 150px; --sp-d: 20px; }
  .oa-shelf-caption { font-size: 14px; }
}

/* ===========================================================================
   3. THE LIBRARY VIEW — the reading room
   =========================================================================== */
#view-library { background: var(--lib-bg); }

.oa-libhero {
  position: relative; overflow: hidden;
  border-bottom: 1px solid var(--lib-bd);
  background: linear-gradient(180deg, var(--lib-surf) 0%, var(--lib-bg) 72%);
}
.oa-libhero-mark {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -46%);
  font-family: 'Noto Serif TC', serif; font-weight: 900;
  font-size: min(44vw, 460px); line-height: .8;
  color: var(--lib-t1); opacity: .035;
  pointer-events: none; user-select: none; white-space: nowrap;
}
.oa-libhero-inner {
  position: relative; z-index: 1;
  max-width: 1180px; margin: 0 auto; padding: 78px 28px 22px;
  text-align: center;
}
.oa-libhero-eyebrow {
  font-family: 'Inter', sans-serif; font-size: 11.5px; font-weight: 600;
  letter-spacing: .22em; text-transform: uppercase; color: var(--lib-acc);
  margin-bottom: 22px;
}
.oa-libhero h1 {
  margin: 0 0 18px;
  font-family: 'Spectral', serif; font-weight: 800;
  font-size: clamp(38px, 5.2vw, 64px); line-height: 1.03; letter-spacing: -1.2px;
  color: var(--lib-t1); text-wrap: balance;
}
.oa-libhero h1 .oa-libhero-han {
  display: block; margin-top: 12px;
  font-family: 'Noto Serif TC', serif; font-weight: 700;
  font-size: clamp(18px, 2vw, 24px); letter-spacing: .18em;
  color: var(--lib-t3);
}
.oa-libhero-lead {
  margin: 0 auto; max-width: 620px;
  font-family: 'Source Serif 4', serif; font-size: 18.5px; line-height: 1.62;
  color: var(--lib-t2); text-wrap: pretty;
}

/* --- the reading room: ranks on the left, one desk on the right ----------
   The filter pills are gone. With this few works, hiding some of them was
   never help; the shelves themselves are the grouping, and the spines are
   both the index and the control. Selecting one puts its record on the desk.

   Without JavaScript every record is simply visible in sequence, which is a
   perfectly good index; §3b's `html.oa-lib-live` gate is what collapses them
   down to one. */
/* ONE layout, phone and desktop alike: the shelves run down the middle of
   the page at full width, and the record is not a column beside them but a
   card that is summoned by taking a volume down and dismissed by putting it
   back. The old two-column desk meant the page read differently depending on
   the window, and the record was always on screen whether or not anyone had
   asked for it. */
.oa-room {
  max-width: 1180px; margin: 0 auto; padding: 8px 28px 96px;
}
.oa-room-shelves { display: flex; flex-direction: column; gap: 46px; }

.oa-rack { position: relative; }
.oa-rack-head { margin-bottom: 6px; }
.oa-rack-head h2 {
  display: inline; margin: 0;
  font-family: 'Spectral', serif; font-weight: 700; font-size: 20px;
  letter-spacing: -.3px; color: var(--lib-t1);
}
.oa-rack-head .oa-rack-han {
  font-family: 'Noto Serif TC', serif; font-size: 14px; color: var(--lib-t3);
  letter-spacing: .1em; margin-left: 10px;
}
.oa-rack-head p {
  margin: 7px 0 0;
  font-family: 'Source Serif 4', serif; font-size: 14.5px; line-height: 1.5;
  color: var(--lib-t3); max-width: 52ch;
}

.oa-rack-stage { position: relative; perspective: 1300px; perspective-origin: 50% 40%; padding-top: 22px; }
/* The rank is centred on its board with `width: fit-content; margin: 0 auto`
   rather than `justify-content: center`: when a rank is wider than its stage
   and the stage scrolls, centring via justify-content clips the first spines
   out of reach on the left. This centres and still scrolls to both ends. */
.oa-rack-rank {
  display: flex; align-items: flex-end; gap: 8px; padding: 0 6px;
  width: fit-content; margin: 0 auto;
  transform-style: preserve-3d;
  transform: rotateX(4deg) rotateY(var(--rack-ry, -6deg));
  transition: transform .5s var(--lib-ease);
  min-height: 170px;
}
/* Slightly smaller than the landing shelf: here a rank is one shelf of a
   whole room rather than the page's one hero object. */
.oa-rack-spine { --sp-w: 40px; --sp-h: 152px; --sp-d: 22px; }
.oa-rack-spine .oa-spine-han { font-size: 16px; }
.oa-rack-spine .oa-spine-rom { font-size: 8px; max-height: 58px; }

/* Hovering lifts a volume a little, the way a finger on a shelf does.
   Taking it down (a click) pulls it right out and holds it there while its
   record is open. Both are pure CSS except `.is-out`, which library.js owns. */
.oa-rack-spine:hover,
.oa-rack-spine:focus-visible {
  transform: translateZ(14px) translateY(-9px) rotateY(-9deg);
  z-index: 2;
}
.oa-rack-spine:hover .oa-spine-cast,
.oa-rack-spine:focus-visible .oa-spine-cast { opacity: .62; transform: rotateX(90deg) scale(1.1); }
.oa-rack-spine.is-out,
.oa-rack-spine.is-out:hover {
  transform: translateZ(40px) translateY(-20px) rotateY(-21deg);
  z-index: 3;
}
.oa-rack-spine.is-out .oa-spine-cast { opacity: .42; transform: rotateX(90deg) scale(1.24); }

/* --- the record card ----------------------------------------------------
   In the document it is an ordinary block under the shelves, which is what a
   visitor with no JavaScript gets. Once library.js is live it becomes a card
   that is summoned and dismissed: docked to the right on a wide window, a
   sheet rising from the bottom edge on a narrow one. Same content, same
   close, both ways. */
.oa-desk { position: relative; margin-top: 46px; }

html.oa-lib-live .oa-desk {
  position: fixed; z-index: 70;
  right: 24px; top: 50%;
  width: min(370px, calc(100vw - 48px));
  max-height: min(78vh, 660px); overflow-y: auto; overscroll-behavior: contain;
  margin-top: 0;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-50%) translateX(22px);
  transition: opacity .34s var(--lib-ease),
              transform .44s var(--lib-ease2),
              visibility .34s;
}
html.oa-lib-live .oa-desk.is-open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(-50%) translateX(0);
}
@media (max-width: 980px) {
  html.oa-lib-live .oa-desk {
    right: 0; left: 0; top: auto; bottom: 0;
    width: auto; max-height: 74dvh;
    border-radius: 18px 18px 0 0;
    transform: translateY(26px);
    box-shadow: 0 -20px 50px -28px rgba(var(--lib-shadow), calc(var(--lib-shadow-a) + .3));
  }
  html.oa-lib-live .oa-desk.is-open { transform: none; }
  html.oa-lib-live .oa-desk .oa-desk-inner { border-radius: 18px 18px 0 0; }
}

/* the close, in the top right corner of the card */
.oa-desk-close {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  width: 30px; height: 30px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid transparent; cursor: pointer;
  font-family: 'Inter', sans-serif; font-size: 21px; line-height: 1;
  color: var(--lib-t3);
  transition: color .2s var(--lib-ease), background-color .2s var(--lib-ease),
              border-color .2s var(--lib-ease);
}
.oa-desk-close:hover { color: var(--lib-t1); background: var(--lib-surf2); border-color: var(--lib-bd); }
html:not(.oa-lib-live) .oa-desk-close { display: none; }   /* nothing to close */
.oa-desk-inner {
  position: relative;
  background: var(--lib-surf);
  border: 1px solid var(--lib-bd); border-radius: 16px;
  padding: 26px 28px 24px;
  box-shadow: 0 26px 56px -34px rgba(var(--lib-shadow), calc(var(--lib-shadow-a) + .18));
}
.oa-desk-inner::before {           /* the ink edge, in the current work's colour */
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  border-radius: 16px 16px 0 0;
  background: var(--ink, var(--lib-acc));
  transition: background-color .4s var(--lib-ease);
}
.oa-desk-label {
  font-family: 'Inter', sans-serif; font-size: 10px; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--lib-t4);
  margin-bottom: 18px;
}

.oa-rec { position: relative; }
.oa-rec + .oa-rec { margin-top: 34px; padding-top: 30px; border-top: 1px solid var(--lib-bd); }
.oa-rec-plate {
  display: grid; place-items: center;
  width: 52px; height: 66px; border-radius: 4px; margin-bottom: 16px;
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--ink, var(--lib-acc)) 22%, transparent),
    color-mix(in srgb, var(--ink, var(--lib-acc)) 9%, transparent));
  border: 1px solid color-mix(in srgb, var(--ink, var(--lib-acc)) 32%, transparent);
  font-family: 'Noto Serif TC', serif; font-size: 27px; line-height: 1;
  color: var(--ink, var(--lib-acc));
}
.oa-rec-kicker {
  font-family: 'Inter', sans-serif; font-size: 10.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink, var(--lib-acc));
}
.oa-rec h3 {
  margin: 9px 0 2px;
  font-family: 'Spectral', serif; font-weight: 700; font-size: 25px;
  line-height: 1.12; letter-spacing: -.45px; color: var(--lib-t1);
}
.oa-rec-han {
  font-family: 'Noto Serif TC', serif; font-size: 14px; color: var(--lib-t3);
  letter-spacing: .1em; margin-bottom: 14px;
}
.oa-rec p {
  margin: 0 0 16px;
  font-family: 'Source Serif 4', serif; font-size: 15.5px; line-height: 1.6;
  color: var(--lib-t2);
}
.oa-rec-meta {
  display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 20px;
  font-family: 'Inter', sans-serif; font-size: 11.5px; color: var(--lib-t3);
}
.oa-rec-go {
  display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
  font-family: 'Inter', sans-serif; font-size: 13.5px; font-weight: 600;
  padding: 11px 20px; border-radius: 8px; white-space: nowrap;
  background: var(--ink, var(--lib-acc)); color: #fff; border: 1px solid transparent;
  transition: filter .24s var(--lib-ease), transform .24s var(--lib-ease);
}
html[data-theme="dark"] .oa-rec-go { color: var(--d-bg, #17130f); }
.oa-rec-go:hover { filter: brightness(1.1); }
.oa-rec-go:active { transform: translateY(1px); }
.oa-rec-go .oa-arrow { transition: transform .28s var(--lib-ease2); }
.oa-rec-go:hover .oa-arrow { transform: translateX(4px); }

/* Still-being-bound spines in the reading room: clickable (they open a teaser
   on the desk) but visibly not yet on the shelf, so they read as "coming" and
   not as a live volume. */
.oa-rack-spine.oa-spine-soon { opacity: .58; }
.oa-rack-spine.oa-spine-soon:hover,
.oa-rack-spine.oa-spine-soon:focus-visible,
.oa-rack-spine.oa-spine-soon.is-out { opacity: 1; }
.oa-rack-spine.oa-spine-soon .oa-spine-front {
  background-image: repeating-linear-gradient(-45deg,
    transparent 0 6px,
    color-mix(in srgb, var(--lib-t4) 12%, transparent) 6px 7px);
}

/* The teaser record for a work that is still being bound: no call to action,
   because there is nothing yet to open — a plain note stands in for the go. */
.oa-rec-soontag {
  margin-top: 16px; padding: 11px 14px;
  border: 1px dashed var(--lib-bd); border-radius: 10px;
  font-family: 'Inter', sans-serif; font-size: 12px; line-height: 1.5;
  color: var(--lib-t4); background: color-mix(in srgb, var(--lib-t4) 5%, transparent);
}
.oa-rec-soon .oa-rec-kicker { color: color-mix(in srgb, var(--lib-t3) 80%, var(--lib-t4)); }

/* One record at a time, only once library.js is driving the selection. */
html.oa-lib-live .oa-rec { display: none; }
html.oa-lib-live .oa-rec.is-open { display: block; }
html.oa-lib-live .oa-rec + .oa-rec { margin-top: 0; padding-top: 0; border-top: none; }
@media (prefers-reduced-motion: no-preference) {
  html.oa-lib-live .oa-rec.is-open { animation: oaRecIn .42s var(--lib-ease) both; }
}
@keyframes oaRecIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* --- the bamboo slip, right under the hero -----------------------------
   A bound bamboo book 簡牘: narrow slips ruled vertically, a roller at each
   end, one line of the collection written across it. It sits between the
   hero and the shelves the way a scholar hands you a reference on the way
   in, so it is deliberately quiet: no card, no shadow stack, just the strip.

   It unrolls once, from the middle out, when it first comes into view. The
   unrolled state is the resting state in CSS; §3b's gate is what allows the
   rolled-up one to exist at all. */
.oa-yuscroll {
  max-width: 1180px; margin: 0 auto; padding: 4px 28px 10px;
  display: flex; justify-content: center;
}
.oa-yuscroll-inner {
  position: relative;
  width: min(640px, 100%);
  padding: 0 18px;
}
/* the two rollers the slips are wound on */
.oa-yuscroll-roller {
  position: absolute; top: 50%; width: 11px; height: calc(100% + 22px);
  transform: translateY(-50%);
  border-radius: 6px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--lib-t3) 55%, var(--lib-bg)) 0%,
    color-mix(in srgb, var(--lib-t2) 70%, var(--lib-bg)) 42%,
    color-mix(in srgb, var(--lib-t3) 40%, var(--lib-bg)) 100%);
  box-shadow: 0 1px 3px rgba(var(--lib-shadow), calc(var(--lib-shadow-a) + .1));
}
.oa-yuscroll-roller-l { left: 0; }
.oa-yuscroll-roller-r { right: 0; }

/* the slips themselves: a ruled field, not a panel */
.oa-yuscroll-slips {
  position: relative;
  padding: 20px 26px 18px;
  border-top: 1px solid var(--lib-bd);
  border-bottom: 1px solid var(--lib-bd);
  background:
    repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--lib-yu) 3%, transparent) 0 13px,
      transparent 13px 14px);
  text-align: center;
}
.oa-yuscroll-label {
  font-family: 'Inter', sans-serif; font-size: 9.5px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase;
  color: color-mix(in srgb, var(--lib-yu) 70%, var(--lib-t4));
  margin-bottom: 12px;
}
.oa-yuscroll-body { display: block; }
.oa-yuscroll-han {
  display: block;
  font-family: 'Noto Serif TC', serif; font-weight: 700;
  font-size: clamp(22px, 3.4vw, 30px); letter-spacing: 4px; line-height: 1.3;
  color: var(--lib-t1); word-break: keep-all;
}
.oa-yuscroll-py {
  display: block; margin-top: 4px;
  font-family: 'Source Serif 4', serif; font-style: italic; font-size: 13.5px;
  color: var(--lib-t3); padding-bottom: 1px;      /* italic descender clearance */
}
.oa-yuscroll-lit {
  margin: 10px 0 0;
  font-family: 'Source Serif 4', serif; font-size: 14.5px; line-height: 1.5;
  color: var(--lib-t2);
}
.oa-yuscroll-src {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  margin-top: 8px;
  font-family: 'Inter', sans-serif; font-size: 10.5px; color: var(--lib-t4);
}
.oa-yuscroll-src .oa-dot { width: 5px; height: 5px; border-radius: 50%; background: #2e6b3e; flex: 0 0 auto; }
html[data-theme="dark"] .oa-yuscroll-src .oa-dot { background: #63b07a; }
.oa-yuscroll-actions {
  display: flex; align-items: center; justify-content: center; gap: 20px;
  flex-wrap: wrap; margin-top: 14px;
}
.oa-yuscroll-btn {
  background: none; border: none; padding: 2px 0; cursor: pointer; white-space: nowrap;
  font-family: 'Inter', sans-serif; font-size: 12px; font-weight: 600;
  color: var(--lib-yu);
  display: inline-flex; align-items: center; gap: 6px;
  transition: color .22s var(--lib-ease);
}
.oa-yuscroll-btn .oa-arrow { transition: transform .28s var(--lib-ease2); }
.oa-yuscroll-btn:hover .oa-arrow { transform: translate(3px, -3px); }
.oa-yuscroll-btn-quiet { color: var(--lib-t3); font-weight: 500; }
.oa-yuscroll-btn-quiet:hover { color: var(--lib-t1); }
.oa-yuscroll-btn-quiet:hover .oa-arrow { transform: translateX(3px); }

/* the unroll: the slips open from the centre and the rollers ride outward */
html.oa-lib-live .oa-yuscroll-slips {
  clip-path: inset(0 50% 0 50%);
  transition: clip-path 1.05s var(--lib-ease2);
}
html.oa-lib-live .oa-yuscroll-roller {
  transition: transform .95s var(--lib-ease2), opacity .5s var(--lib-ease);
  opacity: 0;
}
html.oa-lib-live .oa-yuscroll-roller-l { transform: translate(calc(50% + 300px), -50%); }
html.oa-lib-live .oa-yuscroll-roller-r { transform: translate(calc(-50% - 300px), -50%); }
html.oa-lib-live .oa-yuscroll-inner > * { transition-delay: .1s; }
html.oa-lib-live .oa-yuscroll-inner.is-open .oa-yuscroll-slips { clip-path: inset(0 0 0 0); }
html.oa-lib-live .oa-yuscroll-inner.is-open .oa-yuscroll-roller { transform: translateY(-50%); opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .oa-proverb-swap { animation: oaProverbIn .5s var(--lib-ease) both; }
}
@keyframes oaProverbIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 720px) {
  .oa-yuscroll { padding: 2px 16px 6px; }
  .oa-yuscroll-inner { padding: 0 13px; }
  .oa-yuscroll-roller { width: 8px; }
  .oa-yuscroll-slips { padding: 16px 14px 14px; }
  .oa-yuscroll-han { letter-spacing: 2px; }
  .oa-yuscroll-actions { gap: 16px; }
}

@media (max-width: 980px) {
  .oa-room {
    grid-template-columns: minmax(0, 1fr); gap: 40px;
    padding: 8px 20px 72px;
  }
  /* The desk becomes the rising sheet here, not at 860px: below this width the
     room is already one column and there is no right-hand gutter to dock to.
     The rule lives in the `html.oa-lib-live` block below, because a bare
     `.oa-desk` override loses to it on specificity no matter what a media
     query says — which is exactly how this breakpoint used to do nothing. */
  .oa-rack-stage { overflow-x: auto; overscroll-behavior-x: contain; }
  /* keep the pointer lean: dropping rotateY() here left library.js writing
     --rack-ry every frame into a transform that no longer read it */
  .oa-rack-rank { transform: rotateX(3deg) rotateY(var(--rack-ry, -6deg)); }
}
@media (max-width: 720px) {
  .oa-libhero-inner { padding: 56px 20px 18px; }
  .oa-rack-spine { --sp-w: 36px; --sp-h: 136px; --sp-d: 20px; }
  .oa-desk-inner { padding: 22px 20px 20px; }
}

/* ===========================================================================
   4. HOME — the shelf movement + the featured work
   =========================================================================== */
.oa-shelfsec { position: relative; border-bottom: 1px solid var(--lib-bd); }
.oa-shelfsec-inner { max-width: 1180px; margin: 0 auto; padding: 92px 28px 88px; }
.oa-shelfsec-head { max-width: 660px; margin: 0 0 10px; }
.oa-shelfsec-head h2 {
  margin: 0 0 14px;
  font-family: 'Spectral', serif; font-weight: 800;
  font-size: clamp(30px, 4vw, 46px); line-height: 1.06; letter-spacing: -.8px;
  color: var(--lib-t1); text-wrap: balance;
}
.oa-shelfsec-head p {
  margin: 0;
  font-family: 'Source Serif 4', serif; font-size: 17.5px; line-height: 1.64;
  color: var(--lib-t2); text-wrap: pretty;
}
.oa-shelfsec-foot {
  display: flex; align-items: center; justify-content: center; gap: 18px;
  flex-wrap: wrap; margin-top: 40px;
}
.oa-shelfsec-foot-note {
  margin: 0; max-width: 30ch; text-align: right;
  font-family: 'Source Serif 4', serif; font-size: 15.5px; line-height: 1.5;
  color: var(--lib-t2); text-wrap: pretty;
}
@media (max-width: 620px) {
  .oa-shelfsec-foot { flex-direction: column; gap: 20px; }
  .oa-shelfsec-foot-note { text-align: center; }
}
.oa-btn {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: 'Inter', sans-serif; font-size: 14.5px; font-weight: 600;
  padding: 14px 26px; border-radius: 8px; cursor: pointer;
  border: 1px solid transparent;
  transition: background-color .24s var(--lib-ease), color .24s var(--lib-ease),
              border-color .24s var(--lib-ease), transform .24s var(--lib-ease);
}
.oa-btn .oa-arrow { transition: transform .28s var(--lib-ease2); }
.oa-btn:hover .oa-arrow { transform: translateX(4px); }
.oa-btn-solid { background: var(--lib-acc); color: #fff; }
html[data-theme="dark"] .oa-btn-solid { color: var(--d-bg, #17130f); }
.oa-btn-solid:hover { background: var(--lib-acc2); }
.oa-btn-quiet {
  background: none; color: var(--lib-t2); border-color: var(--lib-bds);
}
.oa-btn-quiet:hover { color: var(--lib-t1); border-color: var(--lib-t3); }
.oa-btn:active { transform: translateY(1px); }

/* ===========================================================================
   4b. HOME — the editor's pick, and the volume that comes down to it
   The section is deliberately taller than its content: that surplus is the
   scroll distance library.js scrubs the book along. A sticky stage inside it
   holds the book and the panel, so the reader watches one object move rather
   than a page of parts appearing.

   The book is the same six-face construction as a shelf spine, plus a cover:
   spine + page block + cover, hinged at the spine so the cover can swing.
   RESTING STATE (no GSAP, no JavaScript, reduced motion) is the finished
   pose: cover open, panel beside it, everything readable. GSAP only ever
   winds it BACK to the closed pose and plays it forward.
   =========================================================================== */
.oa-pick { position: relative; border-bottom: 1px solid var(--lib-bd); }
.oa-pick-track { min-height: 168vh; }        /* the scrub runway */
.oa-pick-stage {
  position: sticky; top: 0;
  min-height: 100dvh;
  display: grid; grid-template-columns: 250px minmax(0, 1fr);
  grid-template-rows: auto;
  align-items: center; gap: 0 48px;
  max-width: 1000px; margin: 0 auto; padding: 0 28px;
  perspective: 1500px; perspective-origin: 40% 45%;
}
.oa-pick-stage > .oa-featured { grid-area: 1 / 2; }

/* --- the volume ----------------------------------------------------------
   The slot holds the book's place in the grid so the layout never moves; the
   book itself sits on top of it, and goes `position: fixed` only while it is
   in flight from the shelf (see library.js §4b). */
.oa-pick-slot {
  width: 132px; height: 196px;
  justify-self: center; grid-area: 1 / 1;
  pointer-events: none;
}
.oa-pick-book { grid-area: 1 / 1; }
.oa-pick-book.is-flying {
  position: fixed; left: 0; top: 0; z-index: 4;
  margin: 0; will-change: transform;
}
/* The spine that lent the volume stands empty while it is out — and stops
   answering to the pointer and to Tab, so an invisible spine is never a live
   control sitting in the middle of the shelf. */
.oa-spine.is-lent { opacity: 0; visibility: hidden; pointer-events: none; }
.oa-pick-book {
  --pb-w: 132px;      /* cover width  */
  --pb-h: 196px;      /* cover height */
  --pb-d: 30px;       /* the block of pages, i.e. the spine's width */
  position: relative;
  width: var(--pb-w); height: var(--pb-h);
  justify-self: center;
  transform-style: preserve-3d;
  transform: rotateY(22deg) rotateX(6deg);
  --ink: var(--lib-acc);
}
.oa-pick-face {
  position: absolute; inset: 0;
  border-radius: 2px 4px 4px 2px;
  backface-visibility: hidden;
}
/* the back board, which the pages sit on */
.oa-pick-back {
  transform: translateZ(calc(var(--pb-d) / -2));
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--ink) 78%, #14110f) 0%,
    color-mix(in srgb, var(--ink) 88%, #14110f) 100%);
  box-shadow: inset 0 0 0 .5px rgba(20, 17, 15, .3);
}
/* the block of pages between the boards */
.oa-pick-block {
  inset: 5px 4px 5px 0;
  transform: translateZ(0);
  background:
    repeating-linear-gradient(180deg, #efe9dd 0 1px, #ddd4c4 1px 2px),
    linear-gradient(180deg, #f7f2e8, #d9d0bf);
  box-shadow: inset 0 0 0 .5px rgba(20, 17, 15, .14);
  border-radius: 1px 3px 3px 1px;
}
html[data-theme="dark"] .oa-pick-block {
  background:
    repeating-linear-gradient(180deg, #cfc6b6 0 1px, #b3aa9a 1px 2px),
    linear-gradient(180deg, #d8cfbf, #a9a091);
}
/* the front cover, hinged at its own left edge so it opens like a cover */
.oa-pick-cover {
  transform-origin: 0 50%;
  transform: translateZ(calc(var(--pb-d) / 2)) rotateY(-56deg);
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--ink) 88%, #14110f) 0%,
    var(--ink) 46%,
    color-mix(in srgb, var(--ink) 80%, #14110f) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    inset 0 -1px 0 rgba(20, 17, 15, .26),
    inset 0 0 0 .5px rgba(20, 17, 15, .3);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 24px 12px;
}
html[data-theme="dark"] .oa-pick-cover {
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--ink) 44%, #0c0a08) 0%,
    color-mix(in srgb, var(--ink) 60%, #0c0a08) 46%,
    color-mix(in srgb, var(--ink) 40%, #0c0a08) 100%);
}
.oa-pick-cover-rule { width: 46%; height: 1px; background: rgba(255, 255, 255, .42); flex: 0 0 auto; }
.oa-pick-cover-han {
  font-family: 'Noto Serif TC', serif; font-weight: 700; font-size: 40px; line-height: 1;
  color: rgba(255, 255, 255, .96);
  text-shadow: 0 1px 2px rgba(20, 17, 15, .34);
}
.oa-pick-cover-rom {
  font-family: 'Inter', sans-serif; font-size: 9px; font-weight: 600;
  letter-spacing: .22em; text-transform: uppercase;
  color: rgba(255, 255, 255, .74);
}

/* --- the panel ----------------------------------------------------------- */
.oa-featured {
  position: relative;
  display: block; width: 100%; max-width: 520px;
  text-align: left; font-family: inherit;
  background: var(--lib-surf);
  border: 1px solid var(--lib-bd); border-radius: 14px;
  padding: 28px 30px 26px;
  box-shadow: 0 26px 56px -34px rgba(var(--lib-shadow), calc(var(--lib-shadow-a) + .2));
  transition: border-color .34s var(--lib-ease), box-shadow .34s var(--lib-ease),
              transform .34s var(--lib-ease);
}
/* The card lifts for the control inside it — :focus-within, because the card
   itself is no longer the thing that takes focus. */
.oa-featured:hover, .oa-featured:focus-within {
  border-color: color-mix(in srgb, var(--lib-acc) 42%, var(--lib-bd));
  box-shadow: 0 30px 60px -30px rgba(var(--lib-shadow), calc(var(--lib-shadow-a) + .28));
  transform: translateY(-3px);
}
.oa-featured-tag { display: block; margin-bottom: 14px; }
.oa-featured-tag b {
  display: inline-block;
  background: var(--lib-acc); color: #fff;
  font-family: 'Inter', sans-serif; font-size: 10.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  padding: 5px 12px; border-radius: 999px;
}
html[data-theme="dark"] .oa-featured-tag b { color: var(--d-bg, #17130f); }
.oa-featured h3 {
  margin: 0 0 12px;
  font-family: 'Spectral', serif; font-weight: 800;
  font-size: clamp(25px, 3vw, 33px); line-height: 1.07; letter-spacing: -.7px;
  color: var(--lib-t1);
}
.oa-featured p {
  margin: 0 0 20px;
  font-family: 'Source Serif 4', serif; font-size: 16px; line-height: 1.6;
  color: var(--lib-t2);
}
.oa-featured-meta {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  font-family: 'Inter', sans-serif; font-size: 12px; color: var(--lib-t3);
}
.oa-featured-go {
  font-family: inherit;
  font-size: 14.5px; font-weight: 600; color: var(--lib-acc);
  display: inline-flex; align-items: center; gap: 8px;
  background: none; border: none; padding: 0; cursor: pointer;
  border-radius: 4px;
}
.oa-featured-meta .oa-arrow { transition: transform .28s var(--lib-ease2); }
.oa-featured:hover .oa-featured-go .oa-arrow { transform: translateX(5px); }

@media (max-width: 900px) {
  /* No runway, no scrub: the book simply sits above the panel, open, in
     normal flow. The slot exists only to reserve the book's place in the
     two-column desktop grid, so here it goes away entirely and the explicit
     grid areas are handed back — a `grid-area: 1 / 2` against a one-column
     grid would invent a second column and push the panel off the page. */
  .oa-pick-track { min-height: 0; }
  .oa-pick-stage {
    position: static; min-height: 0;
    grid-template-columns: minmax(0, 1fr); gap: 34px;
    padding: 64px 20px 68px; justify-items: center;
  }
  .oa-pick-slot { display: none; }
  .oa-pick-stage > .oa-featured,
  .oa-pick-book { grid-area: auto; }
  .oa-pick-book { --pb-w: 104px; --pb-h: 154px; --pb-d: 24px; }
  .oa-pick-cover-han { font-size: 32px; }
  .oa-featured { max-width: none; padding: 24px 22px 22px; }

  /* Mobile signature moment: the featured volume rests closed in a three-
     quarter view, low, and the first time it scrolls into view it rises and
     swings its cover open on one smooth ease — no fade and no overshoot, so a
     compositor never has to flatten the 3-D book mid-move (that flicker was
     the glitch). The panel's lines then cascade up beside it. Gated on
     `.oa-pick-mobfx`, which library.js (§4b, mobile branch) adds only when an
     observer is primed to add `.oa-pick-open`; without JS or under reduced
     motion the book and panel keep their finished, open pose. */
  .oa-pick.oa-pick-mobfx .oa-pick-book {
    transform: translateY(40px) rotateY(38deg) rotateX(9deg);
    transition: transform 1.15s var(--lib-ease);
    will-change: transform;
  }
  .oa-pick.oa-pick-mobfx .oa-pick-cover {
    transform: translateZ(calc(var(--pb-d) / 2)) rotateY(-2deg);
    transition: transform 1.25s var(--lib-ease) .12s;
    will-change: transform;
  }
  .oa-pick.oa-pick-mobfx.oa-pick-open .oa-pick-book {
    transform: translateY(0) rotateY(20deg) rotateX(6deg);
  }
  .oa-pick.oa-pick-mobfx.oa-pick-open .oa-pick-cover {
    transform: translateZ(calc(var(--pb-d) / 2)) rotateY(-58deg);
  }
  /* the panel's lines cascade up once the cover has swung open */
  .oa-pick.oa-pick-mobfx .oa-featured > * {
    opacity: 0; transform: translateY(22px);
    transition: opacity .7s var(--lib-ease), transform .7s var(--lib-ease);
  }
  .oa-pick.oa-pick-mobfx.oa-pick-open .oa-featured > * { opacity: 1; transform: none; }
  .oa-pick.oa-pick-mobfx.oa-pick-open .oa-featured > *:nth-child(1) { transition-delay: .34s; }
  .oa-pick.oa-pick-mobfx.oa-pick-open .oa-featured > *:nth-child(2) { transition-delay: .43s; }
  .oa-pick.oa-pick-mobfx.oa-pick-open .oa-featured > *:nth-child(3) { transition-delay: .52s; }
  .oa-pick.oa-pick-mobfx.oa-pick-open .oa-featured > *:nth-child(4) { transition-delay: .61s; }
}

/* ===========================================================================
   5. HOME — the thesis
   The premise and the three things the archive holds to, as one editorial
   movement. Each claim keeps a figure beside it, and that figure sticks
   through the reading of its own claim: the rail feels pinned without a single
   pinned element, so there is no ScrollTrigger to get out of step, no layout
   shift, and the whole thing collapses to figure-above-text on a phone by
   turning one property off.

   Every figure is drawn rather than decorated, and every number in them is
   read off the data this site already ships (proverbs-data.json's four
   provenance grades; the spectrum the diagnostic study builds).
   =========================================================================== */
.oa-thesis { position: relative; border-bottom: 1px solid var(--lib-bd); }

.oa-thesis-intro {
  max-width: 720px; margin: 0 auto; padding: 104px 28px 24px; text-align: center;
}
.oa-thesis-eyebrow {
  font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 600;
  letter-spacing: .2em; text-transform: uppercase; color: var(--lib-acc);
  margin-bottom: 18px;
}
.oa-thesis-intro h2 {
  margin: 0 0 20px;
  font-family: 'Spectral', serif; font-weight: 800;
  font-size: clamp(32px, 4.4vw, 52px); line-height: 1.05; letter-spacing: -1px;
  color: var(--lib-t1); text-wrap: balance;
}
.oa-thesis-intro p {
  margin: 0;
  font-family: 'Source Serif 4', serif; font-size: 18.5px; line-height: 1.64;
  color: var(--lib-t2); text-wrap: pretty;
}

.oa-thesis-steps { max-width: 1180px; margin: 0 auto; padding: 0 28px 40px; }

.oa-step {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
  gap: 0 72px;
  align-items: stretch;          /* both columns run the full row, so the
                                    figure column has room to stick inside */
  min-height: 92vh;
  padding: 30px 0;
}
.oa-step-figwrap { position: relative; }
.oa-fig {
  position: sticky; top: 21vh;
  margin: 0; padding: 0;
  display: flex; flex-direction: column; justify-content: center;
  min-height: 300px;
}
.oa-step-text {
  display: flex; flex-direction: column; justify-content: center;
  padding: 40px 0;
}
.oa-step-n {
  font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 600;
  letter-spacing: .16em; color: var(--lib-t4);
  font-variant-numeric: tabular-nums;
  margin-bottom: 18px;
}
.oa-step-text h3 {
  margin: 0 0 20px;
  font-family: 'Spectral', serif; font-weight: 700;
  font-size: clamp(26px, 3.2vw, 38px); line-height: 1.1; letter-spacing: -.7px;
  color: var(--lib-t1); text-wrap: balance;
}
.oa-step-text p {
  margin: 0 0 16px;
  font-family: 'Source Serif 4', serif; font-size: 17px; line-height: 1.66;
  color: var(--lib-t2); text-wrap: pretty; max-width: 44ch;
}
.oa-step-text p:last-child { margin-bottom: 0; }
.oa-step-note {
  padding-left: 18px; border-left: 2px solid var(--lib-acc);
  font-size: 15.5px !important; color: var(--lib-t3) !important;
}
.oa-step-text .oa-han { font-family: 'Noto Serif TC', serif; color: var(--lib-t1); }

/* --- figure furniture shared by all four ------------------------------- */
.oa-fig-cap {
  font-family: 'Inter', sans-serif; font-size: 10.5px; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--lib-t4);
  margin-bottom: 16px;
}
.oa-fig-note {
  margin: 16px 0 0; max-width: 40ch;
  font-family: 'Source Serif 4', serif; font-size: 13px; line-height: 1.5;
  color: var(--lib-t4);
}

/* --- figure 01: what a long line leaves behind --------------------------
   Survivorship, drawn plainly: many attempts start at each generation, most
   stop within one or two, and one line runs the whole width. The frame, the
   generation ticks and the dropped attempts are all stationary; only the
   surviving line and its two labels fade up, so the eye lands on the claim
   rather than watching a diagram assemble itself. */
.sv-frame line { stroke: var(--lib-bd); stroke-width: 1; }
.sv-drop line { stroke: var(--lib-bds); stroke-width: 1.5; stroke-linecap: round; opacity: .7; }
.sv-hold { stroke: var(--lib-acc); stroke-width: 3; stroke-linecap: round; }
.sv-dot { fill: var(--lib-acc); }
.sv-lab {
  font-family: Inter, sans-serif; font-size: 10.5px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; fill: var(--lib-acc);
}
.sv-lab-drop { fill: var(--lib-t4); font-weight: 500; }
.sv-gen {
  font-family: Inter, sans-serif; font-size: 9.5px; letter-spacing: .1em;
  text-transform: uppercase; fill: var(--lib-t4);
}

/* --- figure 02: the three mirrors --------------------------------------- */
.mir-disc {
  fill: color-mix(in srgb, var(--lib-acc) 7%, transparent);
  stroke: var(--lib-acc); stroke-width: 1.4;
}
.mir-han {
  font-family: 'Noto Serif TC', serif; font-size: 27px; font-weight: 700;
  fill: var(--lib-t1);
}
.mir-rom {
  font-family: 'Inter', sans-serif; font-size: 9px; font-weight: 600;
  letter-spacing: .16em; fill: var(--lib-acc);
}
.mir-txt {
  font-family: 'Source Serif 4', serif; font-size: 11.5px; fill: var(--lib-t3);
}

/* --- figure 03: the two branches and their root ------------------------- */
.oa-fig svg { width: 100%; max-width: 330px; height: auto; display: block; margin: 0 auto; overflow: visible; }
.fig-ink path {
  fill: none; stroke: var(--lib-t3); stroke-width: 1.6; stroke-linecap: round;
}
.fig-draw {
  /* pathLength="1" on every drawn path, so the dash maths is exact and the
     three strokes take the same time regardless of their real length */
  stroke-dasharray: 1;
}
.fig-node { fill: var(--lib-surf); stroke: var(--lib-acc); stroke-width: 1.8; }
.fig-han {
  font-family: 'Noto Serif TC', serif; font-size: 26px; font-weight: 700;
  fill: var(--lib-t1);
}
.fig-l3.fig-han { font-size: 19px; }
.fig-rom {
  font-family: 'Inter', sans-serif; font-size: 10.5px; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase; fill: var(--lib-t4);
}

/* --- figure 04: the apparatus a page carries ----------------------------
   The evidence discipline drawn as what it actually is on the page: three
   marks in the margin. Two of them every serious archive makes; the third,
   闕, marks where the record simply stops, and is the one usually left out.
   Deliberately carries no numbers: it is the rule, not a measurement of one
   collection. */
.ap-page rect { fill: var(--lib-surf); stroke: var(--lib-bd); stroke-width: 1; }
.ap-line { stroke: var(--lib-bds); stroke-width: 3; stroke-linecap: round; }
.ap-line-cited { stroke: color-mix(in srgb, var(--lib-acc) 52%, var(--lib-bds)); }
.ap-line-gap { stroke: var(--lib-bd); stroke-dasharray: 5 6; }
.ap-tick { stroke: var(--lib-bds); stroke-width: 1; }
.ap-tick-gap { stroke: var(--lib-acc); stroke-dasharray: 3 3; }
.ap-han {
  font-family: 'Noto Serif TC', serif; font-size: 17px; font-weight: 700;
  fill: var(--lib-acc);
}
.ap-lab {
  font-family: 'Inter', sans-serif; font-size: 9px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; fill: var(--lib-t4);
}
.ap-lab-gap { fill: var(--lib-acc); }
/* the vermilion seal that marks a lacuna */
.ap-seal { fill: var(--lib-acc); }
.ap-seal-han {
  font-family: 'Noto Serif TC', serif; font-size: 12px; font-weight: 700;
  fill: var(--lib-surf);
}
html[data-theme="dark"] .ap-seal-han { fill: var(--d-bg, #17130f); }

/* --- the draw ------------------------------------------------------------
   MOTION SPLIT: the words move, the diagrams do not.

   The figures used to draw themselves in, line by line, and it read wrong:
   in an editorial layout the eye follows the prose, and a diagram that is
   still assembling itself pulls attention away from the sentence that
   explains it. So the choreography now lives entirely in the text column
   (staggered rise, one line after another, which is the movement this page
   was liked for), and every figure simply arrives: the frame and the plate
   are stationary, and the marks that carry the meaning fade up behind them.

   Everything that HIDES anything is gated on html.oa-lib-live, which
   library.js sets only once it holds an observer that will put it back
   (same contract as §8's reveal). With no JavaScript, a failed load, or
   reduced motion, every figure and every line renders finished.

   Only opacity and transform, so nothing here costs a layout. */

/* --- the text: the movement --------------------------------------------- */
html.oa-lib-live .oa-step-text > * {
  opacity: 0; transform: translateY(26px);
  transition: opacity .78s var(--lib-ease), transform .78s var(--lib-ease);
}
html.oa-lib-live .oa-step.is-on .oa-step-text > * { opacity: 1; transform: none; }
html.oa-lib-live .oa-step.is-on .oa-step-text > *:nth-child(1) { transition-delay: .02s; }
html.oa-lib-live .oa-step.is-on .oa-step-text > *:nth-child(2) { transition-delay: .10s; }
html.oa-lib-live .oa-step.is-on .oa-step-text > *:nth-child(3) { transition-delay: .19s; }
html.oa-lib-live .oa-step.is-on .oa-step-text > *:nth-child(4) { transition-delay: .28s; }
html.oa-lib-live .oa-step.is-on .oa-step-text > *:nth-child(5) { transition-delay: .37s; }
html.oa-lib-live .oa-step.is-on .oa-step-text > *:nth-child(6) { transition-delay: .46s; }

/* --- the figures: stationary, and a quiet fade --------------------------- */
/* the frame arrives as one piece, without travelling */
html.oa-lib-live .oa-fig {
  opacity: 0;
  transition: opacity .7s var(--lib-ease);
}
html.oa-lib-live .oa-fig.is-on { opacity: 1; }

/* Inside it, only the marks that carry the claim are given their own beat,
   and even those only fade: the axes, plates and frames never move. */
html.oa-lib-live .oa-fig .fig-late {
  opacity: 0;
  transition: opacity .6s var(--lib-ease);
}
html.oa-lib-live .oa-fig.is-on .fig-late { opacity: 1; }
html.oa-lib-live .oa-fig.is-on .fig-late-1 { transition-delay: .34s; }
html.oa-lib-live .oa-fig.is-on .fig-late-2 { transition-delay: .50s; }
html.oa-lib-live .oa-fig.is-on .fig-late-3 { transition-delay: .66s; }

@media (prefers-reduced-motion: reduce) {
  html.oa-lib-live .oa-step-text > *,
  html.oa-lib-live .oa-fig,
  html.oa-lib-live .oa-fig .fig-late {
    opacity: 1 !important; transform: none !important; transition: none !important;
  }
}

@media (max-width: 900px) {
  .oa-thesis-intro { padding: 68px 20px 8px; }
  .oa-thesis-steps { padding: 0 20px 20px; }
  .oa-step {
    grid-template-columns: minmax(0, 1fr);
    gap: 26px; min-height: 0; padding: 44px 0;
    border-top: 1px solid var(--lib-bd);
  }
  .oa-step:first-child { border-top: none; }
  .oa-fig { position: static; min-height: 0; }     /* nothing to stick to */
  .oa-step-text { padding: 0; }
  .oa-step-text p { max-width: none; }

  /* Fancier reveal on a phone. The lines travel further and settle on the
     spring; the step number leads — sliding in from the margin and overshooting
     a touch, so each point announces itself before its prose; and the figure
     lifts and scales in rather than only fading. These override the base reveal
     (§6) purely on transform/opacity, so reduced motion and no-JS are untouched:
     both keep the resting pose the base rules already guarantee. */
  html.oa-lib-live .oa-step-text > * {
    transform: translateY(36px);
    transition: opacity .82s var(--lib-ease), transform .92s var(--lib-ease);
  }
  html.oa-lib-live .oa-step .oa-step-n {
    transform: translateX(-28px) translateY(6px) scale(.8);
    transform-origin: left center;
    transition: opacity .68s var(--lib-ease), transform .9s var(--lib-spring);
  }
  html.oa-lib-live .oa-step.is-on .oa-step-n { transform: none; }
  html.oa-lib-live .oa-fig {
    transform: translateY(20px) scale(.96);
    transition: opacity .8s var(--lib-ease), transform .9s var(--lib-ease);
  }
  html.oa-lib-live .oa-fig.is-on { transform: none; }
}

/* ===========================================================================
   7. HOME — the closing
   The one dark movement, kept from the original page as a deliberate coda.
   =========================================================================== */
.oa-close { background: var(--lib-panel); color: #f3efe7; }
html[data-theme="dark"] .oa-close { background: var(--d-bg-2, #1b1712); }
.oa-close-inner {
  max-width: 900px; margin: 0 auto; padding: 96px 28px 92px; text-align: center;
}
.oa-close h2 {
  margin: 0 0 20px;
  font-family: 'Spectral', serif; font-weight: 800;
  font-size: clamp(28px, 3.8vw, 44px); line-height: 1.08; letter-spacing: -.8px;
  color: #f6f3ee; text-wrap: balance;
}
.oa-close p {
  margin: 0 auto 34px; max-width: 620px;
  font-family: 'Source Serif 4', serif; font-size: 17.5px; line-height: 1.66;
  color: #c4bbae; text-wrap: pretty;
}
.oa-close .oa-btn-solid { background: #f6f3ee; color: #17130f; }
.oa-close .oa-btn-solid:hover { background: #fff; color: #17130f; }
.oa-close .oa-btn-quiet { color: #d4cabc; border-color: #4a423a; }
.oa-close .oa-btn-quiet:hover { color: #f6f3ee; border-color: #7d7266; }
.oa-close-sign {
  margin-top: 44px; padding-top: 26px; border-top: 1px solid #322c26;
  display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;
  font-family: 'Inter', sans-serif; font-size: 12.5px; color: #9a9086;
}
.oa-close-sign .oa-han { font-family: 'Noto Serif TC', serif; font-size: 14px; color: #c4bbae; }
@media (max-width: 720px) { .oa-close-inner { padding: 64px 20px 60px; } }

/* ===========================================================================
   8. Shared bits
   =========================================================================== */
.oa-arrow { display: inline-block; }

/* A call to action never breaks across lines. Where a label sits opposite a
   meta string in the same row, the meta wraps and the label does not. */
.oa-btn,
.oa-rec-go,
.oa-featured-go,
.oa-instrument-foot b,
.oa-proverb-btn { white-space: nowrap; }
.oa-instrument-foot,
.oa-featured-meta,
.oa-rec-meta { flex-wrap: wrap; }

/* The hidden state is gated on html.oa-lib-reveal-live, which wireReveal adds
   only once it has an IntersectionObserver in hand and knows it can put these
   back. Deliberately NOT the shared html.oa-lib-live: that class also means
   "the reading room is running", and wireRoom sets it unconditionally, so
   sharing it let opening the Library hide the whole home page on a browser
   where wireReveal had already declined to open its gate.
   Same contract as html.oa-intro / html.oa-motion elsewhere on the site: with
   no JavaScript, a failed load, or reduced motion, the class is never set and
   every section is simply visible. */
html.oa-lib-reveal-live .oa-lib [data-lib-reveal] { opacity: 0; transform: translateY(22px); }
html.oa-lib-reveal-live .oa-lib [data-lib-reveal].oa-lib-in {
  opacity: 1; transform: none;
  transition: opacity .8s var(--lib-ease), transform .8s var(--lib-ease);
  transition-delay: calc(var(--i, 0) * 70ms);       /* standard stagger, caps <500ms */
}

/* ===========================================================================
   9. Reduced motion — everything above collapses to its resting state
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .oa-lib *,
  .oa-lib *::before,
  .oa-lib *::after {
    transition: none !important;
    animation: none !important;
  }
  html.oa-lib-reveal-live .oa-lib [data-lib-reveal] { opacity: 1 !important; transform: none !important; }
  .oa-shelf { transform: none !important; }
  .oa-spine:hover, .oa-spine:focus-visible { transform: translateY(-6px) !important; }
  .oa-spine.is-raised { transform: translateY(-12px) !important; }
  .oa-featured:hover { transform: none !important; }
  .oa-instrument:hover { transform: none !important; }
  .oa-fig-rail::after { transform: scaleX(1) !important; }
}

/* ============================================================
   INSTRUMENT OBJECTS
   The Instruments rack shows a bespoke object per instrument
   instead of a book spine. Each button keeps .oa-rack-spine +
   data-work, so wireRoom() drives it unchanged (hover lifts,
   click takes it "out" and opens the record, a second click
   fires the record's own control). .oa-inst just swaps the
   3-D book for a made object. Object colours are hard-set
   (a compass looks the same in either theme); they read on
   the light board and keep their glow on the dark one.
   ============================================================ */
.oa-rack[data-shelf="instruments"] .oa-rack-rank {
  transform: none;                 /* objects stand square, they do not lean like books */
  gap: 34px; min-height: 172px; align-items: flex-end;
}
.oa-inst {
  position: relative; display: flex; align-items: flex-end; justify-content: center;
  width: auto; height: auto; color: inherit;
  transition: transform .5s cubic-bezier(.2,.85,.25,1), filter .4s;
  -webkit-tap-highlight-color: transparent;
}
.oa-inst > span { display: block; }
.oa-inst .oa-i-han { font-family: 'Noto Serif TC', serif; line-height: 1; }
/* a little contact shadow grounds each object on the board */
.oa-inst::after {
  content: ""; position: absolute; left: 50%; bottom: -7px; transform: translateX(-50%);
  width: 78%; height: 12px; border-radius: 50%; z-index: 0;
  background: radial-gradient(50% 100% at 50% 50%, rgba(52,38,22,.36), transparent 72%);
  transition: opacity .4s, transform .4s;
}
/* hover lifts; taking it down (.is-out) lifts more and brightens — no book rotateY */
.oa-rack-spine.oa-inst:hover,
.oa-rack-spine.oa-inst:focus-visible { transform: translateY(-8px) scale(1.02); z-index: 3; }
.oa-rack-spine.oa-inst.is-out,
.oa-rack-spine.oa-inst.is-out:hover { transform: translateY(-13px) scale(1.05); z-index: 4; }
.oa-rack-spine.oa-inst.is-out { filter: drop-shadow(0 8px 18px rgba(40,26,12,.4)); }
/* keep the contact shadow on the board while the object itself rises */
.oa-rack-spine.oa-inst:hover::after,
.oa-rack-spine.oa-inst:focus-visible::after { transform: translateX(-50%) translateY(8px); opacity: .8; }
.oa-rack-spine.oa-inst.is-out::after,
.oa-rack-spine.oa-inst.is-out:hover::after { transform: translateX(-50%) translateY(13px); opacity: .7; }
.oa-inst:focus-visible { outline: 2px solid var(--ink, #b23a33); outline-offset: 8px; border-radius: 10px; }

/* --- 派 · Luopan compass --- */
.oa-icmp { width: 120px; height: 120px; border-radius: 50%; position: relative; z-index: 1;
  background: radial-gradient(circle at 40% 34%, #7d5f37 0%, #4a3720 58%, #2c2013 100%);
  box-shadow: 0 13px 22px -9px rgba(28,17,8,.7), inset 0 0 16px rgba(0,0,0,.5); }
.oa-icmp::before { content: ""; position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: inset 0 0 0 3px #dcb85f, inset 0 0 0 5px #7c5f24, inset 0 2px 3px rgba(255,255,255,.4); }
.oa-icmp-r { position: absolute; border-radius: 50%; border: 1px solid #e4c877; }
.oa-icmp-r.r1 { inset: 9px; opacity: .9; }
.oa-icmp-r.r2 { inset: 27px; opacity: .72; border-color: #b8943f; }
.oa-icmp-r.r3 { inset: 40px; border-style: dashed; opacity: .6; }
.oa-icmp-tk { position: absolute; inset: 9px; border-radius: 50%;
  background: repeating-conic-gradient(from 0deg, #e4c877 0 .9deg, transparent .9deg 15deg); opacity: .72;
  -webkit-mask: radial-gradient(circle, transparent 40px, #000 41px); mask: radial-gradient(circle, transparent 40px, #000 41px); }
.oa-icmp-nd { position: absolute; top: 50%; left: 50%; width: 3px; height: 86px; transform: translate(-50%,-50%) rotate(22deg);
  background: linear-gradient(180deg, #e46a60 0 50%, #dcd0bd 50% 100%); border-radius: 3px;
  box-shadow: 0 0 7px rgba(228,106,96,.5); transition: transform 1s cubic-bezier(.3,.9,.2,1); z-index: 3; }
.oa-icmp-hb { position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; transform: translate(-50%,-50%); border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, #3a2a20, #17110c); z-index: 4; display: grid; place-items: center;
  box-shadow: inset 0 0 0 1px #e46a60, 0 0 12px rgba(158,43,37,.45); }
.oa-icmp-hb .oa-i-han { font-size: 22px; color: #e46a60; text-shadow: 0 0 8px rgba(0,0,0,.55); }
.oa-inst:hover .oa-icmp-nd,
.oa-inst.is-out .oa-icmp-nd { transform: translate(-50%,-50%) rotate(-49deg); }

/* --- 語 · Clapper-bell (木鐸) --- */
.oa-ibell { width: 104px; height: 118px; position: relative; }
.oa-ibell-loop { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 24px; height: 21px;
  border: 5px solid #7f568f; border-radius: 50%; background: transparent;
  box-shadow: inset 0 0 0 1px #b98ccb, 0 1px 2px rgba(0,0,0,.4); }
.oa-ibell-body { position: absolute; top: 18px; left: 6px; right: 6px; bottom: 20px;
  border-radius: 34% 34% 30% 30% / 44% 44% 22% 22%;
  background: linear-gradient(155deg, #97a37b 0%, #6e7c54 42%, #34411f 100%);
  box-shadow: 0 12px 20px -10px rgba(20,26,10,.7), inset 0 3px 6px rgba(255,255,255,.2), inset 0 -8px 14px rgba(0,0,0,.4);
  display: flex; align-items: center; justify-content: center; }
.oa-ibell-body .oa-i-han { font-size: 32px; color: #f2ead2; text-shadow: 0 2px 5px rgba(0,0,0,.5); margin-top: 7px; }
.oa-ibell-lip { position: absolute; bottom: 15px; left: 3px; right: 3px; height: 13px; border-radius: 10px / 7px;
  background: linear-gradient(180deg, #c49bd6, #5a3468); box-shadow: 0 3px 8px -3px rgba(0,0,0,.5); }
.oa-ibell-clap { position: absolute; bottom: 4px; left: 50%; width: 10px; height: 10px; border-radius: 50%; transform: translateX(-50%);
  background: radial-gradient(circle at 35% 30%, #e4c877, #7c5f24); box-shadow: 0 2px 4px rgba(0,0,0,.5);
  transition: margin-left .5s ease; }
.oa-inst:hover .oa-ibell-clap,
.oa-inst.is-out .oa-ibell-clap { animation: oaBellRing .6s ease; }
@keyframes oaBellRing { 0%,100% { margin-left: 0; } 30% { margin-left: -12px; } 70% { margin-left: 10px; } }

/* --- 法 · A pile of scrolls (卷帙) — the External Wushu lexicon --- */
.oa-ipile { width: 150px; height: 120px; position: relative; }
.oa-iroll { position: absolute; }
.oa-iroll-end { position: absolute; inset: 0; border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, #f0e8cf 0 22%, #d3c39c 22% 30%, #efe7cd 30% 38%, #cdbd93 38% 46%, #efe7cd 46% 55%, #c4b488 55% 64%, #e0d5b4 64% 100%);
  box-shadow: inset 0 0 0 3px #9c8a4c, 0 8px 14px -8px rgba(30,22,8,.6); }
.oa-iroll-end::after { content: ""; position: absolute; inset: 40%; border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, #7a6a34, #463c1f); }
.oa-iroll-body { position: absolute; top: 2px; bottom: 2px; left: 12px; right: -2px; border-radius: 0 10px 10px 0;
  background: linear-gradient(180deg, #e8dcbb, #cbba8b); box-shadow: 0 8px 14px -8px rgba(30,22,8,.55); }
.oa-iroll.p1 { width: 118px; height: 42px; left: 16px; bottom: 2px; } .oa-iroll.p1 .oa-iroll-end { width: 42px; height: 42px; }
.oa-iroll.p2 { width: 118px; height: 38px; left: 30px; bottom: 40px; } .oa-iroll.p2 .oa-iroll-end { width: 38px; height: 38px; }
.oa-iroll.p3 { width: 118px; height: 36px; left: 20px; bottom: 76px; transition: transform .6s ease; } .oa-iroll.p3 .oa-iroll-end { width: 36px; height: 36px; }
.oa-ipile-tag { position: absolute; left: 52px; bottom: 46px; width: 30px; padding: 7px 0 9px; border-radius: 3px; z-index: 5;
  background: linear-gradient(180deg, #3f608a, #22405f); box-shadow: 0 6px 12px -5px rgba(0,0,0,.55); display: flex; justify-content: center; }
.oa-ipile-tag .oa-i-han { font-size: 17px; color: #f3e6cf; writing-mode: vertical-rl; text-shadow: 0 1px 2px rgba(0,0,0,.5); }
.oa-inst:hover .oa-iroll.p3,
.oa-inst.is-out .oa-iroll.p3 { transform: translate(9px, -5px); }

@media (max-width: 560px) {
  .oa-rack[data-shelf="instruments"] .oa-rack-rank { gap: 18px; }
  .oa-icmp { width: 104px; height: 104px; }
  .oa-ibell { width: 92px; height: 104px; }
  .oa-ipile { width: 132px; height: 108px; transform: scale(.9); transform-origin: bottom center; }
}
