/* ==========================================================================
   Cover plate  (.pf-plate)                                       v0.21.0
   A case-study cover drawn in HTML instead of a device screenshot: a simplified redraw of the
   study's key product moment that sits on the .pf-cover gradient, plays once (5s or less) when
   it is half on screen, rests on its last frame, and replays on hover or keyboard focus of its
   card. Decided in the cover-loops exploration (portfolio/prototypes/_explorations/
   cover-loops-2026-10-14/, plan item H7, Louis 2026-10-14).

   Two parts:
     • THE KIT (this file): the stage, the 3:2 drawing box and its vocabulary (windows, bars,
       labels, controls, a pointer), plus the motion contract. Geometry is in --pf-plate-u (1% of
       the drawing's width, --pf-plate-cw), the way .pf-mark's SVG has its own coordinates:
       a plate is an illustration, so it scales as one piece from card to hero band.
     • ONE CANVAS, SCALED AS A UNIT (v0.21.0, R14): plate.js draws every plate on a fixed
       --pf-plate-canvas (1767px: the 589px desktop card at 3×) in px and scales it to its cover
       with `scale`, like an image. Browser zoom, window width and the phone card then show the
       exact frame approved at 100%. Why: WebKit applies page zoom twice to container-unit font
       sizes, so at 115% zoom the labels outgrew the drawing; and it never zooms text written at
       9px or more below 9px on screen, so at 50% zoom small labels were forced up to 18px. The 3×
       canvas writes every label above that floor (Louis, 2026-10-25). With JS off the
       drawing falls back to container units (--pf-plate-cw: 100cqw), correct at 100% zoom.
       --pf-plate-inset (v0.12.0) is the one gap between the drawing's edge and its outer window,
       top, left and right, shared by every plate.
     • THE DRAWING (one per study): a figure fragment, site-content/figures/<slug>/cover-plate.html,
       markup plus its own <style> holding positions and keyframes inside @layer pf-plate-drawing.
       The renderer inlines it into the card's and the hero's .pf-cover (card.plate front matter).

   Markup:
     <div class="pf-cover" style="--pf-cover-from:…; --pf-cover-to:…;">
       <div class="pf-plate pf-theme-light">            ← stage; light tokens in either theme
         <div class="pf-plate__art <id>" role="img" aria-label="…"> … </div>
       </div>
     </div>

   Rules:
     • Placeholder bars for text; real words only where they carry the story, set small in the
       display face like UI labels. Demo data only, no logos or product names (DISCLOSURE.md).
     • Colours and type are tokens. Pink only as a thin graphic: a ring, a line, a dot (R5).
     • Spacing inside a drawing is padding, gap or offsets, never margin: reset.css's unlayered
       `* { margin: 0 }` beats every layered margin.
     • No z-index (R8): later in the DOM paints on top, so pointers and links go last, and a menu
       or popover goes last in its window, after the rows it opens over. A row that is positioned
       (a swap) or animating a transform paints above an earlier menu (Outlook, 2026-10-25).
     • Ink drawn straight on the gradient uses --pf-plate-on-cover (the gradient never swaps).
     • The outer window sits at --pf-plate-inset from the top, left and right (a centred tall
       pane: from the top). A drawing reaches it by zooming out, not by reflowing: it sets its
       own --pf-plate-u so the window's width fits, e.g. calc((var(--pf-plate-cw) - 2 * inset) / 92)
       for a 92-unit window, and places the window at the inset.
     • R14: a drawing never uses container or viewport units (cq*, vw, vh). Every length is
       --pf-plate-cw or --pf-plate-u, and any fixed px (a hairline, a radius token, a 1.5px stroke)
       is multiplied by var(--pf-plate-k). A drawing that crops in on the phone card raises
       --pf-plate-fit (the zoom over the cover's 3:2 fit) instead of setting a width.
       fragment-check.mjs fails a fragment that breaks this; plate-check.js gates it at publish.

   Cascade: the kit sits in @layer pf-plate-kit and drawings in @layer pf-plate-drawing (declared
   after it), so a drawing overrides the kit by plain specificity. The motion contract below is
   unlayered, so it beats both.
   ========================================================================== */

@layer pf-plate-kit, pf-plate-drawing;

