/* ═══════════════════════════════════════════════════════════════════════
   DEVMARK LIBRARY — the two immersive sections
   ═══════════════════════════════════════════════════════════════════════

   Thriller & Suspense and Anime & Manga are meant to feel like somewhere
   else in the building, not like a different building. So this file adds
   exactly two things and touches nothing that exists:

     1. a SCOPE. Both sections re-declare the design tokens on their own
        wrapper. Every component inside — card, kicker, sec-head, shelf —
        is already written against those tokens, so it re-tones itself
        without a single component rule being rewritten or overridden.
     2. an ATMOSPHERE and an ENTRANCE, which are what make the section read
        as deliberate rather than merely recoloured.

   WHY THE ALIASES ARE RE-DECLARED TOO
   tokens.css defines --paper as var(--ink-1) on :root. A custom property
   that references another is substituted where it is DECLARED, so a
   descendant redefining --ink-1 does not move --paper: the alias would keep
   its light value and the stylesheets use the aliases far more than the
   semantic names (--paper 64 uses, --paper-4 49, against --ink-1 twice).
   Re-declaring only the semantic names would therefore have produced dark
   cards carrying dark text. Both sets are declared below, together.

   RULES THIS FILE KEEPS (the same five motion.js keeps)
   Only transform, opacity and filter are animated — never a layout
   property, so nothing here can cause a reflow or a layout shift. Every
   atmospheric layer is decorative, aria-hidden, sits behind the content and
   is painted by CSS alone: if the stylesheet loads and the JS does not, the
   section is still complete. Content is never hidden waiting for an
   animation. Reduced motion stops the movement and keeps the colour.
   ═══════════════════════════════════════════════════════════════════════ */


/* ══════════════════════════════════════════════════════ shared frame ════ */

.imm {
  position: relative;
  isolation: isolate;               /* keeps the atmosphere's blend local   */
  background: var(--ground);
  color: var(--paper-2);
  /* The section is a band inside a page whose header and footer stay in the
     library's own colours. Bleeding it to the viewport edge is what makes it
     read as a place rather than as a large card. */
  margin-inline: calc(50% - 50vw);
  padding-inline: calc(50vw - 50%);
  padding-block: var(--s-9) var(--s-10);
  overflow: clip;                   /* contains the atmosphere, no scrollbar */
}

/* `overflow: clip` is recent enough to deserve a floor. `hidden` does the
   same containment here; it is not used as the default because it creates a
   scroll container, which would break position:sticky inside the section. */
@supports not (overflow: clip) {
  .imm { overflow: hidden; }
}

.imm > .wrap { position: relative; z-index: 1; }

/* ── The atmosphere ──────────────────────────────────────────────────────
   Two or three soft fields drifting behind the content. They are large,
   slow and low-contrast: the point is that the page feels like it has air
   moving through it, not that anything is seen to move. */
.imm__atmos {
  position: absolute;
  inset: -20% -10%;
  z-index: 0;
  pointer-events: none;
  contain: strict;                  /* cannot affect layout or hit-testing  */
}

.imm__atmos i {
  position: absolute;
  display: block;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0;
  /* Faded in rather than shown, so a slow device paints the text first and
     the atmosphere arrives behind it a beat later. */
  animation: imm-atmos-in var(--d-signature) var(--e-out) forwards;
}

@keyframes imm-atmos-in { to { opacity: var(--atmos-o, .5); } }

/* Drift paths. Three different periods, all prime-ish against each other so
   the composition never visibly repeats. Transform only. */
@keyframes imm-drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(6%, -4%, 0) scale(1.12); }
}
@keyframes imm-drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.06); }
  50%      { transform: translate3d(-7%, 5%, 0) scale(.94); }
}
@keyframes imm-drift-c {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(4%, 7%, 0); }
}


/* ── The section identity ────────────────────────────────────────────────
   The brief asks for a heading bold enough that a reader knows immediately
   that this part of the library is different. It is built from the type
   scale the rest of the site uses — a bigger, louder setting of the same
   voice, not a second typeface. */

