/* ===========================================================================
   card.css — the card itself.

   Geometry and breakpoints (§3.1), the corner-anchored interior grid (§3.2),
   the five ground layers and the five-band carbon lid (§3.3), and the
   sacrificial lay-down sheet (§3.6).

   The card is the page. Everything else is either the world behind it
   (#backdrop) or content relocated into its stage.
   ======================================================================== */


/* ---------------------------------------------------------------------------
   PROHIBITION — load-bearing. Read this before touching anything below.

   The card carries `container-type: inline-size` and all display type is
   sized in `cqw`. NEVER animate the card's `inset`, `width`, `height` or
   `scale` per frame. Any velocity-driven geometry change on the card would
   recompute container queries and every `clamp()` font-size every frame.
   Card geometry changes only at breakpoints and once, during lay-down
   (§3.6), and that one change is a `clip-path` on a sacrificial sibling,
   not on the card.
   ------------------------------------------------------------------------ */


/* === 3.1 Geometry ======================================================== */

.card{
  position: fixed; z-index: 2;
  inset: 4.4svh 4.6vw;
  border-radius: 20px;
  background: transparent;              /* grounds are child layers — see 3.3 */
  color: var(--on-plate);
  container-type: inline-size;
  overflow: hidden;
  contain: paint;

  /* Elevation reads as paper on a table, not as a glow: three shadows plus an
     inner cut edge. */
  box-shadow:
    0 1px 0 rgba(27,29,33,.05),
    0 2px 9px -4px rgba(20,22,26,.20),
    0 30px 64px -30px rgba(20,22,26,.44),
    inset 0 0 0 1px var(--rule);

  /* The shadow↔halo swap rides the same cross-fade as the grounds. */
  transition: box-shadow var(--d-ground) var(--e-grade);
}

@media (max-width:1279px){ .card{ inset:3.0svh 3.6vw; border-radius:18px } }
@media (max-width:767px){  .card{ inset:1.8svh 2.6vw; border-radius:15px } }

/* Carbon: the drop shadow is replaced by a light halo. The list is padded to
   four slots so it interpolates against the light-ground list above instead of
   swapping discretely — slot 1 goes fully transparent, slot 4's dark inner
   hairline is invisible on carbon and its job is done by the halo. */
.card[data-plate="carbon"]{
  box-shadow:
    0 1px 0 rgba(27,29,33,0),
    0 0 0 1px rgba(241,241,238,.10),
    0 24px 80px -20px rgba(0,0,0,.7),
    inset 0 0 0 1px rgba(241,241,238,0);
}


/* === 3.3 Grounds — five solid layers, opacity cross-fade ================= */

/* Type colour is never tweened across hundreds of inherited nodes: the ground
   cross-fades, and each section declares its own ink locally (see leaf.css). */

.grounds{
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
}

.grounds > i{ position:absolute; inset:0; opacity:0;
  transition: opacity var(--d-ground) var(--e-grade); }
.grounds > i[data-on]{ opacity:1 }

.grounds > i[data-ground="foam"]  { background-color: var(--plate-foam) }
.grounds > i[data-ground="kraft"] { background-color: var(--plate-kraft) }
.grounds > i[data-ground="seal"]  { background-color: var(--plate-seal) }
.grounds > i[data-ground="cream"] { background-color: var(--plate-cream) }
.grounds > i[data-ground="carbon"]{ background-color: var(--plate-carbon) }


/* === 3.3 The carbon escalation — a five-band lid ========================= */

/* Tint-to-tint changes are the plain ground cross-fade above. Into and out of
   carbon ONLY, five bands run on top of that cross-fade. It is an escalation
   of the universal mask dissolve (§3.7), never a competing alternative.

   Contract: plate.js toggles `data-on` on `.lid` — set on the way into
   carbon, removed on the way out. Nothing else needs to change. */

.lid{
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column;
  pointer-events: none;
}