@layer pf-plate-kit {
  /* ---- the stage and the 3:2 drawing, centred in whatever cover box they sit in ---- */
  .pf-plate {
    --pf-plate-on-cover: var(--pf-on-accent);
    /* The canvas (v0.21.0): the desktop card's drawing (589px) at 3×, so the smallest plate text is
       written at ~26px. WebKit never zooms text written at 9px+ below 9px on screen; at Safari's
       lowest zoom (50%) 26px shows at 13px, so the floor never enlarges a label against its drawing. */
    --pf-plate-canvas: 1767px;
    --pf-plate-canvas-k: 3; /* 1767 ÷ 589: the canvas over the desktop card */
    position: relative;
    width: 100%;
    height: 100%;
    container-type: size;
    display: grid;
    place-items: center;
  }
  .pf-plate__art {
    --pf-plate-k: 1;       /* px multiplier for hairlines and radii: 1 at real size, the canvas k on the canvas */
    --pf-plate-cw: 100cqw; /* the drawing's width; the no-JS fallback. plate.js swaps in the fixed canvas */
    --pf-plate-fit: 1;     /* a drawing's zoom over the 3:2 fit (crops the cover's empty margins) */
    --pf-plate-u: calc(var(--pf-plate-cw) / 100);
    --pf-plate-inset: calc(var(--pf-plate-cw) * 0.0815); /* Louis 2026-10-14: one padding for all four plates, top, left and right; 48px on the desktop card (v0.13.0) */
    position: relative;
    width: min(100cqw, 150cqh * var(--pf-plate-fit));
    aspect-ratio: 3 / 2;
    container-type: inline-size;
    overflow: hidden;
    font-family: var(--pf-font-body);
    color: var(--pf-ink);
  }
  .pf-plate__art * { box-sizing: border-box; }
  /* A window that runs off the drawing's bottom runs off the cover's too: the cover box is not
     exactly 3:2, so a clipped drawing would leave a strip of gradient under it. The cover
     (overflow: hidden) does the clipping instead. */
  .pf-plate--bleed .pf-plate__art { overflow: visible; }

  /* ---- windows and panels ---- */
  .pf-plate__win {
    position: absolute;
    background: var(--pf-surface);
    border: calc(var(--pf-border) * var(--pf-plate-k)) solid var(--pf-hairline);
    border-radius: calc(var(--pf-radius-md) * var(--pf-plate-k));
    box-shadow: 0 calc(var(--pf-plate-u) * 0.6) calc(var(--pf-plate-u) * 3) rgba(0, 0, 0, 0.18); /* the cover device's elevation */
    overflow: hidden;
  }
  .pf-plate__pad { padding: calc(var(--pf-plate-u) * 2.2); }
  .pf-plate__col { display: flex; flex-direction: column; gap: calc(var(--pf-plate-u) * 1.3); }
  .pf-plate__row { display: flex; align-items: center; gap: calc(var(--pf-plate-u) * 1.3); }
  .pf-plate__rule { height: calc(1px * var(--pf-plate-k)); background: var(--pf-hairline); flex: none; }
  .pf-plate__grow { flex: 1; min-width: 0; }

  /* ---- placeholder text ---- */
  .pf-plate__bar {
    display: block;
    flex: none;
    width: var(--pf-plate-w, 60%);
    height: calc(var(--pf-plate-u) * 1.4);
    border-radius: var(--pf-radius-full);
    background: var(--pf-hairline);
  }
  .pf-plate__bar--ink { background: var(--pf-ink); height: calc(var(--pf-plate-u) * 1.8); }
  .pf-plate__bar--mid { background: var(--pf-hairline-strong); }
  .pf-plate__bar--soft { background: var(--pf-hairline-soft); }

  /* ---- real words: small display caps, like UI labels ---- */
  .pf-plate__label {
    font-family: var(--pf-font-display);
    font-weight: var(--pf-weight-display);
    font-size: calc(var(--pf-plate-u) * 2);
    line-height: 1;
    letter-spacing: var(--pf-tracking-caps);
    text-transform: uppercase;
    color: var(--pf-ink-secondary);
    white-space: nowrap;
  }
  .pf-plate__label--ink { color: var(--pf-ink); }
  .pf-plate__label--lg { font-size: calc(var(--pf-plate-u) * 2.6); }

  /* ---- controls (drawn, never interactive) ---- */
  .pf-plate__box {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: calc(var(--pf-plate-u) * 1);
    height: calc(var(--pf-plate-u) * 4.6);
    padding-inline: calc(var(--pf-plate-u) * 1.6);
    border: calc(var(--pf-border) * var(--pf-plate-k)) solid var(--pf-hairline-strong);
    border-radius: calc(var(--pf-radius-sm) * var(--pf-plate-k));
    background: var(--pf-surface);
  }
  .pf-plate__chev {
    flex: none;
    width: calc(var(--pf-plate-u) * 1.3);
    height: calc(var(--pf-plate-u) * 1.3);
    border-right: calc(1.5px * var(--pf-plate-k)) solid var(--pf-ink-secondary);
    border-bottom: calc(1.5px * var(--pf-plate-k)) solid var(--pf-ink-secondary);
    transform: translateY(-25%) rotate(45deg);
  }
  .pf-plate__check {
    position: relative;
    flex: none;
    width: calc(var(--pf-plate-u) * 2.5);
    height: calc(var(--pf-plate-u) * 2.5);
    border: calc(1.5px * var(--pf-plate-k)) solid var(--pf-ink-secondary);
    border-radius: calc(var(--pf-radius-sm) * var(--pf-plate-k));
    background: var(--pf-surface);
  }
  .pf-plate__check--on { background: var(--pf-ink); border-color: var(--pf-ink); }
  .pf-plate__check--on::after {
    content: "";
    position: absolute;
    left: 32%;
    top: 14%;
    width: 30%;
    height: 52%;
    border-right: calc(1.5px * var(--pf-plate-k)) solid var(--pf-surface);
    border-bottom: calc(1.5px * var(--pf-plate-k)) solid var(--pf-surface);
    transform: rotate(45deg);
  }
  .pf-plate__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: calc(var(--pf-plate-u) * 0.6);
    height: calc(var(--pf-plate-u) * 4.4);
    padding-inline: calc(var(--pf-plate-u) * 1.8);
    border: calc(var(--pf-border) * var(--pf-plate-k)) solid var(--pf-ink);
    border-radius: calc(var(--pf-radius-sm) * var(--pf-plate-k));
  }
  .pf-plate__btn--solid { background: var(--pf-ink); }
  .pf-plate__btn--solid .pf-plate__label { color: var(--pf-surface); }
  .pf-plate__chip {
    display: inline-flex;
    align-items: center;
    gap: calc(var(--pf-plate-u) * 0.5);
    height: calc(var(--pf-plate-u) * 3);
    padding-inline: calc(var(--pf-plate-u) * 1.2);
    border: calc(var(--pf-border) * var(--pf-plate-k)) solid var(--pf-hairline-strong);
    border-radius: var(--pf-radius-full);
    background: var(--pf-surface);
  }
  .pf-plate__chip .pf-plate__label { font-size: calc(var(--pf-plate-u) * 1.6); }
  .pf-plate__file { /* a sheet with a folded corner: filled, so it never reads as a second checkbox */
    flex: none;
    width: calc(var(--pf-plate-u) * 2.2);
    height: calc(var(--pf-plate-u) * 2.8);
    background: var(--pf-hairline-strong);
    clip-path: polygon(0 0, 62% 0, 100% 30%, 100% 100%, 0 100%);
  }
  .pf-plate__avatar { flex: none; width: calc(var(--pf-plate-u) * 4); height: calc(var(--pf-plate-u) * 4); border-radius: var(--pf-radius-full); background: var(--pf-hairline); }

  /* ---- bar widths (no inline styles): .pf-plate__w10 … __w100 set --pf-plate-w ---- */
  .pf-plate__w10 { --pf-plate-w: 10%; }
  .pf-plate__w15 { --pf-plate-w: 15%; }
  .pf-plate__w20 { --pf-plate-w: 20%; }
  .pf-plate__w25 { --pf-plate-w: 25%; }
  .pf-plate__w30 { --pf-plate-w: 30%; }
  .pf-plate__w35 { --pf-plate-w: 35%; }
  .pf-plate__w40 { --pf-plate-w: 40%; }
  .pf-plate__w45 { --pf-plate-w: 45%; }
  .pf-plate__w50 { --pf-plate-w: 50%; }
  .pf-plate__w55 { --pf-plate-w: 55%; }
  .pf-plate__w60 { --pf-plate-w: 60%; }
  .pf-plate__w65 { --pf-plate-w: 65%; }
  .pf-plate__w70 { --pf-plate-w: 70%; }
  .pf-plate__w75 { --pf-plate-w: 75%; }
  .pf-plate__w80 { --pf-plate-w: 80%; }
  .pf-plate__w85 { --pf-plate-w: 85%; }
  .pf-plate__w90 { --pf-plate-w: 90%; }
  .pf-plate__w95 { --pf-plate-w: 95%; }
  .pf-plate__w100 { --pf-plate-w: 100%; }

  /* ---- the pointer: a small arrow that travels to what gets clicked (last in the DOM) ---- */
  .pf-plate__cursor { position: absolute; width: calc(var(--pf-plate-u) * 3.2); height: calc(var(--pf-plate-u) * 3.2); }
  .pf-plate__cursor svg { display: block; width: 100%; height: 100%; }
  .pf-plate__cursor path { fill: var(--pf-ink); stroke: var(--pf-surface); stroke-width: 1; stroke-linejoin: round; }

  /* ---- the one accent: pink as a thin graphic only (R5) ---- */
  .pf-plate__dot { flex: none; width: calc(var(--pf-plate-u) * 1.2); height: calc(var(--pf-plate-u) * 1.2); border-radius: var(--pf-radius-full); background: var(--pf-accent); }
  .pf-plate__ring { outline: calc(1.5px * var(--pf-plate-k)) solid var(--pf-accent); outline-offset: calc(var(--pf-plate-u) * 0.4); }
  .pf-plate__pinkline { background: var(--pf-accent); }

  /* Text drawn straight on the cover gradient: the gradient keeps its colours in dark theme, so
     this ink does not swap either (--pf-on-accent is a fixed #111). */
  .pf-plate__oncover { color: var(--pf-plate-on-cover); }
}