/* The numeral is set BEHIND the heading rather than above it.

   It was first laid out in flow with a negative bottom margin, which is the
   usual trick for tucking a display figure under a headline — and it put the
   numeral straight through the "Shelf 15 of 16" kicker, because a negative
   margin moves the box but not the line box the kicker starts on. Taking it
   out of flow removes the collision at its source: nothing else has to be
   nudged, and the heading below can then be measured on its own. */
.imm__id {
  position: relative;
  padding-top: clamp(2.5rem, 9vw, 6rem);
}

.imm__numeral {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: -0.06em;
  z-index: 0;
  font-family: var(--font-display);
  font-size: clamp(5rem, 22vw, 15rem);
  line-height: .8;
  letter-spacing: var(--ls-hero);
  color: transparent;
  -webkit-text-stroke: 1px var(--hair-2);
  user-select: none;
  pointer-events: none;
}

/* Everything that is read sits above the numeral. */
.imm__id > :not(.imm__numeral) { position: relative; z-index: 1; }

.imm__title {
  font-family: var(--font-display);
  /* A display serif at this size needs room below the baseline: at
     line-height .96 the descender of the g in "Manga" was being cut off by
     the line box. 1.04 plus a little padding clears it at every width. */
  font-size: clamp(2.4rem, 7.6vw, 6rem);
  line-height: 1.04;
  padding-bottom: .06em;
  letter-spacing: var(--ls-hero);
  color: var(--paper);
  margin: 0 0 var(--s-5);
  /* "Thriller & Suspense" broke as three lines with the ampersand stranded
     on its own. Keeping the ampersand with the word before it fixes that
     without forcing a width the narrow viewports cannot honour. */
  text-wrap: balance;
}

.imm__title em {
  font-style: italic;
  color: var(--accent);
}

.imm__blurb {
  max-width: 54ch;
  font-size: var(--t-lead);
  line-height: var(--lh-body);
  color: var(--paper-2);
  margin: 0 0 var(--s-5);
}

.imm__meta {
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--paper-4);
}

/* The rule under the identity block draws itself in once, left to right.
   scaleX on a full-width element: no layout involvement. */
.imm__rule {
  height: 1px;
  background: var(--hair-2);
  transform-origin: left center;
  transform: scaleX(0);
  animation: imm-rule var(--d-signature) var(--e-out) 120ms forwards;
  margin: var(--s-7) 0 var(--s-8);
}
@keyframes imm-rule { to { transform: scaleX(1); } }


/* ══════════════════════════════════════════ 15 · thriller & suspense ════ */
/*
   The library after dark. Every value below is the existing ladder
   inverted, not a new palette: five warm surfaces in the same order, warm
   off-white inks in place of warm charcoal ones, and the same terracotta
   accent lifted until it clears AA against the dark.

   Measured against the WORST of the five surfaces in this scope:
     paper   #F7F2E8  11.43:1      ink-3/paper-3 #B3A997  5.49:1
     paper-2 #DCD4C5   8.66:1      accent        #E89B69  5.66:1
     paper-4 #8E8372   3.42:1  — large text and rules only, as upstream

   The sunk→ground step is 1.06 rather than the 1.12 the light ladder holds.
   That is a limit of the contrast formula near black, where the +0.05 flare
   term dominates any real difference, and not a value that can be tuned
   out. The shelf recess is separated by an inset shadow instead, which is
   what actually sells the recess in the light theme too.
*/
.imm--thriller {
  /* grounds */
  --ground:  #14100C;
  --surface: #221C15;
  --band:    #2D261C;
  --raised:  #3A3126;
  --sunk:    #0A0806;
  --ink-900: #14100C;
  --ink-850: #2D261C;
  --ink-800: #221C15;
  --ink-750: #221C15;
  --ink-700: #3A3126;
  --ink-650: #0A0806;

  /* ink, both naming generations */
  --ink-1: #F7F2E8;  --paper:   #F7F2E8;
  --ink-2: #DCD4C5;  --paper-2: #DCD4C5;
  --ink-3: #B3A997;  --paper-3: #B3A997;
  --ink-4: #8E8372;  --paper-4: #8E8372;

  --accent: #E89B69;
  --display-accent: #E9A0A0;
  --accent-soft: rgba(232, 155, 105, 0.16);
  --accent-line: rgba(232, 155, 105, 0.38);
  --signal-line: rgba(232, 155, 105, 0.55);

  /* On a dark ground a shadow cannot be seen; separation comes from a light
     hairline instead. These keep the same names so components are unchanged. */
  --hair:   rgba(247, 242, 232, 0.13);
  --hair-2: rgba(247, 242, 232, 0.22);
  --hair-3: rgba(247, 242, 232, 0.34);
  --ink-600: rgba(247, 242, 232, 0.34);

  --lift-1: 0 1px 2px rgba(0, 0, 0, .55);
  --lift-2: 0 6px 18px rgba(0, 0, 0, .60), 0 1px 3px rgba(0, 0, 0, .50);
  --lift-3: 0 18px 44px rgba(0, 0, 0, .66), 0 6px 14px rgba(0, 0, 0, .52);
  --lift-4: 0 48px 104px rgba(0, 0, 0, .72), 0 14px 34px rgba(0, 0, 0, .58);
  --contact: 0 2px 4px rgba(0, 0, 0, .70), 0 8px 18px rgba(0, 0, 0, .45);
}