/* Out: bands reverse from transform-origin bottom, 340 ms each, --e-fall.
   The lid comes off faster than it went on. */
.lid > i{
  flex: 1;
  background-color: var(--plate-carbon);
  margin-top: -1px; padding-bottom: 1px;   /* kill sub-pixel seams */
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform var(--d-lid-off) var(--e-fall);
}
.lid > i:nth-child(5){ transition-delay: calc(0 * var(--d-lid-stagger)) }
.lid > i:nth-child(4){ transition-delay: calc(1 * var(--d-lid-stagger)) }
.lid > i:nth-child(3){ transition-delay: calc(2 * var(--d-lid-stagger)) }
.lid > i:nth-child(2){ transition-delay: calc(3 * var(--d-lid-stagger)) }
.lid > i:nth-child(1){ transition-delay: calc(4 * var(--d-lid-stagger)) }

/* In: scaleY(0) → scaleY(1) from the top, top band first, 40 ms stagger,
   460 ms each, --e-grade. A lid closing. transform-origin is not transitioned,
   so it flips the instant the state changes. */
.lid[data-on] > i{
  transform: scaleY(1);
  transform-origin: top;
  transition: transform var(--d-lid) var(--e-grade);
}
.lid[data-on] > i:nth-child(1){ transition-delay: calc(0 * var(--d-lid-stagger)) }
.lid[data-on] > i:nth-child(2){ transition-delay: calc(1 * var(--d-lid-stagger)) }
.lid[data-on] > i:nth-child(3){ transition-delay: calc(2 * var(--d-lid-stagger)) }
.lid[data-on] > i:nth-child(4){ transition-delay: calc(3 * var(--d-lid-stagger)) }
.lid[data-on] > i:nth-child(5){ transition-delay: calc(4 * var(--d-lid-stagger)) }


/* === 3.2 Interior grid — corner anchoring with an enormous empty middle == */

.card__inner{
  position:absolute; inset:0; z-index:3;
  display:grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "mark  cube   menu"
    "stage stage stage"
    "index note   sign";
  padding: var(--card-pad);
  gap: var(--card-gap);
}

/* `.card__inner` is pointer-events:none; the pill, links and controls
   re-enable it (see chrome.css and the [data-live] rule in leaf.css). */
.card__inner{ pointer-events:none }

/* §3.6 step 1 — the card exists at its FINAL resting geometry from the first
   frame, with the interior at opacity 0. All internal layout is therefore
   correct and computed once; nothing lays out during lay-down.

   Contract: main.js sets `data-on` on `.card__inner` once the shader is warm,
   immediately before the corner chrome arrives (§3.6 step 6). Without JS the
   interior is simply visible — see the html:not(.js) line below and nojs.css. */
html.js .card__inner{
  opacity: 0;
  transition: opacity var(--d-chrome) var(--e-lay);
}
html.js .card__inner[data-on],
html.js .card[data-on] .card__inner,
html.js.laid .card__inner{ opacity: 1 }
html:not(.js) .card__inner{ opacity: 1 }

/* Corner slots. */
.card__inner > .chrome--mark { grid-area:mark;  justify-self:start;  align-self:start }
.card__inner > .pill--menu   { grid-area:menu;  justify-self:end;    align-self:start }
.card__inner > .pill--index  { grid-area:index; justify-self:start;  align-self:end }
.card__inner > .note         { grid-area:note;  justify-self:center; align-self:end }
.card__inner > .mark-rotated { grid-area:sign;  justify-self:end;    align-self:end }

/* The stage holds exactly one object per leaf. */
.stage{
  grid-area: stage;
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0; min-height: 0;
}

/* Below 560px the bottom row collapses to `index | note` and `sign` moves to
   the top-right column, under the Menu pill. Corner logic survives; the
   crowding does not. */
@media (max-width:560px){
  .card__inner{
    grid-template-rows: auto auto 1fr auto;
    grid-template-areas:
      "mark  cube   menu"
      ".     .      sign"
      "stage stage stage"
      "index note   .";
  }
  .card__inner > .mark-rotated{ align-self:start }
}