/* ---- one canvas, scaled as a unit (unlayered: it beats the kit and every drawing) ----
   plate.js adds --fixed and sets --pf-plate-scale = min(stage width, stage height × 1.5 × fit)
   ÷ canvas. The drawing lays out at the canvas size in px, then scales about its centre. Every
   fixed px in a plate (hairlines, radii, strokes) is written × --pf-plate-k so it keeps its
   on-screen weight at any canvas size. */
.pf-plate--fixed > .pf-plate__art {
  --pf-plate-cw: var(--pf-plate-canvas);
  --pf-plate-k: var(--pf-plate-canvas-k);
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--pf-plate-canvas);
  translate: -50% -50%;
  scale: var(--pf-plate-scale, 1);
}

/* ---- shared keyframes (drawings may add their own, prefixed with their id) ---- */
@keyframes pf-plate-fade-in { from { opacity: 0; } }
@keyframes pf-plate-fade-out { to { opacity: 0; } }
@keyframes pf-plate-rise { from { opacity: 0; transform: translateY(calc(var(--pf-plate-u) * 1.5)); } }
@keyframes pf-plate-pop { from { opacity: 0; transform: scale(0.6); } 70% { transform: scale(1.06); } }
@keyframes pf-plate-ring-in { from { outline-color: transparent; } }
@keyframes pf-plate-check-in { from { background: var(--pf-surface); border-color: var(--pf-ink-secondary); } }
@keyframes pf-plate-press { 50% { transform: scale(0.94); } }