/* The searchlight. One warm field and one cold one, crossing slowly. */
.imm--thriller .imm__atmos i:nth-child(1) {
  --atmos-o: .40;
  inset-inline-start: -6%;
  inset-block-start: -8%;
  width: 62vw; height: 62vw;
  background: radial-gradient(circle, #8A4A22 0%, rgba(138, 74, 34, 0) 68%);
  animation-name: imm-atmos-in, imm-drift-a;
  animation-duration: var(--d-signature), 31s;
  animation-timing-function: var(--e-out), var(--e-in-out);
  animation-iteration-count: 1, infinite;
  animation-fill-mode: forwards, none;
}
.imm--thriller .imm__atmos i:nth-child(2) {
  --atmos-o: .32;
  inset-inline-end: -10%;
  inset-block-start: 18%;
  width: 54vw; height: 54vw;
  background: radial-gradient(circle, #2C4C6B 0%, rgba(44, 76, 107, 0) 70%);
  animation-name: imm-atmos-in, imm-drift-b;
  animation-duration: var(--d-signature), 43s;
  animation-timing-function: var(--e-out), var(--e-in-out);
  animation-iteration-count: 1, infinite;
  animation-fill-mode: forwards, none;
}
.imm--thriller .imm__atmos i:nth-child(3) {
  --atmos-o: .22;
  inset-inline-start: 22%;
  inset-block-end: -18%;
  width: 48vw; height: 48vw;
  background: radial-gradient(circle, #6E2B2B 0%, rgba(110, 43, 43, 0) 72%);
  animation-name: imm-atmos-in, imm-drift-c;
  animation-duration: var(--d-signature), 37s;
  animation-timing-function: var(--e-out), var(--e-in-out);
  animation-iteration-count: 1, infinite;
  animation-fill-mode: forwards, none;
}

/* A vignette closes the edges so the band reads as a lit room rather than a
   dark rectangle. Painted above the fields, below the content. */
.imm--thriller::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 80% at 50% 30%, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, .55) 100%);
}

.imm--thriller .imm__numeral { -webkit-text-stroke-color: rgba(232, 155, 105, .30); }

/* Covers are the only saturated thing on the page, upstream and here. In the
   dark scope they are given a fractionally cooler, dimmer resting state so
   that hovering one feels like light falling on it. */
.imm--thriller .card__cover { filter: brightness(.88) saturate(.92); transition: filter var(--d-ui) var(--e-out); }
.imm--thriller .card:hover .card__cover,
.imm--thriller .card:focus-within .card__cover { filter: brightness(1.04) saturate(1.02); }