/* === 3.6 Lay-down — the sacrificial sheet ================================ */

/* The page paints as one blank cool-white sheet. There is visibly nothing
   loading, so there is nothing to count. `.sheet` then shrinks its clip-path
   to the card's exact rect and is removed: same colour, same radius, same
   rect, so the handoff is invisible. clip-path does not affect layout.

   Contract: main.js sets `data-lay` on #sheet to start the shrink, waits
   --d-lay, then removes the element outright. */

.sheet{
  position: fixed; inset: 0; z-index: 4;
  background: var(--plate-foam);
  border-radius: 0;
  pointer-events: none;

  /* The card's own rect at this breakpoint, held as a value so the three
     breakpoints are declared once and every trigger below reuses them. */
  --sheet-rect: inset(4.4svh 4.6vw round 20px);

  clip-path: inset(0 0 0 0 round 0px);
  transition: clip-path var(--d-lay) var(--e-lay);
}
@media (max-width:1279px){ .sheet{ --sheet-rect: inset(3.0svh 3.6vw round 18px) } }
@media (max-width:767px){  .sheet{ --sheet-rect: inset(1.8svh 2.6vw round 15px) } }

.sheet[data-lay],
.sheet[data-on],
html.laid .sheet{ clip-path: var(--sheet-rect) }

/* ==========================================================================
   #backdrop — the world the card floats over.

   The header above says card.css does not own the backdrop. Nothing else did
   either, and the omission was severe, so it lives here now.

   LOAD-BEARING: the backdrop MUST be taken out of flow. It holds one WebGL
   canvas plus eight full-resolution <img> fallbacks (5712x4284 and friends).
   Left in normal flow they stack ~11,900px tall and push `main#doc` that far
   down the document — every section's offsetTop then sits past the damped
   scroll position, SectionWatch reports progress 0 forever, the active leaf
   never changes, and the page reads as "scrolling does nothing".
   ========================================================================== */
#backdrop{
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--plate-carbon);
  pointer-events: none;
}
#backdrop canvas{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}

/* A line's own footage, playing silently behind the card. No controls, no
   audio track requested, no way to interact with it: it is scenery, and the
   card is what you read. js/bgvideo.js owns playback. */
#backdrop .fall-video{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 900ms var(--e-grade);
  pointer-events: none;
}
#backdrop .fall-video.is-on{ opacity: 1 }

/* With footage behind it the card needs a touch more separation from the
   moving image than it does from a still photograph. */
#backdrop:has(.fall-video.is-on)::after{
  content: "";
  position: absolute; inset: 0;
  background: rgba(20, 22, 26, .28);
  pointer-events: none;
}

/* The fallbacks are stacked and hidden — the shader draws the real thing. Only
   the no-webgl path reveals one, swapped by js/main.js via `.is-on`. */
#backdrop .fall{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
}
html.no-webgl #backdrop .fall{
  transition: opacity var(--d-ground) var(--e-grade);
}
html.no-webgl #backdrop .fall.is-on{ opacity: 1 }

/* Per-state colour layers, standing in for the shader's duotone grade when
   there is no WebGL. js/main.js sets `.is-on` on the active one. */
#backdrop .grade{
  position: absolute; inset: 0;
  opacity: 0;
  pointer-events: none;
  mix-blend-mode: color;
  transition: opacity var(--d-ground) var(--e-grade);
}
html.no-webgl #backdrop .grade.is-on{ opacity: .55 }
#backdrop .grade[data-grade="foam"]  { background: var(--plate-foam) }
#backdrop .grade[data-grade="kraft"] { background: var(--kraft-mid) }
#backdrop .grade[data-grade="seal"]  { background: var(--cobalt) }
#backdrop .grade[data-grade="cream"] { background: var(--plate-cream) }
#backdrop .grade[data-grade="carbon"]{ background: var(--plate-carbon) }