/* ---- the motion contract (unlayered: it beats the kit and every drawing) ----
   • No JS (no --armed): no animation at all, the last frame, which tells the story on its own.
   • plate.js adds --armed: every animation waits at its first frame (fill-mode both) …
   • … until the plate is half on screen and gets --playing. One play is 5s or less in total
     (WCAG 2.2.2 needs no pause control under 5s). Hover or focus on the card replays it.
   • prefers-reduced-motion: the last frame only (plate.js does not arm, and this rule holds). */
.pf-plate:not(.pf-plate--armed) .pf-plate__art *,
.pf-plate:not(.pf-plate--armed) .pf-plate__art *::before,
.pf-plate:not(.pf-plate--armed) .pf-plate__art *::after { animation: none; }
.pf-plate--armed .pf-plate__art *,
.pf-plate--armed .pf-plate__art *::before,
.pf-plate--armed .pf-plate__art *::after { animation-play-state: paused; }
.pf-plate--armed.pf-plate--playing .pf-plate__art *,
.pf-plate--armed.pf-plate--playing .pf-plate__art *::before,
.pf-plate--armed.pf-plate--playing .pf-plate__art *::after { animation-play-state: running; }
@media (prefers-reduced-motion: reduce) {
  .pf-plate .pf-plate__art *,
  .pf-plate .pf-plate__art *::before,
  .pf-plate .pf-plate__art *::after { animation: none; }
}