/* The recess the spines stand in, separated by shadow rather than by a
   luminance step (see the note above). */
.imm--thriller .shelfstage { box-shadow: inset 0 2px 18px rgba(0, 0, 0, .65); }


/* ═════════════════════════════════════════════════ 16 · anime & manga ═══ */
/*
   Not the dark treatment again in another colour. This section keeps the
   library's paper ground — which is what makes the two sections read as two
   places rather than one effect applied twice — and takes its character
   from motion and from the shelf's own hue chord (268 / 210 / 318).

   The restraint that keeps this out of cartoon territory: the colour stays
   in the atmosphere and the accents, never in the type, and nothing here
   moves faster than the library's own --d-ui.
*/
.imm--anime {
  --ground:  #F6F2EA;
  --surface: #FFFDF8;
  --accent:  #6D3FA8;
  --display-accent: #6E2B2B;
  --accent-soft: rgba(109, 63, 168, 0.12);
  --accent-line: rgba(109, 63, 168, 0.34);
  --ink-900: #F6F2EA;
  --ink-800: #FFFDF8;
}

/* Three washes from the shelf's hues, drifting. Lighter and more saturated
   than the thriller fields because they sit on paper, not on ink. */
.imm--anime .imm__atmos i:nth-child(1) {
  --atmos-o: .62;
  inset-inline-start: -8%;
  inset-block-start: -10%;
  width: 58vw; height: 58vw;
  background: radial-gradient(circle, #B49AE8 0%, rgba(180, 154, 232, 0) 68%);
  animation-name: imm-atmos-in, imm-drift-b;
  animation-duration: var(--d-signature), 29s;
  animation-timing-function: var(--e-out), var(--e-in-out);
  animation-iteration-count: 1, infinite;
  animation-fill-mode: forwards, none;
}
.imm--anime .imm__atmos i:nth-child(2) {
  --atmos-o: .58;
  inset-inline-end: -12%;
  inset-block-start: 8%;
  width: 52vw; height: 52vw;
  background: radial-gradient(circle, #8EC2EC 0%, rgba(142, 194, 236, 0) 70%);
  animation-name: imm-atmos-in, imm-drift-a;
  animation-duration: var(--d-signature), 41s;
  animation-timing-function: var(--e-out), var(--e-in-out);
  animation-iteration-count: 1, infinite;
  animation-fill-mode: forwards, none;
}
.imm--anime .imm__atmos i:nth-child(3) {
  --atmos-o: .48;
  inset-inline-start: 30%;
  inset-block-end: -20%;
  width: 46vw; height: 46vw;
  background: radial-gradient(circle, #EDA6D2 0%, rgba(237, 166, 210, 0) 72%);
  animation-name: imm-atmos-in, imm-drift-c;
  animation-duration: var(--d-signature), 34s;
  animation-timing-function: var(--e-out), var(--e-in-out);
  animation-iteration-count: 1, infinite;
  animation-fill-mode: forwards, none;
}

/* Speed lines. The one piece of overt anime grammar in the section, and it
   is kept to a decorative strip behind the identity block rather than laid
   over the catalogue. repeating-linear-gradient on a skewed, masked band:
   no elements, no script, and it animates on transform alone. */
.imm--anime .imm__speed {
  position: absolute;
  inset-block-start: 0;
  inset-inline: -25%;
  height: 46vh;
  z-index: 0;
  pointer-events: none;
  opacity: .22;
  background: repeating-linear-gradient(
    102deg,
    var(--accent) 0 2px,
    transparent 2px 26px
  );
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 78%);
          mask-image: linear-gradient(to bottom, #000 0%, transparent 78%);
  transform: translate3d(0, 0, 0);
  animation: imm-speed 18s linear infinite;
}
@keyframes imm-speed {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(26px, 0, 0); }   /* exactly one stripe period */
}

.imm--anime .imm__numeral { -webkit-text-stroke-color: rgba(109, 63, 168, .34); }

/* The card treatment. A sheen crossing the cover on hover — the single
   interactive flourish the section gets, on a pseudo-element so no extra
   markup is needed and nothing is animated but transform and opacity. */
.imm--anime .card__cover { position: relative; overflow: hidden; }
.imm--anime .card__cover::after {
  content: "";
  position: absolute;
  inset: -40% -60%;
  background: linear-gradient(
    102deg, transparent 42%, rgba(255, 255, 255, .55) 50%, transparent 58%);
  transform: translate3d(-70%, 0, 0);
  opacity: 0;
  pointer-events: none;
}
.imm--anime .card:hover .card__cover::after,
.imm--anime .card:focus-within .card__cover::after {
  animation: imm-sheen var(--d-content) var(--e-out) forwards;
}
@keyframes imm-sheen {
  0%   { transform: translate3d(-70%, 0, 0); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translate3d(70%, 0, 0);  opacity: 0; }
}


/* ══════════════════════════════════════════════════════ the entrance ════ */
/*
   The entrance itself is NOT here. It is driven from immersive.js through
   the Web Animations API, for the reason motion.js states as its rule 3:
   content is visible by default, and an element is hidden only in the same
   frame a script takes responsibility for revealing it again.

   Declaring `opacity: 0` in this stylesheet would have broken exactly that
   — a reader whose JS failed, or who arrived before it ran, would have got
   a section of invisible books that no safety net could reach, because the
   rule would keep re-applying. The only thing the entrance needs from CSS
   is somewhere to put the will-change hint, which the script sets and
   clears itself.

   Reduced motion is handled in the script as well as below: it never hides
   the cards in the first place.
*/


/* ═══════════════════════════════════════════════════════ small screens ══ */
/*
   Mobile is where an atmospheric section actually costs something: three
   70px-blurred fields the size of the viewport are real compositing work on
   a phone GPU. So the blur is cut, the fields are fewer and the drift stops.
   The section keeps its colour and its identity, which is the part that
   carries the meaning.
*/
@media (max-width: 720px) {
  .imm { padding-block: var(--s-8) var(--s-9); }
  .imm__atmos i {
    filter: blur(44px);
    animation-name: imm-atmos-in;          /* fade in, then hold still */
    animation-duration: var(--d-signature);
  }
  .imm__atmos i:nth-child(3) { display: none; }
  .imm--anime .imm__speed { animation: none; opacity: .12; }
  .imm__numeral { font-size: clamp(4rem, 28vw, 8rem); }
}

/* Coarse pointers have no hover, so the two hover flourishes are inert
   weight there. Declaring them only for fine pointers also stops the sheen
   firing on the tap that opens a book. */
@media (hover: none) {
  .imm--anime .card__cover::after { display: none; }
  .imm--thriller .card__cover { filter: none; }
}


/* ═════════════════════════════════════════════════════ reduced motion ══ */
/*
   Removed at source, in the same spirit as tokens.css: the drift, the
   sheen, the speed lines and the staggered entrance all stop. The colour,
   the atmosphere's composition and the identity all remain, because those
   carry the meaning and none of them move. Nothing is left mid-animation
   and nothing stays invisible.
*/
@media (prefers-reduced-motion: reduce) {
  .imm__atmos i {
    animation: none;
    opacity: var(--atmos-o, .4);
  }
  .imm__rule { animation: none; transform: scaleX(1); }
  .imm--anime .imm__speed { animation: none; }
  .imm--anime .card__cover::after { display: none; }
  .imm--thriller .card__cover { transition: none; }

  /* Belt and braces: if a card were ever left mid-entrance when the setting
     changed, it is put back on screen rather than left part-way. */
  .imm [data-imm-card] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}

/* A last belt-and-braces for print and for any context that drops the
   stylesheet's animation support: content must never be left at opacity 0. */
@media print {
  .imm { margin-inline: 0; padding-inline: 0; background: none; color: #000; }
  .imm__atmos, .imm--anime .imm__speed, .imm--thriller::after { display: none; }
  .imm [data-imm-card] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}
