Skills로 돌아가기
heygen-com/hyperframes실행 전 동작 확인

SKILL DETAIL

hyperframes

heygen-com/hyperframes/hyperframes

This is the mandatory entry point for HyperFrames: read it first for any request to make, create, edit, animate, or render a video, animation, or motion graphic, including a promo, explainer, captioned clip, title card, overlay, slideshow or interactive deck, Remotion port, or any HyperFrames HTML composition. It also applies to inspecting, diagnosing, validating, previewing, publishing, or batch-rendering an existing HyperFrames project. Inputs may be a website URL, GitHub PR, Figma design or URL, text or brief, existing footage, or music. It resumes project state, captures intent when applicable, selects and installs the owning workflow, and routes domain capabilities. HyperFrames is the default output framework unless the user explicitly chooses another framework for the deliverable or asks only to record a browser session.

설치 수 · 26,001출처 보기

Installation

npx skills add https://github.com/heygen-com/hyperframes --skill hyperframes

스킬 파일

SKILL.md

최근 동기화 · 2026. 10. 22.

assets/frame-presets/biennale-yellow/caption-skin.html›
<!--
  Biennale Yellow — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a biennale literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a warm-parchment card on a single 1px hairline indigo-ink border — biennale's
  ONLY border — square corners, NO shadow (biennale's depth is atmospheric, never cast).
  Instrument Serif, sentence case, ink glyphs. Upcoming words sit in faint ink; the
  current word gets a solar-yellow underprint (the brand's signature "sun as panel /
  underprint", a hard square block, no blur) with ink reading on top; spoken words
  settle to solid ink, underprint cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE parchment card — single 1px hairline ink border (biennale's only border),
     square corners, NO shadow */
  .caption-pill {
    max-width: 78%;
    padding: 20px 44px 24px;
    background: var(--cap-canvas, #e9e5db);
    border: 1px solid var(--cap-ink, #1b2566);
    border-radius: 0;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.1em 0.34em;
    font-family: var(--font-display, "Instrument Serif"), Georgia, serif;
    font-weight: 400;
    font-size: clamp(40px, 4.4vw, 58px);
    line-height: 1.16;
    letter-spacing: -0.01em;
  }

  /* upcoming — faint indigo ink, no underprint */
  .caption-word {
    display: inline-block;
    padding: 0 0.06em;
    color: color-mix(in srgb, var(--cap-ink, #1b2566) 42%, var(--cap-canvas, #e9e5db));
  }
  /* current word — THE solar-yellow underprint (box-shadow spread = a hard square
     block w/o layout shift; no blur), indigo ink reading on top */
  .caption-word.is-active {
    color: var(--cap-ink, #1b2566);
    background: var(--cap-accent, #f1ee2e);
    box-shadow: 0 0 0 0.06em var(--cap-accent, #f1ee2e);
  }
  /* already spoken — solid indigo ink, underprint cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #1b2566);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — Instrument Serif italic, biennale's only display inflection */
  .caption-word.cap-brand {
    font-style: italic;
  }
  /* numerals / data — the mono voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "JetBrains Mono"), ui-monospace, monospace;
    color: color-mix(in srgb, var(--cap-ink, #1b2566) 72%, var(--cap-canvas, #e9e5db));
  }
  /* emphasis — an ember-red hairline underline (the counter-temperature accent) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--cap-accent-2, #e26b4a);
    text-underline-offset: 0.1em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.97 }, { scale: 1, duration: 0.14, ease: "power2.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/biennale-yellow/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Biennale Yellow</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Archivo:wght@400;600&family=JetBrains+Mono:wght@400&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --paper: #e9e5db;
        --paper-deep: #dcd6c4;
        --sun: #f1ee2e;
        --sun-soft: #f8f39b;
        --haze: #f0da7c;
        --ink: #1b2566;
        --ember: #e26b4a;
        --serif: "Instrument Serif", Georgia, serif;
        --sans: "Archivo", sans-serif;
        --mono: "JetBrains Mono", ui-monospace, monospace;
        --bloom: radial-gradient(
          circle at 50% 50%,
          rgba(241, 238, 46, 0.92) 0%,
          rgba(241, 238, 46, 0.5) 30%,
          rgba(240, 218, 124, 0.22) 55%,
          rgba(233, 229, 219, 0) 78%
        );
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #0c0d1a;
      }
      body {
        background: var(--paper);
        color: var(--ink);
        font-family: var(--sans);
        -webkit-font-smoothing: antialiased;
      }
      .serif {
        font-family: var(--serif);
        font-weight: 400;
        letter-spacing: -0.018em;
        line-height: 0.9;
      }
      .micro {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.18em;
        font-size: 13px;
      }

      section {
        padding: 120px 76px;
        position: relative;
        overflow: hidden;
      }
      .sec-head {
        display: flex;
        align-items: baseline;
        gap: 24px;
        border-bottom: 1px solid var(--ink);
        padding-bottom: 18px;
        margin-bottom: 56px;
        position: relative;
        z-index: 2;
      }
      .sec-head .micro {
        color: var(--ink);
      }
      .sec-head h2 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 56px;
        line-height: 1;
        letter-spacing: -0.01em;
      }
      .sec-head .sp {
        flex: 1;
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        padding: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
        overflow: hidden;
      }
      .cover .bloom {
        position: absolute;
        width: 62%;
        height: 70%;
        left: 8%;
        top: 14%;
        background: var(--bloom);
        z-index: 0;
        pointer-events: none;
      }
      .cover .ember {
        position: absolute;
        width: 36%;
        height: 40%;
        right: -6%;
        bottom: -6%;
        background: radial-gradient(
          circle at 60% 60%,
          rgba(226, 107, 74, 0.2) 0%,
          rgba(226, 107, 74, 0) 70%
        );
        z-index: 0;
        pointer-events: none;
      }
      .cover .daterail {
        position: absolute;
        top: 64px;
        right: 76px;
        font-family: var(--serif);
        font-weight: 400;
        font-size: clamp(48px, 5.2vw, 84px);
        line-height: 0.96;
        text-align: right;
        letter-spacing: -0.005em;
        z-index: 2;
      }
      .cover .inner {
        padding: 0 76px;
        position: relative;
        z-index: 2;
      }
      .cover .micro {
        margin-bottom: 28px;
      }
      .cover h1 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: clamp(110px, 15vw, 230px);
        line-height: 0.84;
        letter-spacing: -0.018em;
      }
      .cover h1 em {
        font-style: italic;
        letter-spacing: -0.005em;
      }
      .cover .footer {
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        display: grid;
        grid-template-columns: 1.1fr 1fr 1.4fr 2fr;
        gap: 36px;
        padding: 0 76px 56px;
        z-index: 2;
      }
      .cover .fcell {
        border-top: 1px solid var(--ink);
        padding-top: 14px;
      }
      .cover .fcell .t {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 11px;
        margin-bottom: 8px;
      }
      .cover .fcell .b {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 13px;
        line-height: 1.5;
        color: var(--ink);
      }
      .cover .pagenum {
        position: absolute;
        bottom: 30px;
        right: 48px;
        font-family: var(--mono);
        font-size: 13px;
        letter-spacing: 0.08em;
        opacity: 0.75;
        z-index: 2;
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 28px;
        position: relative;
        z-index: 2;
      }
      .sw .chip {
        height: 130px;
        border: 1px solid var(--ink);
      }
      .sw .name {
        font-family: var(--serif);
        font-size: 26px;
        margin-top: 14px;
      }
      .sw .hex {
        font-family: var(--mono);
        font-size: 12px;
        color: var(--ink);
        opacity: 0.7;
        margin-top: 6px;
      }
      .sw .role {
        font-family: var(--sans);
        font-size: 12px;
        line-height: 1.5;
        color: var(--ink);
        margin-top: 8px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 220px 1fr;
        align-items: center;
        padding: 26px 0;
        border-bottom: 1px solid rgba(27, 37, 102, 0.2);
      }
      .type-row .tok {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 11px;
      }
      .type-row .m {
        font-family: var(--mono);
        font-size: 11px;
        color: var(--ink);
        opacity: 0.65;
        margin-top: 8px;
        line-height: 1.5;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 12px;
        margin-bottom: 22px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 48px;
        margin-bottom: 56px;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .s8 {
        grid-column: span 8;
      }
      .ledger .row {
        display: grid;
        grid-template-columns: 92px 1.6fr 0.9fr 80px;
        gap: 24px;
        padding: 14px 0;
        border-bottom: 1px solid rgba(27, 37, 102, 0.2);
        align-items: baseline;
      }
      .ledger .dt {
        font-family: var(--mono);
        font-size: 14px;
      }
      .ledger .ti {
        font-family: var(--serif);
        font-size: 26px;
        line-height: 1.1;
      }
      .ledger .vn {
        font-family: var(--sans);
        font-size: 13px;
        color: var(--ink);
      }
      .ledger .du {
        font-family: var(--mono);
        font-size: 13px;
        text-align: right;
      }
      .strand .row {
        display: grid;
        grid-template-columns: 56px 1fr;
        gap: 20px;
        padding: 14px 0;
        border-bottom: 1px solid rgba(27, 37, 102, 0.2);
        align-items: baseline;
      }
      .strand .nm {
        font-family: var(--serif);
        font-size: 34px;
        line-height: 1;
      }
      .strand .ti {
        font-family: var(--serif);
        font-size: 24px;
        line-height: 1.1;
      }
      .strand .bd {
        font-family: var(--sans);
        font-size: 13px;
        line-height: 1.5;
        color: var(--ink);
        margin-top: 4px;
      }
      .bars .row {
        display: grid;
        grid-template-columns: 60px 1fr 60px;
        gap: 16px;
        align-items: center;
        margin-bottom: 14px;
      }
      .bars .yr {
        font-family: var(--mono);
        font-size: 13px;
      }
      .bars .bar {
        height: 20px;
        background: var(--ink);
      }
      .bars .bar.lit {
        background: var(--sun);
        border: 1px solid var(--ink);
      }
      .bars .vl {
        font-family: var(--mono);
        font-size: 13px;
        text-align: right;
      }
      .ypanel {
        background: var(--sun);
        padding: 36px;
        color: var(--ink);
      }
      .ypanel .l {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 11px;
      }
      .ypanel .h {
        font-family: var(--serif);
        font-size: 44px;
        line-height: 0.95;
        margin-top: 12px;
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
        position: relative;
        z-index: 2;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 12px;
        color: var(--ink);
        opacity: 0.6;
      }
      .fw .flabel b {
        opacity: 1;
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--paper);
        box-shadow: 0 0 0 1px rgba(27, 37, 102, 0.15);
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 3;
      }
      .frame .bloom {
        position: absolute;
        background: var(--bloom);
        z-index: 1;
        pointer-events: none;
      }
      .frame .pn {
        position: absolute;
        right: 3cqw;
        bottom: 2.6cqw;
        font-family: var(--mono);
        font-size: 1cqw;
        opacity: 0.75;
        z-index: 4;
      }

      /* f1 cover */
      .yf1 .bloom {
        width: 58%;
        height: 64%;
        left: 6%;
        top: 16%;
      }
      .yf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .yf1 .micro {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.3cqw;
        font-size: 1cqw;
        margin-bottom: 1.8cqw;
      }
      .yf1 h3 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 13cqw;
        line-height: 0.84;
        letter-spacing: -0.018em;
      }
      .yf1 h3 em {
        font-style: italic;
      }
      .yf1 .dr {
        position: absolute;
        top: 4cqw;
        right: 4cqw;
        font-family: var(--serif);
        font-size: 5cqw;
        line-height: 0.96;
        text-align: right;
        letter-spacing: -0.005em;
        z-index: 4;
      }

      /* f2 chapter divider */
      .yf2 .bloom {
        width: 60%;
        height: 80%;
        right: -10%;
        top: -10%;
        left: auto;
      }
      .yf2 .rail {
        position: absolute;
        left: 3cqw;
        top: 50%;
        transform: rotate(-90deg) translateX(50%);
        transform-origin: left center;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.32em;
        font-size: 1cqw;
        white-space: nowrap;
        z-index: 4;
      }
      .yf2 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw 0 9cqw;
      }
      .yf2 .num {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 38cqw;
        line-height: 0.84;
        letter-spacing: -0.04em;
        color: var(--ink);
      }
      .yf2 .ti {
        font-family: var(--serif);
        font-size: 3.6cqw;
        line-height: 1.05;
        margin-top: -2cqw;
        max-width: 46cqw;
      }

      /* f3 ledger */
      .yf3 .body {
        padding: 6cqw 5cqw;
        display: flex;
        flex-direction: column;
      }
      .yf3 .top {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        border-bottom: 1px solid var(--ink);
        padding-bottom: 1.2cqw;
        margin-bottom: 1.6cqw;
      }
      .yf3 .top h4 {
        font-family: var(--serif);
        font-size: 3.6cqw;
        line-height: 1;
      }
      .yf3 .top .micro {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.2cqw;
        font-size: 0.9cqw;
      }
      .yf3 .row {
        display: grid;
        grid-template-columns: 8cqw 1.6fr 0.9fr 6cqw;
        gap: 1.6cqw;
        padding: 1cqw 0;
        border-bottom: 1px solid rgba(27, 37, 102, 0.2);
        align-items: baseline;
      }
      .yf3 .dt {
        font-family: var(--mono);
        font-size: 1cqw;
      }
      .yf3 .ti {
        font-family: var(--serif);
        font-size: 1.9cqw;
        line-height: 1.1;
      }
      .yf3 .vn {
        font-family: var(--sans);
        font-size: 0.95cqw;
      }
      .yf3 .du {
        font-family: var(--mono);
        font-size: 0.95cqw;
        text-align: right;
      }

      /* f4 manifesto */
      .yf4 .bloom {
        width: 54%;
        height: 60%;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
      .yf4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        padding: 0 9cqw;
      }
      .yf4 .qm {
        font-family: var(--serif);
        font-size: 14cqw;
        line-height: 0.5;
        height: 7cqw;
      }
      .yf4 .q {
        font-family: var(--serif);
        font-style: italic;
        font-size: 5cqw;
        line-height: 1.04;
        letter-spacing: -0.005em;
      }
      .yf4 .at {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.25cqw;
        font-size: 1cqw;
        margin-top: 2.4cqw;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 56px;
        position: relative;
        z-index: 2;
      }
      .do-card h4 {
        font-family: var(--serif);
        font-size: 34px;
        margin-bottom: 8px;
      }
      .do-card .rule {
        height: 1px;
        background: var(--ink);
        margin-bottom: 22px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-family: var(--sans);
        font-size: 14px;
        line-height: 1.6;
        padding-left: 22px;
        position: relative;
        margin-bottom: 13px;
        color: var(--ink);
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--serif);
      }
      .do-card.do li::before {
        content: "\2014";
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: var(--ember);
      }

      .foot {
        padding: 64px 76px;
        border-top: 1px solid var(--ink);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--serif);
        font-size: 30px;
      }
      .foot .r {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 11px;
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital,wght@0,400;1,400&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #e9e5db !important;
        --secondary: #1b2566 !important;
        --tertiary: #dcd6c4 !important;
        --accent: #f1ee2e !important;
        --f-disp: "Instrument Serif", sans-serif !important;
        --f-body: "Archivo", sans-serif !important;
        --paper: #e9e5db !important;
        --paper-deep: #dcd6c4 !important;
        --sun: #f1ee2e !important;
        --ink: #1b2566 !important;
        --serif: "Instrument Serif", sans-serif !important;
        --sans: "Archivo", sans-serif !important;
        --sun-soft: #f8f39b !important;
        --haze: #f0da7c !important;
        --ember: #e26b4a !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="bloom"></div>
      <div class="ember"></div>
      <div class="daterail">2026<br />—2027</div>
      <div class="inner">
        <div class="micro">Frame System · Vol. 01</div>
        <h1>Paper, ink<br />&amp; <em>yellow.</em></h1>
      </div>
      <div class="footer">
        <div class="fcell">
          <div class="t">Register</div>
          <div class="b">Literary-editorial, biennale catalogue.</div>
        </div>
        <div class="fcell">
          <div class="t">Ground</div>
          <div class="b">Warm parchment, never white.</div>
        </div>
        <div class="fcell">
          <div class="t">Type</div>
          <div class="b">Instrument Serif · Archivo · JetBrains Mono.</div>
        </div>
        <div class="fcell">
          <div class="t">Depth</div>
          <div class="b">Atmospheric blooms, no shadows, hairline rules.</div>
        </div>
      </div>
      <div class="pagenum">01 / 06</div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="micro">01</span>
        <h2>Palette</h2>
        <span class="sp"></span><span class="micro">Paper · Ink · Yellow</span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--paper)"></div>
          <div class="name">Paper</div>
          <div class="hex">#E9E5DB</div>
          <div class="role">The warm parchment ground.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="name">Ink</div>
          <div class="hex">#1B2566</div>
          <div class="role">The single text + rule color.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--sun)"></div>
          <div class="name">Sun</div>
          <div class="hex">#F1EE2E</div>
          <div class="role">Panel, bloom, tile underprint.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ember)"></div>
          <div class="name">Ember</div>
          <div class="hex">#E26B4A</div>
          <div class="role">Subordinate counter-bloom only.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--paper-deep)"></div>
          <div class="name">Paper Deep</div>
          <div class="hex">#DCD6C4</div>
          <div class="role">Secondary parchment band.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--sun-soft)"></div>
          <div class="name">Sun Soft</div>
          <div class="hex">#F8F39B</div>
          <div class="role">Mid stop in sun blooms.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--haze)"></div>
          <div class="name">Haze</div>
          <div class="hex">#F0DA7C</div>
          <div class="role">Outer stop in sun blooms.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--bloom), var(--paper)"></div>
          <div class="name">Sun Bloom</div>
          <div class="hex">radial · atmosphere</div>
          <div class="role">The primary depth layer.</div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <span class="micro">02</span>
        <h2>Typography</h2>
        <span class="sp"></span><span class="micro">Serif · Sans · Mono</span>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display</div>
          <div class="m">Instrument Serif 400 · line 0.86 · −0.018em</div>
        </div>
        <div class="spec"><span class="serif" style="font-size: 104px">Biennale</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display-it</div>
          <div class="m">Instrument Serif italic · slow-reading</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-style: italic; font-size: 72px; line-height: 1.04"
            >quietly,</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">numeral-md</div>
          <div class="m">Instrument Serif 400 · hero stat</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-size: 96px; letter-spacing: -0.04em">240</span>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Archivo 400 · line 1.5 · ink</div>
        </div>
        <div class="spec">
          <p style="font-family: var(--sans); font-size: 16px; line-height: 1.5; max-width: 620px">
            Archivo carries every paragraph — the quiet sans beneath the serif display, always in
            ink.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">micro-label</div>
          <div class="m">Archivo 600 · uppercase · 0.18em</div>
        </div>
        <div class="spec">
          <span class="micro">Programme · Section</span> &nbsp;
          <span
            class="serif"
            style="font-family: var(--mono); font-size: 14px; letter-spacing: 0.04em"
            >12 — 14 JUN</span
          >
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="micro">03</span>
        <h2>Components</h2>
        <span class="sp"></span><span class="micro">Hairlines · Blooms · Panels</span>
      </div>

      <div class="cgrid">
        <div class="s7">
          <div class="cnote">
            Ledger rows — mono date · serif title · sans venue · mono duration
          </div>
          <div class="ledger">
            <div class="row">
              <span class="dt">12 JUN</span><span class="ti">Opening Address</span
              ><span class="vn">Giardini Pavilion</span><span class="du">45m</span>
            </div>
            <div class="row">
              <span class="dt">13 JUN</span><span class="ti">On Slow Reading</span
              ><span class="vn">Arsenale Hall</span><span class="du">90m</span>
            </div>
            <div class="row">
              <span class="dt">14 JUN</span><span class="ti">Closing Colophon</span
              ><span class="vn">Central Library</span><span class="du">30m</span>
            </div>
          </div>
        </div>
        <div class="s5">
          <div class="cnote">Strand list + chart bars</div>
          <div class="strand">
            <div class="row">
              <span class="nm">01</span>
              <div><div class="ti">Parchment</div></div>
            </div>
            <div class="row">
              <span class="nm">02</span>
              <div><div class="ti">Ink</div></div>
            </div>
          </div>
          <div class="bars" style="margin-top: 20px">
            <div class="row">
              <span class="yr">'24</span><span class="bar" style="width: 60%"></span
              ><span class="vl">60</span>
            </div>
            <div class="row">
              <span class="yr">'25</span><span class="bar lit" style="width: 88%"></span
              ><span class="vl">88</span>
            </div>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s5">
          <div class="cnote">Yellow panel — strongest color statement</div>
          <div class="ypanel">
            <div class="l">Poster Fill</div>
            <div class="h">Ink on Sun.</div>
          </div>
        </div>
        <div class="s7">
          <div class="cnote">Sun bloom + ember counter-bloom</div>
          <div
            style="
              position: relative;
              height: 170px;
              border: 1px solid rgba(27, 37, 102, 0.2);
              overflow: hidden;
            "
          >
            <div
              style="
                position: absolute;
                width: 60%;
                height: 130%;
                left: 6%;
                top: -15%;
                background: var(--bloom);
              "
            ></div>
            <div
              style="
                position: absolute;
                width: 40%;
                height: 90%;
                right: -8%;
                bottom: -20%;
                background: radial-gradient(circle, rgba(226, 107, 74, 0.22), transparent 70%);
              "
            ></div>
            <div style="position: absolute; left: 24px; bottom: 20px" class="serif">
              <span style="font-size: 40px">atmosphere.</span>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <span class="micro">04</span>
        <h2>Frame Compositions</h2>
        <span class="sp"></span><span class="micro">True 16:9 · cqw</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Cover</b><span>· identity · sun bloom · date rail</span></div>
          <div class="frame yf1">
            <div class="bloom"></div>
            <div class="dr">2026<br />—27</div>
            <div class="body">
              <div class="micro">Frame System</div>
              <h3>Paper, ink<br />&amp; <em>yellow.</em></h3>
            </div>
            <div class="pn">01 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Chapter Divider</b><span>· section · jumbo numeral · vertical rail</span>
          </div>
          <div class="frame yf2">
            <div class="bloom"></div>
            <div class="rail">Section — Two</div>
            <div class="body">
              <div class="num">02</div>
              <div class="ti">On measure &amp; the slow page.</div>
            </div>
            <div class="pn">02 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Ledger</b><span>· catalog · hairline rows · the dense frame</span>
          </div>
          <div class="frame yf3">
            <div class="body">
              <div class="top">
                <h4>Programme</h4>
                <span class="micro">12 — 14 JUN</span>
              </div>
              <div class="row">
                <span class="dt">12 JUN</span><span class="ti">Opening Address</span
                ><span class="vn">Giardini</span><span class="du">45m</span>
              </div>
              <div class="row">
                <span class="dt">13 JUN</span><span class="ti">On Slow Reading</span
                ><span class="vn">Arsenale</span><span class="du">90m</span>
              </div>
              <div class="row">
                <span class="dt">14 JUN</span><span class="ti">Closing Colophon</span
                ><span class="vn">Library</span><span class="du">30m</span>
              </div>
            </div>
            <div class="pn">03 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Manifesto</b><span>· quote · italic serif · centered bloom</span>
          </div>
          <div class="frame yf4">
            <div class="bloom"></div>
            <div class="body">
              <div class="qm">&ldquo;</div>
              <div class="q">Read it slowly,<br />or not at all.</div>
              <div class="at">— The System Voice</div>
            </div>
            <div class="pn">04 / 06</div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="micro">05</span>
        <h2>Frame Rules</h2>
        <span class="sp"></span><span class="micro">Restraint</span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <div class="rule"></div>
          <ul>
            <li>Start on warm parchment; add at least one sun bloom for atmosphere.</li>
            <li>
              Set every line in ink — display, body, label, mono; contrast is size, not color.
            </li>
            <li>Use Instrument Serif 400, tight line-height, negative tracking, for display.</li>
            <li>
              Make every separator a 1px ink hairline; flood a yellow panel for poster moments.
            </li>
            <li>Reserve JetBrains Mono for dates, numerals, and the bottom-right pagenum.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <div class="rule"></div>
          <ul>
            <li>No drop shadows, no rounded corners, no bordered cards.</li>
            <li>No second text color; no bold Instrument Serif.</li>
            <li>No borders thicker than 1px; no inverted ink grounds.</li>
            <li>No mono for body or headlines; no font substitutes.</li>
            <li>Don't crowd the canvas — sparse reads as elegant.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--paper);
        --cap-accent: var(--sun);
        --cap-accent-2: var(--ember);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        border: 1px solid var(--cap-ink);
        border-radius: 0;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.1em 0.34em;
        font-family: var(--serif);
        font-weight: 400;
        font-size: 3.4cqw;
        line-height: 1.16;
        letter-spacing: -0.01em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.06em;
        color: color-mix(in srgb, var(--cap-ink) 42%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.06em var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 1px dashed rgba(27, 37, 102, 0.28);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        font-size: 0.95cqw;
        letter-spacing: 0.04em;
        color: var(--ink);
        opacity: 0.5;
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="micro">06</span>
        <h2>Captions</h2>
        <span class="sp"></span><span class="micro">Lower-third · Karaoke</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="bloom" style="width: 54%; height: 60%; left: 10%; top: 12%"></div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
            <div class="pn">05 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Settled line</b><span>· all spoken · underprint cleared</span>
          </div>
          <div class="frame capdemo">
            <div
              class="bloom"
              style="width: 50%; height: 58%; right: -6%; bottom: -6%; left: auto"
            ></div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
            <div class="pn">06 / 06</div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Biennale Yellow</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#E9E5DB" />
        <circle cx="42" cy="42" r="34" fill="#F1EE2E" opacity="0.55" />
        <text x="14" y="64" font-family="Georgia" font-size="30" fill="#1B2566">Aa</text>
        <rect x="14" y="74" width="60" height="1.5" fill="#1B2566" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/biennale-yellow/FRAME.md›
---
version: alpha
name: Biennale Yellow — Frame (video / frame layer)
description: >
  Video-first companion to Biennale Yellow's design.md. The unit is the frame (1920×1080). Atoms
  are identical and sacred — warm parchment grounds, a single deep indigo ink, solar yellow deployed
  as bloom / panel / tile underprint, Instrument Serif display + Archivo sans + JetBrains Mono data,
  1px hairline rules as the only border, atmospheric depth (no shadows), and the bottom-right
  pagenum. Composition + frame scale rewritten. Restraint is the rule; motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  paper: "#E9E5DB"
  paper-deep: "#DCD6C4"
  sun: "#F1EE2E"
  sun-soft: "#F8F39B"
  haze: "#F0DA7C"
  ink: "#1B2566"
  ember: "#E26B4A"

typography:
  # — reading + data ramp —
  body:    { fontFamily: "Archivo", cqw: 0.85, weight: 400, lineHeight: 1.5, color: "ink" }
  body-lede:{ fontFamily: "Archivo", cqw: 0.95, weight: 400, lineHeight: 1.55 }
  micro-label:{ fontFamily: "Archivo", px: 13, weight: 600, tracking: "0.18em", upper: true }
  rail-label:{ fontFamily: "Archivo", px: 13, weight: 600, tracking: "0.32em", upper: true }
  mono-data:{ fontFamily: "JetBrains Mono", cqw: 0.73, weight: 400, tracking: "0.04em" }
  pagenum: { fontFamily: "JetBrains Mono", px: 13, weight: 400, tracking: "0.08em", opacity: 0.75 }
  # — display ramp (Instrument Serif 400, tight, negative tracking) —
  ledger-title:{ fontFamily: "Instrument Serif", cqw: 1.55, weight: 400, lineHeight: 1.15 }
  strand-title:{ fontFamily: "Instrument Serif", cqw: 1.7, weight: 400, lineHeight: 1.1 }
  headline-sm:{ fontFamily: "Instrument Serif", cqw: 2.9, weight: 400, lineHeight: 1.0 }
  headline:{ fontFamily: "Instrument Serif", cqw: 4.6, weight: 400, lineHeight: 1.06, tracking: "-0.005em" }
  date-rail:{ fontFamily: "Instrument Serif", cqw: 5.0, weight: 400, lineHeight: 0.96, tracking: "-0.005em" }
  display-it:{ fontFamily: "Instrument Serif", cqw: 7.0, weight: 400, italic: true, lineHeight: 1.04, tracking: "-0.005em" }
  numeral-md:{ fontFamily: "Instrument Serif", cqw: 7.5, weight: 400, lineHeight: 0.92, tracking: "-0.01em" }
  display:{ fontFamily: "Instrument Serif", cqw: 14.6, weight: 400, lineHeight: 0.86, tracking: "-0.018em" }
  numeral-jumbo:{ fontFamily: "Instrument Serif", cqw: 28.0, weight: 400, lineHeight: 0.84, tracking: "-0.04em" }

spacing:
  pad-edge: "4cqw"
  pad-region: "4.2cqw"
  gap-region: "2.5cqw"

components:
  sun-bloom:
    background: "radial gradient {colors.sun} core → {colors.sun-soft} → {colors.haze} → transparent on {colors.paper}"
    size: "42–70% of the frame, off-center or behind the focal element"
    description: "The primary depth layer. One per frame; a flat parchment frame reads as broken."
  ember-bloom:
    background: "radial {colors.ember} at 15–22% opacity"
    placement: "corner opposite the sun-bloom"
    description: "Subordinate counter-temperature balance; never dominant."
  block-tile:
    backgroundColor: "{colors.sun} at 40–70% opacity"
    layout: "rectangles on an 8×4 grid behind cover/colophon"
    description: "A layered poster underprint."
  yellow-panel:
    backgroundColor: "{colors.sun}"
    textColor: "{colors.ink}"
    rounded: "0"
    border: "none (meets paper directly)"
    description: "Full-bleed column/third — the strongest color statement, ink on top."
  hairline-rule:
    rule: "1px solid {colors.ink} (soft variant: {colors.ink} at 18–20%)"
    description: "The ONLY border — header underlines, footer tops, ledger separators. No thicker rule exists."
  strand-row:
    borderBottom: "1px {colors.ink} 18–20%"
    typography: "{typography.strand-title} + {typography.body}"
    description: "Serif numeral + serif title + sans body. Numbered editorial lists."
  ledger-row:
    borderBottom: "1px {colors.ink} 18–20%"
    typography: "{typography.mono-date} · {typography.ledger-title} · {typography.body} · {typography.mono-data}"
    description: "4-col tabular — mono date · serif title · sans venue · mono duration (right)."
  footer-band:
    borderTop: "1px solid {colors.ink} per cell"
    typography: "{typography.micro-label} + {typography.body-sm}"
    description: "4-column metadata strip at the foot of cover/colophon."
  vertical-rail:
    typography: "{typography.rail-label}"
    transform: "rotated up the left edge"
    description: "Section marker on chapter/divider frames."
  pagenum:
    typography: "{typography.pagenum}"
    color: "{colors.ink} at 75%"
    placement: "bottom-right"
    description: "The only persistent chrome."
---

# Biennale Yellow — Frame (video / frame layer)

## Overview

Biennale Yellow at frame scale is a **literary-editorial system** in the register of an art
biennale catalogue: warm parchment, a single deep indigo ink, and solar yellow as atmosphere. No
cards, no buttons, no shadows, no rounded corners — the structural vocabulary is just **paper, ink,
and yellow.** Depth is delivered by soft radial **sun blooms**, not elevation.

The voice is three faces in rigid roles: **Instrument Serif** (weight 400, tight line-height,
negative tracking) carries every display, numeral, and quote from 40px to 720px+; **Archivo** carries
body and the wide-tracked uppercase micro-label; **JetBrains Mono** carries every date, figure, and
the pagenum. Text is **always ink** — contrast comes from size and weight, never color. The mood
sits between a folded museum brochure and a slow-reading literary quarterly: confident, atmospheric,
deeply restrained.

**Key characteristics at frame scale:**

- **Warm parchment ground** on every frame; never white, never gray.
- **Single ink** (`{colors.ink}`) for all type and all rules; **solar yellow** as bloom / panel / tile.
- **Instrument Serif 400** display (tight, negative-tracked); **Archivo** body + micro-labels; **JetBrains Mono** data.
- **1px hairline rules** are the only border — no thicker weight exists; **no shadows, no rounded corners**.
- **Sun bloom** is the primary depth layer on every frame; an ember counter-bloom adds warm-cool tension.
- **Editorial-restrained** — sparse reads as elegant; crowding breaks the catalogue feel.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one Instrument Serif moment dominates at 3–6× its neighbor; the sun bloom centers the eye.
- **Silence** — frames read **55–60% empty**; the **ledger is the one dense exception** (density via quiet hairline repetition, not richness).
- **Restraint** — **one ink color** for all type and rules; **one sun bloom** per frame (+ optional subordinate ember); never invert (no yellow text on ink).
- **Reference** — aim at an **art-biennale catalogue / slow exhibition poster / literary quarterly**; failure looks like a **flat CMS template** (no bloom) or a **bordered-card deck**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-edge` 4cqw (40–76px equivalent) — the elegance depends on edge negative space; only blooms, tiles, and panels bleed.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. Hairlines stay 1px; bloom sizes scale
as `%` of the frame.

## Colors

Tokens identical to the source. `{colors.paper}` is the universal ground; `{colors.ink}` (deep
indigo navy) is **every line of type and every rule** — there is no secondary text color.
`{colors.sun}` deploys three ways: the **bloom** (radial atmosphere), the **yellow-panel**
(full-bleed poster fill, ink on top), and the **block-tile** underprint. `{colors.sun-soft}`/`haze`
are bloom mid/outer stops. `{colors.ember}` appears **only** as a 15–22% counter-bloom — never a
fill, never text. **The system never inverts** — ink on sun is correct; yellow text on ink does not exist.

## Typography

Two ramps. The **reading/data ramp** (Archivo body 0.85cqw ink, micro-labels in px, JetBrains Mono
data) carries copy + chrome; the **display ramp** (Instrument Serif `ledger-title` 1.55cqw →
`numeral-jumbo` 28cqw) carries every headline, numeral, and quote.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono/labels in px are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`; 4–6 → `headline`; 7+ → `headline-sm`. The jumbo numeral is a divider's whole point — don't shrink it.
- **Instrument Serif is weight 400 only** (its contrast is the weight signal), tight line-height (0.84–1.06), negative tracking; **micro-labels uppercase Archivo 600, ≥0.16em**; **mono for all numerals/dates**. No bold serif, no second text color, no mono in body/display.

## Depth & Surface

Atmospheric, not structural. Depth from:

- **Sun bloom** — the primary layer: a layered radial (sun 70–95% core → sun-soft → haze 18–22% → paper 0%), 42–70% of the frame. One per frame.
- **Ember bloom** — a 15–22% peach counter-bloom in the opposite corner; always subordinate.
- **Block-tile underprint** — translucent yellow rectangles on an 8×4 grid (cover/colophon).
- **Yellow panel** — the one "hard" color statement: a flooded column/third, ink on top.
- **Hairline rules** — 1px ink for structural separation (soft 18–20% variant for dense rows).

**Ceiling:** zero box-shadow, zero text-shadow, zero rounded corner, no border thicker than 1px.

## Shapes

- **0 radius on everything** — strict rectangles. Blooms are edgeless (they fade into paper).

## Components

- **sun-bloom / ember-bloom / block-tile** — the atmospheric depth set. **yellow-panel** — the poster-fill statement.
- **hairline-rule** — the only border (1px ink; soft variant for dense rows).
- **strand-row / ledger-row / footer-band** — the editorial list, tabular calendar, and metadata strip.
- **vertical-rail** — rotated chapter marker. **pagenum** — the bottom-right mono chrome.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> A sun bloom + bottom-right pagenum on every frame; sparse is the default.

### 1 · Cover (identity · move: display + sun bloom · left)

**Ground** paper + a large sun-bloom (left-of-center) + an ember counter-bloom (opposite corner).
**Composes** micro-label, display, date-rail, footer-band, pagenum. **Focal** a 2-line Instrument
Serif `display` (italic key word) in ink, left, under a micro-label. **Chrome** a serif date-rail
top-right; a 4-column footer-band at the foot. **Accent** the sun bloom + yellow. **Silence** the
bloom holds the open space. **Fixed** ink type, one bloom, 1px footer rules, no shadow. **Free**
title, date, footer cells. **Density** low.

### 2 · Chapter Divider (section · move: jumbo numeral · vertical rail)

**Ground** paper + a corner-anchored sun-bloom. **Composes** vertical-rail, numeral-jumbo,
headline-sm. **Focal** a single huge Instrument Serif `numeral-jumbo` (≈28cqw) dominating, with a
serif title beneath. **Chrome** a rotated vertical-rail label up the left edge; pagenum. **Accent**
the bloom behind the numeral. **Silence** ~60%. **Fixed** serif 400, jumbo numeral, rail label.
**Free** the ordinal, title, rail text. **Density** low.

### 3 · Ledger (catalog · move: hairline tabular rows · the dense frame)

**Ground** paper (bloom optional, subtle). **Composes** headline-sm + micro-label topbar, ledger-rows.
**Focal** a 4-column tabular calendar — mono date · serif title · sans venue · mono duration —
separated by hairline-soft rules under a 1px ink header rule. **Chrome** pagenum. **Accent** none —
density through quiet repetition, not color. **Silence** tight — the density exception (achieved by
repetition, not richness). **Fixed** hairline rules, mono dates, serif titles. **Free** rows, venues.
**Density** dense-exception.

### 4 · Manifesto / Quote (quote · move: italic serif · centered bloom)

**Ground** paper + a centered sun-bloom. **Composes** numeral-lg quote mark, display-it, attribution.
**Focal** a 2-line Instrument Serif **italic** `display-it` quote in ink, centered, under an
oversized serif quote mark. **Chrome** a micro-label attribution. **Accent** the centered bloom.
**Silence** ~60% — deliberately open. **Fixed** italic serif, one bloom, ink. **Free** quote,
attribution. **Density** low.

### 5 · Poster Panel (statement · move: yellow panel · split)

**Ground** paper with a full-bleed `{colors.sun}` `yellow-panel` (column or third). **Composes**
yellow-panel, micro-label, headline. **Focal** an Instrument Serif `headline` in ink sitting **on
the sun panel** (ink-on-yellow — a signature). **Chrome** micro-label; pagenum. **Accent** the panel
itself. **Silence** the paper side stays open. **Fixed** ink-on-sun, panel meets paper directly (no
border), no shadow. **Free** headline, panel side/width. **Density** low.

### 6 · Strand List (programme · move: numbered editorial rows · left)

**Ground** paper + a subtle sun-bloom. **Composes** micro-label, headline-sm, strand-rows. **Focal**
a numbered list — serif numeral + serif title + sans body, hairline-soft separators. **Chrome**
micro-label; pagenum. **Accent** the bloom. **Silence** moderate; rows breathe. **Fixed** serif
numerals, hairline-soft rules. **Free** items, copy. **Density** standard.

## Composition Rules

### Do

- Start on **warm parchment**; add **one sun bloom** (optionally an ember counter-bloom) — atmosphere is the depth.
- Set every line in **ink**; use **Instrument Serif 400** (tight, negative-tracked) for display, **Archivo** body, **JetBrains Mono** for all numerals/dates.
- Make every separator a **1px ink hairline** (soft variant for dense rows); flood a **yellow panel** for poster moments (ink on top).
- Keep micro-labels **uppercase Archivo 600, 0.16–0.32em**; pin the **pagenum** bottom-right.
- Lean sparse; left/asymmetric on cover/chapter/list, centered on manifesto.

### Don't

- No drop shadows, no rounded corners, no bordered cards, no border thicker than 1px.
- No second text color; no bold Instrument Serif; no inverted ink grounds.
- No mono for body/display; no font substitutes.
- Don't crowd the canvas — sparse reads as elegant; don't omit the bloom (flat parchment reads as a CMS template).
- Don't blow a headline edge-to-edge — step the ramp down.

## Aspect-Ratio Behavior

| Treatment       | 16:9                                           | 9:16                       | 1:1                        |
| --------------- | ---------------------------------------------- | -------------------------- | -------------------------- |
| Cover           | display left, date-rail top-right, footer foot | display top, footer stacks | display upper, footer foot |
| Chapter Divider | jumbo numeral, rail left                       | numeral centered, rail top | numeral centered           |
| Ledger          | 4-col rows                                     | drop venue col → 3-col     | 3-col                      |
| Manifesto       | centered italic                                | centered, taller           | centered                   |
| Poster Panel    | side panel + paper                             | top/bottom panel band      | panel third                |
| Strand List     | numbered rows                                  | rows (tighter)             | rows                       |

`pad-edge` holds on the short edge; display clamps use the shorter axis so portrait doesn't blow out
headlines. Keep load-bearing lines ≥ 1.4cqw. Ledger/strand fixed first columns may tighten on 9:16.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Programme titles, venues, and dates are content; the system supplies paper, ink, yellow.

## Numerals & Claims (hard rule)

Never invent figures, dates, durations, or counts at frame scale. Render slots as `— figure —`,
`{metric}`, `NN JUN`, `N m`. Ledger dates/durations and chart values especially carry placeholders
until the script supplies them. Chapter ordinals (01, 02…) are decorative.

## Pre-Render Self-Audit

- **Squint** — one serif display moment dominates; the bloom centers the eye.
- **Silence** — sparse frames 55–60% open; only the ledger runs dense (via repetition, not richness).
- **One color** — ink for all text + rules; sun for bloom/panel/tile; ember counter-bloom only; no inversion.
- **Type** — Instrument Serif 400 tight negative-tracked, fit-to-measure; micro-labels uppercase 0.16em+; mono numerals; ≥1.4cqw floor.
- **Depth** — 0 shadow, 0 rounded corner, 1px hairlines only; one sun bloom present.
- **Anchor** — left on cover/chapter/list, centered on manifesto; pagenum bottom-right.
- **Fabrication** — every numeral/date traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 280ms crossfade is a deck mechanic.
- **Instrument Serif + Archivo + JetBrains Mono via Google Fonts.** CJK: Smiley Sans (display) / Noto Serif SC (body) / Noto Sans SC (labels); italic serif and mono tabular figures have no exact Hanzi equal — keep ledger dates Latin.
- **9:16 / 1:1 are guidance**; the `min(vw,vh)` clamp pattern keeps display from blowing out — verify per ratio.
- Sun/ember blooms, block tiles, yellow panels, and hairline rules are CSS-only; no external imagery is required.
assets/frame-presets/blockframe/caption-skin.html›
<!--
  BlockFrame — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a blockframe literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a white card carved by a 4px black border with an 8px hard offset shadow
  (solid black, zero blur, bottom-right — blockframe's sacred depth), square corners.
  Inter 900, UPPERCASE, negative-tracked. Upcoming words sit in faded ink; the current
  word gets a saturated-yellow stamp-block — a hard square highlight (box-shadow spread,
  no blur) with black ink reading on top, blockframe's signature stamp gesture; spoken
  words settle to solid black ink, stamp cleared. The word pops in with a snappy
  back.out overshoot — loud, a little crooked.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE bordered card — 4px black border + 8px hard offset shadow (solid black,
     zero blur, bottom-right — blockframe's sacred depth), square corners */
  .caption-pill {
    max-width: 80%;
    padding: 18px 40px 22px;
    background: var(--cap-canvas, #ffffff);
    border: 4px solid var(--cap-ink, #000000);
    border-radius: 0;
    box-shadow: 8px 8px 0 var(--cap-ink, #000000);
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.12em 0.32em;
    font-family: var(--font-display, "Inter"), system-ui, sans-serif;
    font-weight: 900;
    text-transform: uppercase;
    font-size: clamp(38px, 4.2vw, 56px);
    line-height: 1.12;
    letter-spacing: -0.02em;
  }

  /* upcoming — faded black ink, no stamp */
  .caption-word {
    display: inline-block;
    padding: 0 0.1em;
    color: color-mix(in srgb, var(--cap-ink, #000000) 40%, var(--cap-canvas, #ffffff));
  }
  /* current word — THE yellow stamp-block (box-shadow spread = a hard square block
     w/o layout shift; no blur), black ink reading on top — blockframe's signature stamp */
  .caption-word.is-active {
    color: var(--cap-ink, #000000);
    background: var(--cap-accent, #f7cb46);
    box-shadow: 0 0 0 0.08em var(--cap-accent, #f7cb46);
  }
  /* already spoken — solid black ink, stamp cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #000000);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — heavier ink weight, blockframe's display is already 900 */
  .caption-word.cap-brand {
    color: var(--cap-ink, #000000);
  }
  /* numerals / data — the body voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Inter"), ui-monospace, monospace;
    color: color-mix(in srgb, var(--cap-ink, #000000) 74%, var(--cap-canvas, #ffffff));
  }
  /* emphasis — a cream hard underline bar (the second-chroma accent) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 4px;
    text-decoration-color: var(--cap-accent-2, #ffdc8b);
    text-underline-offset: 0.12em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.86 }, { scale: 1, duration: 0.2, ease: "back.out(2.4)" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/blockframe/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>BlockFrame</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;800;900&family=Space+Grotesk:wght@500;600;700&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --black: #000000;
        --white: #ffffff;
        --offwhite: #fffdf5;
        --pink: #fe90e8;
        --blue: #c0f7fe;
        --green: #99e885;
        --yellow: #f7cb46;
        --cream: #ffdc8b;
        --disp: "Inter", sans-serif;
        --mono: "Space Grotesk", monospace;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #111;
      }
      body {
        background: var(--offwhite);
        color: var(--black);
        font-family: var(--disp);
        -webkit-font-smoothing: antialiased;
      }
      .disp {
        font-family: var(--disp);
        text-transform: uppercase;
        letter-spacing: -0.03em;
        line-height: 0.95;
        font-weight: 900;
      }
      .pill {
        display: inline-block;
        border: 3px solid var(--black);
        background: var(--white);
        box-shadow: 4px 4px 0 var(--black);
        padding: 6px 16px;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 13px;
      }
      .dotgrid {
        background-image: radial-gradient(circle, var(--black) 1.2px, transparent 1.2px);
        background-size: 24px 24px;
      }

      section {
        padding: 90px 60px;
        position: relative;
        border-bottom: 4px solid var(--black);
      }
      .sec-head {
        display: flex;
        align-items: center;
        gap: 20px;
        margin-bottom: 48px;
      }
      .sec-head h2 {
        font-family: var(--disp);
        font-weight: 900;
        text-transform: uppercase;
        letter-spacing: -0.03em;
        font-size: 46px;
        line-height: 1;
      }
      .sec-head .sp {
        flex: 1;
        height: 4px;
        background: var(--black);
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        background: var(--offwhite);
        padding: 60px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
        overflow: hidden;
      }
      .cover .dg {
        position: absolute;
        inset: 0;
        opacity: 0.4;
        background-image: radial-gradient(circle, var(--black) 1.2px, transparent 1.2px);
        background-size: 24px 24px;
        pointer-events: none;
      }
      .cover .inner {
        position: relative;
        z-index: 3;
        max-width: 72%;
      }
      .cover .pill {
        margin-bottom: 28px;
      }
      .cover h1 {
        font-family: var(--disp);
        font-weight: 900;
        text-transform: uppercase;
        letter-spacing: -0.04em;
        line-height: 0.9;
        font-size: clamp(64px, 9vw, 150px);
      }
      .cover .sub {
        font-family: var(--mono);
        font-weight: 500;
        font-size: 18px;
        line-height: 1.5;
        color: var(--black);
        margin-top: 24px;
        max-width: 540px;
      }
      .cover .deco-rect {
        position: absolute;
        top: 18%;
        right: 8%;
        width: 200px;
        height: 130px;
        background: var(--pink);
        border: 4px solid var(--black);
        box-shadow: 8px 8px 0 var(--black);
        transform: rotate(8deg);
        z-index: 2;
      }
      .cover .deco-rect2 {
        position: absolute;
        bottom: 16%;
        right: 18%;
        width: 120px;
        height: 120px;
        background: var(--blue);
        border: 4px solid var(--black);
        box-shadow: 8px 8px 0 var(--black);
        transform: rotate(-6deg);
        z-index: 1;
      }
      .cover .star {
        position: absolute;
        top: 30%;
        right: 30%;
        width: 90px;
        height: 90px;
        background: var(--yellow);
        border: 3px solid var(--black);
        clip-path: polygon(
          50% 0%,
          61% 35%,
          98% 35%,
          68% 57%,
          79% 91%,
          50% 70%,
          21% 91%,
          32% 57%,
          2% 35%,
          39% 35%
        );
        z-index: 4;
      }
      .cover .counter {
        position: absolute;
        bottom: 30px;
        left: 60px;
        border: 3px solid var(--black);
        background: var(--white);
        box-shadow: 4px 4px 0 var(--black);
        padding: 10px 18px;
        font-family: var(--mono);
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        font-size: 14px;
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 28px;
      }
      .sw {
        border: 4px solid var(--black);
        background: var(--white);
        box-shadow: 8px 8px 0 var(--black);
      }
      .sw .chip {
        height: 120px;
        border-bottom: 4px solid var(--black);
      }
      .sw .meta {
        padding: 16px 18px;
      }
      .sw .name {
        font-family: var(--disp);
        font-weight: 800;
        text-transform: uppercase;
        letter-spacing: -0.01em;
        font-size: 18px;
      }
      .sw .hex {
        font-family: var(--mono);
        font-weight: 500;
        font-size: 12px;
        letter-spacing: 0.04em;
        margin-top: 6px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 220px 1fr;
        align-items: center;
        padding: 24px 0;
        border-bottom: 4px solid var(--black);
      }
      .type-row .tok {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 12px;
      }
      .type-row .m {
        font-family: var(--mono);
        font-weight: 500;
        font-size: 11px;
        color: #444;
        margin-top: 8px;
        line-height: 1.5;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 12px;
        margin-bottom: 20px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 32px;
        margin-bottom: 48px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s6 {
        grid-column: span 6;
      }
      .s8 {
        grid-column: span 8;
      }
      .fcard {
        border: 4px solid var(--black);
        background: var(--white);
        box-shadow: 8px 8px 0 var(--black);
        padding: 32px;
        position: relative;
        height: 100%;
      }
      .fcard .notch {
        position: absolute;
        top: -12px;
        right: 24px;
        width: 44px;
        height: 44px;
        background: var(--yellow);
        border: 3px solid var(--black);
      }
      .fcard .ic {
        width: 60px;
        height: 60px;
        border: 3px solid var(--black);
        background: var(--blue);
        display: grid;
        place-items: center;
        font-family: var(--disp);
        font-weight: 700;
        font-size: 26px;
        margin-bottom: 20px;
      }
      .fcard .t {
        font-family: var(--disp);
        font-weight: 700;
        text-transform: uppercase;
        font-size: 21px;
        line-height: 1.2;
      }
      .fcard .bd {
        font-family: var(--disp);
        font-weight: 500;
        font-size: 14px;
        line-height: 1.6;
        color: #222;
        margin-top: 10px;
      }
      .statcard {
        border: 3px solid var(--black);
        background: var(--white);
        box-shadow: 4px 4px 0 var(--black);
        padding: 24px;
        position: relative;
      }
      .statcard .dot {
        position: absolute;
        top: 14px;
        right: 14px;
        width: 12px;
        height: 12px;
        border-radius: 50%;
        border: 2px solid var(--black);
      }
      .statcard .n {
        font-family: var(--disp);
        font-weight: 900;
        font-size: 48px;
        line-height: 1;
      }
      .statcard .l {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 12px;
        margin-top: 8px;
      }
      .btn {
        display: inline-block;
        border: 3px solid var(--black);
        background: var(--yellow);
        box-shadow: 4px 4px 0 var(--black);
        padding: 14px 32px;
        font-family: var(--disp);
        font-weight: 700;
        font-size: 16px;
      }
      .stripe {
        height: 80px;
        border: 3px solid var(--black);
        background: repeating-linear-gradient(
          45deg,
          var(--black),
          var(--black) 4px,
          var(--green) 4px,
          var(--green) 12px
        );
      }
      .listnum {
        display: inline-grid;
        place-items: center;
        width: 36px;
        height: 36px;
        border: 3px solid var(--black);
        background: var(--yellow);
        font-family: var(--mono);
        font-weight: 700;
        font-size: 14px;
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 12px;
        color: #555;
      }
      .fw .flabel b {
        color: var(--black);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--offwhite);
        border: 4px solid var(--black);
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 3;
      }

      /* f1 cover */
      .bf1 {
        background: var(--cream);
      }
      .bf1 .dg {
        position: absolute;
        inset: 0;
        opacity: 0.35;
        background-image: radial-gradient(circle, var(--black) 0.15cqw, transparent 0.15cqw);
        background-size: 3cqw 3cqw;
        z-index: 1;
      }
      .bf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .bf1 .pill {
        display: inline-block;
        border: 0.25cqw solid var(--black);
        background: var(--white);
        box-shadow: 0.4cqw 0.4cqw 0 var(--black);
        padding: 0.5cqw 1.2cqw;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 1cqw;
        margin-bottom: 2cqw;
        align-self: flex-start;
      }
      .bf1 h3 {
        font-family: var(--disp);
        font-weight: 900;
        text-transform: uppercase;
        letter-spacing: -0.04em;
        line-height: 0.9;
        font-size: 9.5cqw;
        max-width: 60cqw;
      }
      .bf1 .star {
        position: absolute;
        top: 18%;
        right: 8%;
        width: 14cqw;
        height: 14cqw;
        background: var(--pink);
        border: 0.3cqw solid var(--black);
        clip-path: polygon(
          50% 0%,
          61% 35%,
          98% 35%,
          68% 57%,
          79% 91%,
          50% 70%,
          21% 91%,
          32% 57%,
          2% 35%,
          39% 35%
        );
        z-index: 2;
      }
      .bf1 .rect {
        position: absolute;
        bottom: 16%;
        right: 16%;
        width: 16cqw;
        height: 9cqw;
        background: var(--blue);
        border: 0.3cqw solid var(--black);
        box-shadow: 0.6cqw 0.6cqw 0 var(--black);
        transform: rotate(-6deg);
        z-index: 1;
      }

      /* f2 feature cards */
      .bf2 {
        background: var(--blue);
      }
      .bf2 .body {
        padding: 5cqw;
        display: flex;
        flex-direction: column;
      }
      .bf2 .pill {
        align-self: flex-start;
        border: 0.2cqw solid var(--black);
        background: var(--white);
        box-shadow: 0.3cqw 0.3cqw 0 var(--black);
        padding: 0.4cqw 1cqw;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 0.85cqw;
        margin-bottom: 1.4cqw;
      }
      .bf2 h3 {
        font-family: var(--disp);
        font-weight: 800;
        text-transform: uppercase;
        letter-spacing: -0.02em;
        font-size: 3.6cqw;
        margin-bottom: 2cqw;
      }
      .bf2 .row {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 1.6cqw;
        flex: 1;
      }
      .bf2 .c {
        border: 0.3cqw solid var(--black);
        background: var(--white);
        box-shadow: 0.6cqw 0.6cqw 0 var(--black);
        padding: 1.8cqw;
      }
      .bf2 .c .ic {
        width: 4cqw;
        height: 4cqw;
        border: 0.2cqw solid var(--black);
        display: grid;
        place-items: center;
        font-family: var(--disp);
        font-weight: 700;
        font-size: 1.8cqw;
        margin-bottom: 1.2cqw;
      }
      .bf2 .c .t {
        font-family: var(--disp);
        font-weight: 700;
        text-transform: uppercase;
        font-size: 1.5cqw;
        line-height: 1.15;
      }
      .bf2 .c .bd {
        font-family: var(--disp);
        font-weight: 500;
        font-size: 0.95cqw;
        line-height: 1.5;
        color: #222;
        margin-top: 0.6cqw;
      }

      /* f3 stat grid (tilted) */
      .bf3 {
        background: var(--green);
      }
      .bf3 .body {
        padding: 5cqw;
        display: flex;
        flex-direction: column;
      }
      .bf3 .pill {
        align-self: flex-start;
        border: 0.2cqw solid var(--black);
        background: var(--white);
        box-shadow: 0.3cqw 0.3cqw 0 var(--black);
        padding: 0.4cqw 1cqw;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 0.85cqw;
        margin-bottom: 1.4cqw;
      }
      .bf3 h3 {
        font-family: var(--disp);
        font-weight: 800;
        text-transform: uppercase;
        letter-spacing: -0.02em;
        font-size: 3.6cqw;
        margin-bottom: 2.4cqw;
      }
      .bf3 .row {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 2cqw;
      }
      .bf3 .sc {
        border: 0.25cqw solid var(--black);
        background: var(--white);
        box-shadow: 0.4cqw 0.4cqw 0 var(--black);
        padding: 1.8cqw;
        position: relative;
      }
      .bf3 .sc:nth-child(odd) {
        transform: rotate(-2deg);
      }
      .bf3 .sc:nth-child(even) {
        transform: rotate(2deg);
      }
      .bf3 .sc .dot {
        position: absolute;
        top: 1cqw;
        right: 1cqw;
        width: 1cqw;
        height: 1cqw;
        border-radius: 50%;
        border: 0.15cqw solid var(--black);
      }
      .bf3 .sc .n {
        font-family: var(--disp);
        font-weight: 900;
        font-size: 4.6cqw;
        line-height: 1;
      }
      .bf3 .sc .l {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 0.85cqw;
        margin-top: 0.6cqw;
      }

      /* f4 close (black) */
      .bf4 {
        background: var(--black);
      }
      .bf4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
      }
      .bf4 .cardf {
        border: 0.3cqw solid var(--white);
        box-shadow: 1.2cqw 1.2cqw 0 var(--yellow);
        padding: 4cqw 6cqw;
        position: relative;
      }
      .bf4 .pill {
        border: 0.25cqw solid var(--white);
        background: var(--black);
        color: var(--white);
        box-shadow: 0.5cqw 0.5cqw 0 var(--white);
        padding: 0.5cqw 1.2cqw;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 1cqw;
        margin-bottom: 1.6cqw;
        display: inline-block;
      }
      .bf4 h3 {
        font-family: var(--disp);
        font-weight: 900;
        text-transform: uppercase;
        letter-spacing: -0.03em;
        font-size: 7cqw;
        line-height: 0.95;
        color: var(--white);
      }
      .bf4 .star {
        position: absolute;
        top: -3cqw;
        right: -3cqw;
        width: 9cqw;
        height: 9cqw;
        background: var(--pink);
        border: 0.3cqw solid var(--white);
        clip-path: polygon(
          50% 0%,
          61% 35%,
          98% 35%,
          68% 57%,
          79% 91%,
          50% 70%,
          21% 91%,
          32% 57%,
          2% 35%,
          39% 35%
        );
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 32px;
      }
      .do-card {
        border: 4px solid var(--black);
        box-shadow: 8px 8px 0 var(--black);
        padding: 36px;
      }
      .do-card.do {
        background: var(--green);
      }
      .do-card.dont {
        background: var(--white);
      }
      .do-card h4 {
        font-family: var(--disp);
        font-weight: 900;
        text-transform: uppercase;
        letter-spacing: -0.02em;
        font-size: 30px;
        margin-bottom: 20px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-family: var(--disp);
        font-weight: 500;
        font-size: 15px;
        line-height: 1.55;
        padding-left: 26px;
        position: relative;
        margin-bottom: 13px;
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--disp);
        font-weight: 900;
      }
      .do-card.do li::before {
        content: "+";
      }
      .do-card.dont li::before {
        content: "\00d7";
      }

      .foot {
        padding: 48px 60px;
        background: var(--black);
        color: var(--white);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--disp);
        font-weight: 900;
        text-transform: uppercase;
        letter-spacing: -0.03em;
        font-size: 28px;
      }
      .foot .r {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 11px;
        color: #aaa;
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #fffdf5 !important;
        --secondary: #000000 !important;
        --tertiary: #ffdc8b !important;
        --accent: #fe90e8 !important;
        --f-disp: "Inter", sans-serif !important;
        --f-body: "Space Grotesk", sans-serif !important;
        --black: #000000 !important;
        --offwhite: #fffdf5 !important;
        --pink: #fe90e8 !important;
        --cream: #ffdc8b !important;
        --disp: "Inter", sans-serif !important;
        --mono: "Space Grotesk", sans-serif !important;
        --white: #ffffff !important;
        --blue: #c0f7fe !important;
        --green: #99e885 !important;
        --yellow: #f7cb46 !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="dg"></div>
      <div class="star"></div>
      <div class="deco-rect"></div>
      <div class="deco-rect2"></div>
      <div class="inner">
        <span class="pill">Frame System · Vol. 01</span>
        <h1>Loud,<br />bordered,<br />a little crooked.</h1>
        <div class="sub">
          A maximalist neobrutalist frame system — 4px ink borders, 8px hard shadows, five candy
          pastels, and tilted decorations that puncture the grid.
        </div>
      </div>
      <div class="counter">01 / 06</div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="pill">Palette</span>
        <h2>Five Candy Pastels</h2>
        <span class="sp"></span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--pink)"></div>
          <div class="meta">
            <div class="name">Pink</div>
            <div class="hex">#FE90E8</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--blue)"></div>
          <div class="meta">
            <div class="name">Blue</div>
            <div class="hex">#C0F7FE</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--green)"></div>
          <div class="meta">
            <div class="name">Green</div>
            <div class="hex">#99E885</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--yellow)"></div>
          <div class="meta">
            <div class="name">Yellow</div>
            <div class="hex">#F7CB46 · CTA</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream)"></div>
          <div class="meta">
            <div class="name">Cream</div>
            <div class="hex">#FFDC8B</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="background: var(--offwhite); border-bottom: 4px solid var(--black)"
          ></div>
          <div class="meta">
            <div class="name">Off-White</div>
            <div class="hex">#FFFDF5 · canvas</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="background: var(--white); border-bottom: 4px solid var(--black)"
          ></div>
          <div class="meta">
            <div class="name">White</div>
            <div class="hex">#FFFFFF · cards</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--black)"></div>
          <div class="meta">
            <div class="name">Black</div>
            <div class="hex">#000000 · all borders</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <span class="pill" style="background: var(--pink)">Type</span>
        <h2>Inter × Space Grotesk</h2>
        <span class="sp"></span>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">heading-xl</div>
          <div class="m">Inter 900 · uppercase · −0.03em</div>
        </div>
        <div class="spec"><span class="disp" style="font-size: 80px">Maximal.</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">heading-lg</div>
          <div class="m">Inter 800 · uppercase · −0.02em</div>
        </div>
        <div class="spec">
          <span class="disp" style="font-weight: 800; font-size: 50px">Bordered & Bold</span>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">stat-number</div>
          <div class="m">Inter 900 · line 1</div>
        </div>
        <div class="spec">
          <span class="disp" style="font-size: 64px; letter-spacing: -0.02em">240</span>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Inter 500 · sentence case · line 1.6</div>
        </div>
        <div class="spec">
          <p style="font-weight: 500; font-size: 18px; line-height: 1.6; max-width: 640px">
            Body runs Inter at weight 500, sentence case — the calm against the heavy uppercase
            display.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">label</div>
          <div class="m">Space Grotesk 600 · uppercase · 0.08em</div>
        </div>
        <div class="spec"><span class="pill">Section Eyebrow</span></div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="pill" style="background: var(--blue)">Components</span>
        <h2>Borders & Hard Shadows</h2>
        <span class="sp"></span>
      </div>

      <div class="cgrid">
        <div class="s4">
          <div class="cnote">Feature card + notch + icon</div>
          <div class="fcard">
            <div class="notch"></div>
            <div class="ic" style="background: var(--pink)">A</div>
            <div class="t">Bordered Block</div>
            <div class="bd">4px ink border + 8px hard offset shadow, square corners.</div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Stat cards (tilted) + deco dot</div>
          <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px">
            <div class="statcard" style="transform: rotate(-2deg)">
              <div class="dot" style="background: var(--pink)"></div>
              <div class="n">240</div>
              <div class="l">Output</div>
            </div>
            <div class="statcard" style="transform: rotate(2deg)">
              <div class="dot" style="background: var(--blue)"></div>
              <div class="n">3.4×</div>
              <div class="l">Faster</div>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">CTA · list number · stripe</div>
          <span class="btn">Click Here →</span>
          <div style="display: flex; align-items: center; gap: 14px; margin-top: 24px">
            <span class="listnum">01</span
            ><span style="font-family: var(--disp); font-weight: 500; font-size: 15px"
              >Numbered list bullet</span
            >
          </div>
          <div class="stripe" style="margin-top: 24px"></div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s6">
          <div class="cnote">Label pills — pastel variants</div>
          <div style="display: flex; gap: 16px; flex-wrap: wrap">
            <span class="pill" style="background: var(--pink)">Pink</span
            ><span class="pill" style="background: var(--blue)">Blue</span
            ><span class="pill" style="background: var(--green)">Green</span
            ><span class="pill" style="background: var(--yellow)">Yellow</span
            ><span class="pill" style="background: var(--cream)">Cream</span>
          </div>
        </div>
        <div class="s6">
          <div class="cnote">Icon squares + star burst</div>
          <div style="display: flex; align-items: center; gap: 20px">
            <span
              style="
                width: 64px;
                height: 64px;
                border: 3px solid var(--black);
                background: var(--green);
                display: grid;
                place-items: center;
                font-family: var(--disp);
                font-weight: 700;
                font-size: 26px;
              "
              >B</span
            >
            <span
              style="
                width: 64px;
                height: 64px;
                border: 3px solid var(--black);
                background: var(--yellow);
                display: grid;
                place-items: center;
                font-family: var(--disp);
                font-weight: 700;
                font-size: 26px;
              "
              >C</span
            >
            <span
              style="
                width: 64px;
                height: 64px;
                background: var(--pink);
                border: 3px solid var(--black);
                clip-path: polygon(
                  50% 0%,
                  61% 35%,
                  98% 35%,
                  68% 57%,
                  79% 91%,
                  50% 70%,
                  21% 91%,
                  32% 57%,
                  2% 35%,
                  39% 35%
                );
              "
            ></span>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <span class="pill" style="background: var(--yellow)">Frames</span>
        <h2>Frame Compositions</h2>
        <span class="sp"></span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Cover</b><span>· identity · cream · decorations · left</span></div>
          <div class="frame bf1">
            <div class="dg"></div>
            <div class="star"></div>
            <div class="rect"></div>
            <div class="body">
              <span class="pill">Frame System</span>
              <h3>Loud, bordered, crooked.</h3>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Feature Cards</b><span>· catalog · blue ground · 3-up</span></div>
          <div class="frame bf2">
            <div class="body">
              <span class="pill">Three Laws</span>
              <h3>Cards on Grounds</h3>
              <div class="row">
                <div class="c">
                  <div class="ic" style="background: var(--pink)">A</div>
                  <div class="t">Border</div>
                  <div class="bd">4px ink on all.</div>
                </div>
                <div class="c">
                  <div class="ic" style="background: var(--green)">B</div>
                  <div class="t">Shadow</div>
                  <div class="bd">8px hard offset.</div>
                </div>
                <div class="c">
                  <div class="ic" style="background: var(--yellow)">C</div>
                  <div class="t">Tilt</div>
                  <div class="bd">A little crooked.</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Stat Grid</b><span>· data · green ground · tilted · the dense frame</span>
          </div>
          <div class="frame bf3">
            <div class="body">
              <span class="pill">By The Numbers</span>
              <h3>The Tally</h3>
              <div class="row">
                <div class="sc">
                  <div class="dot" style="background: var(--pink)"></div>
                  <div class="n">240</div>
                  <div class="l">Output</div>
                </div>
                <div class="sc">
                  <div class="dot" style="background: var(--blue)"></div>
                  <div class="n">3.4×</div>
                  <div class="l">Faster</div>
                </div>
                <div class="sc">
                  <div class="dot" style="background: var(--yellow)"></div>
                  <div class="n">12K</div>
                  <div class="l">Shipped</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Closing Plate</b><span>· closer · black ground · yellow shadow</span>
          </div>
          <div class="frame bf4">
            <div class="body">
              <div class="cardf">
                <div class="star"></div>
                <span class="pill">The End</span>
                <h3>Stay loud.</h3>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section style="border-bottom: none">
      <div class="sec-head">
        <span class="pill" style="background: var(--green)">Rules</span>
        <h2>Frame Rules</h2>
        <span class="sp"></span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <ul>
            <li>4px borders + 8px shadows on primary cards; 3px + 4px on chrome.</li>
            <li>Cycle pastel grounds across frames; keep the rhythm.</li>
            <li>Inter 800–900 uppercase, negative tracking, for all display.</li>
            <li>Open every region with a label-pill eyebrow.</li>
            <li>Tilt decorations ±2°–12°; add one to every frame.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <ul>
            <li>No rounded corners (save the stat-deco dot); no blurred shadows.</li>
            <li>No colored borders (black only, save the close-frame white).</li>
            <li>No sentence-case Inter display; no sixth pastel.</li>
            <li>No label rendered as plain text — pill or nothing.</li>
            <li>Don't blow a headline edge-to-edge — fit to measure.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--black);
        --cap-canvas: var(--white);
        --cap-accent: var(--yellow);
        --cap-accent-2: var(--cream);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        border: 0.4cqw solid var(--cap-ink);
        border-radius: 0;
        box-shadow: 0.8cqw 0.8cqw 0 var(--cap-ink);
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.12em 0.32em;
        font-family: var(--disp);
        font-weight: 900;
        text-transform: uppercase;
        font-size: 3.2cqw;
        line-height: 1.12;
        letter-spacing: -0.02em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.1em;
        color: color-mix(in srgb, var(--cap-ink) 40%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.08em var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 0.15cqw dashed rgba(0, 0, 0, 0.4);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        font-size: 0.9cqw;
        letter-spacing: 0.08em;
        color: var(--black);
        opacity: 0.55;
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="pill" style="background: var(--yellow)">Captions</span>
        <h2>Lower-Third Karaoke</h2>
        <span class="sp"></span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo" style="background: var(--cream)">
            <div
              class="dg"
              style="
                position: absolute;
                inset: 0;
                opacity: 0.3;
                background-image: radial-gradient(
                  circle,
                  var(--black) 0.15cqw,
                  transparent 0.15cqw
                );
                background-size: 3cqw 3cqw;
                z-index: 1;
              "
            ></div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · stamp cleared</span></div>
          <div class="frame capdemo" style="background: var(--blue)">
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">BlockFrame</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#FFFDF5" />
        <rect x="16" y="34" width="46" height="32" fill="#FE90E8" stroke="#000" stroke-width="4" />
        <rect x="22" y="40" width="46" height="32" fill="#C0F7FE" stroke="#000" stroke-width="4" />
        <path
          d="M74 16l4 11h12l-9 7 3 11-10-6-10 6 3-11-9-7h12z"
          fill="#F7CB46"
          stroke="#000"
          stroke-width="2"
        />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/blockframe/FRAME.md›
---
version: alpha
name: BlockFrame — Frame (video / frame layer)
description: >
  Video-first companion to BlockFrame's design.md. The unit is the frame (1920×1080). Atoms are
  identical and sacred — 4px black borders + 8px hard offset shadows, the five-pastel candy palette
  (pink/blue/green/yellow/cream) plus black/white/off-white, Inter 800–900 uppercase display +
  Space Grotesk label chrome, square corners, label-pills, tilted decorations, star bursts, stripe
  blocks, dot grids. Composition + frame scale rewritten. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  black: "#000000"
  white: "#FFFFFF"
  offwhite: "#FFFDF5"
  pink: "#FE90E8"
  blue: "#C0F7FE"
  green: "#99E885"
  yellow: "#F7CB46"
  cream: "#FFDC8B"

borders: { primary: "4px solid black", thin: "3px solid black" }
shadows: { default: "8px 8px 0 black", small: "4px 4px 0 black", hover: "6px 6px 0 black", close-yellow: "12px 12px 0 yellow", close-white: "6px 6px 0 white" }

typography:
  # — reading + chrome ramp —
  body:    { fontFamily: "Inter", cqw: 0.95, weight: 500, lineHeight: 1.6 }
  card-title:{ fontFamily: "Inter", cqw: 1.15, weight: 700, upper: true, lineHeight: 1.2 }
  label:   { fontFamily: "Space Grotesk", px: 13, weight: 600, tracking: "0.08em", upper: true }
  counter: { fontFamily: "Space Grotesk", px: 14, weight: 700, tracking: "0.1em", upper: true }
  # — display ramp (Inter 800–900, uppercase, negative tracking) —
  heading-md:{ fontFamily: "Inter", cqw: 2.1, weight: 700, lineHeight: 1.1, tracking: "-0.01em", upper: true }
  quote-text:{ fontFamily: "Inter", cqw: 2.7, weight: 900, lineHeight: 1.15, tracking: "-0.02em", upper: true }
  stat-number:{ fontFamily: "Inter", cqw: 3.3, weight: 900, lineHeight: 1.0 }
  heading-lg:{ fontFamily: "Inter", cqw: 3.3, weight: 800, lineHeight: 1.0, tracking: "-0.02em", upper: true }
  close-title:{ fontFamily: "Inter", cqw: 4.2, weight: 900, lineHeight: 0.95, tracking: "-0.03em", upper: true }
  heading-xl:{ fontFamily: "Inter", cqw: 5.0, weight: 900, lineHeight: 0.95, tracking: "-0.03em", upper: true }

spacing:
  slide-pad: "3.1cqw"   # 60px @1920
  gap-md: "1.7cqw"

components:
  card-elevated:
    backgroundColor: "{colors.white}"
    border: "0.4cqw solid {colors.black}"
    rounded: "0"
    shadow: "0.8cqw 0.8cqw 0 {colors.black}"
    description: "Primary card. Border/shadow coupled: 4px↔8px, 3px↔4px."
  card-small:
    backgroundColor: "{colors.white}"
    border: "0.3cqw solid {colors.black}"
    rounded: "0"
    shadow: "0.4cqw 0.4cqw 0 {colors.black}"
    description: "Stat cards, team cards, timeline steps."
  label-pill:
    border: "0.3cqw solid {colors.black}"
    backgroundColor: "any pastel / {colors.white}"
    rounded: "9999px"
    shadow: "0.4cqw 0.4cqw 0 {colors.black}"
    typography: "{typography.label}"
    description: "The universal eyebrow — never plain text."
  button-primary:
    backgroundColor: "{colors.yellow}"
    textColor: "{colors.black}"
    border: "0.3cqw solid {colors.black}"
    rounded: "0"
    shadow: "0.4cqw 0.4cqw 0 {colors.black}"
    typography: "Inter 700"
    description: "The CTA."
  star-burst:
    backgroundColor: "any pastel"
    border: "0.3cqw solid {colors.black}"
    clip: "10-point clip-path star"
    description: "Corner attention-grabber."
  stripe-block:
    backgroundImage: "45° {colors.black} + pastel diagonal stripes"
    border: "0.3cqw solid {colors.black}"
    description: "Poster decoration."
  bg-dot-grid:
    backgroundImage: "radial-dot ~1.2px dots, 24px grid, {colors.black}"
    description: "Faint corner/ground overlay."
  tilt-deco:
    transform: "rotate(±2°–12°)"
    description: "Rotated rectangle/badge/star. Stat cards alternate −2/+2°. The grid-puncture signature."
  stat-deco-dot:
    backgroundColor: "any pastel"
    border: "2px solid {colors.black}"
    rounded: "50%"
    size: "12px"
    description: "The ONLY round shape, pinned to stat cards."
  close-frame:
    backgroundColor: "{colors.black}"
    textColor: "{colors.white}"
    border: "0.4cqw solid {colors.white}"
    rounded: "0"
    shadow: "1.2cqw 1.2cqw 0 {colors.yellow}"
    description: "Inverted closer — the only colored shadow."
---

# BlockFrame — Frame (video / frame layer)

## Overview

BlockFrame at frame scale is a **maximalist neobrutalist** system on five laws: every region has a
4px black border, every elevated element an 8px hard offset shadow, every corner square, every
accent a saturated pastel, and every layout allowed to be a little crooked. The joy is the
deliberate collision — bordered cards meeting bordered cards, shadows stacking, tilted decorations
puncturing the grid.

The voice is **Inter** at weight 800–900 in tight uppercase with negative tracking (display) +
weight 500 sentence body, and **Space Grotesk** weight 600 uppercase 0.08em as the label/chrome
voice. Five candy pastels (pink/blue/green/yellow/cream) cycle as full-bleed grounds across frames
— the color cycling is the primary rhythm. Depth is **hard offset shadow** (8px/4px, solid black,
zero blur, bottom-right); the close-frame's 12px yellow shadow is the one colored exception.

**Key characteristics at frame scale:**

- **4px black borders + 8px hard shadows** on primary cards; 3px + 4px on chrome (weights coupled).
- **Five-pastel palette** cycled as full-bleed grounds; black/white/off-white structural.
- **Inter 800–900 uppercase** negative-tracked display; **Space Grotesk** label chrome.
- **Square corners** everywhere (only the stat-deco dot is round); **tilted decorations** puncture the grid.
- **Label-pills** open every region; star bursts, stripe blocks, dot grids are reusable attention units.
- **Comfortably dense** — packed reads as authoritative; empty corners read as broken.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one Inter display moment dominates at 3–6× its neighbor; cards read as a system, not rivals.
- **Silence** — cover/quote/close keep air (decorations, not content); the **feature-card and stat grids are the dense exception**.
- **Restraint** — the 4px→08px / 3px→4px coupling holds; black borders only (white on close); pastel ground **cycles** one per frame; no sixth pastel.
- **Reference** — aim at a **zine / 1990s sticker-book / toy-packaging spread** (bordered blocks, tilted decorations); failure looks like a **flat, borderless, blurred-shadow web card grid**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `slide-pad` ~3.1cqw; decorations may bleed off edges.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. Borders/shadows scale in `cqw` so the
4px↔8px coupling holds proportionally; corners stay square.

## Colors

Tokens identical to the source. `{colors.offwhite}` is the default ground, but frames **cycle**
through `{colors.cream}`/`blue`/`pink`/`green`/`yellow` grounds — the cycle is the rhythm.
`{colors.black}` is every border + structural text; `{colors.white}` is card fills. The five pastels
are interchangeable with **no semantic meaning** — pair by juxtaposition (pink+blue+green trio,
cream+yellow warm pair). `{colors.yellow}` is the CTA + the one colored (close) shadow;
`{colors.black}` ground is the close surface. **No sixth pastel.**

## Typography

Two ramps. The **reading/chrome ramp** (Inter body 0.95cqw weight 500, Space Grotesk labels in px)
carries copy + chrome; the **display ramp** (Inter `heading-md` 2.1cqw → `heading-xl` 5cqw, weight
800–900 uppercase) carries every headline + stat.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `heading-xl`; 4–6 → `heading-lg`; 7+ → `heading-md`.
- **Inter display is uppercase, weight 800–900, negative-tracked** (−0.01..−0.03em); body weight 500 sentence case; **Space Grotesk labels uppercase 0.08em**. No sentence-case display, no untracked display.

## Depth & Surface

Hard offset shadow, solid black, zero blur, bottom-right:

- **0.8cqw (8px)** primary cards; **0.4cqw (4px)** chrome; **0.6cqw (6px)** hover.
- **Border-based depth** — the 4px/3px borders do much of the lift; shadow makes it "elevated."
- **Tilt** — ±2°–12° rotation breaks the grid for perceived dimension.
- **Inverted close** — 12px YELLOW shadow (and 6px white on close-btn) — the only colored shadows.

**Ceiling:** no blurred shadow, no rounded corner (save the stat-deco dot), no gradient depth.

## Shapes

- **0 radius everywhere** except the 12px stat-deco dot (50%). Square corners are the structural identity.

## Components

- **card-elevated / card-small** — the bordered+shadowed content cards (weight-coupled).
- **label-pill** — the universal eyebrow (border+shadow+pastel). **button-primary** — yellow CTA.
- **star-burst / stripe-block / bg-dot-grid / tilt-deco** — the reusable decoration units (one per frame min).
- **stat-deco-dot** — the lone round shape. **close-frame** — the inverted black+white+yellow-shadow closer.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> Cycle the ground color; add ≥1 decoration per frame; open with a label-pill.

### 1 · Cover (identity · move: decorations puncture · left)

**Ground** `{colors.cream}` (or offwhite) + faint dot-grid. **Composes** label-pill, heading-xl, tilt-deco rect, star-burst, counter. **Focal** a 2–3 line Inter `heading-xl` uppercase, left, under a label-pill; tilted pastel rects + a star burst puncture the right. **Chrome** counter pill. **Accent** the decorations' pastels. **Silence** right third holds decorations. **Fixed** 4px/8px coupling, uppercase display, square. **Free** title, decoration placement/colors. **Density** comfortable.

### 2 · Feature Cards (catalog · move: 3-up bordered grid · blue ground)

**Ground** `{colors.blue}`, `slide-pad`. **Composes** label-pill, heading-lg, 3× card-elevated (icon-square + card-title + body). **Focal** three white bordered+shadowed cards. **Chrome** label-pill eyebrow. **Accent** the pastel icon-squares (pink/green/yellow). **Silence** tight — dense by design. **Fixed** 4px border + 8px shadow, square, uppercase card-titles. **Free** card content, icon hues. **Density** dense-exception.

### 3 · Stat Grid (data · move: tilted stat cards · green ground)

**Ground** `{colors.green}`, `slide-pad`. **Composes** label-pill, heading-lg, 3× card-small (tilted −2/+2°, stat-deco dot, stat-number + label). **Focal** three tilted bordered stat cards. **Accent** the deco-dots' pastels + black stat numerals. **Silence** moderate. **Fixed** alternating tilt, 3px+4px, round deco-dot only. **Free** figures (from script), dot hues. **Density** dense-exception.

### 4 · Closing Plate (closer · move: inverted black · centered)

**Ground** `{colors.black}`. **Composes** close-frame (4px white border, 12px yellow shadow), label-pill (inverted), close-title, star-burst. **Focal** a white `close-title` inside the white-bordered frame with the yellow offset shadow; a pink star punctures a corner. **Accent** the yellow shadow + pink star. **Silence** ~50%. **Fixed** white-on-black, 12px yellow shadow (only here), square. **Free** sign-off, star placement. **Density** low.

### 5 · Quote (quote · move: bordered quote frame · pink ground)

**Ground** `{colors.pink}` (or offwhite). **Composes** label-pill, quote-text in a card-elevated, attribution. **Focal** an Inter `quote-text` (900 uppercase) inside a white bordered+shadowed frame. **Accent** the ground + one decoration. **Silence** moderate. **Fixed** uppercase quote, 4px/8px. **Free** quote, ground. **Density** comfortable.

### 6 · Timeline (process · move: stepped bordered cards · offwhite)

**Ground** `{colors.offwhite}`. **Composes** label-pill, heading-lg, 3–4 card-small steps + step-connectors. **Focal** a row of bordered step cards linked by black connector bars, each a pastel + step-num. **Accent** the step pastels. **Silence** moderate. **Fixed** 3px+4px steps, square, connectors. **Free** steps, hues. **Density** standard.

## Composition Rules

### Do

- Pair **4px borders with 8px shadows**, **3px with 4px** — the coupling is non-negotiable.
- **Cycle pastel grounds** across frames; keep the deck visually rhythmic.
- Set Inter display **uppercase, 800–900, negative-tracked**; open every region with a **label-pill**.
- Render shadows **solid black, zero blur, bottom-right**; add **≥1 decoration** (tilt/star/stripe/dots) per frame.
- Use **yellow** for CTAs; use the **inverted black + 12px yellow shadow** close-frame for the closer.
- Tilt decorations ±2°–12°; lean left on most frames.

### Don't

- No rounded corners (save the stat-deco dot); no blurred shadows.
- No colored borders (black only, save the close-frame white); no sixth pastel.
- No sentence-case Inter display; no untracked display; no label as plain text.
- Don't keep everything perfectly aligned (tilt is the signature); don't leave corners empty.
- Don't blow a headline edge-to-edge — fit to measure.

## Aspect-Ratio Behavior

| Treatment     | 16:9                          | 9:16                         | 1:1                      |
| ------------- | ----------------------------- | ---------------------------- | ------------------------ |
| Cover         | title left, decorations right | title top, decorations below | title, fewer decorations |
| Feature Cards | 3 across                      | 3 stacked                    | 2+1                      |
| Stat Grid     | 3 tilted across               | 3 tilted stacked             | 2×2                      |
| Closing       | centered close-frame          | centered, taller             | centered                 |
| Quote         | quote frame                   | quote stacked                | centered                 |
| Timeline      | horizontal steps              | vertical (connectors hidden) | 2×2                      |

`slide-pad` holds on the short edge; re-step display above the 1.4cqw floor. Decoration count drops
on tighter ratios so the frame stays bordered, not cluttered.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Pastel fills, decorations, and tilts are content-agnostic.

## Numerals & Claims (hard rule)

Never invent figures, stats, or counts at frame scale. Render slots as `— figure —`, `{metric}`,
`N×`. Stat cards and charts carry placeholders until the script supplies values. Slide counters and
list numbers are decorative.

## Pre-Render Self-Audit

- **Squint** — one Inter display moment dominates; cards read as a system.
- **Silence** — only feature/stat grids run dense; cover/quote/close keep air with decorations.
- **Borders/shadows** — 4px↔8px / 3px↔4px coupling holds; solid black, zero blur.
- **Color** — pastel ground cycles; black borders only (white on close); no sixth pastel.
- **Type** — Inter uppercase 800–900 negative-tracked, fit-to-measure; Space Grotesk labels 0.08em; ≥1.4cqw floor.
- **Shape** — square corners (only stat-deco dot round); ≥1 tilt/decoration per frame.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the source toggles slides via display, no transition.
- **Inter + Space Grotesk via Google Fonts.** CJK: Noto Sans SC 900 (sentence case — the uppercase signal drops); lean harder on borders/shadows/decoration to carry the brutalist identity.
- **9:16 / 1:1 are guidance**; verify the floor and that decoration count scales down.
- Star bursts (clip-path), stripe blocks, dot grids, and tilts are CSS-only; no external imagery is required.
assets/frame-presets/blue-professional/caption-skin.html›
<!--
  Blue Professional — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a blue-professional
  literal fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a soft cobalt-tinted card on warm cream — a 4% cobalt fill inside a 1.5px
  20%-cobalt border, soft 14px rounded corners, NO shadow (the system's premium signal
  is the absence of cast depth; the tinted card does the lift). Space Grotesk, sentence
  case, near-black glyphs. Upcoming words sit in muted gray; the current word becomes a
  solid cobalt chip — the brand's signature single-accent gesture, a pill-rounded fill
  with cream reading on top; spoken words settle to near-black ink, chip cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE tinted card — 4% cobalt fill, 1.5px 20%-cobalt border, soft 14px corners,
     NO shadow (the tinted card does the lift; cast depth is forbidden) */
  .caption-pill {
    max-width: 78%;
    padding: 20px 44px 24px;
    background: color-mix(in srgb, var(--cap-accent, #1e2bfa) 4%, var(--cap-canvas, #fdfae7));
    border: 1.5px solid color-mix(in srgb, var(--cap-accent, #1e2bfa) 20%, transparent);
    border-radius: 14px;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.1em 0.34em;
    font-family: var(--font-display, "Space Grotesk"), system-ui, sans-serif;
    font-weight: 600;
    font-size: clamp(40px, 4.4vw, 58px);
    line-height: 1.16;
    letter-spacing: -0.02em;
  }

  /* upcoming — muted gray, no chip */
  .caption-word {
    display: inline-block;
    padding: 0 0.18em;
    border-radius: 0.32em;
    color: color-mix(in srgb, var(--cap-ink, #111111) 52%, var(--cap-canvas, #fdfae7));
  }
  /* current word — THE solid cobalt chip (the single-accent gesture; pill-rounded
     fill, NO shadow), cream reading on top */
  .caption-word.is-active {
    color: var(--cap-canvas, #fdfae7);
    background: var(--cap-accent, #1e2bfa);
    box-shadow: none;
  }
  /* already spoken — solid near-black ink, chip cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #111111);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — cobalt, the system's one accent on a key word */
  .caption-word.cap-brand {
    color: var(--cap-accent, #1e2bfa);
  }
  /* numerals / data — cobalt 700, the figure voice */
  .caption-word.cap-num {
    font-family: var(--font-display, "Space Grotesk"), system-ui, sans-serif;
    font-weight: 700;
    color: var(--cap-accent, #1e2bfa);
  }
  /* emphasis — a directional-red hairline underline (inline only, never a fill) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--cap-accent-2, #dc2626);
    text-underline-offset: 0.1em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.985 }, { scale: 1, duration: 0.18, ease: "power1.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/blue-professional/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Blue Professional</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --bg: #fdfae7;
        --primary: #1e2bfa;
        --text: #111111;
        --muted: #6b6b6b;
        --light: #9a9a9a;
        --accent-light: rgba(30, 43, 250, 0.08);
        --accent-medium: rgba(30, 43, 250, 0.15);
        --border: rgba(30, 43, 250, 0.2);
        --card-bg: rgba(30, 43, 250, 0.04);
        --positive: #059669;
        --negative: #dc2626;
        --disp: "Space Grotesk", sans-serif;
        --body: "Inter", sans-serif;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #0b0b16;
      }
      body {
        background: var(--bg);
        color: var(--muted);
        font-family: var(--body);
        -webkit-font-smoothing: antialiased;
      }
      .disp {
        font-family: var(--disp);
      }
      .eyebrow {
        font-family: var(--disp);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 14px;
        color: var(--primary);
      }

      section {
        padding: 108px 76px;
        position: relative;
      }
      .sec-head {
        display: flex;
        align-items: flex-start;
        gap: 20px;
        margin-bottom: 60px;
      }
      .sec-head .ey {
        display: flex;
        flex-direction: column;
        gap: 14px;
      }
      .sec-head .aline {
        width: 60px;
        height: 4px;
        background: var(--primary);
        border-radius: 2px;
      }
      .sec-head h2 {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 42px;
        line-height: 1.1;
        letter-spacing: -0.02em;
        color: var(--text);
      }
      .sec-head .sp {
        flex: 1;
      }
      .sec-head .tagpill {
        background: var(--accent-light);
        color: var(--primary);
        padding: 0.4rem 1rem;
        border-radius: 100px;
        font-family: var(--disp);
        font-weight: 500;
        font-size: 12px;
        align-self: center;
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        padding: 0 7vw;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
        overflow: hidden;
      }
      .cover .deco {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        width: 36%;
        background: var(--accent-light);
        clip-path: polygon(30% 0, 100% 0, 100% 100%, 0% 100%);
      }
      .cover .dots {
        position: absolute;
        top: 14%;
        right: 10%;
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 12px;
        opacity: 0.25;
        z-index: 2;
      }
      .cover .dots i {
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background: var(--primary);
      }
      .cover .inner {
        position: relative;
        z-index: 3;
        max-width: 64%;
      }
      .cover .aline {
        width: 60px;
        height: 4px;
        background: var(--primary);
        border-radius: 2px;
        margin-bottom: 28px;
      }
      .cover .meta {
        font-family: var(--disp);
        font-weight: 400;
        font-size: 13px;
        letter-spacing: 0.05em;
        color: var(--light);
        margin-bottom: 20px;
      }
      .cover h1 {
        font-family: var(--disp);
        font-weight: 700;
        font-size: clamp(46px, 5.4vw, 76px);
        line-height: 1.08;
        letter-spacing: -0.02em;
        color: var(--text);
      }
      .cover .sub {
        font-family: var(--body);
        font-weight: 400;
        font-size: clamp(16px, 1.4vw, 20px);
        line-height: 1.6;
        color: var(--muted);
        margin-top: 26px;
        max-width: 560px;
      }
      .cover .pbar {
        position: absolute;
        bottom: 0;
        left: 0;
        height: 3px;
        width: 16%;
        background: var(--primary);
      }
      .cover .counter {
        position: absolute;
        bottom: 24px;
        left: 7vw;
        font-family: var(--disp);
        font-weight: 500;
        font-size: 13px;
        letter-spacing: 0.05em;
        color: var(--muted);
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 20px;
      }
      .sw {
        background: var(--card-bg);
        border: 1.5px solid var(--border);
        border-radius: 14px;
        overflow: hidden;
      }
      .sw .chip {
        height: 92px;
        border-radius: 10px;
        margin: 14px 14px 0;
      }
      .sw .meta {
        padding: 14px 18px 18px;
      }
      .sw .name {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 17px;
        color: var(--text);
        letter-spacing: -0.01em;
      }
      .sw .hex {
        font-family: var(--disp);
        font-size: 12px;
        color: var(--muted);
        margin-top: 4px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 220px 1fr;
        align-items: center;
        padding: 24px 0;
        border-bottom: 1px solid var(--border);
      }
      .type-row .tok {
        font-family: var(--disp);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 12px;
        color: var(--primary);
      }
      .type-row .m {
        font-size: 12px;
        color: var(--muted);
        margin-top: 8px;
        line-height: 1.5;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--disp);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 12px;
        color: var(--primary);
        margin-bottom: 20px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 1.2rem;
        margin-bottom: 48px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s6 {
        grid-column: span 6;
      }
      .s8 {
        grid-column: span 8;
      }
      .card {
        background: var(--card-bg);
        border: 1.5px solid var(--border);
        border-radius: 14px;
        padding: 1.5rem 1.6rem;
        height: 100%;
      }
      .card .mv {
        font-family: var(--disp);
        font-weight: 700;
        font-size: 46px;
        line-height: 1;
        color: var(--primary);
      }
      .card .ml {
        font-family: var(--body);
        font-weight: 600;
        font-size: 16px;
        color: var(--text);
        margin-top: 10px;
      }
      .card .md {
        font-size: 13px;
        line-height: 1.5;
        color: var(--muted);
        margin-top: 8px;
      }
      .card .chg {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 12.5px;
        margin-top: 10px;
      }
      .card .chg.pos {
        color: var(--positive);
      }
      .card .chg.neg {
        color: var(--negative);
      }
      .statcell {
        background: var(--card-bg);
        border: 1px solid var(--border);
        border-radius: 12px;
        padding: 1.4rem 1.5rem;
      }
      .statcell .n {
        font-family: var(--disp);
        font-weight: 700;
        font-size: 32px;
        color: var(--primary);
        line-height: 1;
      }
      .statcell .nm {
        font-family: var(--body);
        font-weight: 500;
        font-size: 14px;
        color: var(--text);
        margin-top: 8px;
      }
      .statcell .ctx {
        font-size: 12px;
        color: var(--light);
        margin-top: 8px;
        padding-top: 8px;
        border-top: 1px solid var(--border);
      }
      .bars .row {
        display: flex;
        align-items: center;
        gap: 14px;
        margin-bottom: 14px;
      }
      .bars .bl {
        font-family: var(--body);
        font-weight: 500;
        font-size: 14px;
        color: var(--text);
        width: 90px;
      }
      .bars .track {
        flex: 1;
        height: 28px;
        background: var(--accent-light);
        border-radius: 6px;
        overflow: hidden;
      }
      .bars .fill {
        height: 100%;
        display: block;
        background: var(--primary);
        border-radius: 6px;
      }
      .bars .pct {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 15px;
        color: var(--primary);
        width: 48px;
        text-align: right;
      }
      .steps {
        display: flex;
        gap: 20px;
      }
      .step {
        flex: 1;
      }
      .step .circ {
        width: 56px;
        height: 56px;
        border-radius: 50%;
        background: var(--primary);
        color: var(--bg);
        display: grid;
        place-items: center;
        font-family: var(--disp);
        font-weight: 700;
        font-size: 21px;
      }
      .step .t {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 17px;
        color: var(--text);
        margin-top: 14px;
      }
      .step .d {
        font-size: 13px;
        line-height: 1.5;
        color: var(--muted);
        margin-top: 6px;
      }
      .hilite {
        background: var(--accent-light);
        border-left: 4px solid var(--primary);
        border-radius: 12px;
        padding: 1.3rem 1.5rem;
      }
      .hilite .q {
        font-family: var(--disp);
        font-weight: 500;
        font-size: 22px;
        line-height: 1.4;
        color: var(--text);
      }
      .hilite .c {
        font-family: var(--disp);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        font-size: 12px;
        color: var(--muted);
        margin-top: 14px;
      }
      .cta {
        display: inline-block;
        background: var(--primary);
        color: var(--bg);
        padding: 0.9rem 2.2rem;
        border-radius: 100px;
        font-family: var(--disp);
        font-weight: 600;
        font-size: 15px;
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--disp);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 12px;
        color: var(--muted);
      }
      .fw .flabel b {
        color: var(--text);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--bg);
        border: 1px solid var(--border);
        border-radius: 10px;
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 3;
      }
      .frame .pbar {
        position: absolute;
        bottom: 0;
        left: 0;
        height: 0.3cqw;
        background: var(--primary);
        z-index: 5;
      }

      /* f1 cover */
      .gf1 .deco {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        width: 36%;
        background: var(--accent-light);
        clip-path: polygon(30% 0, 100% 0, 100% 100%, 0% 100%);
        z-index: 1;
      }
      .gf1 .dots {
        position: absolute;
        top: 14%;
        right: 9%;
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 1cqw;
        opacity: 0.25;
        z-index: 2;
      }
      .gf1 .dots i {
        width: 0.7cqw;
        height: 0.7cqw;
        border-radius: 50%;
        background: var(--primary);
      }
      .gf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .gf1 .aline {
        width: 5cqw;
        height: 0.3cqw;
        background: var(--primary);
        border-radius: 0.2cqw;
        margin-bottom: 2cqw;
      }
      .gf1 .ey {
        font-family: var(--disp);
        font-weight: 400;
        font-size: 1cqw;
        letter-spacing: 0.05em;
        color: var(--light);
        margin-bottom: 1.4cqw;
      }
      .gf1 h3 {
        font-family: var(--disp);
        font-weight: 700;
        font-size: 5.6cqw;
        line-height: 1.06;
        letter-spacing: -0.02em;
        color: var(--text);
        max-width: 58cqw;
      }
      .gf1 .sub {
        font-size: 1.4cqw;
        line-height: 1.5;
        color: var(--muted);
        margin-top: 1.8cqw;
        max-width: 48cqw;
      }

      /* f2 dashboard */
      .gf2 .body {
        padding: 5.5cqw 5cqw;
        display: flex;
        flex-direction: column;
      }
      .gf2 .hdr {
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        margin-bottom: 2.4cqw;
      }
      .gf2 .ey {
        font-family: var(--disp);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 0.95cqw;
        color: var(--primary);
      }
      .gf2 h3 {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 3.2cqw;
        letter-spacing: -0.02em;
        color: var(--text);
        margin-top: 0.6cqw;
      }
      .gf2 .tp {
        background: var(--accent-light);
        color: var(--primary);
        padding: 0.4cqw 1cqw;
        border-radius: 100px;
        font-family: var(--disp);
        font-weight: 500;
        font-size: 0.9cqw;
      }
      .gf2 .grid {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 1.4cqw;
        flex: 1;
      }
      .gf2 .c {
        background: var(--card-bg);
        border: 0.1cqw solid var(--border);
        border-radius: 1cqw;
        padding: 1.6cqw;
      }
      .gf2 .c .n {
        font-family: var(--disp);
        font-weight: 700;
        font-size: 3.4cqw;
        color: var(--primary);
        line-height: 1;
      }
      .gf2 .c .nm {
        font-family: var(--body);
        font-weight: 600;
        font-size: 1.1cqw;
        color: var(--text);
        margin-top: 0.6cqw;
      }
      .gf2 .c .d {
        font-size: 0.92cqw;
        line-height: 1.45;
        color: var(--muted);
        margin-top: 0.5cqw;
      }

      /* f3 bars */
      .gf3 .body {
        padding: 5.5cqw 5cqw;
        display: flex;
        flex-direction: column;
      }
      .gf3 .ey {
        font-family: var(--disp);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 0.95cqw;
        color: var(--primary);
        margin-bottom: 0.6cqw;
      }
      .gf3 h3 {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 3cqw;
        letter-spacing: -0.02em;
        color: var(--text);
        margin-bottom: 2.4cqw;
      }
      .gf3 .row {
        display: flex;
        align-items: center;
        gap: 1.4cqw;
        margin-bottom: 1.4cqw;
      }
      .gf3 .bl {
        font-family: var(--body);
        font-weight: 500;
        font-size: 1.2cqw;
        color: var(--text);
        width: 14cqw;
      }
      .gf3 .track {
        flex: 1;
        height: 2.6cqw;
        background: var(--accent-light);
        border-radius: 0.6cqw;
        overflow: hidden;
      }
      .gf3 .fill {
        height: 100%;
        display: block;
        background: var(--primary);
        border-radius: 0.6cqw;
      }
      .gf3 .pct {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 1.4cqw;
        color: var(--primary);
        width: 5cqw;
        text-align: right;
      }

      /* f4 quote/closing */
      .gf4 {
        position: relative;
      }
      .gf4 .rings {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 34cqw;
        height: 34cqw;
        border: 0.1cqw solid var(--border);
        border-radius: 50%;
        opacity: 0.4;
        z-index: 1;
      }
      .gf4 .rings::before {
        content: "";
        position: absolute;
        inset: 20%;
        border: 0.1cqw solid var(--border);
        border-radius: 50%;
      }
      .gf4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        padding: 0 9cqw;
      }
      .gf4 .qm {
        font-family: var(--disp);
        font-weight: 700;
        font-size: 9cqw;
        line-height: 0.5;
        color: var(--primary);
        opacity: 0.15;
        height: 4cqw;
      }
      .gf4 .q {
        font-family: var(--disp);
        font-weight: 500;
        font-size: 3.4cqw;
        line-height: 1.35;
        color: var(--text);
        margin-top: 1cqw;
      }
      .gf4 .c {
        font-family: var(--disp);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        font-size: 1cqw;
        color: var(--muted);
        margin-top: 2cqw;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 24px;
      }
      .do-card {
        background: var(--card-bg);
        border: 1.5px solid var(--border);
        border-radius: 14px;
        padding: 36px;
      }
      .do-card h4 {
        font-family: var(--disp);
        font-weight: 600;
        font-size: 24px;
        color: var(--text);
        margin-bottom: 8px;
        letter-spacing: -0.01em;
      }
      .do-card .aline {
        width: 48px;
        height: 4px;
        background: var(--primary);
        border-radius: 2px;
        margin-bottom: 22px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-size: 14px;
        line-height: 1.6;
        padding-left: 24px;
        position: relative;
        margin-bottom: 13px;
        color: var(--muted);
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--disp);
        font-weight: 700;
        color: var(--primary);
      }
      .do-card.do li::before {
        content: "+";
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: var(--light);
      }

      .foot {
        padding: 52px 76px;
        border-top: 1px solid var(--border);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--disp);
        font-weight: 700;
        font-size: 26px;
        color: var(--text);
        letter-spacing: -0.02em;
      }
      .foot .r {
        font-family: var(--disp);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 11px;
        color: var(--muted);
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #fdfae7 !important;
        --secondary: #111111 !important;
        --tertiary: #6b6b6b !important;
        --accent: #1e2bfa !important;
        --f-disp: "Space Grotesk", sans-serif !important;
        --f-body: "Inter", sans-serif !important;
        --bg: #fdfae7 !important;
        --primary: #1e2bfa !important;
        --text: #111111 !important;
        --muted: #6b6b6b !important;
        --disp: "Space Grotesk", sans-serif !important;
        --body: "Inter", sans-serif !important;
        --light: #9a9a9a !important;
        --positive: #059669 !important;
        --negative: #dc2626 !important;
        --accent-light: rgba(30, 43, 250, 0.08) !important;
        --accent-medium: rgba(30, 43, 250, 0.15) !important;
        --border: rgba(30, 43, 250, 0.2) !important;
        --card-bg: rgba(30, 43, 250, 0.04) !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="deco"></div>
      <div class="dots"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
      <div class="inner">
        <div class="aline"></div>
        <div class="meta">FRAME SYSTEM · VOL. 01 · CONFIDENTIAL</div>
        <h1>Restraint, with one strong commitment.</h1>
        <div class="sub">
          A consulting-grade frame system — warm cream, a single saturated cobalt carrying every
          accent, and soft tinted cards that never shout.
        </div>
      </div>
      <div class="counter">01 / 06</div>
      <div class="pbar"></div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <div class="ey">
          <div class="aline"></div>
          <h2>Palette</h2>
        </div>
        <span class="sp"></span><span class="tagpill">One Accent, Three Grays</span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div
            class="chip"
            style="background: var(--bg); box-shadow: inset 0 0 0 1px var(--border)"
          ></div>
          <div class="meta">
            <div class="name">Canvas</div>
            <div class="hex">#fdfae7</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--primary)"></div>
          <div class="meta">
            <div class="name">Cobalt</div>
            <div class="hex">#1e2bfa · the only accent</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--text)"></div>
          <div class="meta">
            <div class="name">Text</div>
            <div class="hex">#111111</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--muted)"></div>
          <div class="meta">
            <div class="name">Muted</div>
            <div class="hex">#6b6b6b · body</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--card-bg)"></div>
          <div class="meta">
            <div class="name">Card Tint</div>
            <div class="hex">cobalt · 4%</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--border)"></div>
          <div class="meta">
            <div class="name">Border</div>
            <div class="hex">cobalt · 20%</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--positive)"></div>
          <div class="meta">
            <div class="name">Positive</div>
            <div class="hex">#059669 · inline only</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--negative)"></div>
          <div class="meta">
            <div class="name">Negative</div>
            <div class="hex">#dc2626 · inline only</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <div class="ey">
          <div class="aline"></div>
          <h2>Typography</h2>
        </div>
        <span class="sp"></span><span class="tagpill">Space Grotesk + Inter</span>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">h1</div>
          <div class="m">Space Grotesk 700 · −0.02em · near-black</div>
        </div>
        <div class="spec">
          <span
            class="disp"
            style="font-weight: 700; font-size: 66px; letter-spacing: -0.02em; color: var(--text)"
            >Measured.</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">h4-eyebrow</div>
          <div class="m">Space Grotesk 600 · uppercase · 0.08em · cobalt</div>
        </div>
        <div class="spec"><span class="eyebrow">Executive Summary</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">metric-value</div>
          <div class="m">Space Grotesk 700 · cobalt</div>
        </div>
        <div class="spec">
          <span class="disp" style="font-weight: 700; font-size: 54px; color: var(--primary)"
            >$24.3M</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Inter 400 · line 1.6 · muted gray</div>
        </div>
        <div class="spec">
          <p style="font-size: 17px; line-height: 1.6; max-width: 660px">
            Inter carries every paragraph in muted gray — readable, premium, never competing with
            the cobalt accent or the near-black headline.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">cta</div>
          <div class="m">Space Grotesk 600 · cobalt pill · cream text</div>
        </div>
        <div class="spec"><span class="cta">Book a Briefing</span></div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <div class="ey">
          <div class="aline"></div>
          <h2>Components</h2>
        </div>
        <span class="sp"></span><span class="tagpill">Soft Tints, No Shadows</span>
      </div>

      <div class="cgrid">
        <div class="s4">
          <div class="cnote">Metric card</div>
          <div class="card">
            <div class="mv">$24.3M</div>
            <div class="ml">Annual Revenue</div>
            <div class="md">Recurring, across every region.</div>
            <div class="chg pos">↑ +18% YoY</div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Stat cells</div>
          <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem">
            <div class="statcell">
              <div class="n">94%</div>
              <div class="nm">Retention</div>
              <div class="ctx">Net revenue</div>
            </div>
            <div class="statcell">
              <div class="n">3.4×</div>
              <div class="nm">Multiple</div>
              <div class="ctx">On capital</div>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Bar ranking</div>
          <div class="bars">
            <div class="row">
              <span class="bl">Cobalt</span
              ><span class="track"><span class="fill" style="width: 88%"></span></span
              ><span class="pct">88</span>
            </div>
            <div class="row">
              <span class="bl">Restraint</span
              ><span class="track"><span class="fill" style="width: 64%"></span></span
              ><span class="pct">64</span>
            </div>
            <div class="row">
              <span class="bl">Density</span
              ><span class="track"><span class="fill" style="width: 46%"></span></span
              ><span class="pct">46</span>
            </div>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s8">
          <div class="cnote">Step timeline — fade into future</div>
          <div class="steps">
            <div class="step">
              <div class="circ" style="opacity: 1">1</div>
              <div class="t">Survey</div>
              <div class="d">Establish the cream baseline.</div>
            </div>
            <div class="step">
              <div class="circ" style="opacity: 0.85">2</div>
              <div class="t">Tint</div>
              <div class="d">Lift cards with 4% cobalt.</div>
            </div>
            <div class="step">
              <div class="circ" style="opacity: 0.7">3</div>
              <div class="t">Accent</div>
              <div class="d">One cobalt carries emphasis.</div>
            </div>
            <div class="step">
              <div class="circ" style="opacity: 0.55">4</div>
              <div class="t">Deliver</div>
              <div class="d">Executive-ready briefing.</div>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Highlight callout</div>
          <div class="hilite">
            <div class="q">One accent does all the emphasis work.</div>
            <div class="c">— The System Principle</div>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <div class="ey">
          <div class="aline"></div>
          <h2>Frame Compositions</h2>
        </div>
        <span class="sp"></span><span class="tagpill">True 16:9 · cqw</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Cover</b><span>· identity · diagonal accent · left</span></div>
          <div class="frame gf1">
            <div class="deco"></div>
            <div class="dots"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
            <div class="body">
              <div class="aline"></div>
              <div class="ey">FRAME SYSTEM · VOL. 01</div>
              <h3>Restraint, with one commitment.</h3>
              <div class="sub">Cream, cobalt, and soft tinted cards.</div>
            </div>
            <div class="pbar" style="width: 16%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Dashboard</b><span>· data · 3-up metrics · the dense frame</span>
          </div>
          <div class="frame gf2">
            <div class="body">
              <div class="hdr">
                <div>
                  <div class="ey">Performance</div>
                  <h3>Q3 At a Glance</h3>
                </div>
                <span class="tp">FY2026</span>
              </div>
              <div class="grid">
                <div class="c">
                  <div class="n">$24M</div>
                  <div class="nm">Revenue</div>
                  <div class="d">Up across every region.</div>
                </div>
                <div class="c">
                  <div class="n">94%</div>
                  <div class="nm">Retention</div>
                  <div class="d">Net revenue, trailing 12mo.</div>
                </div>
                <div class="c">
                  <div class="n">+18%</div>
                  <div class="nm">Growth</div>
                  <div class="d">Year over year, compounding.</div>
                </div>
              </div>
            </div>
            <div class="pbar" style="width: 34%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Bar Ranking</b><span>· data · cobalt fills · left</span></div>
          <div class="frame gf3">
            <div class="body">
              <div class="ey">Drivers</div>
              <h3>What Moves the Number</h3>
              <div class="row">
                <span class="bl">Expansion</span
                ><span class="track"><span class="fill" style="width: 88%"></span></span
                ><span class="pct">88</span>
              </div>
              <div class="row">
                <span class="bl">New Logos</span
                ><span class="track"><span class="fill" style="width: 62%"></span></span
                ><span class="pct">62</span>
              </div>
              <div class="row">
                <span class="bl">Pricing</span
                ><span class="track"><span class="fill" style="width: 41%"></span></span
                ><span class="pct">41</span>
              </div>
            </div>
            <div class="pbar" style="width: 58%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Pull Quote</b><span>· quote · concentric rings · centered</span>
          </div>
          <div class="frame gf4">
            <div class="rings"></div>
            <div class="body">
              <div class="qm">&ldquo;</div>
              <div class="q">Quiet is the most expensive thing on the page.</div>
              <div class="c">— Managing Partner</div>
            </div>
            <div class="pbar" style="width: 82%"></div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <div class="ey">
          <div class="aline"></div>
          <h2>Frame Rules</h2>
        </div>
        <span class="sp"></span><span class="tagpill">Single Accent</span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <div class="aline"></div>
          <ul>
            <li>Start every frame on warm cream; cobalt carries every accent.</li>
            <li>
              Set headlines near-black with −0.02em tracking; eyebrows cobalt, uppercase, 0.08em.
            </li>
            <li>Render every numeral in cobalt Space Grotesk 600–700.</li>
            <li>Use tinted cards — 4% fill, 20% border, 10–14px radius; soft, never outlined.</li>
            <li>Pill chrome at 100px; body in Inter 400, muted gray, line 1.6.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <div class="aline"></div>
          <ul>
            <li>No second accent color; no cobalt headlines.</li>
            <li>No drop shadows (save the one soft cobalt CTA hover).</li>
            <li>No square corners; no opaque cobalt borders.</li>
            <li>No font substitutes; no uppercase body.</li>
            <li>Don't fill space with heavier borders — add substance, not noise.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--text);
        --cap-canvas: var(--bg);
        --cap-accent: var(--primary);
        --cap-accent-2: var(--negative);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: color-mix(in srgb, var(--cap-accent) 4%, var(--cap-canvas));
        border: 0.16cqw solid color-mix(in srgb, var(--cap-accent) 20%, transparent);
        border-radius: 1cqw;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.1em 0.34em;
        font-family: var(--disp);
        font-weight: 600;
        font-size: 3.4cqw;
        line-height: 1.16;
        letter-spacing: -0.02em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.18em;
        border-radius: 0.32em;
        color: color-mix(in srgb, var(--cap-ink) 52%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-canvas);
        background: var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 1px dashed var(--border);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--disp);
        font-weight: 500;
        font-size: 0.95cqw;
        letter-spacing: 0.04em;
        color: var(--muted);
        opacity: 0.7;
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <div class="ey">
          <div class="aline"></div>
          <h2>Captions</h2>
        </div>
        <span class="sp"></span><span class="tagpill">Lower-third · Karaoke</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="deco"></div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
            <div class="pbar" style="width: 50%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · chip cleared</span></div>
          <div class="frame capdemo">
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
            <div class="pbar" style="width: 100%"></div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Blue Professional</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#fdfae7" />
        <rect x="58" y="0" width="42" height="100" fill="#1e2bfa" opacity="0.08" />
        <rect x="14" y="40" width="6" height="20" fill="#1e2bfa" />
        <rect x="26" y="44" width="44" height="6" rx="3" fill="#111111" />
        <rect x="26" y="56" width="30" height="4" rx="2" fill="#1e2bfa" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/blue-professional/FRAME.md›
---
version: alpha
name: Blue Professional — Frame (video / frame layer)
description: >
  Video-first companion to Blue Professional's design.md. The unit is the frame (1920×1080). Atoms
  are identical and sacred — the warm cream canvas, a single saturated cobalt (#1e2bfa) as the only
  accent, the three-step gray text ladder, Space Grotesk (display/numerals/chrome) + Inter (body),
  soft cobalt-tinted cards (4% fill / 20% border / 10–14px radius) with NO shadows, pill chrome, and
  the cobalt progress bar. Composition + frame scale rewritten. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  bg: "#fdfae7"
  primary: "#1e2bfa"
  text: "#111111"
  text-muted: "#6b6b6b"
  text-light: "#9a9a9a"
  accent-light: "rgba(30,43,250,0.08)"
  accent-medium: "rgba(30,43,250,0.15)"
  border: "rgba(30,43,250,0.2)"
  card-bg: "rgba(30,43,250,0.04)"
  positive: "#059669"
  negative: "#dc2626"

radii:
  pill: "100px"
  card-lg: "14px"
  card-md: "12px"
  card-sm: "10px"
  bar: "6px"
  circle: "50%"

typography:
  # — reading ramp (Inter body + Space Grotesk chrome) —
  body:    { fontFamily: "Inter", cqw: 0.85, weight: 400, lineHeight: 1.6, color: "text-muted" }
  h4-eyebrow:{ fontFamily: "Space Grotesk", cqw: 0.8, weight: 600, tracking: "0.08em", upper: true, color: "primary" }
  tag:     { fontFamily: "Space Grotesk", px: 12, weight: 500, color: "primary" }
  counter: { fontFamily: "Space Grotesk", px: 13, weight: 500, tracking: "0.05em", color: "text-muted" }
  # — display / numerical ramp (Space Grotesk, near-black headings / cobalt numerals) —
  h3:      { fontFamily: "Space Grotesk", cqw: 1.25, weight: 500, lineHeight: 1.3, tracking: "-0.02em", color: "text" }
  stat-num:{ fontFamily: "Space Grotesk", cqw: 1.9, weight: 700, lineHeight: 1.0, color: "primary" }
  blockquote:{ fontFamily: "Space Grotesk", cqw: 2.4, weight: 500, lineHeight: 1.35, color: "text" }
  h2:      { fontFamily: "Space Grotesk", cqw: 2.6, weight: 600, lineHeight: 1.1, tracking: "-0.02em", color: "text" }
  metric-value:{ fontFamily: "Space Grotesk", cqw: 3.0, weight: 700, lineHeight: 1.0, color: "primary" }
  h1:      { fontFamily: "Space Grotesk", cqw: 4.2, weight: 700, lineHeight: 1.08, tracking: "-0.02em", color: "text" }
  quote-mark:{ fontFamily: "Space Grotesk", cqw: 8.0, weight: 700, lineHeight: 0.5, color: "primary", opacity: 0.15 }

spacing:
  pad-x: "5cqw"
  pad-y-top: "5cqw"
  gap-cards: "1.4cqw"
  accent-line: "60px × 4px"

components:
  card-tinted:
    backgroundColor: "{colors.card-bg}"
    border: "1.5px solid {colors.border}"
    rounded: "{radii.card-lg}"
    shadow: "none"
    description: "Universal content card. Never solid-colored, never opaque-bordered, NO shadow."
  metric-card:
    backgroundColor: "{colors.card-bg}"
    border: "1.5px solid {colors.border}"
    rounded: "{radii.card-lg}"
    typography: "{typography.metric-value} ({colors.primary}) + {typography.metric-label} + {typography.metric-desc}"
    description: "+ optional inline ↑/↓ change chip ({colors.positive}/{colors.negative} text, no fill)."
  tag-pill:
    backgroundColor: "{colors.accent-light}"
    textColor: "{colors.primary}"
    rounded: "{radii.pill}"
    typography: "{typography.tag}"
    description: "Top-right of the slide-header."
  cta-button:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.bg}"
    rounded: "{radii.pill}"
    typography: "Space Grotesk 600"
    shadow: "soft cobalt on hover only — the system's only shadow"
    description: "The one solid element."
  accent-line:
    backgroundColor: "{colors.primary}"
    size: "60×4, 2px radius"
    description: "Above cover titles / eyebrow separators."
  bar-track:
    backgroundColor: "{colors.accent-light}"
    fill: "{colors.primary} (display:block so width resolves)"
    rounded: "{radii.bar}"
    description: "28px track; fill carries the value."
  step-circle:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.bg}"
    rounded: "50%"
    size: "56px"
    description: "Sequential steps fade opacity 1.0→0.85→0.7→0.55."
  split-highlight:
    backgroundColor: "{colors.accent-light}"
    borderLeft: "4px solid {colors.primary}"
    rounded: "{radii.card-md}"
    description: "Inline pull-quote callout."
  slide-header:
    typography: "{typography.h4-eyebrow} (cobalt) left, tag-pill right; {typography.h2} below"
    description: "Top band of every content frame."
  atmosphere:
    elements: "clipped diagonal cobalt-tint panel, 3×3 cobalt dot grid, concentric closing rings"
    description: "Cover/closing only. Never on content frames."
  progress-bar:
    backgroundColor: "{colors.primary}"
    size: "3px tall, bottom edge, width grows with index"
    description: "Persistent progress strip."
---

# Blue Professional — Frame (video / frame layer)

## Overview

Blue Professional at frame scale is a **consulting-grade system: restraint with one strong
commitment.** A warm cream canvas and a single saturated cobalt that carries every accent —
eyebrow, metric, CTA, chart fill, progress bar. No secondary brand color, no pastels, just cream,
cobalt, and a tight ladder of grays. The register is investment-research / McKinsey briefing:
measured, data-dense without crowding, executive-readable at distance.

The voice is two faces in fixed roles: **Space Grotesk** (display, every numeral, all chrome —
eyebrows uppercase 0.08em) and **Inter** (body, muted gray, line 1.6). Headlines are near-black;
cobalt is reserved for accent moments. Depth is **soft and tinted** — 4% cobalt card fills with 20%
cobalt borders and 10–14px radii — never shadowed. The lack of harsh shadows is the premium signal.

**Key characteristics at frame scale:**

- **Warm cream ground** on every frame; **single cobalt** as the only accent.
- **Space Grotesk** (display/numerals/chrome) + **Inter** (body) — near-black headlines, cobalt numerals.
- **Tinted cards** — cobalt 4% fill, cobalt 20% 1.5px border, 10–14px radius, **no shadow**.
- **Pill chrome** (100px) — tag pills + the one solid cobalt CTA; cobalt **progress bar**.
- **Soft rounded corners everywhere** (no square corners save the progress bar).
- **Atmosphere** (diagonal panel, dot grid, concentric rings) on cover/closing only.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one **near-black headline or cobalt numeral** dominates at 3–6× its neighbor.
- **Silence** — content frames read **balanced, not crowded**; the **dashboard is the one dense exception**.
- **Restraint** — a **single cobalt accent** carries everything; headlines stay near-black (never cobalt); no shadows (tinted cards do the lift); positive/negative inline-text only.
- **Reference** — aim at an **investment-research / McKinsey quarterly briefing**; failure looks like a **heavy-outlined, multi-color dashboard**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-x` 5cqw; bottom reserves room for the counter + progress bar.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. Card radii stay px (10–14px); the
pill radius stays 100px; borders stay 1–1.5px.

## Colors

Tokens identical to the source. `{colors.bg}` cream is the universal ground; `{colors.primary}`
cobalt is the **only** accent — every eyebrow, numeral, CTA, chart fill, progress bar, and the 4px
highlight left-rule. Headlines are `{colors.text}` near-black (never cobalt); body is
`{colors.text-muted}`; tertiary is `{colors.text-light}`. Cards fill `{colors.card-bg}` (4%) with
`{colors.border}` (20%) borders. `{colors.positive}`/`{colors.negative}` appear **only inline** on
directional change chips — never as fills. **No second accent color.**

## Typography

Two ramps. The **reading ramp** (Inter body 0.85cqw muted; Space Grotesk eyebrow uppercase 0.08em
cobalt) carries copy + chrome; the **display/numerical ramp** (Space Grotesk `h3` 1.25cqw → `h1`
4.2cqw near-black; numerals `stat-num`/`metric-value` in cobalt) carries headings and figures.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px chrome (tag/counter) is colophon only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `h1`; 4–6 → `h2`; 7+ → `h3`. Cobalt numerals scale `metric-value`→`stat-num` by card size.
- **Headlines near-black, −0.02em**; **eyebrows cobalt, uppercase, 0.08em**; **numerals cobalt 600–700**; **body Inter 400 muted, line 1.6**. No italic, no uppercase body, no cobalt headline.

## Depth & Surface

Soft and tinted — never offset. Depth from:

- **Tinted cards** — 4% cobalt fill + 20% cobalt 1.5px border + 10–14px radius reads as lifted without offset.
- **Border-left accent** — the 4px cobalt rule on split-highlight blocks pulls a callout forward.
- **Rounded corners** — the 10–14px radius is part of the softness; square corners break it.

**Ceiling:** zero box-shadow on content (the only shadow is a soft cobalt CTA _hover_); no opaque cobalt borders; no harsh outlines.

## Shapes

- **100px** — tag pills, CTA, nav buttons (pill chrome).
- **14/12/10px** — cards by size (large metric / standard stat / detail + mini).
- **6px** — bar tracks + fills. **50%** — step circles, nav circles, dots, closing rings.
- **0** — only the progress bar. No square-cornered content.

## Components

- **card-tinted / metric-card** — the universal soft-tint content cards (no shadow).
- **tag-pill / cta-button / accent-line** — the cobalt pill chrome + the one solid CTA + the 60×4 rule.
- **bar-track / step-circle / split-highlight** — cobalt data + sequence + callout patterns.
- **slide-header** (eyebrow + tag pill) — the structural rhythm; **atmosphere** (diagonal/dots/rings) on cover/closing only; **progress-bar** on every frame.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> Atmosphere only on cover/closing; content frames carry the slide-header rhythm.

### 1 · Cover (identity · move: diagonal accent · left)

**Ground** cream + the clipped diagonal cobalt-tint panel (right ~36%) + a 3×3 cobalt dot grid.
**Composes** accent-line, meta, h1, body sub. **Focal** a 2-line Space Grotesk `h1` near-black, left,
under a cobalt accent-line + meta. **Chrome** counter + progress bar. **Accent** the cobalt line +
diagonal panel. **Silence** the diagonal panel holds the right third. **Fixed** near-black h1, cobalt
accents, atmosphere here only. **Free** title, meta. **Density** low.

### 2 · Dashboard (data · move: 3-up metric grid · the dense frame)

**Ground** cream, `pad-x`. **Composes** slide-header (eyebrow + tag-pill), h2, 3× metric/tinted card.
**Focal** a row of tinted cards — cobalt `metric-value` + Inter label + muted desc + optional
green/red change chip. **Chrome** eyebrow left, tag-pill right; progress bar. **Accent** the cobalt
numerals. **Silence** tight — the density exception. **Fixed** 4% tint cards, 20% borders, no shadow,
cobalt numerals. **Free** figures (from script), labels. **Density** dense-exception.

### 3 · Bar Ranking (data · move: cobalt bars · left)

**Ground** cream, `pad-x`. **Composes** eyebrow, h2, bar-track rows. **Focal** 3–5 labeled
cobalt-fill bars on cobalt-8% tracks with cobalt percentages. **Chrome** eyebrow; progress bar.
**Accent** the cobalt fills + figures. **Silence** moderate. **Fixed** 6px tracks, cobalt fills.
**Free** rows, values (from script). **Density** standard.

### 4 · Pull Quote (quote · move: concentric rings · centered)

**Ground** cream, centered, with faint concentric closing-rings behind. **Composes** quote-mark,
blockquote, cite. **Focal** a Space Grotesk `blockquote` near-black under a 15%-opacity cobalt
quote-mark; an uppercase cobalt-muted cite beneath. **Accent** the faint rings + quote-mark. **Silence**
~55%. **Fixed** near-black quote, soft rings. **Free** quote, cite. **Density** low.

### 5 · Split + Highlight (content · move: asymmetric split · left)

**Ground** cream, two columns. **Composes** eyebrow, h2, body, split-highlight block. **Focal** an
Inter body column beside a cobalt-8% highlight block (4px cobalt left rule) carrying an inline pull
quote. **Accent** the highlight's left rule. **Silence** generous gutter. **Fixed** tinted highlight,
4px cobalt rule. **Free** body, callout. **Density** standard.

### 6 · Closing / CTA (closer · move: centered rings + CTA)

**Ground** cream + concentric closing-rings. **Composes** accent-line, h1, body, cta-button. **Focal**
a Space Grotesk `h1` near-black, centered, with the one solid cobalt `cta-button` pill below. **Accent**
the CTA + rings. **Silence** ~60%. **Fixed** one CTA, near-black h1, soft rings. **Free** sign-off, CTA
label. **Density** low.

## Composition Rules

### Do

- Start every frame on **warm cream**; let **cobalt carry every accent** (eyebrow, numeral, CTA, bar, progress).
- Set headlines **near-black, −0.02em**; eyebrows **cobalt uppercase 0.08em**; numerals **cobalt 600–700**.
- Use **tinted cards** (4% fill, 20% border, 10–14px radius, no shadow); body Inter 400 muted, line 1.6.
- Keep all chrome **pill-shaped (100px)**; one solid cobalt CTA per closing frame.
- Reserve **atmosphere** (diagonal panel, dots, rings) for cover/closing; content frames keep the slide-header rhythm.
- Lean left on cover/dashboard/split, centered on quote/closer.

### Don't

- No second accent color; no cobalt headlines.
- No drop shadows on content (only the soft cobalt CTA hover); no opaque cobalt borders.
- No square corners (save the progress bar); no font substitutes; no uppercase body.
- Don't use the green/red change chips as general accents — directional comparisons only.
- Don't fill space with heavier borders — add substance; don't blow a headline edge-to-edge.

## Aspect-Ratio Behavior

| Treatment         | 16:9                       | 9:16                           | 1:1                      |
| ----------------- | -------------------------- | ------------------------------ | ------------------------ |
| Cover             | title left, diagonal right | title top, diagonal band below | title upper, dots corner |
| Dashboard         | 3 cards across             | 3 stacked                      | 2×2                      |
| Bar Ranking       | 3–5 bars                   | 3–5 bars (tighter)             | 3 bars                   |
| Pull Quote        | centered, rings behind     | centered, taller               | centered                 |
| Split + Highlight | side-by-side               | stacked                        | stacked                  |
| Closing / CTA     | centered + CTA             | centered + CTA                 | centered + CTA           |

`pad-x` holds on the short edge; re-step display above the 1.4cqw floor. The diagonal cover panel
becomes a top/bottom band on 9:16. Numerals stay Latin Arabic digits in CJK builds.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Figures, metrics, and quotes are content; the system supplies cream + cobalt + grays.

## Numerals & Claims (hard rule)

Never invent figures, financials, percentages, or dates at frame scale. Render slots as `— figure —`,
`{metric}`, `+NN%`, `↑ —`. Metric cards, bar values, and stat cells especially carry placeholders
until the script supplies them. Directional chips require a real comparison from the script.

## Pre-Render Self-Audit

- **Squint** — one near-black headline or cobalt numeral dominates per frame.
- **Silence** — content frames balanced, not crowded; only the dashboard runs dense.
- **Single accent** — cobalt only; headlines near-black; positive/negative inline only.
- **Type** — Space Grotesk headings −0.02em near-black, cobalt eyebrows 0.08em + numerals; Inter body muted line 1.6; ≥1.4cqw floor.
- **Depth** — tinted cards (no shadow), soft rounded corners, 20% cobalt borders; no square content corners.
- **Anchor** — left on cover/dashboard/split, centered on quote/closer; atmosphere on cover/closing only.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 500ms translateX transitions + bar-fill animations are deck mechanics.
- **Space Grotesk + Inter via Google Fonts.** CJK pairing (Noto Sans SC 700 display / Noto Serif SC 400 body) carries over; the eyebrow's uppercase+tracking signal weakens in CJK — pair it with the accent-line.
- **9:16 / 1:1 are guidance**; verify the floor and that the diagonal panel reflows to a band.
- Diagonal panel (clip-path), dot grid, concentric rings, and bars are CSS-only; no external imagery is required.
assets/frame-presets/bold-poster/caption-skin.html›
<!--
  Bold Poster — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a bold-poster
  literal fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a fresh-paper card with bold poster framing — a heavy 1.5px brown-black ink
  rule plus a 4px tomato-red leftbar (the brand's cantilevered-card signature), SQUARE
  corners, NO drop shadow (the system's only shadow is the stacked text-shadow on red
  display). Serif body face, ink glyphs. Upcoming words sit in faint ink; the current
  word slams into a tomato-red panel (the brand's signature surface inversion, a hard
  square block on paper-white reading, no blur) with the stacked text-shadow on it;
  spoken words settle to solid ink, panel cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE paper card — heavy 1.5px brown-black ink rule + a 4px tomato-red leftbar
     (bold poster's cantilevered red-leftbar-card signature), square corners, NO
     drop shadow (the only shadow in the system is the stacked text-shadow on red) */
  .caption-pill {
    max-width: 78%;
    padding: 20px 44px 24px;
    background: var(--cap-canvas, #ffffff);
    border: 1.5px solid var(--cap-ink, #1c1410);
    border-left: 4px solid var(--cap-accent, #d8000f);
    border-radius: 0;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.1em 0.34em;
    font-family: var(--font-display, "Shrikhand"), Georgia, serif;
    font-weight: 400;
    font-size: clamp(40px, 4.4vw, 58px);
    line-height: 1.16;
    letter-spacing: -0.01em;
  }

  /* upcoming — faint brown-black ink, no panel */
  .caption-word {
    display: inline-block;
    padding: 0 0.06em;
    color: color-mix(in srgb, var(--cap-ink, #1c1410) 42%, var(--cap-canvas, #ffffff));
  }
  /* current word — THE tomato-red panel (box-shadow spread = a hard square block
     w/o layout shift; no blur), paper-white reading on top carrying the stacked
     text-shadow — bold poster's surface-inversion + only-shadow signatures */
  .caption-word.is-active {
    color: var(--cap-canvas, #ffffff);
    background: var(--cap-accent, #d8000f);
    box-shadow: 0 0 0 0.06em var(--cap-accent, #d8000f);
    text-shadow:
      0.03em 0.03em 0 rgba(28, 20, 16, 0.25),
      0.06em 0.06em 0 rgba(28, 20, 16, 0.2),
      0.09em 0.09em 0 rgba(28, 20, 16, 0.15);
  }
  /* already spoken — solid brown-black ink, panel cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #1c1410);
    background: transparent;
    box-shadow: none;
    text-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — Shrikhand display, bold poster's display face inflection */
  .caption-word.cap-brand {
    font-family: var(--font-display, "Shrikhand"), Georgia, serif;
  }
  /* numerals / data — the mono chrome voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Libre Baskerville"), ui-monospace, monospace;
    color: color-mix(in srgb, var(--cap-ink, #1c1410) 72%, var(--cap-canvas, #ffffff));
  }
  /* emphasis — a tomato-red em-dash-weight underline (the lone accent) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--cap-accent-2, #f5f2ef);
    text-underline-offset: 0.1em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.9 }, { scale: 1, duration: 0.16, ease: "back.out(2.2)" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/bold-poster/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Bold Poster</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Shrikhand&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Space+Grotesk:wght@400;600&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --bg: #ffffff;
        --dark: #1c1410;
        --red: #d8000f;
        --light: #f5f2ef;
        --disp: "Shrikhand", cursive;
        --body: "Libre Baskerville", serif;
        --mono: "Space Grotesk", sans-serif;
        --stack-shadow:
          2px 2px 0 rgba(28, 20, 16, 0.25), 4px 4px 0 rgba(28, 20, 16, 0.2),
          6px 6px 0 rgba(28, 20, 16, 0.15);
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #0c0a08;
      }
      body {
        background: var(--bg);
        color: var(--dark);
        font-family: var(--body);
        -webkit-font-smoothing: antialiased;
      }
      .disp {
        font-family: var(--disp);
        font-weight: 400;
        color: var(--dark);
      }
      .label {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--red);
      }

      section {
        padding: 100px 64px;
        position: relative;
      }
      .sec-head {
        display: flex;
        align-items: baseline;
        gap: 20px;
        margin-bottom: 52px;
        border-bottom: 3px solid var(--dark);
        padding-bottom: 18px;
      }
      .sec-head .k {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 3px;
        font-size: 12px;
        color: var(--red);
      }
      .sec-head h2 {
        font-family: var(--disp);
        font-weight: 400;
        font-size: 52px;
        line-height: 1;
        color: var(--dark);
      }
      .sec-head .sp {
        flex: 1;
      }
      .sec-head .n {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--dark);
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        padding: 7vh 64px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
      }
      .cover .meta {
        font-family: var(--body);
        font-size: 14px;
        color: var(--dark);
        margin-bottom: 24px;
      }
      .cover .meta .label {
        display: inline-block;
        margin-bottom: 8px;
      }
      .cover .stack h1 {
        font-family: var(--disp);
        font-weight: 400;
        line-height: 0.84;
      }
      .cover .l1 {
        font-size: clamp(60px, 12vw, 150px);
        color: var(--dark);
      }
      .cover .l2 {
        font-size: clamp(72px, 14vw, 180px);
        color: var(--red);
        transform: rotate(-4deg);
        transform-origin: left;
        display: inline-block;
        margin: 4px 0;
      }
      .cover .l3 {
        font-size: clamp(56px, 11vw, 140px);
        color: var(--dark);
        transform: rotate(2deg);
        transform-origin: left;
        display: inline-block;
      }
      .cover .tagline {
        font-family: var(--body);
        font-size: clamp(15px, 1.5vw, 19px);
        line-height: 1.6;
        color: var(--dark);
        max-width: 560px;
        margin-top: 36px;
      }
      .cover .pbar {
        position: absolute;
        bottom: 0;
        left: 0;
        height: 5px;
        width: 16%;
        background: var(--red);
      }
      .cover .counter {
        position: absolute;
        bottom: 20px;
        right: 32px;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--dark);
        opacity: 0.5;
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 0;
        border: 3px solid var(--dark);
      }
      .sw {
        border-right: 1.5px solid var(--dark);
      }
      .sw:last-child {
        border-right: 0;
      }
      .sw .chip {
        height: 150px;
        border-bottom: 1.5px solid var(--dark);
      }
      .sw .meta {
        padding: 18px 20px;
      }
      .sw .name {
        font-family: var(--disp);
        font-size: 24px;
        color: var(--dark);
      }
      .sw .hex {
        font-family: var(--mono);
        font-size: 11px;
        letter-spacing: 0.06em;
        color: var(--dark);
        opacity: 0.6;
        margin-top: 8px;
      }
      .sw .role {
        font-family: var(--body);
        font-size: 12px;
        line-height: 1.5;
        color: var(--dark);
        margin-top: 10px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 200px 1fr;
        align-items: center;
        padding: 26px 0;
        border-bottom: 1.5px solid var(--dark);
      }
      .type-row .tok {
        font-family: var(--mono);
        font-weight: 600;
        font-size: 11px;
        letter-spacing: 2px;
        text-transform: uppercase;
        color: var(--red);
      }
      .type-row .m {
        font-family: var(--body);
        font-size: 12px;
        color: var(--dark);
        opacity: 0.65;
        margin-top: 8px;
        line-height: 1.5;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--red);
        margin-bottom: 20px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 36px;
        margin-bottom: 48px;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .s8 {
        grid-column: span 8;
      }
      .fingrid {
        border: 3px solid var(--dark);
        display: grid;
        grid-template-columns: repeat(3, 1fr);
      }
      .fingrid .cell {
        border: 1.5px solid var(--dark);
        padding: 22px 20px;
      }
      .fingrid .cell .num {
        font-family: var(--disp);
        font-size: 44px;
        color: var(--red);
        line-height: 1;
      }
      .fingrid .cell .lab {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 10px;
        color: var(--dark);
        margin-top: 10px;
      }
      .fingrid .cell .bd {
        font-family: var(--body);
        font-size: 12px;
        line-height: 1.55;
        color: var(--dark);
        margin-top: 8px;
      }
      .leftcard {
        border-left: 4px solid var(--red);
        padding-left: 18px;
      }
      .leftcard .t {
        font-family: var(--disp);
        font-size: 30px;
        color: var(--dark);
        line-height: 1.1;
      }
      .leftcard .bd {
        font-family: var(--body);
        font-size: 14px;
        line-height: 1.6;
        color: var(--dark);
        margin-top: 10px;
      }
      .leftcard ul {
        list-style: none;
        margin-top: 14px;
      }
      .leftcard li {
        font-family: var(--mono);
        font-weight: 400;
        font-size: 12px;
        line-height: 1.5;
        color: var(--dark);
        padding-left: 16px;
        position: relative;
        margin-bottom: 6px;
      }
      .leftcard li::before {
        content: "—";
        position: absolute;
        left: 0;
        color: var(--red);
        font-weight: 700;
      }
      .redpanel {
        background: var(--red);
        color: var(--bg);
        padding: 48px;
      }
      .redpanel .q {
        font-family: var(--disp);
        font-size: 48px;
        line-height: 1.15;
        color: var(--bg);
        text-shadow: var(--stack-shadow);
      }
      .redpanel .cite {
        font-family: var(--body);
        font-size: 14px;
        color: var(--bg);
        margin-top: 24px;
        opacity: 0.85;
      }
      .tag {
        display: inline-block;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 3px;
        font-size: 11px;
        color: var(--red);
        border: 0;
        padding: 0;
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--dark);
        opacity: 0.55;
      }
      .fw .flabel b {
        color: var(--dark);
        opacity: 1;
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--bg);
        border: 1.5px solid var(--dark);
      }
      .frame.red {
        background: var(--red);
      }
      .frame.dark {
        background: var(--dark);
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 2;
      }
      .frame .pbar {
        position: absolute;
        bottom: 0;
        left: 0;
        height: 0.5cqw;
        background: var(--red);
        z-index: 5;
      }

      /* f1 hero stack */
      .pf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .pf1 .label {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.25cqw;
        font-size: 1cqw;
        color: var(--red);
        margin-bottom: 1.6cqw;
      }
      .pf1 h3 {
        font-family: var(--disp);
        font-weight: 400;
        line-height: 0.84;
      }
      .pf1 .a {
        font-size: 11cqw;
        color: var(--dark);
      }
      .pf1 .b {
        font-size: 13cqw;
        color: var(--red);
        transform: rotate(-4deg);
        transform-origin: left;
        display: inline-block;
        margin: 0.3cqw 0;
      }
      .pf1 .c {
        font-size: 10cqw;
        color: var(--dark);
        transform: rotate(2deg);
        transform-origin: left;
        display: inline-block;
      }

      /* f2 stat-big red panel */
      .pf2 {
        background: var(--red);
      }
      .pf2 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        color: var(--bg);
      }
      .pf2 .label {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.3cqw;
        font-size: 1.1cqw;
        color: var(--bg);
        opacity: 0.8;
        margin-bottom: 1cqw;
      }
      .pf2 .big {
        font-family: var(--disp);
        font-weight: 400;
        font-size: 30cqw;
        line-height: 0.82;
        color: var(--bg);
        transform: rotate(-6deg);
        text-shadow:
          0.15cqw 0.15cqw 0 rgba(28, 20, 16, 0.25),
          0.3cqw 0.3cqw 0 rgba(28, 20, 16, 0.2),
          0.45cqw 0.45cqw 0 rgba(28, 20, 16, 0.15);
      }
      .pf2 .sub {
        font-family: var(--body);
        font-size: 1.6cqw;
        color: var(--bg);
        margin-top: 2cqw;
        opacity: 0.9;
      }

      /* f3 financial grid */
      .pf3 .body {
        padding: 6cqw 5cqw;
        display: flex;
        flex-direction: column;
      }
      .pf3 .label {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.25cqw;
        font-size: 0.95cqw;
        color: var(--red);
        margin-bottom: 0.8cqw;
      }
      .pf3 h3 {
        font-family: var(--disp);
        font-size: 4.4cqw;
        color: var(--dark);
        margin-bottom: 2cqw;
      }
      .pf3 .grid {
        border: 0.3cqw solid var(--dark);
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        flex: 1;
      }
      .pf3 .cell {
        border: 0.15cqw solid var(--dark);
        padding: 1.6cqw;
      }
      .pf3 .cell .num {
        font-family: var(--disp);
        font-size: 4cqw;
        color: var(--red);
        line-height: 1;
      }
      .pf3 .cell .lab {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.15cqw;
        font-size: 0.8cqw;
        color: var(--dark);
        margin-top: 0.8cqw;
      }
      .pf3 .cell .bd {
        font-family: var(--body);
        font-size: 0.95cqw;
        line-height: 1.5;
        color: var(--dark);
        margin-top: 0.6cqw;
      }

      /* f4 quote red panel */
      .pf4 {
        background: var(--red);
      }
      .pf4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 7cqw;
        color: var(--bg);
      }
      .pf4 .q {
        font-family: var(--disp);
        font-weight: 400;
        font-size: 5.2cqw;
        line-height: 1.15;
        color: var(--bg);
        text-shadow:
          0.15cqw 0.15cqw 0 rgba(28, 20, 16, 0.25),
          0.3cqw 0.3cqw 0 rgba(28, 20, 16, 0.2),
          0.45cqw 0.45cqw 0 rgba(28, 20, 16, 0.15);
      }
      .pf4 .cite {
        font-family: var(--body);
        font-size: 1.3cqw;
        color: var(--bg);
        opacity: 0.85;
        margin-top: 2.4cqw;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0;
        border: 3px solid var(--dark);
      }
      .do-card {
        padding: 38px;
      }
      .do-card.do {
        border-right: 1.5px solid var(--dark);
      }
      .do-card h4 {
        font-family: var(--disp);
        font-size: 34px;
        color: var(--dark);
        margin-bottom: 22px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-family: var(--body);
        font-size: 14px;
        line-height: 1.55;
        padding-left: 24px;
        position: relative;
        margin-bottom: 13px;
        color: var(--dark);
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-weight: 700;
      }
      .do-card.do li::before {
        content: "—";
        color: var(--red);
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: var(--dark);
        opacity: 0.4;
      }

      .foot {
        padding: 48px 64px;
        border-top: 3px solid var(--dark);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--disp);
        font-size: 30px;
        color: var(--dark);
      }
      .foot .r {
        font-family: var(--mono);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--dark);
        opacity: 0.55;
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Shrikhand&display=swap" />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #ffffff !important;
        --secondary: #1c1410 !important;
        --tertiary: #f5f2ef !important;
        --accent: #d8000f !important;
        --f-disp: "Shrikhand", sans-serif !important;
        --f-body: "Libre Baskerville", sans-serif !important;
        --bg: #ffffff !important;
        --dark: #1c1410 !important;
        --red: #d8000f !important;
        --light: #f5f2ef !important;
        --disp: "Shrikhand", sans-serif !important;
        --body: "Libre Baskerville", sans-serif !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="meta">
        <span class="label">Frame System · Vol. 01</span><br />A populist editorial poster system —
        printed, loud, unmistakable.
      </div>
      <div class="stack">
        <h1 class="l1">ink,</h1>
        <br />
        <h1 class="l2">red &amp;</h1>
        <br />
        <h1 class="l3">paper.</h1>
      </div>
      <div class="tagline">
        Vintage Italian sports-magazine display, classical serif body, one saturated tomato red, and
        grids ruled in ink.
      </div>
      <div class="pbar"></div>
      <div class="counter">01 / 06</div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="k">01</span>
        <h2>Palette</h2>
        <span class="sp"></span><span class="n">Four Colors, No More</span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div
            class="chip"
            style="background: var(--bg); border-bottom: 1.5px solid var(--dark)"
          ></div>
          <div class="meta">
            <div class="name">Paper</div>
            <div class="hex">#FFFFFF</div>
            <div class="role">Fresh newsprint canvas.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--dark)"></div>
          <div class="meta">
            <div class="name">Ink</div>
            <div class="hex">#1C1410</div>
            <div class="role">Warm brown-black — text + rules.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--red)"></div>
          <div class="meta">
            <div class="name">Red</div>
            <div class="hex">#D8000F</div>
            <div class="role">The only accent — numerals, rules, panels.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--light)"></div>
          <div class="meta">
            <div class="name">Off-White</div>
            <div class="hex">#F5F2EF</div>
            <div class="role">Alternating striped panels.</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section style="background: var(--light)">
      <div class="sec-head">
        <span class="k">02</span>
        <h2>Typography</h2>
        <span class="sp"></span><span class="n">Shrikhand · Baskerville · Grotesk</span>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">stat-big</div>
          <div class="m">Shrikhand 400 · rotated −6° · always red</div>
        </div>
        <div class="spec">
          <span
            class="disp"
            style="
              font-size: 110px;
              color: var(--red);
              display: inline-block;
              transform: rotate(-6deg);
            "
            >94</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">section-header</div>
          <div class="m">Shrikhand 400 · ink</div>
        </div>
        <div class="spec"><span class="disp" style="font-size: 60px">The Printed Page</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Libre Baskerville 400 · line 1.75 · ink</div>
        </div>
        <div class="spec">
          <p style="font-family: var(--body); font-size: 16px; line-height: 1.75; max-width: 640px">
            Libre Baskerville carries every paragraph — the literary serif is what makes the system
            feel printed, not digital.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">label</div>
          <div class="m">Space Grotesk 600 · uppercase · 2–3px</div>
        </div>
        <div class="spec">
          <span class="label" style="font-size: 13px; letter-spacing: 3px"
            >Section Eyebrow / Metadata</span
          >
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="k">03</span>
        <h2>Components</h2>
        <span class="sp"></span><span class="n">Heavy Rules · One Shadow</span>
      </div>

      <div class="cgrid">
        <div class="s7">
          <div class="cnote">Financial grid — 3px outer + 1.5px inner double border</div>
          <div class="fingrid">
            <div class="cell">
              <div class="num">$24M</div>
              <div class="lab">Revenue</div>
              <div class="bd">Up across every region this year.</div>
            </div>
            <div class="cell">
              <div class="num">+18%</div>
              <div class="lab">Growth</div>
              <div class="bd">Year over year, compounding.</div>
            </div>
            <div class="cell">
              <div class="num">3.4×</div>
              <div class="lab">Multiple</div>
              <div class="bd">On invested capital to date.</div>
            </div>
          </div>
        </div>
        <div class="s5">
          <div class="cnote">Red leftbar card</div>
          <div class="leftcard">
            <div class="t">Editorial Block</div>
            <div class="bd">
              Cantilevered off a 4px red rule — the card reads without an enclosing outline.
            </div>
            <ul>
              <li>red em-dash bullets</li>
              <li>capped at three</li>
              <li>serif body, mono markers</li>
            </ul>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s7">
          <div class="cnote">Red panel — the one stacked text-shadow</div>
          <div class="redpanel">
            <div class="q">printed and pressed.</div>
            <div class="cite">— White Shrikhand on red, stamped three times off-register.</div>
          </div>
        </div>
        <div class="s5">
          <div class="cnote">Tilted statement + tags</div>
          <div style="display: flex; flex-direction: column; gap: 24px">
            <span
              class="disp"
              style="
                font-size: 80px;
                color: var(--red);
                transform: rotate(-5deg);
                transform-origin: left;
                display: inline-block;
              "
              >loud.</span
            >
            <div style="display: flex; gap: 18px">
              <span class="tag">Sports Annual</span><span class="tag">Wine Catalogue</span>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section style="background: var(--light)">
      <div class="sec-head">
        <span class="k">04</span>
        <h2>Frame Compositions</h2>
        <span class="sp"></span><span class="n">True 16:9 · cqw</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Hero Stack</b><span>· identity · 3-line tilted · left</span></div>
          <div class="frame pf1">
            <div class="body">
              <div class="label">Frame System · Vol. 01</div>
              <h3>
                <span class="a">ink,</span><br /><span class="b">red &amp;</span><br /><span
                  class="c"
                  >paper.</span
                >
              </h3>
            </div>
            <div class="pbar" style="width: 16%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Hero Stat</b><span>· statement · red panel · centered</span></div>
          <div class="frame red pf2">
            <div class="body">
              <div class="label">Readership</div>
              <div class="big">94</div>
              <div class="sub">percent recall, unaided.</div>
            </div>
            <div class="pbar" style="width: 48%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Financial Grid</b><span>· data · double border · the dense frame</span>
          </div>
          <div class="frame pf3">
            <div class="body">
              <div class="label">By The Numbers</div>
              <h3>The Annual</h3>
              <div class="grid">
                <div class="cell">
                  <div class="num">$24M</div>
                  <div class="lab">Revenue</div>
                  <div class="bd">Up across every region.</div>
                </div>
                <div class="cell">
                  <div class="num">+18%</div>
                  <div class="lab">Growth</div>
                  <div class="bd">Year over year.</div>
                </div>
                <div class="cell">
                  <div class="num">3.4×</div>
                  <div class="lab">Multiple</div>
                  <div class="bd">On capital to date.</div>
                </div>
              </div>
            </div>
            <div class="pbar" style="width: 64%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Pull Quote</b><span>· quote · red panel · stacked shadow</span>
          </div>
          <div class="frame red pf4">
            <div class="body">
              <div class="q">type that<br />shouts.</div>
              <div class="cite">— The system voice, printed and pressed.</div>
            </div>
            <div class="pbar" style="width: 82%"></div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="k">05</span>
        <h2>Frame Rules</h2>
        <span class="sp"></span><span class="n">One Red · Square Corners</span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <ul>
            <li>Stack hero titles in 3 Shrikhand lines — at least one tilted, one red.</li>
            <li>Make every numeral red Shrikhand; tilt statement display −5° to −6°.</li>
            <li>Set eyebrows in Space Grotesk 600 uppercase, 2–3px tracked, red.</li>
            <li>Build data grids with 3px outer + 1.5px inner double borders.</li>
            <li>Use red em-dash bullets, capped at three; serif body at line 1.75.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <ul>
            <li>No second accent color; no rounded corners (square only).</li>
            <li>No drop shadows — the stacked text-shadow on red is the only one.</li>
            <li>No font substitutes; no Shrikhand body, no Baskerville labels.</li>
            <li>No default disc bullets; no untilted red statement display.</li>
            <li>Don't crowd a statement frame — reserve the negative space.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--dark);
        --cap-canvas: var(--bg);
        --cap-accent: var(--red);
        --cap-accent-2: var(--light);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        border: 1.5px solid var(--cap-ink);
        border-left: 0.35cqw solid var(--cap-accent);
        border-radius: 0;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.1em 0.34em;
        font-family: var(--body);
        font-weight: 400;
        font-size: 3.2cqw;
        line-height: 1.16;
        letter-spacing: -0.01em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.06em;
        color: color-mix(in srgb, var(--cap-ink) 42%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-canvas);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.06em var(--cap-accent);
        text-shadow:
          0.03em 0.03em 0 rgba(28, 20, 16, 0.25),
          0.06em 0.06em 0 rgba(28, 20, 16, 0.2),
          0.09em 0.09em 0 rgba(28, 20, 16, 0.15);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 1px dashed rgba(28, 20, 16, 0.3);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        font-size: 0.95cqw;
        letter-spacing: 0.04em;
        color: var(--dark);
        opacity: 0.5;
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="k">06</span>
        <h2>Captions</h2>
        <span class="sp"></span><span class="n">Lower-third · Karaoke</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
            <div class="pbar" style="width: 88%"></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · red panel cleared</span></div>
          <div class="frame capdemo">
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
            <div class="pbar" style="width: 100%"></div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Bold Poster</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#FFFFFF" />
        <text x="12" y="42" font-family="Georgia" font-weight="700" font-size="26" fill="#1C1410">
          ink
        </text>
        <text
          x="14"
          y="70"
          font-family="Georgia"
          font-weight="700"
          font-size="30"
          fill="#D8000F"
          transform="rotate(-5 14 70)"
        >
          red.
        </text>
        <rect x="0" y="96" width="58" height="4" fill="#D8000F" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/bold-poster/FRAME.md›
---
version: alpha
name: Bold Poster — Frame (video / frame layer)
description: >
  Video-first companion to Bold Poster's design.md. The unit is the frame (1920×1080). Atoms are
  identical and sacred — the four-color palette (white / brown-black ink / tomato red / off-white),
  the three-face stack (Shrikhand display tilted at poster scale, Libre Baskerville serif body,
  Space Grotesk mono chrome), the stacked text-shadow on red display, the 3px+1.5px double-border
  grid, red leftbar cards, red em-dash bullets, and the red progress bar. Composition + frame scale
  rewritten. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  bg: "#FFFFFF"
  dark: "#1C1410"
  red: "#D8000F"
  light: "#F5F2EF"

typography:
  # — reading + chrome ramp —
  body:    { fontFamily: "Libre Baskerville", cqw: 0.85, weight: 400, lineHeight: 1.75, color: "dark" }
  body-cell:{ fontFamily: "Libre Baskerville", cqw: 0.7, weight: 400, lineHeight: 1.55 }
  label:   { fontFamily: "Space Grotesk", px: 10, weight: 600, tracking: "2px", upper: true, color: "red" }
  bullet-body:{ fontFamily: "Space Grotesk", cqw: 0.62, weight: 400, lineHeight: 1.45 }
  # — display / hero ramp (Shrikhand 400, tilted) —
  card-title:{ fontFamily: "Shrikhand", cqw: 1.9, weight: 400, lineHeight: 1.1, color: "dark" }
  cell-number:{ fontFamily: "Shrikhand", cqw: 2.7, weight: 400, lineHeight: 1.0, color: "red" }
  section-header:{ fontFamily: "Shrikhand", cqw: 3.3, weight: 400, lineHeight: 1.0, color: "dark" }
  red-quote:{ fontFamily: "Shrikhand", cqw: 4.7, weight: 400, lineHeight: 1.15, color: "bg", shadow: "stacked" }
  hero-title-bottom:{ fontFamily: "Shrikhand", cqw: 10.4, weight: 400, lineHeight: 0.9, color: "dark", rotate: "2deg" }
  hero-title:{ fontFamily: "Shrikhand", cqw: 11.5, weight: 400, lineHeight: 0.88, color: "dark" }
  hero-title-red:{ fontFamily: "Shrikhand", cqw: 13.5, weight: 400, lineHeight: 0.85, color: "red", rotate: "-4deg" }
  close-big:{ fontFamily: "Shrikhand", cqw: 13.5, weight: 400, lineHeight: 0.88, color: "red", rotate: "-5deg" }
  stat-big:{ fontFamily: "Shrikhand", cqw: 22.0, weight: 400, lineHeight: 0.82, color: "red", rotate: "-6deg" }

spacing:
  pad-slide: "3cqw 3.6cqw"
  gap-grid: "1.5cqw 2cqw"

components:
  progress-bar:
    backgroundColor: "{colors.red}"
    size: "0.5cqw tall, bottom edge, width grows with index"
    description: "The most prominent chrome."
  hero-title-stack:
    typography: "{typography.hero-title} + {typography.hero-title-red} + {typography.hero-title-bottom}"
    transform: "≥4 tilt (−4°/+2°), ≥1 line in {colors.red}"
    description: "A 3-line Shrikhand composition — the signature opener."
  stat-big:
    typography: "{typography.stat-big}"
    color: "{colors.red} (or {colors.bg} on red with stacked shadow)"
    transform: "rotate(-6deg)"
    description: "Hero numeral at poster scale."
  fin-grid:
    border: "0.3cqw solid {colors.dark} outer"
    rule: "0.15cqw solid {colors.dark} inner (touching at intersections)"
    rounded: "0"
    typography: "{typography.cell-number} ({colors.red}) + {typography.label} + {typography.body-cell}"
    description: "The double-border tabular signature."
  red-leftbar-card:
    borderLeft: "4px solid {colors.red}"
    padding: "0 0 0 ~1cqw"
    rounded: "0"
    shadow: "none"
    typography: "{typography.card-title} + {typography.body} + red em-dash bullets"
    description: "Editorial card cantilevered off a red left rule — no outline."
  red-panel:
    backgroundColor: "{colors.red}"
    textColor: "{colors.bg}"
    description: "Full-bleed statement surface; display carries the stacked text-shadow."
  dark-panel:
    backgroundColor: "{colors.dark}"
    textColor: "{colors.bg}"
    description: "Full-bleed dark statement surface; red accents."
  stacked-text-shadow:
    shadow: "2px 2px 0 rgba(28,20,16,.25), 4px 4px 0 rgba(28,20,16,.2), 6px 6px 0 rgba(28,20,16,.15)"
    appliesTo: "red display text on red panels only"
    description: "The ONLY shadow in the system — text-shadow, not box-shadow."
  bullet:
    marker: "red em-dash (—) or round (•) glyph at absolute-left"
    color: "{colors.red}"
    description: "No default disc bullets; capped at three."
---

# Bold Poster — Frame (video / frame layer)

## Overview

Bold Poster at frame scale is a **populist editorial poster** — vintage Italian sports-magazine
display, classical serif body, one saturated tomato red, grids ruled in ink. Every frame should
feel _printed_: heavy display type at poster scale, locked to one red accent, on a white/off-white
sheet (or a full red/dark statement panel), with decoration kept to a strict minimum.

The voice is a three-face stack: **Shrikhand** (heavy slab-script, weight 400 only, routinely
tilted −6°..+2°) carries every hero title, section header, stat, and card title; **Libre
Baskerville** (literary serif) carries every body paragraph — it's what makes the system feel
printed; **Space Grotesk** (uppercase, 2–3px tracked) is chrome only — labels, eyebrows, counters,
bullet bodies. The plane is flat; the _only_ shadow is the stacked text-shadow on red display.

**Key characteristics at frame scale:**

- **Four colors only** — white / brown-black ink / tomato red / off-white. Red is the lone accent.
- **Shrikhand display, tilted** (−6° stat, −5° close, −4° hero-red, +2° hero-bottom) — the signature movement.
- **Libre Baskerville serif body** (line 1.75); **Space Grotesk** chrome (uppercase tracked).
- **Double-border grids** (3px outer + 1.5px inner ink); **red leftbar cards**; **red em-dash bullets** (max 3).
- **Stacked text-shadow** on red display — the only shadow; flat plane otherwise; square corners.
- **Red progress bar** at the bottom edge of every frame.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — **one display moment dominates** at 3–6× everything else (the hero stack, `stat-big`, or `close-big`); nothing competes.
- **Silence** — statement frames reserve **~50–60% negative space**; the **financial grid is the one dense exception**.
- **Restraint** — **four colors only**, red the lone accent; one display moment per frame; the stacked text-shadow on red is the only shadow; bullets capped at three.
- **Reference** — aim at a **vintage Italian sports-magazine cover / mid-century European annual report**; failure looks like a **rounded, soft-shadowed multi-accent slide**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-slide` ~3cqw — deliberately tight so the poster type crowds the frame.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. Borders stay px; rotation transforms hold.

## Colors

Tokens identical to the source. `{colors.bg}` white is the default ground; `{colors.dark}` is body,
borders, headers; `{colors.red}` is the only accent — every numeral, section rule, eyebrow,
leftbar, bullet, progress bar, and the full statement-panel ground. `{colors.light}` off-white
stripes alternating panels. **No fifth color** (no green/blue/yellow); categorical difference comes
from position, label, and tilt. Red is never body text, never a tint, never an untexted fill.

## Typography

Two ramps. The **reading/chrome ramp** (Baskerville body 0.85cqw, Space Grotesk labels in px)
carries copy + chrome; the **display ramp** (Shrikhand `section-header` 3.3cqw → `stat-big` 22cqw)
carries every statement and numeral.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤2 words → `stat-big`/`hero-title-red`; 3–4 → `hero-title`; 5+ → `section-header`. The hero is a stacked 3-line composition.
- **Shrikhand is weight 400, tilted on statement/hero elements, red on numerals**; **Baskerville body at line ≥1.5**; **Space Grotesk chrome uppercase, 2–3px**. Inline `<strong>` switches face to Space Grotesk 600. No italic display, no untilted red hero.

## Depth & Surface

Flat plane. Depth from:

- **Heavy borders** — 3px+1.5px double-border grids, 2px global-card outline, 4px red leftbar rules.
- **Surface inversion** — full-bleed red or dark statement panels.
- **Tilt** — rotated Shrikhand breaks the baseline for perceived dimension.
- **The single shadow** — stacked text-shadow on red display only (text-shadow, three steps).

**Ceiling:** no box-shadow, no rounded surface (square corners; only the hint pill is 4px), no gradient.

## Shapes

- **0 radius everywhere** except the hint pill (4px). Cards, cells, panels, callouts — sharp rectangles.

## Components

- **hero-title-stack** — the 3-line tilted opener. **stat-big** — the poster numeral.
- **fin-grid** — the double-border tabular signature. **red-leftbar-card** — the cantilevered editorial card.
- **red-panel / dark-panel** — statement surfaces. **stacked-text-shadow** — the one shadow.
- **bullet** — red em-dash (max 3). **progress-bar** — the red bottom strip.

## Frame Treatments

> Recipe: ground · register · composes · focal · chrome · accent · silence · Fixed/Free · density.
> One display moment per frame; statement frames reserve massive negative space.

### 1 · Hero Stack (identity · move: 3-line tilted stack · left)

**Ground** white. **Composes** hero-title-stack, label, body tagline, progress-bar. **Focal** a
3-line Shrikhand stack (e.g. ink / red-tilted / paper) — one red line, one+ tilted. **Chrome** mono
eyebrow + Baskerville tagline; bottom progress bar + counter. **Accent** the red line. **Silence**
right half open. **Fixed** Shrikhand 400, ≥1 tilt + ≥1 red, square. **Free** the words, line sizes.
**Density** low.

### 2 · Hero Stat (statement · move: poster numeral · red panel · centered)

**Ground** full `{colors.red}`. **Composes** stat-big, label, body sub. **Focal** a `stat-big`
numeral rotated −6° in white with the stacked shadow, centered, with a mono label above + Baskerville
sub below. **Accent** the ink stacked-shadow on white. **Silence** ~60%. **Fixed** white-on-red +
stacked shadow, −6° tilt. **Free** the figure (from script), label. **Density** low.

### 3 · Financial Grid (data · move: double-border matrix · the dense frame)

**Ground** white, `pad-slide`. **Composes** label, section-header, fin-grid. **Focal** a 3px-outer/
1.5px-inner ink grid of cells (red Shrikhand numeral + mono label + Baskerville body). **Chrome** mono
eyebrow; progress bar. **Accent** the red numerals. **Silence** tight — the density exception.
**Fixed** double-border, red numerals, serif body. **Free** figures (from script), labels. **Density** dense-exception.

### 4 · Pull Quote (quote · move: tilted/stacked display · red panel · left)

**Ground** full `{colors.red}`. **Composes** red-quote, Baskerville cite. **Focal** a 2-line Shrikhand
quote in white with the stacked shadow; a Baskerville cite beneath. **Accent** the stacked shadow.
**Silence** ~50%. **Fixed** white-on-red + stacked shadow. **Free** quote, cite. **Density** low.

### 5 · Editorial Cards (content · move: red leftbar cards · left)

**Ground** white (or alternating off-white stripes). **Composes** label, section-header, 2–3×
red-leftbar-card. **Focal** cards cantilevered off 4px red rules — Shrikhand title + Baskerville body

- red em-dash bullets (max 3). **Accent** the red left rules + bullets. **Silence** moderate. **Fixed**
  4px red leftbar, em-dash bullets, no outline. **Free** card content. **Density** standard.

### 6 · Closing Statement (closer · move: tilted close-big · centered/left)

**Ground** white or `{colors.dark}`. **Composes** close-big, label, body sub, progress-bar. **Focal**
a Shrikhand `close-big` (rotated −5°, red) sign-off; mono eyebrow + Baskerville contact line. **Accent**
the red tilted title. **Silence** ~60%. **Fixed** −5° tilt, red close-big. **Free** sign-off, contact.
**Density** low.

## Composition Rules

### Do

- Stack hero titles in 3 Shrikhand lines — at least one tilted, at least one red.
- Make every numeral **red Shrikhand**; tilt statement display **−5° to −6°**.
- Set eyebrows in **Space Grotesk 600 uppercase, 2–3px**, red; body in **Baskerville, line 1.75**.
- Build data grids with the **3px outer + 1.5px inner double border**; use red leftbar cards elsewhere.
- Use **red em-dash bullets, capped at three**; apply the stacked text-shadow on red display.
- One display moment per frame; reserve negative space on statement frames.

### Don't

- No second accent color; no rounded corners (square only, save the hint pill).
- No drop shadow — the stacked text-shadow on red is the only one.
- No font substitutes; no Shrikhand body, no Baskerville labels, no Space Grotesk headlines.
- No default disc bullets; no untilted red statement display.
- Don't crowd a statement frame; don't blow a long line edge-to-edge (step down).

## Aspect-Ratio Behavior

| Treatment       | 16:9                      | 9:16                     | 1:1                  |
| --------------- | ------------------------- | ------------------------ | -------------------- |
| Hero Stack      | stack left, tagline below | stack centered, taller   | stack, tagline below |
| Hero Stat       | numeral centered          | numeral centered, taller | centered             |
| Financial Grid  | 3 cells across            | 2 across / stacked       | 2×2                  |
| Pull Quote      | quote left                | quote stacked            | centered             |
| Editorial Cards | 2–3 across                | stacked                  | stacked              |
| Closing         | close-big centered        | close-big stacked        | centered             |

`pad-slide` stays tight on the short edge; re-step display so the line stays ≤78cqw above the floor.
Tilts hold; the progress bar spans the bottom on every ratio.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. The system supplies type, one red, and ink rules, not brands.

## Numerals & Claims (hard rule)

Never invent figures, financials, percentages, or dates at frame scale. Render slots as `— figure —`,
`{metric}`, `+NN%`. Fin-grid cells and stat-big especially carry placeholders until the script
supplies them. Catalogue numbers / progress are decorative.

## Pre-Render Self-Audit

- **Squint** — one display moment dominates; nothing competes.
- **Silence** — statement frames reserve ~50–60% negative space; only the fin-grid runs dense.
- **Color** — four colors only; red is the lone accent; no red body text.
- **Type** — Shrikhand 400 tilted on statements, red numerals, fit-to-measure; Baskerville body line 1.75; mono chrome uppercase 2–3px; ≥1.4cqw floor.
- **Depth** — flat; the stacked text-shadow on red display is the only shadow; square corners.
- **Bullets** — red em-dash, capped at three.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 550ms slide transitions are deck mechanics.
- **Shrikhand + Libre Baskerville + Space Grotesk via Google Fonts.** CJK: Noto Serif SC 900/400 + Noto Sans SC; Shrikhand's slab-script has no Hanzi equal — keep tilts + red + double-borders to carry the identity.
- **9:16 / 1:1 are guidance**; verify the one big line ≤78cqw per ratio and that tilts don't clip.
- Grids, leftbar cards, and the stacked shadow are CSS-only; no external imagery is required.
assets/frame-presets/broadside/caption-skin.html›
<!--
  Broadside — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a broadside literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a protest-poster lower third — an ink-black plate on a single 1px fire-orange
  hairline, SHARP corners, NO shadow (broadside's plane is flat, never cast). Massive
  lowercase Barlow 900 as graphic primitive, cream glyphs on ink. Upcoming words sit in
  faint cream; the current word flips register — a hard flat fire-orange block with ink
  reading on top (broadside's signature two-register flip, no blur), the brand's lone
  accent; spoken words settle to solid cream, the orange block cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE ink-black plate — single 1px fire-orange hairline (broadside's lone accent),
     SHARP corners (0 radius), NO shadow (the plane is flat) */
  .caption-pill {
    max-width: 80%;
    padding: 18px 44px 22px;
    background: var(--cap-ink, #111111);
    border: 1px solid var(--cap-accent, #e85d26);
    border-radius: 0;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.04em 0.3em;
    font-family: var(--font-display, "Barlow"), system-ui, sans-serif;
    font-weight: 900;
    font-size: clamp(38px, 4.2vw, 56px);
    line-height: 1.02;
    letter-spacing: -0.03em;
    text-transform: lowercase;
  }

  /* upcoming — faint cream, no block (massive lowercase Barlow 900 as graphic primitive) */
  .caption-word {
    display: inline-block;
    padding: 0 0.08em;
    color: color-mix(in srgb, var(--cap-canvas, #f0ece5) 42%, var(--cap-ink, #111111));
  }
  /* current word — THE register flip: a hard flat fire-orange block (box-shadow spread =
     a sharp rectangle w/o layout shift; no blur), ink reading on top — the lone accent */
  .caption-word.is-active {
    color: var(--cap-ink, #111111);
    background: var(--cap-accent, #e85d26);
    box-shadow: 0 0 0 0.06em var(--cap-accent, #e85d26);
  }
  /* already spoken — solid cream, the orange block cleared */
  .caption-word.is-spoken {
    color: var(--cap-canvas, #f0ece5);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — fire-orange ink, the cream-on-ink register's only inflection */
  .caption-word.cap-brand {
    color: var(--cap-accent, #e85d26);
  }
  /* numerals / data — the IBM Plex Mono chrome voice, uppercase + tracked, recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "IBM Plex Mono"), ui-monospace, monospace;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: color-mix(in srgb, var(--cap-canvas, #f0ece5) 72%, var(--cap-ink, #111111));
  }
  /* emphasis — a fire-orange hairline underline (no second hue; the lone accent again) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--cap-accent-2, #e85d26);
    text-underline-offset: 0.1em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(32px, 5.2vw, 46px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.92 }, { scale: 1, duration: 0.1, ease: "power3.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/broadside/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Broadside</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;600;700;800;900&family=IBM+Plex+Mono:wght@400;500&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --ink: #111111;
        --ink-alt: #1a1a18;
        --orange: #e85d26;
        --cream: #f0ece5;
        --cream-muted: #888880;
        --cream-hint: #505048;
        --border-dark: #282826;
        --on-orange-muted: rgba(17, 17, 17, 0.75);
        --on-orange-hint: rgba(17, 17, 17, 0.55);
        --on-orange-faint: rgba(17, 17, 17, 0.4);
        --on-orange-border: rgba(17, 17, 17, 0.2);
        --sans: "Barlow", sans-serif;
        --mono: "IBM Plex Mono", monospace;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #000;
      }
      body {
        background: var(--ink);
        color: var(--cream);
        font-family: var(--sans);
        -webkit-font-smoothing: antialiased;
      }
      .mono {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
      }
      .disp {
        font-family: var(--sans);
        font-weight: 900;
        letter-spacing: -0.04em;
        line-height: 0.9;
      }

      section {
        padding: 110px 88px;
        position: relative;
      }
      .sec-head {
        display: flex;
        align-items: center;
        gap: 18px;
        border-bottom: 1px solid var(--border-dark);
        padding-bottom: 18px;
        margin-bottom: 56px;
      }
      .sec-head .k {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 13px;
        color: var(--orange);
      }
      .sec-head h2 {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 52px;
        line-height: 1;
        letter-spacing: -0.03em;
        text-transform: lowercase;
      }
      .sec-head .sp {
        flex: 1;
      }
      .sec-head .num {
        font-family: var(--mono);
        font-size: 13px;
        color: var(--cream-hint);
        letter-spacing: 0.1em;
      }

      /* COVER (orange register) */
      .cover {
        min-height: 100vh;
        background: var(--orange);
        color: var(--ink);
        padding: 80px 88px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
      }
      .cover .topnum {
        position: absolute;
        top: 64px;
        left: 88px;
        font-family: var(--mono);
        font-size: 14px;
        letter-spacing: 0.1em;
        color: rgba(17, 17, 17, 0.45);
      }
      .cover .topmeta {
        position: absolute;
        top: 64px;
        right: 88px;
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 13px;
        color: var(--on-orange-hint);
      }
      .cover .rule {
        width: 48px;
        height: 3px;
        background: var(--ink);
        margin-bottom: 28px;
      }
      .cover .kick {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 14px;
        color: var(--on-orange-hint);
        margin-bottom: 20px;
      }
      .cover h1 {
        font-family: var(--sans);
        font-weight: 900;
        font-size: clamp(80px, 13vw, 210px);
        line-height: 0.86;
        letter-spacing: -0.04em;
        text-transform: lowercase;
      }
      .cover .lead {
        font-family: var(--sans);
        font-weight: 400;
        font-size: clamp(18px, 1.6vw, 26px);
        line-height: 1.45;
        color: var(--on-orange-muted);
        margin-top: 32px;
        max-width: 60%;
      }
      .cover .foot {
        position: absolute;
        bottom: 64px;
        left: 88px;
        right: 88px;
        display: flex;
        justify-content: space-between;
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 13px;
        color: var(--on-orange-hint);
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 0;
        border: 1px solid var(--border-dark);
      }
      .sw {
        border-right: 1px solid var(--border-dark);
        border-bottom: 1px solid var(--border-dark);
        padding: 0;
      }
      .sw:nth-child(4n) {
        border-right: 0;
      }
      .sw:nth-child(n + 5) {
        border-bottom: 0;
      }
      .sw .chip {
        height: 120px;
        border-bottom: 1px solid var(--border-dark);
      }
      .sw .meta {
        padding: 16px 20px;
      }
      .sw .name {
        font-family: var(--sans);
        font-weight: 700;
        font-size: 19px;
        text-transform: lowercase;
      }
      .sw .hex {
        font-family: var(--mono);
        font-size: 11px;
        letter-spacing: 0.06em;
        color: var(--cream-hint);
        margin-top: 6px;
      }
      .sw .role {
        font-size: 12px;
        line-height: 1.5;
        color: var(--cream-muted);
        margin-top: 10px;
        font-family: var(--sans);
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 200px 1fr;
        align-items: baseline;
        padding: 24px 0;
        border-bottom: 1px solid var(--border-dark);
      }
      .type-row .tok {
        font-family: var(--mono);
        font-size: 12px;
        letter-spacing: 0.08em;
        color: var(--orange);
        text-transform: uppercase;
      }
      .type-row .m {
        font-family: var(--mono);
        font-size: 11px;
        color: var(--cream-hint);
        margin-top: 8px;
        line-height: 1.6;
        letter-spacing: 0.04em;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 12px;
        color: var(--orange);
        margin-bottom: 20px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 40px;
        margin-bottom: 52px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .statcard {
        border-top: 1px solid var(--border-dark);
        padding: 20px 20px 20px 0;
      }
      .statcard .v {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 64px;
        line-height: 1;
        letter-spacing: -0.04em;
        color: var(--orange);
      }
      .statcard .l {
        font-family: var(--sans);
        font-size: 16px;
        color: var(--cream);
        margin-top: 8px;
      }
      .statcard .n {
        font-family: var(--mono);
        font-size: 11px;
        letter-spacing: 0.1em;
        color: var(--cream-hint);
        margin-top: 6px;
        text-transform: uppercase;
      }
      .blist {
        list-style: none;
      }
      .blist li {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 22px;
        color: var(--cream);
        padding: 10px 0 10px 36px;
        position: relative;
      }
      .blist li::before {
        content: "/";
        position: absolute;
        left: 0;
        font-family: var(--mono);
        font-weight: 700;
        color: var(--orange);
      }
      .tag {
        display: inline-block;
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 12px;
        color: var(--orange);
        border: 1px solid var(--orange);
        padding: 0.3em 0.8em;
      }
      .barchart {
        display: flex;
        align-items: flex-end;
        gap: 18px;
        height: 200px;
        border-left: 1px solid var(--border-dark);
        padding-left: 18px;
      }
      .barchart .bar {
        flex: 1;
        background: var(--cream-hint);
      }
      .barchart .bar.accent {
        background: var(--orange);
      }
      .barlabels {
        display: flex;
        gap: 18px;
        padding-left: 18px;
        margin-top: 10px;
      }
      .barlabels span {
        flex: 1;
        text-align: center;
        font-family: var(--mono);
        font-size: 11px;
        letter-spacing: 0.08em;
        color: var(--cream-hint);
      }
      .compare {
        display: grid;
        grid-template-columns: 1fr 1fr;
        border: 1px solid var(--border-dark);
      }
      .compare .l {
        padding: 32px;
        border-right: 1px solid var(--border-dark);
      }
      .compare .r {
        padding: 32px;
        background: var(--orange);
        color: var(--ink);
      }
      .compare .ch {
        font-family: var(--sans);
        font-weight: 700;
        font-size: 24px;
        text-transform: lowercase;
      }
      .compare .cl {
        font-family: var(--mono);
        font-size: 11px;
        letter-spacing: 0.1em;
        text-transform: uppercase;
        margin-bottom: 14px;
      }
      .compare .l .cl {
        color: var(--orange);
      }
      .compare .r .cl {
        color: var(--on-orange-hint);
      }
      .compare .cx {
        font-size: 14px;
        line-height: 1.5;
        margin-top: 10px;
      }
      .compare .l .cx {
        color: var(--cream-muted);
      }
      .compare .r .cx {
        color: var(--on-orange-muted);
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 12px;
        color: var(--cream-hint);
      }
      .fw .flabel b {
        color: var(--cream);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--ink);
        border: 1px solid var(--border-dark);
      }
      .frame.orange {
        background: var(--orange);
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 2;
      }

      /* f1 cover orange */
      .bf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5.5cqw;
        color: var(--ink);
      }
      .bf1 .topnum {
        position: absolute;
        top: 4cqw;
        left: 5.5cqw;
        font-family: var(--mono);
        font-size: 1cqw;
        letter-spacing: 0.1em;
        color: rgba(17, 17, 17, 0.45);
        z-index: 3;
      }
      .bf1 .rule {
        width: 3.4cqw;
        height: 0.22cqw;
        background: var(--ink);
        margin-bottom: 1.6cqw;
      }
      .bf1 .kick {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 1cqw;
        color: var(--on-orange-hint);
        margin-bottom: 1.4cqw;
      }
      .bf1 h3 {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 13cqw;
        line-height: 0.84;
        letter-spacing: -0.04em;
        text-transform: lowercase;
      }

      /* f2 statement dark */
      .bf2 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5.5cqw;
      }
      .bf2 .kick {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 1cqw;
        color: var(--orange);
        margin-bottom: 1.6cqw;
      }
      .bf2 h3 {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 10cqw;
        line-height: 0.86;
        letter-spacing: -0.04em;
        text-transform: lowercase;
        color: var(--cream);
      }
      .bf2 h3 em {
        font-style: normal;
        color: var(--orange);
      }

      /* f3 stats dark */
      .bf3 .body {
        padding: 6cqw 5.5cqw;
        display: flex;
        flex-direction: column;
      }
      .bf3 .top {
        display: flex;
        justify-content: space-between;
        border-bottom: 1px solid var(--border-dark);
        padding-bottom: 1.2cqw;
        margin-bottom: 3cqw;
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 1cqw;
        color: var(--cream-hint);
      }
      .bf3 .top .k {
        color: var(--orange);
      }
      .bf3 .row {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 3cqw;
      }
      .bf3 .sc {
        border-top: 1px solid var(--border-dark);
        padding-top: 1.4cqw;
      }
      .bf3 .sc .v {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 6cqw;
        line-height: 1;
        letter-spacing: -0.04em;
        color: var(--orange);
      }
      .bf3 .sc .l {
        font-family: var(--sans);
        font-size: 1.3cqw;
        color: var(--cream);
        margin-top: 0.6cqw;
      }
      .bf3 .sc .n {
        font-family: var(--mono);
        font-size: 0.85cqw;
        letter-spacing: 0.1em;
        color: var(--cream-hint);
        margin-top: 0.5cqw;
        text-transform: uppercase;
      }
      .bf3 .foot {
        margin-top: auto;
        display: flex;
        justify-content: space-between;
        border-top: 1px solid var(--border-dark);
        padding-top: 1.2cqw;
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 0.9cqw;
        color: var(--cream-hint);
      }

      /* f4 fadelist */
      .bf4 {
        background: var(--ink);
      }
      .bf4 .body {
        display: grid;
        grid-template-columns: 1fr 1fr;
        align-items: center;
        padding: 0 5.5cqw;
        gap: 3cqw;
      }
      .bf4 .stack div {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 7cqw;
        line-height: 1;
        letter-spacing: -0.03em;
        text-transform: lowercase;
        color: var(--cream);
      }
      .bf4 .stack div:nth-child(2) {
        opacity: 0.5;
      }
      .bf4 .stack div:nth-child(3) {
        opacity: 0.22;
      }
      .bf4 .title {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 9cqw;
        line-height: 0.9;
        letter-spacing: -0.04em;
        text-transform: lowercase;
        color: var(--orange);
        text-align: right;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0;
        border: 1px solid var(--border-dark);
      }
      .do-card {
        padding: 40px;
      }
      .do-card.do {
        border-right: 1px solid var(--border-dark);
      }
      .do-card h4 {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 34px;
        text-transform: lowercase;
        margin-bottom: 24px;
      }
      .do-card.do h4 {
        color: var(--orange);
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-family: var(--sans);
        font-size: 15px;
        line-height: 1.55;
        padding-left: 28px;
        position: relative;
        margin-bottom: 14px;
        color: var(--cream-muted);
      }
      .do-card li::before {
        content: "/";
        position: absolute;
        left: 0;
        font-family: var(--mono);
        font-weight: 700;
        color: var(--orange);
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: var(--cream-hint);
      }

      .foot {
        padding: 56px 88px;
        border-top: 1px solid var(--border-dark);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--sans);
        font-weight: 900;
        font-size: 30px;
        text-transform: lowercase;
      }
      .foot .r {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 11px;
        color: var(--cream-hint);
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;600;700&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #f0ece5 !important;
        --secondary: #111111 !important;
        --tertiary: #282826 !important;
        --accent: #e85d26 !important;
        --f-disp: "Barlow", sans-serif !important;
        --f-body: "IBM Plex Mono", sans-serif !important;
        --ink: #111111 !important;
        --orange: #e85d26 !important;
        --cream: #f0ece5 !important;
        --border-dark: #282826 !important;
        --sans: "Barlow", sans-serif !important;
        --mono: "IBM Plex Mono", sans-serif !important;
        --ink-alt: #1a1a18 !important;
        --cream-muted: #888880 !important;
        --cream-hint: #505048 !important;
        --on-orange-muted: rgba(17, 17, 17, 0.75) !important;
        --on-orange-hint: rgba(17, 17, 17, 0.55) !important;
        --on-orange-faint: rgba(17, 17, 17, 0.4) !important;
        --on-orange-border: rgba(17, 17, 17, 0.2) !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="topnum">No. 01 / 06</div>
      <div class="topmeta">Broadside — Frame System</div>
      <div class="rule"></div>
      <div class="kick">Protest-Poster Editorial</div>
      <h1>ink on fire.</h1>
      <div class="lead">
        Massive Barlow type as graphic primitive, one fire-orange environment, and a flat plane
        built from negative space — dark to document, orange to declare.
      </div>
      <div class="foot"><span>Barlow 900 · IBM Plex Mono</span><span>1920 × 1080</span></div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="k">01</span>
        <h2>palette</h2>
        <span class="sp"></span><span class="num">TWO REGISTERS</span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="meta">
            <div class="name">ink black</div>
            <div class="hex">#111111</div>
            <div class="role">Dark canvas + ink on orange.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--orange)"></div>
          <div class="meta">
            <div class="name">fire orange</div>
            <div class="hex">#E85D26</div>
            <div class="role">The accent and the environment.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream)"></div>
          <div class="meta">
            <div class="name">cream</div>
            <div class="hex">#F0ECE5</div>
            <div class="role">Text on dark — never pure white.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream-muted)"></div>
          <div class="meta">
            <div class="name">cream muted</div>
            <div class="hex">#888880</div>
            <div class="role">Secondary text on dark.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream-hint)"></div>
          <div class="meta">
            <div class="name">cream hint</div>
            <div class="hex">#505048</div>
            <div class="role">Tertiary / axis labels.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink-alt)"></div>
          <div class="meta">
            <div class="name">ink alt</div>
            <div class="hex">#1A1A18</div>
            <div class="role">Slightly raised dark surface.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--border-dark)"></div>
          <div class="meta">
            <div class="name">border dark</div>
            <div class="hex">#282826</div>
            <div class="role">The 1px hairline on dark.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--orange); opacity: 0.2"></div>
          <div class="meta">
            <div class="name">ink-on-orange</div>
            <div class="hex">17,17,17 · .75–.20</div>
            <div class="role">Muted ink tones on orange.</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <span class="k">02</span>
        <h2>typography</h2>
        <span class="sp"></span><span class="num">BARLOW · PLEX MONO</span>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display</div>
          <div class="m">Barlow 900 · 13vw · lowercase · −.04em</div>
        </div>
        <div class="spec"><span class="disp" style="font-size: 104px">shout.</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">h2</div>
          <div class="m">Barlow 700 · primary headline</div>
        </div>
        <div class="spec">
          <span class="disp" style="font-weight: 700; font-size: 58px; letter-spacing: -0.02em"
            >type as graphic primitive</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">stat-value</div>
          <div class="m">Barlow 900 · orange on dark</div>
        </div>
        <div class="spec">
          <span class="disp" style="font-size: 80px; color: var(--orange)">94%</span>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Barlow 400 · cream · line 1.6</div>
        </div>
        <div class="spec">
          <p style="font-size: 18px; line-height: 1.6; max-width: 660px; color: var(--cream)">
            Barlow carries every text role — the expressive range comes from weight and size, not a
            second face.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">label</div>
          <div class="m">IBM Plex Mono 500 · uppercase · 0.14em</div>
        </div>
        <div class="spec">
          <span class="mono" style="font-size: 14px; color: var(--orange)"
            >Kicker / Catalogue Mark</span
          >
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="k">03</span>
        <h2>components</h2>
        <span class="sp"></span><span class="num">FLAT · ONE ACCENT</span>
      </div>

      <div class="cgrid">
        <div class="s5">
          <div class="cnote">Stat cards — top border only</div>
          <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 32px">
            <div class="statcard">
              <div class="v">187px</div>
              <div class="l">Display at 13vw</div>
              <div class="n">Graphic primitive</div>
            </div>
            <div class="statcard">
              <div class="v">2</div>
              <div class="l">Color registers</div>
              <div class="n">Dark / orange</div>
            </div>
          </div>
          <div class="cnote" style="margin-top: 36px">Bullet list — capped at three</div>
          <ul class="blist">
            <li>one statement per slide</li>
            <li>one accent color</li>
            <li>breathing room</li>
          </ul>
        </div>
        <div class="s3">
          <div class="cnote">Vertical bars — one accent</div>
          <div class="barchart">
            <div class="bar" style="height: 52%"></div>
            <div class="bar" style="height: 70%"></div>
            <div class="bar accent" style="height: 100%"></div>
            <div class="bar" style="height: 44%"></div>
          </div>
          <div class="barlabels"><span>q1</span><span>q2</span><span>q3</span><span>q4</span></div>
          <div class="cnote" style="margin-top: 32px">Mono tag</div>
          <span class="tag">Section / 03</span>
        </div>
        <div class="s4">
          <div class="cnote">Compare panels — orange payoff</div>
          <div class="compare">
            <div class="l">
              <div class="cl">Before</div>
              <div class="ch">document</div>
              <div class="cx">Dark register carries the evidence.</div>
            </div>
            <div class="r">
              <div class="cl">After</div>
              <div class="ch">declare</div>
              <div class="cx">Orange register makes the call.</div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <span class="k">04</span>
        <h2>frame compositions</h2>
        <span class="sp"></span><span class="num">TRUE 16:9 · CQW</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Cover</b><span>· identity · orange register · left</span></div>
          <div class="frame orange bf1">
            <div class="topnum">No. 01 / 06</div>
            <div class="body">
              <div class="rule"></div>
              <div class="kick">Protest-Poster Editorial</div>
              <h3>ink on fire.</h3>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Statement</b><span>· declarative · dark register · left</span>
          </div>
          <div class="frame bf2">
            <div class="body">
              <div class="kick">The Thesis</div>
              <h3>type is the <em>composition.</em></h3>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Stat Grid</b><span>· data · dark · the dense frame</span></div>
          <div class="frame bf3">
            <div class="body">
              <div class="top"><span class="k">By The Numbers</span><span>FIG.03</span></div>
              <div class="row">
                <div class="sc">
                  <div class="v">13vw</div>
                  <div class="l">Display scale</div>
                  <div class="n">≈187px @1440</div>
                </div>
                <div class="sc">
                  <div class="v">3</div>
                  <div class="l">Bullets max</div>
                  <div class="n">Impact over info</div>
                </div>
                <div class="sc">
                  <div class="v">1</div>
                  <div class="l">Accent color</div>
                  <div class="n">Fire orange only</div>
                </div>
              </div>
              <div class="foot"><span>Broadside</span><span>03 / 06</span></div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Fadelist</b><span>· narrative · before/during/after</span></div>
          <div class="frame bf4">
            <div class="body">
              <div class="stack">
                <div>past.</div>
                <div>present.</div>
                <div>future.</div>
              </div>
              <div class="title">the<br />arc.</div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="k">05</span>
        <h2>frame rules</h2>
        <span class="sp"></span><span class="num">INK ON FIRE</span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>do</h4>
          <ul>
            <li>Set every Barlow display in lowercase weight 900, negative-tracked.</li>
            <li>
              Make orange the full environment on declarative frames, the lone accent on dark.
            </li>
            <li>Keep chrome in IBM Plex Mono uppercase, 0.14em tracked.</li>
            <li>Cap bullet lists at three; one statement per frame.</li>
            <li>Build hierarchy from weight, size, and 1px hairlines only.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>don't</h4>
          <ul>
            <li>Never uppercase Barlow display — lowercase is the identity.</li>
            <li>No second accent color; no cream text on orange (ink on fire).</li>
            <li>No drop shadows, rounded surfaces, or gradient grounds.</li>
            <li>No serif companion; chrome is never set in Barlow.</li>
            <li>Don't pack two display moments into one frame.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--cream);
        --cap-accent: var(--orange);
        --cap-accent-2: var(--orange);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 82cqw;
        padding: 1.2cqw 3cqw 1.5cqw;
        background: var(--cap-ink);
        border: 1px solid var(--cap-accent);
        border-radius: 0;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.04em 0.3em;
        font-family: var(--sans);
        font-weight: 900;
        font-size: 3.2cqw;
        line-height: 1.02;
        letter-spacing: -0.03em;
        text-transform: lowercase;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.08em;
        color: color-mix(in srgb, var(--cap-canvas) 42%, var(--cap-ink));
      }
      .capdemo .w.spoken {
        color: var(--cap-canvas);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.06em var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 1px dashed var(--border-dark);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        text-transform: uppercase;
        font-size: 0.9cqw;
        letter-spacing: 0.1em;
        color: var(--cream-hint);
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="k">06</span>
        <h2>captions</h2>
        <span class="sp"></span><span class="num">LOWER-THIRD · KARAOKE</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Settled line</b><span>· all spoken · orange block cleared</span>
          </div>
          <div class="frame capdemo">
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">broadside</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#E85D26" />
        <rect x="14" y="40" width="26" height="4" fill="#111111" />
        <text x="14" y="68" font-family="Arial" font-weight="900" font-size="22" fill="#111111">
          ink.
        </text>
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/broadside/FRAME.md›
---
version: alpha
name: Broadside — Frame (video / frame layer)
description: >
  Video-first companion to Broadside's design.md. The unit is the frame (1920×1080). Atoms are
  identical and sacred — the two-register surface system (dark ink-black / fire-orange), massive
  Barlow in lowercase weight 900 treated as graphic primitive, IBM Plex Mono chrome (uppercase,
  0.14em), the single fire-orange accent, the flat plane, and 1px hairline dividers. Composition +
  frame scale rewritten for the frame. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  ink-black: "#111111"
  ink-black-alt: "#1A1A18"
  fire-orange: "#E85D26"
  cream: "#F0ECE5"
  cream-muted: "#888880"
  cream-hint: "#505048"
  border-dark: "#282826"
  ink-on-orange-muted: "rgba(17,17,17,0.75)"
  ink-on-orange-hint: "rgba(17,17,17,0.55)"
  ink-on-orange-faint: "rgba(17,17,17,0.40)"
  ink-on-orange-border: "rgba(17,17,17,0.20)"

typography:
  # — reading ramp —
  body:    { fontFamily: "Barlow", cqw: 1.2, weight: 400, lineHeight: 1.6 }
  lead:    { fontFamily: "Barlow", cqw: 1.6, weight: 400, lineHeight: 1.5 }
  caption: { fontFamily: "Barlow", cqw: 0.9, weight: 400, lineHeight: 1.5 }
  label:   { fontFamily: "IBM Plex Mono", cqw: 0.72, weight: 500, tracking: "0.14em", upper: true }
  # — display / hero ramp (Barlow, lowercase, negative tracking) —
  h3:      { fontFamily: "Barlow", cqw: 2.8, weight: 600, lineHeight: 1.2, lower: true }
  quote-text:{ fontFamily: "Barlow", cqw: 3.8, weight: 700, lineHeight: 1.15, tracking: "-0.02em", lower: true }
  h2:      { fontFamily: "Barlow", cqw: 4.5, weight: 700, lineHeight: 1.1, tracking: "-0.02em", lower: true }
  stat-value:{ fontFamily: "Barlow", cqw: 5.5, weight: 900, lineHeight: 1.0, tracking: "-0.04em" }
  h1:      { fontFamily: "Barlow", cqw: 7.5, weight: 800, lineHeight: 0.9, tracking: "-0.03em", lower: true }
  fadelist-item:{ fontFamily: "Barlow", cqw: 7.5, weight: 900, lineHeight: 1.0, tracking: "-0.03em", lower: true }
  quote-mark:{ fontFamily: "Barlow", cqw: 10.0, weight: 900, lineHeight: 0.6 }
  fadelist-title:{ fontFamily: "Barlow", cqw: 10.5, weight: 900, lineHeight: 0.9, tracking: "-0.04em", lower: true }
  display: { fontFamily: "Barlow", cqw: 13.0, weight: 900, lineHeight: 0.88, tracking: "-0.04em", lower: true }

spacing:
  pad-x: "5.5cqw"
  pad-y: "5.5cqw"
  gap-lg: "3.5cqw"
  gap-md: "2cqw"
  gap-sm: "1cqw"

components:
  registers:
    dark: "ground {colors.ink-black}, text {colors.cream}, accent {colors.fire-orange}"
    orange: "ground {colors.fire-orange}, text {colors.ink-black}"
    description: "Two surfaces only — no cream/paper register. One register per frame."
  slide-chrome:
    rule: "1px solid {colors.border-dark} (dark) / 20% ink (orange)"
    placement: "top + bottom bars (label left, number right)"
    description: "SUPPRESSED on cover/chapter/statement/quote/end — declarative frames let type fill the field."
  kicker:
    typography: "{typography.label}"
    color: "{colors.fire-orange} (dark) / 55% ink (orange)"
    description: "Uppercase mono eyebrow."
  rule:
    backgroundColor: "{colors.fire-orange} (dark) / {colors.ink-black} (orange)"
    size: "36×2px"
    description: "Stub accent bar — the system's only ornament."
  broadside-num:
    typography: "{typography.label}"
    placement: "top-left of orange cover/chapter, low opacity"
    description: "Mono catalogue numeral."
  stat-card:
    borderTop: "1px solid {colors.border-dark}"
    typography: "{typography.stat-value} (orange on dark / ink on orange) + {typography.body} + {typography.label}"
    description: "Top-border-only block, no other borders."
  bullet:
    marker: "orange `/` mono via ::before"
    typography: "{typography.lead}"
    description: "Capped at THREE items."
  bar-track:
    borderLeft: "1px solid {colors.border-dark}"
    bars: "{colors.cream-hint}, one .accent {colors.fire-orange}"
    typography: "{typography.label} axis"
    description: "Vertical bar chart, left axis only."
  compare-panel:
    layout: "two equal panels split by a 1px vertical rule"
    payoff: "right panel may fill {colors.fire-orange}"
    description: "Before/after."
  fadelist:
    typography: "{typography.fadelist-item} ×3 at opacity 1.0/0.5/0.22 + {typography.fadelist-title}"
    description: "Three stacked words + one oversized title opposite."
---

# Broadside — Frame (video / frame layer)

## Overview

Broadside at frame scale is a **protest-poster system where type is so large it stops reading as
text and becomes graphic primitive.** Barlow `display` at 13cqw puts a single lowercase word
nearly across the frame. The system runs in **two registers**: a dark ink-black ground with cream
text for documentation, and a fire-orange ground with dark ink for declaration. Fire-orange is the
_only_ color — accent on dark, environment on orange. The plane is flat; hierarchy is weight, size,
and 1px hairlines.

**Barlow** carries every text role from display to body — expressive range from weight (400–900)
and size, not face contrast. **IBM Plex Mono** is chrome only (numbers, kickers, tags, axis labels,
the `/` bullet marker), always uppercase and tracked. Display is **lowercase** — the system's most
distinctive single decision, a deliberate inversion of the brutalist norm.

**Key characteristics at frame scale:**

- **Two registers** — dark (cream text) / orange (ink text). No cream/paper register.
- **Massive lowercase Barlow 900**, negative-tracked, as graphic primitive (display 13cqw).
- **Fire-orange is the only color** — accent on dark, full environment on orange.
- **IBM Plex Mono chrome** — uppercase, 0.14em; the `/` bullet marker; mono catalogue numbers.
- **Flat plane** — no shadow, no radius (save nav dots), no gradient; 1px hairlines carry structure.
- **Low density** — one statement per frame, bullets capped at three, chrome suppressed on declarative frames.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — exactly **one display moment dominates** at 3–6× everything else; nothing competes.
- **Silence** — declarative frames read **45–55% empty**; the **stat grid is the one dense exception**.
- **Restraint** — **one register per frame**; **fire-orange is the only color** (accent on dark, environment on orange); one display moment; bullets capped at three.
- **Reference** — aim at **broadside printing / a SPACE10 report / a Wim Crouwel grid with one loud color**; failure looks like a **multi-accent corporate slide deck**.

- **Primary:** 1920×1080 (16:9). Type authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`; or carry the source's `vw` 1:1).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-x`/`pad-y` 5.5cqw — deliberately tight so the massive type crowds the frame edge.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw` (a `vw`-sized frame inflates when not
full-screen). 1px hairlines stay 1px.

## Colors

Tokens identical to the source, in two registers. **Dark:** `{colors.ink-black}` ground,
`{colors.cream}` text, `{colors.fire-orange}` accent (kickers, accent stat, bullet `/`, lead bar,
quote mark, rule stub). **Orange:** `{colors.fire-orange}` ground, `{colors.ink-black}` headlines +
body, with the dark-ink overlays (75/55/40/20%) as the muted tones. Choose one register per frame
and commit. **No second accent color** — on orange, emphasis is weight/opacity on the ink, never a
new hue. Cream text on orange does not exist (ink-on-fire is absolute).

## Typography

Two ramps. The **reading ramp** (Barlow body 1.2cqw, mono label 0.72cqw) carries copy + chrome; the
**display ramp** (Barlow `h2` 4.5cqw → `display` 13cqw, weight 700–900) carries every statement.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤2 words → `display`; 3–4 → `h1`; 5+ → `h2`. Broadside packs only ONE display moment per frame.
- **Barlow display is lowercase, weight 700–900, negative-tracked** (−0.04em largest, −0.02em h2). **Mono chrome is uppercase, 0.1em+.** No italic, no underline, no uppercase display.

## Depth & Surface

Flat plane, the only technique. Hierarchy from:

- **Weight + size contrast** — the dominant signal (900 lowercase display).
- **1px hairlines** — chrome bars, stat-card top, compare divider, bar-track left, chart baseline.
- **Color shift** — orange on ink, ink on cream, cream-muted on cream.
- **Negative space** — generous, intentional empty regions.

**Ceiling:** no box-shadow, no elevation, no rounded surface (save nav dots), no gradient ground.

## Shapes

- **0 radius everywhere** except nav dots (50%). Cards, panels, tags, stat blocks, bars — sharp rectangles.

## Components

- **registers** — the two-surface system. **slide-chrome** — optional hairline bars, suppressed on declarative frames.
- **kicker** (mono eyebrow) / **rule** (36×2 stub) / **broadside-num** (catalogue mark) — the chrome ornament set.
- **stat-card** (top-border only) / **bullet** (orange `/`, max 3) / **bar-track** (one accent bar) / **compare-panel** (orange payoff) / **fadelist** (1.0/0.5/0.22 stack).

## Frame Treatments

> Recipe: ground · register · composes · focal · chrome · accent · silence · Fixed/Free · density.
> One statement per frame; chrome suppressed on declarative frames.

### 1 · Cover (identity · move: massive type · ORANGE register · left)

**Ground** fire-orange. **Composes** broadside-num, rule, kicker, display, lead. **Focal** a 1–2 word
Barlow `display` (13cqw) lowercase in ink, left-anchored, over a small ink rule stub + mono kicker; a
Barlow lead line beneath in 75% ink. **Chrome** mono catalogue number top-left, mono meta top-right
(no chrome bars). **Accent** the ink itself is the pop on orange. **Silence** ~45%. **Fixed** ink-on-fire,
lowercase 900, flat. **Free** the word, kicker, lead. **Density** low.

### 2 · Statement (declarative · move: type IS composition · DARK register · left)

**Ground** ink-black. **Composes** kicker, display. **Focal** a 2–4 word Barlow `display`/`h1`
lowercase in cream, with ONE clause inked `{colors.fire-orange}`. **Chrome** mono kicker; no bars.
**Accent** the orange clause. **Silence** ~55%. **Fixed** lowercase 900, one orange clause, flat.
**Free** the statement, which clause is orange. **Density** low.

### 3 · Stat Grid (data · move: top-border cards · DARK · the dense frame)

**Ground** ink-black, chrome bars present. **Composes** slide-chrome, kicker, 3× stat-card. **Focal** a
row of three top-border-only stat-cards — big Barlow-900 numeral in `{colors.fire-orange}`, Barlow
label, mono note. **Chrome** top + bottom hairline bars (label + number). **Accent** the orange
numerals. **Silence** moderate — the density exception. **Fixed** top-border-only cards, orange
numerals, 1px hairlines. **Free** figures (from script), labels. **Density** dense-exception.

### 4 · Fadelist (narrative · move: opacity stack · DARK)

**Ground** ink-black. **Composes** fadelist (3 stacked Barlow-900 words at 1.0/0.5/0.22), fadelist-title.
**Focal** the three-stage word stack opposite an oversized display title in `{colors.fire-orange}`
(before/during/after). **Accent** the orange title. **Silence** moderate. **Fixed** the opacity
ladder, lowercase 900. **Free** the three words, the title. **Density** low-moderate.

### 5 · Pull Quote (quote · move: oversized mark · DARK · left)

**Ground** ink-black, chrome suppressed. **Composes** quote-mark, quote-text, attribution. **Focal** a
Barlow `quote-text` (700, lowercase) at ≤78cqw under an oversized fire-orange `quote-mark` (10cqw,
line-height 0.6). **Chrome** mono attribution (name + role). **Accent** the orange quote mark. **Silence**
~50%. **Fixed** orange mark, lowercase quote. **Free** quote, attribution. **Density** low.

### 6 · Compare (argument · move: split + orange payoff · DARK→ORANGE)

**Ground** ink-black left panel + fire-orange right (payoff) panel, 1px divider. **Composes**
compare-panel pair, kicker, h3. **Focal** two panels — left documents (cream on dark), right declares
(ink on orange). **Chrome** mono panel labels. **Accent** the orange payoff panel. **Silence** moderate.
**Fixed** ink-on-fire right panel, 1px divider, flat. **Free** the before/after content. **Density** standard.

## Composition Rules

### Do

- Set every Barlow display in **lowercase weight 900**, negative-tracked — the system's signature.
- Use **fire-orange as full environment** on declarative frames, the **lone accent** on dark.
- Keep chrome in **IBM Plex Mono uppercase, 0.14em**; use the `/` mono bullet marker.
- **Cap bullets at three; one statement per frame**; build hierarchy from weight, size, 1px hairlines.
- Suppress chrome bars on cover/chapter/statement/quote/end; let type fill the field.
- Lean left on most frames; the type IS the composition.

### Don't

- Never uppercase Barlow display; never add a second accent color.
- Never put cream text on orange (ink-on-fire is absolute); never a cream/paper register.
- No drop shadow, no rounded surface (save nav dots), no gradient ground.
- No serif companion; chrome is never Barlow.
- Don't pack two display moments into one frame; don't blow a long line edge-to-edge — step down.

## Aspect-Ratio Behavior

| Treatment  | 16:9                       | 9:16                       | 1:1              |
| ---------- | -------------------------- | -------------------------- | ---------------- |
| Cover      | word left, lead below      | word top, lead below       | centered word    |
| Statement  | display left               | display stacked taller     | display centered |
| Stat Grid  | 3 across                   | 3 stacked                  | 2+1              |
| Fadelist   | stack + title side-by-side | stack over title           | stack over title |
| Pull Quote | mark + quote left          | mark top, quote below      | centered         |
| Compare    | side-by-side panels        | stacked (dark over orange) | stacked          |

`pad-x` holds tight on the short edge; re-step display so the one big line stays ≤78cqw and above the
1.4cqw floor. Mono chrome stays Latin/digit-only.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder (the dashed `img-placeholder` at 55cqh). The system supplies type and one color, not brands.

## Numerals & Claims (hard rule)

Never invent figures, percentages, dates, or counts at frame scale. Render slots as `— figure —`,
`{metric}`, `NN%`. Stat-card numerals and bar heights carry placeholders until the script supplies
them. Catalogue numbers (No. 01) are decorative chrome and may be sequential.

## Pre-Render Self-Audit

- **Squint** — exactly one display moment dominates; nothing competes.
- **Silence** — declarative frames ~45–55% empty; only the stat grid runs dense.
- **Register** — one register per frame; ink-on-fire on orange, cream on dark; no second hue.
- **Type** — Barlow lowercase 900 negative-tracked, fit-to-measure; mono chrome uppercase 0.14em; ≥1.4cqw floor.
- **Depth** — 0 shadow, 0 radius (save nav dots); 1px hairlines only.
- **Bullets** — capped at three, orange `/` marker.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 0.8s deck slide + per-element entry animations are deck mechanics.
- **Barlow + IBM Plex Mono via Google Fonts**; Noto Sans SC is the CJK fallback (the lowercase-display signal has no CJK equivalent — the two-register color system carries the identity, per the source).
- **9:16 / 1:1 are guidance**; verify the one big line stays ≤78cqw and above the floor per ratio.
- Bars, compare panels, and the dashed image placeholder are CSS-only; no external imagery is required.
assets/frame-presets/capsule/caption-skin.html›
<!--
  Capsule — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a capsule literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a cream caption pill — fully-rounded (9999px), wrapped in capsule's universal
  2px ink outline, lifted on a soft hard-offset shadow (no blur), Bodoni Moda glyphs in
  ink, sentence case. Upcoming words sit in faint ink; the current word lights up as a
  rounded candy highlight — a candy fill plus a box-shadow spread halo that follows the
  pill radius (a soft candy capsule around the word, no blur, NO layout shift) with ink
  reading on top; spoken words settle to solid ink, the candy highlight cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE caption capsule — cream pill, fully-rounded (9999px), capsule's universal 2px
     ink outline, lifted on a soft hard-offset shadow (no blur, 8% ink) */
  .caption-pill {
    max-width: 80%;
    padding: 18px 46px 20px;
    background: var(--cap-canvas, #f5f5f0);
    border: 2px solid var(--cap-ink, #1a1a1a);
    border-radius: 9999px;
    box-shadow: 8px 8px 0 color-mix(in srgb, var(--cap-ink, #1a1a1a) 8%, transparent);
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.12em 0.36em;
    font-family: var(--font-display, "Bodoni Moda"), Georgia, serif;
    font-weight: 700;
    font-size: clamp(38px, 4.2vw, 56px);
    line-height: 1.16;
    letter-spacing: -0.02em;
  }

  /* upcoming — faint ink, no highlight */
  .caption-word {
    display: inline-block;
    padding: 0 0.1em;
    border-radius: 9999px;
    color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 42%, var(--cap-canvas, #f5f5f0));
  }
  /* current word — THE rounded candy highlight: a candy fill + a box-shadow spread
     halo that follows the pill radius (a soft candy capsule around the word, no blur,
     no layout shift), ink reading on top */
  .caption-word.is-active {
    color: var(--cap-ink, #1a1a1a);
    background: var(--cap-accent, #e85d4e);
    box-shadow: 0 0 0 0.14em var(--cap-accent, #e85d4e);
  }
  /* already spoken — solid ink, candy highlight cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #1a1a1a);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — Bodoni Moda italic, capsule's display inflection */
  .caption-word.cap-brand {
    font-style: italic;
  }
  /* numerals / data — the Space Grotesk voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Space Grotesk"), ui-sans-serif, sans-serif;
    color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 72%, var(--cap-canvas, #f5f5f0));
  }
  /* emphasis — a second-candy rounded underline (the counter-accent) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 3px;
    text-decoration-color: var(--cap-accent-2, #f2d160);
    text-underline-offset: 0.12em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.92 }, { scale: 1, duration: 0.18, ease: "back.out(2)" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/capsule/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Capsule</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Bodoni+Moda:ital,opsz,wght@0,6..96,400..900;1,6..96,400..900&family=Space+Grotesk:wght@400;500;600;700&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --cream: #f5f5f0;
        --ink: #1a1a1a;
        --outline: #1e1e1e;
        --white: #ffffff;
        --coral: #e85d4e;
        --lime: #c4d94e;
        --lavender: #c5b5e0;
        --sky: #8bb4f7;
        --violet: #a06ce8;
        --yellow: #f2d160;
        --peach: #f5b895;
        --mint: #a8e6cf;
        --shadow: rgba(26, 26, 26, 0.08);
        --serif: "Bodoni Moda", serif;
        --sans: "Space Grotesk", sans-serif;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #0e0e0c;
      }
      body {
        background: var(--cream);
        background-image:
          radial-gradient(ellipse at 12% 8%, rgba(229, 93, 78, 0.1), transparent 45%),
          radial-gradient(ellipse at 88% 22%, rgba(139, 180, 247, 0.12), transparent 42%),
          radial-gradient(ellipse at 70% 95%, rgba(196, 217, 78, 0.1), transparent 45%);
        color: var(--ink);
        font-family: var(--sans);
        -webkit-font-smoothing: antialiased;
      }
      .serif {
        font-family: var(--serif);
        color: var(--ink);
      }
      .eyebrow {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 12px;
      }
      .pill {
        border-radius: 9999px;
        border: 2px solid var(--outline);
        font-family: var(--sans);
        font-weight: 600;
        white-space: nowrap;
        display: inline-flex;
        align-items: center;
      }

      section {
        padding: 120px 100px;
        position: relative;
      }
      .sec-head {
        display: flex;
        align-items: center;
        gap: 20px;
        margin-bottom: 64px;
      }
      .sec-head .tagpill {
        background: var(--lavender);
        padding: 0.5rem 1.4rem;
        font-size: 13px;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        font-weight: 600;
      }
      .sec-head h2 {
        font-family: var(--serif);
        font-weight: 700;
        font-size: 52px;
        line-height: 1;
        letter-spacing: -0.02em;
      }
      .sec-head .sp {
        flex: 1;
        height: 2px;
        background: var(--outline);
        border-radius: 9999px;
        opacity: 0.15;
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        padding: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        position: relative;
        overflow: hidden;
      }
      .cover .titlepill {
        background: var(--yellow);
        border: 2px solid var(--outline);
        border-radius: 9999px;
        padding: 1rem 2.6rem;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 15px;
        box-shadow: 6px 6px 0 var(--shadow);
        margin-bottom: 40px;
        position: relative;
        z-index: 3;
        white-space: nowrap;
      }
      .cover h1 {
        font-family: var(--serif);
        font-weight: 800;
        font-size: clamp(72px, 13vw, 200px);
        line-height: 0.88;
        letter-spacing: -0.03em;
        position: relative;
        z-index: 3;
      }
      .cover h1 em {
        font-style: italic;
        color: var(--coral);
      }
      .cover .sub {
        font-family: var(--sans);
        font-weight: 400;
        text-transform: uppercase;
        letter-spacing: 0.18em;
        font-size: 15px;
        color: rgba(26, 26, 26, 0.6);
        margin-top: 32px;
        position: relative;
        z-index: 3;
      }
      .cover .accent-line {
        width: 80px;
        height: 4px;
        background: var(--coral);
        border-radius: 9999px;
        margin: 36px auto 0;
        position: relative;
        z-index: 3;
      }
      /* floating pills */
      .fp {
        position: absolute;
        border: 2px solid var(--outline);
        border-radius: 9999px;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 13px;
        padding: 0.5rem 1.2rem;
        z-index: 1;
      }
      .fp.circ {
        border-radius: 50%;
        width: 84px;
        height: 84px;
        display: grid;
        place-items: center;
        padding: 0;
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 24px;
      }
      .sw {
        background: var(--white);
        border: 2px solid var(--outline);
        border-radius: 1.5rem;
        overflow: hidden;
        box-shadow: 6px 6px 0 var(--shadow);
      }
      .sw .chip {
        height: 96px;
        margin: 0;
      }
      .sw .meta {
        padding: 16px 20px;
      }
      .sw .name {
        font-family: var(--serif);
        font-weight: 700;
        font-size: 20px;
      }
      .sw .hex {
        font-family: var(--sans);
        font-size: 12px;
        letter-spacing: 0.04em;
        color: rgba(26, 26, 26, 0.55);
        margin-top: 4px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 240px 1fr;
        align-items: center;
        padding: 24px 0;
        border-bottom: 2px solid rgba(30, 30, 30, 0.1);
      }
      .type-row .tok {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        font-size: 12px;
      }
      .type-row .m {
        font-size: 12px;
        color: rgba(26, 26, 26, 0.55);
        margin-top: 8px;
        line-height: 1.5;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        font-size: 12px;
        color: rgba(26, 26, 26, 0.55);
        margin-bottom: 22px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 32px;
        margin-bottom: 56px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s6 {
        grid-column: span 6;
      }
      .p-card {
        background: var(--white);
        border: 2px solid var(--outline);
        border-radius: 2rem;
        padding: 2.2rem 1.8rem;
        box-shadow: 8px 8px 0 var(--shadow);
        height: 100%;
      }
      .p-card .ic {
        width: 60px;
        height: 60px;
        border-radius: 50%;
        border: 2px solid var(--outline);
        display: grid;
        place-items: center;
        font-family: var(--serif);
        font-weight: 700;
        font-size: 24px;
        margin-bottom: 22px;
      }
      .p-card .t {
        font-family: var(--serif);
        font-weight: 700;
        font-size: 24px;
        line-height: 1.1;
      }
      .p-card .x {
        font-size: 14px;
        line-height: 1.6;
        color: rgba(26, 26, 26, 0.65);
        margin-top: 12px;
      }
      .stat-pill {
        background: var(--white);
        border: 2px solid var(--outline);
        border-radius: 2rem;
        padding: 2rem 1.5rem;
        box-shadow: 6px 6px 0 var(--shadow);
        text-align: left;
      }
      .stat-pill .n {
        font-family: var(--serif);
        font-weight: 800;
        font-size: 48px;
        line-height: 1;
        letter-spacing: -0.03em;
      }
      .stat-pill .l {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        font-size: 11px;
        color: rgba(26, 26, 26, 0.6);
        margin-top: 12px;
      }
      .stat-pill .bar {
        width: 40px;
        height: 4px;
        border-radius: 9999px;
        margin-top: 16px;
      }
      .bartrack {
        height: 36px;
        background: var(--cream);
        border: 2px solid var(--outline);
        border-radius: 9999px;
        overflow: hidden;
        display: flex;
        align-items: center;
        margin-bottom: 16px;
      }
      .barfill {
        height: 100%;
        border-radius: 9999px;
        border-right: 2px solid var(--outline);
        display: flex;
        align-items: center;
        justify-content: flex-end;
        padding-right: 14px;
        font-family: var(--sans);
        font-weight: 600;
        font-size: 12px;
      }
      .chips {
        display: flex;
        gap: 12px;
        flex-wrap: wrap;
      }
      .chip-pill {
        border: 2px solid var(--outline);
        border-radius: 9999px;
        padding: 0.4rem 1.1rem;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 12px;
      }
      .orbit {
        position: relative;
        height: 280px;
        display: grid;
        place-items: center;
      }
      .orbit .center {
        width: 140px;
        height: 140px;
        border-radius: 50%;
        background: var(--lime);
        border: 2px solid var(--outline);
        display: grid;
        place-items: center;
        font-family: var(--serif);
        font-weight: 700;
        font-size: 40px;
      }
      .orbit .sat {
        position: absolute;
        border: 2px solid var(--outline);
        border-radius: 9999px;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        font-size: 12px;
        padding: 0.4rem 1rem;
        box-shadow: 6px 6px 0 var(--shadow);
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        font-size: 12px;
        color: rgba(26, 26, 26, 0.55);
      }
      .fw .flabel b {
        color: var(--ink);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--cream);
        background-image:
          radial-gradient(ellipse at 15% 12%, rgba(229, 93, 78, 0.1), transparent 46%),
          radial-gradient(ellipse at 85% 85%, rgba(139, 180, 247, 0.12), transparent 46%);
        border: 2px solid var(--outline);
        border-radius: 1.4rem;
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 3;
      }
      .frame .ffp {
        position: absolute;
        border: 0.25cqw solid var(--outline);
        border-radius: 9999px;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.05cqw;
        font-size: 1cqw;
        padding: 0.4cqw 1.1cqw;
        z-index: 1;
      }
      .frame .ffp.circ {
        border-radius: 50%;
        width: 6cqw;
        height: 6cqw;
        display: grid;
        place-items: center;
        padding: 0;
      }

      /* f1 cover */
      .qf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
      }
      .qf1 .tp {
        background: var(--yellow);
        border: 0.25cqw solid var(--outline);
        border-radius: 9999px;
        padding: 0.8cqw 2.2cqw;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.2cqw;
        font-size: 1.1cqw;
        box-shadow: 0.4cqw 0.4cqw 0 var(--shadow);
        margin-bottom: 3cqw;
        z-index: 3;
      }
      .qf1 h3 {
        font-family: var(--serif);
        font-weight: 800;
        font-size: 12cqw;
        line-height: 0.88;
        letter-spacing: -0.3cqw;
        z-index: 3;
      }
      .qf1 h3 em {
        font-style: italic;
        color: var(--coral);
      }
      .qf1 .al {
        width: 6cqw;
        height: 0.3cqw;
        background: var(--coral);
        border-radius: 9999px;
        margin-top: 2.4cqw;
        z-index: 3;
      }

      /* f2 pillar cards (catalog) */
      .qf2 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .qf2 .tp {
        align-self: flex-start;
        background: var(--lavender);
        border: 0.2cqw solid var(--outline);
        border-radius: 9999px;
        padding: 0.5cqw 1.4cqw;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.15cqw;
        font-size: 0.9cqw;
        margin-bottom: 1.6cqw;
      }
      .qf2 h3 {
        font-family: var(--serif);
        font-weight: 700;
        font-size: 4cqw;
        line-height: 1;
        letter-spacing: -0.05cqw;
        margin-bottom: 2.4cqw;
      }
      .qf2 .row {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 2cqw;
      }
      .qf2 .c {
        background: var(--white);
        border: 0.2cqw solid var(--outline);
        border-radius: 1.6cqw;
        padding: 2cqw 1.6cqw;
        box-shadow: 0.6cqw 0.6cqw 0 var(--shadow);
      }
      .qf2 .c .ic {
        width: 4cqw;
        height: 4cqw;
        border-radius: 50%;
        border: 0.2cqw solid var(--outline);
        display: grid;
        place-items: center;
        font-family: var(--serif);
        font-weight: 700;
        font-size: 1.6cqw;
        margin-bottom: 1.4cqw;
      }
      .qf2 .c .t {
        font-family: var(--serif);
        font-weight: 700;
        font-size: 1.9cqw;
        line-height: 1.1;
      }
      .qf2 .c .x {
        font-size: 1cqw;
        line-height: 1.5;
        color: rgba(26, 26, 26, 0.65);
        margin-top: 0.8cqw;
      }

      /* f3 quote */
      .qf3 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 8cqw;
      }
      .qf3 .q {
        font-family: var(--serif);
        font-weight: 600;
        font-size: 4.2cqw;
        line-height: 1.3;
        letter-spacing: -0.03cqw;
      }
      .qf3 .q .hl {
        display: inline-flex;
        align-items: center;
        background: var(--lime);
        border: 0.2cqw solid var(--outline);
        border-radius: 9999px;
        padding: 0 1.2cqw;
        font-family: var(--sans);
        font-weight: 600;
        font-size: 3cqw;
        text-transform: none;
      }
      .qf3 .at {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.2cqw;
        font-size: 1cqw;
        color: rgba(26, 26, 26, 0.6);
        margin-top: 2.4cqw;
      }

      /* f4 closing */
      .qf4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
      }
      .qf4 .tp {
        background: var(--yellow);
        border: 0.25cqw solid var(--outline);
        border-radius: 9999px;
        padding: 0.7cqw 2cqw;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.2cqw;
        font-size: 1cqw;
        box-shadow: 0.4cqw 0.4cqw 0 var(--shadow);
        margin-bottom: 2.6cqw;
        z-index: 3;
      }
      .qf4 h3 {
        font-family: var(--serif);
        font-weight: 800;
        font-size: 8.5cqw;
        line-height: 0.92;
        letter-spacing: -0.2cqw;
        z-index: 3;
      }
      .qf4 h3 em {
        font-style: italic;
        color: var(--violet);
      }
      .qf4 .al {
        width: 5cqw;
        height: 0.3cqw;
        background: var(--coral);
        border-radius: 9999px;
        margin-top: 2.4cqw;
        z-index: 3;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 32px;
      }
      .do-card {
        background: var(--white);
        border: 2px solid var(--outline);
        border-radius: 2rem;
        padding: 36px;
        box-shadow: 8px 8px 0 var(--shadow);
      }
      .do-card h4 {
        font-family: var(--serif);
        font-weight: 700;
        font-size: 30px;
        margin-bottom: 22px;
      }
      .do-card .hpill {
        display: inline-block;
        border-radius: 9999px;
        border: 2px solid var(--outline);
        padding: 0.3rem 1rem;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        font-size: 11px;
        margin-bottom: 20px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-size: 14px;
        line-height: 1.6;
        padding-left: 24px;
        position: relative;
        margin-bottom: 13px;
        color: rgba(26, 26, 26, 0.75);
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--serif);
        font-weight: 700;
      }
      .do-card.do li::before {
        content: "+";
        color: var(--coral);
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: var(--violet);
      }

      .foot {
        padding: 56px 100px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        border-top: 2px solid rgba(30, 30, 30, 0.12);
      }
      .foot .l {
        font-family: var(--serif);
        font-weight: 800;
        font-size: 28px;
      }
      .foot .r {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 11px;
        color: rgba(26, 26, 26, 0.5);
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Bodoni+Moda:ital,wght@0,400;0,700;0,800;1,400&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #f5f5f0 !important;
        --secondary: #1a1a1a !important;
        --tertiary: #c4d94e !important;
        --accent: #e85d4e !important;
        --f-disp: "Bodoni Moda", sans-serif !important;
        --f-body: "Space Grotesk", sans-serif !important;
        --cream: #f5f5f0 !important;
        --ink: #1a1a1a !important;
        --coral: #e85d4e !important;
        --lime: #c4d94e !important;
        --serif: "Bodoni Moda", sans-serif !important;
        --sans: "Space Grotesk", sans-serif !important;
        --outline: #1e1e1e !important;
        --white: #ffffff !important;
        --lavender: #c5b5e0 !important;
        --sky: #8bb4f7 !important;
        --violet: #a06ce8 !important;
        --yellow: #f2d160 !important;
        --peach: #f5b895 !important;
        --mint: #a8e6cf !important;
        --shadow: rgba(26, 26, 26, 0.08) !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <span
        class="fp"
        style="background: var(--coral); top: 14%; left: 8%; transform: rotate(-12deg)"
        >Editorial</span
      >
      <span class="fp" style="background: var(--sky); top: 24%; right: 10%; transform: rotate(9deg)"
        >Playful</span
      >
      <span
        class="fp circ"
        style="background: var(--peach); top: 60%; left: 6%; transform: rotate(-6deg)"
        >★</span
      >
      <span
        class="fp"
        style="background: var(--mint); bottom: 16%; right: 8%; transform: rotate(-14deg)"
        >Memphis</span
      >
      <span
        class="fp"
        style="background: var(--lavender); bottom: 24%; left: 13%; transform: rotate(7deg)"
        >Candy</span
      >
      <span
        class="fp circ"
        style="background: var(--lime); top: 18%; right: 24%; transform: rotate(10deg)"
        >+</span
      >
      <div class="titlepill">Frame System · Vol. 01</div>
      <h1>Everything's<br />a <em>capsule.</em></h1>
      <div class="sub">Bodoni Moda · Space Grotesk · Nine Candy Accents</div>
      <div class="accent-line"></div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="tagpill">Palette</span>
        <h2>Nine Candy Accents</h2>
        <span class="sp"></span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--coral)"></div>
          <div class="meta">
            <div class="name">Coral</div>
            <div class="hex">#E85D4E</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--lime)"></div>
          <div class="meta">
            <div class="name">Lime</div>
            <div class="hex">#C4D94E</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--lavender)"></div>
          <div class="meta">
            <div class="name">Lavender</div>
            <div class="hex">#C5B5E0</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--sky)"></div>
          <div class="meta">
            <div class="name">Sky</div>
            <div class="hex">#8BB4F7</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--violet)"></div>
          <div class="meta">
            <div class="name">Violet</div>
            <div class="hex">#A06CE8</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--yellow)"></div>
          <div class="meta">
            <div class="name">Yellow</div>
            <div class="hex">#F2D160</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--peach)"></div>
          <div class="meta">
            <div class="name">Peach</div>
            <div class="hex">#F5B895</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--mint)"></div>
          <div class="meta">
            <div class="name">Mint</div>
            <div class="hex">#A8E6CF</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="background: var(--cream); border-bottom: 2px solid var(--outline)"
          ></div>
          <div class="meta">
            <div class="name">Cream</div>
            <div class="hex">#F5F5F0 · canvas</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="meta">
            <div class="name">Ink / Outline</div>
            <div class="hex">#1A1A1A · #1E1E1E</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <span class="tagpill" style="background: var(--mint)">Type</span>
        <h2>Bodoni × Grotesk</h2>
        <span class="sp"></span>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display</div>
          <div class="m">Bodoni Moda 800 · sentence case · −0.02em</div>
        </div>
        <div class="spec">
          <span
            class="serif"
            style="font-weight: 800; font-size: 88px; line-height: 0.9; letter-spacing: -0.02em"
            >Inflated.</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">headline</div>
          <div class="m">Bodoni Moda 700 · ink, never colored</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-weight: 700; font-size: 54px">A friendly pill</span>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">stat-number</div>
          <div class="m">Bodoni Moda 800 · color allowed here</div>
        </div>
        <div class="spec">
          <span
            class="serif"
            style="font-weight: 800; font-size: 64px; color: var(--coral); letter-spacing: -0.03em"
            >240%</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Space Grotesk 400 · ink 65%</div>
        </div>
        <div class="spec">
          <p
            style="
              font-size: 17px;
              line-height: 1.6;
              max-width: 680px;
              color: rgba(26, 26, 26, 0.7);
            "
          >
            Space Grotesk carries every paragraph and label — the clean grotesque against Bodoni's
            glamour.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">pill-text</div>
          <div class="m">Space Grotesk 600 · uppercase · 0.12em</div>
        </div>
        <div class="spec">
          <span
            class="pill"
            style="
              background: var(--yellow);
              padding: 0.5rem 1.4rem;
              text-transform: uppercase;
              letter-spacing: 0.12em;
              font-size: 14px;
            "
            >Featured</span
          >
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="tagpill" style="background: var(--sky)">Components</span>
        <h2>Pills, All the Way Down</h2>
        <span class="sp"></span>
      </div>

      <div class="cgrid">
        <div class="s4">
          <div class="cnote">Pillar card</div>
          <div class="p-card">
            <div class="ic" style="background: var(--coral)">I</div>
            <div class="t">Pill Geometry</div>
            <div class="x">Every container is a pill — 9999px small, 2rem for cards.</div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Stat pills</div>
          <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px">
            <div class="stat-pill">
              <div class="n" style="color: var(--coral)">9</div>
              <div class="l">Accents</div>
              <div class="bar" style="background: var(--coral)"></div>
            </div>
            <div class="stat-pill">
              <div class="n" style="color: var(--sky)">2px</div>
              <div class="l">Outline</div>
              <div class="bar" style="background: var(--sky)"></div>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Bar tracks + chips</div>
          <div class="bartrack">
            <div class="barfill" style="width: 78%; background: var(--lime)">78</div>
          </div>
          <div class="bartrack">
            <div class="barfill" style="width: 54%; background: var(--violet); color: var(--white)">
              54
            </div>
          </div>
          <div class="chips">
            <span class="chip-pill" style="background: var(--peach)">Soft</span
            ><span class="chip-pill" style="background: var(--lavender)">Lifted</span
            ><span class="chip-pill" style="background: var(--mint)">Friendly</span>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s6">
          <div class="cnote">Orbit composition</div>
          <div class="orbit">
            <div class="center">01</div>
            <span
              class="sat"
              style="background: var(--coral); top: 14%; left: 18%; transform: rotate(-10deg)"
              >Vision</span
            >
            <span
              class="sat"
              style="background: var(--sky); top: 20%; right: 16%; transform: rotate(8deg)"
              >Future</span
            >
            <span
              class="sat"
              style="background: var(--yellow); bottom: 16%; left: 24%; transform: rotate(6deg)"
              >Next</span
            >
            <span
              class="sat"
              style="background: var(--mint); bottom: 20%; right: 20%; transform: rotate(-7deg)"
              >Now</span
            >
          </div>
        </div>
        <div class="s6">
          <div class="cnote">Diagram nodes + connectors</div>
          <div
            style="display: flex; align-items: center; gap: 0; margin-top: 40px; flex-wrap: wrap"
          >
            <span
              class="pill"
              style="
                background: var(--white);
                padding: 1rem 2rem;
                box-shadow: 6px 6px 0 var(--shadow);
                font-family: var(--serif);
                font-weight: 700;
              "
              >Draft</span
            >
            <span
              style="
                width: 50px;
                height: 4px;
                background: var(--outline);
                position: relative;
                display: inline-block;
              "
            ></span>
            <span
              class="pill"
              style="
                background: var(--lavender);
                padding: 1rem 2rem;
                box-shadow: 6px 6px 0 var(--shadow);
                font-family: var(--serif);
                font-weight: 700;
              "
              >Shape</span
            >
            <span
              style="
                width: 50px;
                height: 4px;
                background: var(--outline);
                position: relative;
                display: inline-block;
              "
            ></span>
            <span
              class="pill"
              style="
                background: var(--lime);
                padding: 1rem 2rem;
                box-shadow: 6px 6px 0 var(--shadow);
                font-family: var(--serif);
                font-weight: 700;
              "
              >Ship</span
            >
          </div>
          <div style="margin-top: 40px" class="cnote">Card icons</div>
          <div style="display: flex; gap: 18px">
            <span
              style="
                width: 60px;
                height: 60px;
                border-radius: 50%;
                border: 2px solid var(--outline);
                background: var(--yellow);
                display: grid;
                place-items: center;
                font-family: var(--serif);
                font-weight: 700;
                font-size: 22px;
              "
              >II</span
            >
            <span
              style="
                width: 60px;
                height: 60px;
                border-radius: 50%;
                border: 2px solid var(--outline);
                background: var(--peach);
                display: grid;
                place-items: center;
                font-family: var(--serif);
                font-weight: 700;
                font-size: 22px;
              "
              >III</span
            >
            <span
              style="
                width: 60px;
                height: 60px;
                border-radius: 50%;
                border: 2px solid var(--outline);
                background: var(--violet);
                color: var(--white);
                display: grid;
                place-items: center;
                font-family: var(--serif);
                font-weight: 700;
                font-size: 22px;
              "
              >IV</span
            >
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <span class="tagpill" style="background: var(--coral)">Frames</span>
        <h2>Frame Compositions</h2>
        <span class="sp"></span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Cover</b><span>· identity · pill wallpaper · centered</span></div>
          <div class="frame qf1">
            <span
              class="ffp"
              style="background: var(--coral); top: 12%; left: 8%; transform: rotate(-12deg)"
              >Editorial</span
            >
            <span
              class="ffp"
              style="background: var(--sky); top: 18%; right: 10%; transform: rotate(9deg)"
              >Playful</span
            >
            <span
              class="ffp circ"
              style="background: var(--lime); bottom: 16%; left: 9%; transform: rotate(-8deg)"
              >+</span
            >
            <span
              class="ffp"
              style="background: var(--mint); bottom: 14%; right: 11%; transform: rotate(-12deg)"
              >Memphis</span
            >
            <div class="body">
              <div class="tp">Frame System</div>
              <h3>Everything's a <em>capsule.</em></h3>
              <div class="al"></div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Pillar Cards</b><span>· catalog · 3-up · left</span></div>
          <div class="frame qf2">
            <div class="body">
              <div class="tp">Three Pillars</div>
              <h3>The capsule rules.</h3>
              <div class="row">
                <div class="c">
                  <div class="ic" style="background: var(--coral)">I</div>
                  <div class="t">Pill</div>
                  <div class="x">Every container is a pill.</div>
                </div>
                <div class="c">
                  <div class="ic" style="background: var(--sky)">II</div>
                  <div class="t">Outline</div>
                  <div class="x">2px ink stroke on all.</div>
                </div>
                <div class="c">
                  <div class="ic" style="background: var(--lime)">III</div>
                  <div class="t">Lift</div>
                  <div class="x">Soft offset shadow.</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Pull Quote</b><span>· quote · highlight pill · left</span></div>
          <div class="frame qf3">
            <span
              class="ffp"
              style="background: var(--peach); top: 14%; right: 9%; transform: rotate(8deg)"
              >Voice</span
            >
            <div class="body">
              <div class="q">
                Wrap emphasis in a <span class="hl">candy pill</span>, never in bold.
              </div>
              <div class="at">— The System Voice</div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Closing Plate</b><span>· closer · pill wallpaper · centered</span>
          </div>
          <div class="frame qf4">
            <span
              class="ffp"
              style="background: var(--lavender); top: 16%; left: 10%; transform: rotate(-10deg)"
              >Thanks</span
            >
            <span
              class="ffp circ"
              style="background: var(--yellow); bottom: 18%; right: 12%; transform: rotate(9deg)"
              >★</span
            >
            <div class="body">
              <div class="tp">The End</div>
              <h3>Stay <em>inflated.</em></h3>
              <div class="al"></div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="tagpill" style="background: var(--yellow)">Rules</span>
        <h2>Frame Rules</h2>
        <span class="sp"></span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <span class="hpill" style="background: var(--lime)">Do</span>
          <ul>
            <li>Make every text container a pill — 9999px small, 2rem for cards.</li>
            <li>Wrap every pill, card, and icon in the 2px ink outline.</li>
            <li>Set Bodoni headlines in ink, sentence case; color lives on stats + pill fills.</li>
            <li>Use soft offset shadows (4/6/8/12px) in 8% ink — never blur.</li>
            <li>Float 5–8 candy pills as wallpaper on declarative frames.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <span class="hpill" style="background: var(--coral)">Don't</span>
          <ul>
            <li>No sharp-cornered text containers; no unstroked pills.</li>
            <li>No colored Bodoni headlines; no uppercase Bodoni.</li>
            <li>No blurred shadows; no tenth accent color.</li>
            <li>No shadows on decorative floating pills (atmosphere is flat).</li>
            <li>Don't blow a headline edge-to-edge — fit to measure.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--cream);
        --cap-accent: var(--coral);
        --cap-accent-2: var(--yellow);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 82cqw;
        padding: 1.3cqw 3.4cqw 1.5cqw;
        background: var(--cap-canvas);
        border: 0.2cqw solid var(--cap-ink);
        border-radius: 9999px;
        box-shadow: 0.6cqw 0.6cqw 0 var(--shadow);
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.12em 0.36em;
        font-family: var(--serif);
        font-weight: 700;
        font-size: 3.2cqw;
        line-height: 1.16;
        letter-spacing: -0.02em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.1em;
        border-radius: 9999px;
        color: color-mix(in srgb, var(--cap-ink) 42%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.14em var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 0.12cqw dashed rgba(30, 30, 30, 0.3);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        font-size: 0.85cqw;
        letter-spacing: 0.08em;
        color: rgba(26, 26, 26, 0.5);
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="tagpill" style="background: var(--peach)">Captions</span>
        <h2>Captions</h2>
        <span class="sp"></span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <span
              class="ffp"
              style="background: var(--lavender); top: 12%; left: 8%; transform: rotate(-11deg)"
              >Live</span
            >
            <span
              class="ffp circ"
              style="background: var(--lime); top: 16%; right: 10%; transform: rotate(8deg)"
              >+</span
            >
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · highlight cleared</span></div>
          <div class="frame capdemo">
            <span
              class="ffp"
              style="
                background: var(--sky);
                bottom: auto;
                top: 14%;
                right: 11%;
                transform: rotate(9deg);
              "
              >Promo</span
            >
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Capsule</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#F5F5F0" />
        <rect
          x="22"
          y="38"
          width="56"
          height="24"
          rx="12"
          fill="#F2D160"
          stroke="#1E1E1E"
          stroke-width="2"
        />
        <rect
          x="14"
          y="20"
          width="26"
          height="13"
          rx="6.5"
          fill="#E85D4E"
          stroke="#1E1E1E"
          stroke-width="2"
          transform="rotate(-10 27 26)"
        />
        <circle cx="76" cy="72" r="8" fill="#C4D94E" stroke="#1E1E1E" stroke-width="2" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/capsule/FRAME.md›
---
version: alpha
name: Capsule — Frame (video / frame layer)
description: >
  Video-first companion to Capsule's design.md. The unit is the frame (1920×1080). Atoms are
  identical and sacred — the pill geometry (9999px small / 2rem cards) with a 2px ink outline on
  everything, the sun-bleached cream canvas, the nine-color candy palette, Bodoni Moda + Space
  Grotesk, soft hard-offset shadows (4/6/8/12px in 8% ink), floating decorative-pill wallpaper,
  radial accent glows, and the grain overlay. Composition + frame scale rewritten. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  cream: "#F5F5F0"
  ink: "#1A1A1A"
  outline: "#1E1E1E"
  white: "#FFFFFF"
  coral: "#E85D4E"
  lime: "#C4D94E"
  lavender: "#C5B5E0"
  sky: "#8BB4F7"
  violet: "#A06CE8"
  yellow: "#F2D160"
  peach: "#F5B895"
  mint: "#A8E6CF"
  shadow: "rgba(26,26,26,0.08)"

typography:
  # — reading ramp (Space Grotesk) —
  body:      { fontFamily: "Space Grotesk", cqw: 0.85, weight: 400, lineHeight: 1.6 }
  subtitle:  { fontFamily: "Space Grotesk", cqw: 0.95, weight: 400, tracking: "0.18em", upper: true }
  pill-text: { fontFamily: "Space Grotesk", cqw: 0.75, weight: 600, tracking: "0.12em", upper: true }
  label:     { fontFamily: "Space Grotesk", px: 14, weight: 500, tracking: "0.1em", upper: true }
  # — display / hero ramp (Bodoni Moda, ink, sentence case) —
  card-headline:{ fontFamily: "Bodoni Moda", cqw: 1.9, weight: 700, lineHeight: 1.1 }
  orbit-numeral:{ fontFamily: "Bodoni Moda", cqw: 2.6, weight: 700, lineHeight: 1.0 }
  quote-display:{ fontFamily: "Bodoni Moda", cqw: 4.2, weight: 600, lineHeight: 1.3, tracking: "-0.01em" }
  stat-number: { fontFamily: "Bodoni Moda", cqw: 3.6, weight: 800, lineHeight: 1.0, tracking: "-0.03em" }
  section-headline:{ fontFamily: "Bodoni Moda", cqw: 4.0, weight: 700, lineHeight: 1.05, tracking: "-0.01em" }
  headline:    { fontFamily: "Bodoni Moda", cqw: 5.0, weight: 700, lineHeight: 1.05, tracking: "-0.02em" }
  closing-display:{ fontFamily: "Bodoni Moda", cqw: 8.5, weight: 800, lineHeight: 0.95, tracking: "-0.03em" }
  display:     { fontFamily: "Bodoni Moda", cqw: 12.0, weight: 800, lineHeight: 0.88, tracking: "-0.03em" }

spacing:
  pad: "5cqw"
  gap-md: "2cqw"
  card-pad: "2cqw 1.6cqw"

components:
  pill:
    rounded: "9999px (small) / 2rem (cards)"
    border: "0.2cqw solid {colors.outline}"
    backgroundColor: "any candy or {colors.white}"
    description: "The universal container — every chip/button/label/stat tile/node/bar. No unstroked pill exists."
  pill-card:
    backgroundColor: "{colors.white}"
    border: "0.2cqw solid {colors.outline}"
    rounded: "2rem"
    shadow: "0.4cqw 0.4cqw 0 {colors.shadow}"
    typography: "{typography.card-headline} + {typography.body}"
    description: "Holds a circular card-icon, Bodoni headline, Space Grotesk body."
  stat-pill:
    backgroundColor: "{colors.white}"
    border: "0.2cqw solid {colors.outline}"
    rounded: "2rem"
    shadow: "0.3cqw 0.3cqw 0 {colors.shadow}"
    typography: "{typography.stat-number} (COLORED) + {typography.pill-text}"
    description: "The one place color touches a numeral; + a 40px accent bar."
  title-pill:
    backgroundColor: "{colors.yellow}"
    border: "0.2cqw solid {colors.outline}"
    rounded: "9999px"
    shadow: "0.3cqw 0.3cqw 0 {colors.shadow}"
    typography: "{typography.pill-text}"
    description: "Sits above the display headline on cover/closing."
  card-icon:
    backgroundColor: "any candy"
    border: "0.2cqw solid {colors.outline}"
    rounded: "50%"
    size: "60px"
    typography: "Bodoni numeral/letter in {colors.ink}"
    description: "Card mark."
  floating-pill:
    backgroundColor: "any candy"
    border: "0.2cqw solid {colors.outline}"
    rounded: "9999px / 50%"
    transform: "rotate(−20°..+25°)"
    shadow: "none"
    typography: "{typography.pill-text}"
    description: "Decorative wallpaper confetti, 5–8 per declarative frame. Flat — no shadow."
  quote-highlight:
    backgroundColor: "{colors.lime} / {colors.sky}"
    border: "0.2cqw solid {colors.outline}"
    rounded: "9999px"
    description: "Inline candy pill wrapping a phrase inside a Bodoni quote — the emphasis mechanism, replacing bold/italic."
  bar-track:
    backgroundColor: "{colors.cream}"
    border: "0.2cqw solid {colors.outline}"
    rounded: "9999px"
    fill: "child candy pill, value at right edge"
    description: "36px pill chart track."
  accent-line:
    backgroundColor: "{colors.coral}"
    rounded: "9999px"
    size: "60×4 (80×4 closing)"
    description: "Coral pill rule."
  atmosphere:
    background: "1–3 radial candy glows (6–15% opacity) over {colors.cream} + 4% grain overlay"
    description: "Baseline canvas layers on every frame. Never absent."
---

# Capsule — Frame (video / frame layer)

## Overview

Capsule at frame scale is a **playful editorial system where every container is a pill.** The
`border-radius: 9999px` (small) / `2rem` (cards) rule plus a 2px ink outline wraps every chip,
card, icon, bar, and node — inflated, friendly, graphically distinct. The canvas is sun-bleached
cream warmed by soft radial candy glows and a permanent 4% grain overlay.

The voice is a two-face conversation: **Bodoni Moda** (didone serif, weight 700–800, always ink,
always sentence case) carries every headline, stat, and quote; **Space Grotesk** carries body and
all uppercase tracked pill/label text. Nine candy accents fill pills interchangeably with no
semantic meaning. Depth is a **soft hard-offset shadow** (4/6/8/12px in 8% ink) — lifted, not
stamped — reserved for content-bearing containers; decorative floating pills are flat.

**Key characteristics at frame scale:**

- **Pill geometry everywhere** — 9999px small, 2rem cards — each wrapped in a 2px `{colors.outline}` stroke.
- **Bodoni Moda** display (ink, sentence case) + **Space Grotesk** body/pills (uppercase, tracked).
- **Nine candy accents**, interchangeable; color touches stat numerals + pill fills, never headlines.
- **Soft offset shadows** (4/6/8/12px, 8% ink) on content containers only; floating pills are flat.
- **Floating decorative-pill wallpaper** (5–8, tilted) + radial glows + 4% grain on declarative frames.
- **Cream canvas** warmed by atmosphere — bare corners read as broken.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one Bodoni headline or stat dominates at **3–6× its nearest neighbor**.
- **Silence** — declarative frames carry **atmosphere, not clutter** (pills as wallpaper, not content); the **pillar-cards and stat grid are the dense exception**.
- **Restraint** — every pill carries the 2px outline; color touches **fills + stat numerals only** (never a headline); soft shadow on content-bearing pills only (floating pills are flat); no tenth accent.
- **Reference** — aim at a **Memphis / ice-cream-parlor editorial spread**; failure looks like a **flat, sticker-less SaaS card grid**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad` (5cqw); floating pills may bleed off edges as wallpaper.

**The container law (load-bearing).** Every frame ground sets `container-type: size` AND carries
the radial-glow + grain atmosphere; ALL frame-relative units are `cqw`/`cqh` against it — never
`vw`. Pill radii stay `9999px`/`2rem` (shape, not scale); shadow offsets scale in `cqw`.

## Colors

Tokens identical to the source. `{colors.cream}` is the ground; `{colors.ink}`/`{colors.outline}`
is type + the universal 2px stroke; the nine candy accents fill pills with **no semantic mapping**
— pair a warm (coral/yellow/peach) with a cool (sky/lavender/violet/mint) with a neutral-bright
(lime); never two same-family adjacent. Color appears on **stat numerals and pill fills only** —
never on a Bodoni headline. Shadow is always `{colors.shadow}` (8% ink). No tenth color.

## Typography

Two ramps. The **reading ramp** (Space Grotesk body 0.85cqw, uppercase tracked pill/label text)
carries copy + chips; the **display ramp** (Bodoni `card-headline` 1.9cqw → `display` 12cqw, all
700–800) carries every headline, stat, and quote.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`/`closing-display`; 4–6 → `headline`; 7+ → `section-headline`.
- **Bodoni 700+ in ink, sentence case** (italic via `<em>` only, color allowed on the italic key word as a candy hue); **Space Grotesk pills/labels uppercase, 0.08em+ tracked**; negative-track Bodoni display.

## Depth & Surface

Soft hard-offset shadow is the only depth, in `{colors.shadow}` (8% ink), bottom-right:

- **0.2cqw (4px)** small nodes; **0.3cqw (6px)** stat-pills, orbit pills, diagram nodes; **0.4cqw (8px)** pillar-cards; **0.6cqw (12px)** the visual frame.
- The **2px outline** does most of the lift against cream; the shadow adds the float.
- **Decorative floating pills cast no shadow** — this separates content from atmosphere at a glance.

**Ceiling:** no blurred shadow, no re-colored shadow, no gradient depth.

## Shapes

- **9999px** — all small pills (chips, buttons, bars, nodes, floating pills, quote highlights, accent lines).
- **2rem** — larger cards (pillar-card, stat-pill, chart container, visual frame).
- **50%** — circular pills (card-icon 60px, step-node 56px, orbit-center 160px, nav dots).
- **0** — only the grain overlay and the gradient region inside a visual frame. No sharp-cornered text container exists.

## Components

- **pill** — the universal 2px-outlined container. **pill-card / stat-pill / title-pill** — the white/yellow content pills with soft shadows.
- **card-icon** — circular candy mark; **floating-pill** — flat tilted wallpaper confetti; **quote-highlight** — inline candy emphasis pill.
- **bar-track** — pill-shaped chart bar; **accent-line** — coral pill rule; **atmosphere** — the baseline glow + grain on every frame.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> Atmosphere (glow + grain) on every frame; declarative frames carry floating-pill wallpaper.

### 1 · Cover (identity · move: title pill + display · centered)

**Ground** cream + atmosphere, floating-pill wallpaper. **Composes** title-pill, display, accent-line, floating-pills (5–8). **Focal** a 1–2 line Bodoni `display` headline in ink (italic key word in a candy hue), centered, under a yellow title-pill; a coral accent-line below. **Chrome** uppercase Space Grotesk sub. **Accent** the italic word + candy floating pills. **Silence** content centered; pills fill the edges. **Fixed** pill outlines, Bodoni ink, flat floating pills. **Free** title, which candy hues, pill words/positions. **Density** medium-atmospheric.

### 2 · Pillar Cards (catalog · move: 3-up grid · left — the dense frame)

**Ground** cream + atmosphere. **Composes** title-pill (lavender), section-headline, 3× pill-card. **Focal** three white 2rem pill-cards (circular candy card-icon, Bodoni card-headline, Space Grotesk body) with 0.4cqw shadows, under a Bodoni section-headline. **Chrome** a lavender header tag-pill. **Accent** the three card-icon fills (coral/sky/lime sequence). **Silence** tight — the density exception (no floating pills here). **Fixed** 2px outlines, soft shadows, ink headlines. **Free** card content, icon hues. **Density** dense-exception.

### 3 · Stat Grid (data · move: stat pills · centered head)

**Ground** cream + atmosphere. **Composes** section-headline, 3–4× stat-pill. **Focal** a row of white stat-pills, each a COLORED Bodoni stat-number + uppercase label + accent bar. **Accent** the colored numerals + bars (the one place color meets type). **Silence** moderate. **Fixed** 2rem pills, colored numerals only, soft shadow. **Free** figures (from script), hues. **Density** standard.

### 4 · Pull Quote (quote · move: highlight pill · left)

**Ground** cream + atmosphere, a few floating pills. **Composes** quote-display, quote-highlight, accent-line. **Focal** a Bodoni quote in ink with one phrase wrapped in a lime/sky `quote-highlight` pill (the emphasis mechanism — never bold). **Chrome** uppercase attribution. **Accent** the highlight pill. **Silence** ~50%. **Fixed** Bodoni 600, highlight-pill emphasis. **Free** quote, highlight color/phrase. **Density** sparse.

### 5 · Orbit (concept · move: gravitational pills · centered)

**Ground** cream + atmosphere. **Composes** orbit-center (160px lime circle, Bodoni ordinal), 4–6 orbit-pill satellites (candy, tilted, soft shadow). **Focal** the lime center anchored by orbiting candy pills. **Accent** the satellite hues. **Silence** moderate. **Fixed** circular center, outlined satellites with shadow. **Free** ordinal, satellite words/positions. **Density** medium.

### 6 · Closing Plate (closer · move: title pill + display · centered)

**Ground** cream + atmosphere, floating-pill wallpaper. **Composes** title-pill (yellow), closing-display, accent-line, floating-pills. **Focal** a Bodoni `closing-display` sign-off in ink (italic word in violet/candy), centered. **Accent** the italic word + pills. **Silence** content centered. **Fixed** Bodoni ink, flat pills. **Free** sign-off, pill words. **Density** medium-atmospheric.

## Composition Rules

### Do

- Make every text container a **pill** (9999px / 2rem) with the **2px ink outline**.
- Set **Bodoni headlines in ink, sentence case**; color lives on stat numerals + pill fills.
- Use **soft offset shadows** (4/6/8/12px, 8% ink) on content containers; keep floating pills flat.
- Float **5–8 candy pills** + radial glows + grain on declarative frames; bare corners read as broken.
- Wrap inline emphasis in a **candy quote-highlight pill**, never bold/italic-alone.
- Pair warm+cool+neutral-bright accents; lean centered on cover/closer, left on cards/quote.

### Don't

- No sharp-cornered text container; no unstroked pill.
- No colored Bodoni headline; no uppercase Bodoni.
- No blurred or re-colored shadow; no tenth accent color.
- No shadow on decorative floating pills.
- Don't blow a headline edge-to-edge — fit to measure.

## Aspect-Ratio Behavior

| Treatment    | 16:9                           | 9:16                        | 1:1                   |
| ------------ | ------------------------------ | --------------------------- | --------------------- |
| Cover        | display centered, pills around | display top, pills band     | centered, fewer pills |
| Pillar Cards | head + 3-up                    | head + 3 stacked            | head + 2+1            |
| Stat Grid    | 3–4 across                     | 2×2                         | 2×2                   |
| Pull Quote   | quote left                     | quote top, highlight inline | centered              |
| Orbit        | center + 4–6 satellites        | center + 3 satellites       | center + 4            |
| Closing      | centered + pills               | centered, pills band        | centered              |

`pad` holds on the short edge; re-step display above the 1.4cqw floor. Floating-pill count drops
on tighter ratios so they stay wallpaper, not clutter.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Floating-pill words are neutral atmospheres ("VISION", "FUTURE", "NEXT"), never content-specific.

## Numerals & Claims (hard rule)

Never invent figures, stats, or counts at frame scale. Render slots as `— figure —`, `{metric}`,
`N%`. Stat-pill numbers and bar-track widths carry placeholders until the script supplies values.

## Pre-Render Self-Audit

- **Squint** — one Bodoni element dominates at 3–5× its neighbor.
- **Silence** — declarative frames carry atmosphere not clutter; only pillar/stat grids run dense.
- **Pills** — every container is a pill with a 2px outline; no sharp text container.
- **Color** — candy on fills + stat numerals only; headlines ink; no tenth hue.
- **Depth** — soft offset shadow on content only; floating pills flat; no blur.
- **Type** — Bodoni ink sentence-case, fit-to-measure; pills uppercase tracked; ≥1.4cqw floor.
- **Anchor** — centered on cover/closer/orbit, left on cards/quote; no 3 consecutive alike.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the 0.6s fade in the source is a deck mechanic.
- **Bodoni Moda + Space Grotesk via Google Fonts.** CJK pairing (ZCOOL XiaoWei / Yozai) carries over from the source.
- **9:16 / 1:1 are guidance**; verify the floor and that floating-pill count scales down.
- The grain overlay, radial glows, floating pills, and bar fills are CSS-only; no external imagery is required.
assets/frame-presets/cartesian/caption-skin.html›
<!--
  Cartesian — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a cartesian literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a museum-catalog lower-third — a warm-stone card on a single 1px taupe
  hairline border (cartesian's universal structural device), SHARP corners, ZERO
  shadow, ZERO fill beyond the stone canvas. Playfair Display 400, sentence case, ink
  glyphs. Upcoming words sit in faint ink; the current word is marked QUIETLY — a faint
  stone wash with a 1px taupe hairline underline (restraint, not a loud block); spoken
  words settle to solid ink, the wash and underline cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE stone card — single 1px taupe hairline border (cartesian's universal
     structural device), SHARP corners, ZERO shadow, ZERO fill beyond the stone canvas */
  .caption-pill {
    max-width: 78%;
    padding: 20px 44px 24px;
    background: var(--cap-canvas, #ede8e0);
    border: 1px solid var(--cap-accent-2, #b8b0a4);
    border-radius: 0;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.1em 0.34em;
    font-family: var(--font-display, "Playfair Display"), Georgia, serif;
    font-weight: 400;
    font-size: clamp(40px, 4.4vw, 58px);
    line-height: 1.16;
    letter-spacing: -0.01em;
  }

  /* upcoming — faint ink, no mark */
  .caption-word {
    display: inline-block;
    padding: 0 0.06em;
    border-bottom: 1px solid transparent;
    color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 42%, var(--cap-canvas, #ede8e0));
  }
  /* current word — a QUIET marker: a faint warm-stone wash behind solid ink, plus a
     1px taupe hairline underline (restraint, not a loud block; SHARP, NO shadow) */
  .caption-word.is-active {
    color: var(--cap-ink, #1a1a1a);
    background: color-mix(in srgb, var(--cap-accent, #8a8178) 18%, var(--cap-canvas, #ede8e0));
    border-bottom: 1px solid var(--cap-accent, #8a8178);
  }
  /* already spoken — solid ink, wash + underline cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #1a1a1a);
    background: transparent;
    border-bottom: 1px solid transparent;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — Playfair Display italic, cartesian's only display inflection */
  .caption-word.cap-brand {
    font-style: italic;
  }
  /* numerals / data — the body sans voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Inter"), ui-sans-serif, sans-serif;
    color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 72%, var(--cap-canvas, #ede8e0));
  }
  /* emphasis — a 1px taupe hairline underline (no second color; stone-and-ink only) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--cap-accent, #8a8178);
    text-underline-offset: 0.12em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.985 }, { scale: 1, duration: 0.2, ease: "power1.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/cartesian/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Cartesian</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;1,400&family=Inter:wght@400;500&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --bg: #ede8e0;
        --bg-2: #e2dbd1;
        --ink: #1a1a1a;
        --gray: #5a5a5a;
        --accent: #8a8178;
        --line: #b8b0a4;
        --white-overlay: rgba(255, 255, 255, 0.3);
        --serif: "Playfair Display", serif;
        --sans: "Inter", sans-serif;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #15140f;
      }
      body {
        background: var(--bg);
        color: var(--gray);
        font-family: var(--sans);
        -webkit-font-smoothing: antialiased;
      }
      .serif {
        font-family: var(--serif);
        font-weight: 400;
        color: var(--ink);
      }
      .label {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 3px;
        font-size: 13px;
        color: var(--accent);
      }
      .micro {
        font-family: var(--sans);
        font-weight: 400;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 12px;
        color: var(--accent);
      }

      section {
        padding: 128px 100px;
        position: relative;
        overflow: hidden;
      }
      .sec-head {
        display: flex;
        align-items: baseline;
        gap: 24px;
        margin-bottom: 72px;
      }
      .sec-head .n {
        font-family: var(--serif);
        font-size: 24px;
        color: var(--accent);
      }
      .sec-head h2 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 48px;
        line-height: 1.1;
        color: var(--ink);
      }
      .sec-head .sp {
        flex: 1;
        height: 1px;
        background: var(--line);
        align-self: center;
      }
      .sec-head .lab {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 3px;
        font-size: 12px;
        color: var(--accent);
      }

      /* geometric decoration */
      .geo {
        position: absolute;
        border: 1px solid var(--line);
        border-radius: 50%;
        pointer-events: none;
      }
      .geo.dash {
        border-style: dashed;
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        padding: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
      }
      .cover .geo-ring {
        position: absolute;
        width: 46vw;
        height: 46vw;
        right: -8vw;
        top: 50%;
        transform: translateY(-50%);
        border: 1px solid var(--line);
        border-radius: 50%;
        opacity: 0.5;
      }
      .cover .geo-ring::before {
        content: "";
        position: absolute;
        inset: 14%;
        border: 1px dashed var(--line);
        border-radius: 50%;
        opacity: 0.7;
      }
      .cover .inner {
        padding: 0 8vw;
        position: relative;
        z-index: 2;
        max-width: 72%;
      }
      .cover .label {
        margin-bottom: 32px;
      }
      .cover h1 {
        font-family: var(--serif);
        font-weight: 400;
        color: var(--ink);
        font-size: clamp(56px, 8vw, 118px);
        line-height: 1.06;
      }
      .cover h1 em {
        font-style: italic;
      }
      .cover .sub {
        font-family: var(--sans);
        font-weight: 400;
        font-size: clamp(16px, 1.5vw, 22px);
        line-height: 1.6;
        color: var(--gray);
        margin-top: 32px;
        max-width: 620px;
      }
      .cover .haccent {
        width: 20vw;
        height: 1px;
        background: var(--ink);
        margin-top: 48px;
      }
      .cover .meta {
        position: absolute;
        left: 8vw;
        bottom: 64px;
        display: flex;
        gap: 48px;
        z-index: 2;
      }
      .cover .meta .m .v {
        font-family: var(--serif);
        font-size: 20px;
        color: var(--ink);
        white-space: nowrap;
      }
      .cover .meta .m .k {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--accent);
        margin-top: 6px;
      }
      .cover .vline {
        position: absolute;
        left: 5vw;
        top: 0;
        bottom: 0;
        width: 1px;
        background: var(--line);
        opacity: 0.4;
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(6, 1fr);
        gap: 28px;
      }
      .sw .chip {
        height: 130px;
        border: 1px solid var(--line);
      }
      .sw .name {
        font-family: var(--serif);
        font-size: 20px;
        color: var(--ink);
        margin-top: 16px;
      }
      .sw .hex {
        font-family: var(--sans);
        font-size: 12px;
        letter-spacing: 1px;
        color: var(--accent);
        margin-top: 6px;
      }
      .sw .role {
        font-size: 12px;
        line-height: 1.5;
        color: var(--gray);
        margin-top: 10px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 220px 1fr;
        align-items: center;
        padding: 26px 0;
        border-bottom: 1px solid var(--line);
      }
      .type-row .tok {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 12px;
        color: var(--accent);
      }
      .type-row .m {
        font-size: 12px;
        color: var(--gray);
        margin-top: 8px;
        line-height: 1.5;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 12px;
        color: var(--accent);
        margin-bottom: 22px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 48px;
        margin-bottom: 64px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .c-card {
        border: 1px solid var(--line);
        background: var(--white-overlay);
        padding: 36px 28px;
      }
      .c-card .ic {
        width: 40px;
        height: 40px;
        border: 1px solid var(--line);
        border-radius: 50%;
        display: grid;
        place-items: center;
        font-family: var(--serif);
        color: var(--accent);
        font-size: 16px;
        margin-bottom: 24px;
      }
      .c-card .t {
        font-family: var(--serif);
        font-size: 21px;
        color: var(--ink);
        line-height: 1.2;
      }
      .c-card .x {
        font-size: 13px;
        line-height: 1.6;
        color: var(--gray);
        margin-top: 12px;
      }
      .agenda .row {
        display: flex;
        align-items: baseline;
        gap: 24px;
        padding: 18px 0;
        border-bottom: 1px solid var(--line);
      }
      .agenda .row .num {
        font-family: var(--serif);
        font-size: 24px;
        color: var(--accent);
        min-width: 48px;
      }
      .agenda .row .t {
        font-family: var(--serif);
        font-size: 22px;
        color: var(--ink);
      }
      .agenda .row .d {
        font-size: 13px;
        color: var(--gray);
        margin-left: auto;
      }
      .timeline {
        border-top: 1px solid var(--line);
        display: flex;
        gap: 40px;
        padding-top: 24px;
      }
      .timeline .it {
        flex: 1;
      }
      .timeline .it .y {
        font-size: 12px;
        letter-spacing: 2px;
        text-transform: uppercase;
        color: var(--accent);
      }
      .timeline .it .t {
        font-family: var(--serif);
        font-size: 19px;
        color: var(--ink);
        margin-top: 8px;
      }
      .timeline .it .x {
        font-size: 12px;
        line-height: 1.5;
        color: var(--gray);
        margin-top: 8px;
      }
      .stats {
        border-top: 1px solid var(--line);
        display: flex;
        gap: 40px;
        padding-top: 24px;
      }
      .stats .st .f {
        font-family: var(--serif);
        font-size: 40px;
        color: var(--ink);
      }
      .stats .st .l {
        font-size: 11px;
        letter-spacing: 2px;
        text-transform: uppercase;
        color: var(--accent);
        margin-top: 8px;
      }
      .imgph {
        aspect-ratio: 4/3;
        background: var(--bg-2);
        border: 1px solid var(--line);
        position: relative;
        display: grid;
        place-items: center;
      }
      .imgph::before,
      .imgph::after {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        width: 150%;
        height: 1px;
        background: var(--line);
        transform-origin: center;
      }
      .imgph::before {
        transform: translate(-50%, -50%) rotate(30deg);
      }
      .imgph::after {
        transform: translate(-50%, -50%) rotate(-30deg);
      }
      .imgph span {
        position: relative;
        z-index: 2;
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--accent);
        background: var(--bg-2);
        padding: 6px 10px;
      }
      .team {
        display: flex;
        gap: 36px;
      }
      .team .p {
        text-align: center;
      }
      .team .ph {
        width: 110px;
        height: 110px;
        border-radius: 50%;
        border: 1px solid var(--line);
        background: var(--bg-2);
        display: grid;
        place-items: center;
        font-family: var(--serif);
        font-size: 34px;
        color: var(--accent);
      }
      .team .nm {
        font-family: var(--serif);
        font-size: 17px;
        color: var(--ink);
        margin-top: 14px;
      }
      .team .ro {
        font-size: 11px;
        letter-spacing: 2px;
        text-transform: uppercase;
        color: var(--accent);
        margin-top: 4px;
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 12px;
        color: var(--accent);
      }
      .fw .flabel b {
        color: var(--ink);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--bg);
        border: 1px solid var(--line);
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 2;
      }
      .frame .fgeo {
        position: absolute;
        border: 1px solid var(--line);
        border-radius: 50%;
        pointer-events: none;
        z-index: 1;
      }
      .frame .fgeo.dash {
        border-style: dashed;
      }

      /* f1 cover */
      .tf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 7cqw;
      }
      .tf1 .lab {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.3cqw;
        font-size: 1cqw;
        color: var(--accent);
        margin-bottom: 2cqw;
      }
      .tf1 h3 {
        font-family: var(--serif);
        font-size: 8cqw;
        line-height: 1.04;
        color: var(--ink);
        max-width: 62cqw;
      }
      .tf1 h3 em {
        font-style: italic;
      }
      .tf1 .sub {
        font-size: 1.4cqw;
        line-height: 1.55;
        color: var(--gray);
        margin-top: 2cqw;
        max-width: 46cqw;
      }
      .tf1 .ha {
        width: 18cqw;
        height: 0.1cqw;
        background: var(--ink);
        margin-top: 3cqw;
      }
      .tf1 .ring {
        width: 34cqw;
        height: 34cqw;
        right: -6cqw;
        top: 50%;
        transform: translateY(-50%);
        opacity: 0.5;
      }
      .tf1 .ring::before {
        content: "";
        position: absolute;
        inset: 16%;
        border: 1px dashed var(--line);
        border-radius: 50%;
      }

      /* f2 agenda (sparse list) */
      .tf2 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 7cqw;
      }
      .tf2 .lab {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.3cqw;
        font-size: 1cqw;
        color: var(--accent);
        margin-bottom: 1.6cqw;
      }
      .tf2 h3 {
        font-family: var(--serif);
        font-size: 4cqw;
        color: var(--ink);
        margin-bottom: 2.4cqw;
      }
      .tf2 .row {
        display: flex;
        align-items: baseline;
        gap: 2cqw;
        padding: 1.3cqw 0;
        border-bottom: 0.06cqw solid var(--line);
      }
      .tf2 .row .nm {
        font-family: var(--serif);
        font-size: 1.8cqw;
        color: var(--accent);
        min-width: 4cqw;
      }
      .tf2 .row .t {
        font-family: var(--serif);
        font-size: 2.2cqw;
        color: var(--ink);
      }

      /* f3 quote */
      .tf3 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        padding: 0 9cqw;
      }
      .tf3 .qm {
        font-family: var(--serif);
        font-size: 9cqw;
        line-height: 0.5;
        color: var(--line);
        height: 5cqw;
      }
      .tf3 .q {
        font-family: var(--serif);
        font-size: 4cqw;
        line-height: 1.2;
        color: var(--ink);
        margin-top: 1cqw;
      }
      .tf3 .at {
        font-family: var(--sans);
        font-weight: 400;
        text-transform: uppercase;
        letter-spacing: 0.25cqw;
        font-size: 1cqw;
        color: var(--accent);
        margin-top: 2.4cqw;
      }
      .tf3 .ring {
        width: 26cqw;
        height: 26cqw;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        opacity: 0.3;
      }

      /* f4 closing */
      .tf4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
      }
      .tf4 .lab {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.3cqw;
        font-size: 1cqw;
        color: var(--accent);
        margin-bottom: 2cqw;
      }
      .tf4 h3 {
        font-family: var(--serif);
        font-size: 7cqw;
        line-height: 1.04;
        color: var(--ink);
      }
      .tf4 h3 em {
        font-style: italic;
      }
      .tf4 .ha {
        width: 14cqw;
        height: 0.1cqw;
        background: var(--ink);
        margin-top: 2.6cqw;
      }
      .tf4 .ring {
        width: 40cqw;
        height: 40cqw;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        opacity: 0.28;
      }
      .tf4 .ring::before {
        content: "";
        position: absolute;
        inset: 18%;
        border: 1px dashed var(--line);
        border-radius: 50%;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .do-card {
        border: 1px solid var(--line);
        background: var(--white-overlay);
        padding: 40px;
      }
      .do-card h4 {
        font-family: var(--serif);
        font-size: 30px;
        color: var(--ink);
        margin-bottom: 24px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-size: 14px;
        line-height: 1.6;
        padding-left: 24px;
        position: relative;
        margin-bottom: 14px;
        color: var(--gray);
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--serif);
        color: var(--accent);
      }
      .do-card.do li::before {
        content: "\2014";
      }
      .do-card.dont li::before {
        content: "\00d7";
      }

      .foot {
        padding: 64px 100px;
        border-top: 1px solid var(--line);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--serif);
        font-size: 26px;
        color: var(--ink);
      }
      .foot .r {
        font-family: var(--sans);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 11px;
        color: var(--accent);
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700;900&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #ede8e0 !important;
        --secondary: #1a1a1a !important;
        --tertiary: #b8b0a4 !important;
        --accent: #8a8178 !important;
        --f-disp: "Playfair Display", sans-serif !important;
        --f-body: "Inter", sans-serif !important;
        --bg: #ede8e0 !important;
        --ink: #1a1a1a !important;
        --accent: #8a8178 !important;
        --line: #b8b0a4 !important;
        --serif: "Playfair Display", sans-serif !important;
        --sans: "Inter", sans-serif !important;
        --bg-2: #e2dbd1 !important;
        --gray: #5a5a5a !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="vline"></div>
      <div class="geo-ring"></div>
      <div class="inner">
        <div class="label">Cartesian — Frame System</div>
        <h1>Restraint,<br />drawn in <em>one line.</em></h1>
        <div class="sub">
          A quiet museum-catalog system in motion — Playfair Display, Inter, and a five-tone
          warm-stone palette, framed by single 1px taupe hairlines and compass-drafted geometry.
        </div>
      </div>
      <div class="meta">
        <div class="m">
          <div class="v">1920 × 1080</div>
          <div class="k">Frame</div>
        </div>
        <div class="m">
          <div class="v">Vol. 01</div>
          <div class="k">Edition</div>
        </div>
        <div class="m">
          <div class="v">Stone / Ink</div>
          <div class="k">Palette</div>
        </div>
      </div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="n">01</span>
        <h2>Palette</h2>
        <span class="sp"></span><span class="lab">Five Stones + Ink</span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--bg)"></div>
          <div class="name">BG Primary</div>
          <div class="hex">#EDE8E0</div>
          <div class="role">Warm sandstone canvas.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--bg-2)"></div>
          <div class="name">BG Secondary</div>
          <div class="hex">#E2DBD1</div>
          <div class="role">Deeper stone for placeholders.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="name">Ink</div>
          <div class="hex">#1A1A1A</div>
          <div class="role">Headlines + the one black rule.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--gray)"></div>
          <div class="name">Gray</div>
          <div class="hex">#5A5A5A</div>
          <div class="role">Body paragraphs.</div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--accent)"></div>
          <div class="name">Accent</div>
          <div class="hex">#8A8178</div>
          <div class="role">Labels, numerals, small text.</div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="
              background: var(--bg);
              border-color: var(--line);
              box-shadow:
                inset 0 0 0 8px var(--bg),
                inset 0 0 0 9px var(--line);
            "
          ></div>
          <div class="name">Line</div>
          <div class="hex">#B8B0A4</div>
          <div class="role">The universal 1px hairline.</div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <span class="n">02</span>
        <h2>Typography</h2>
        <span class="sp"></span><span class="lab">Playfair + Inter</span>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display</div>
          <div class="m">Playfair Display 400 · sentence case</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-size: 84px; line-height: 1.05">Considered.</span>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">h2</div>
          <div class="m">Playfair Display 400 · slide headline</div>
        </div>
        <div class="spec"><span class="serif" style="font-size: 52px">Drawn in one line</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Inter 400 · line 1.6 · warm gray</div>
        </div>
        <div class="spec">
          <p style="font-size: 18px; line-height: 1.6; max-width: 680px; color: var(--gray)">
            Inter carries every paragraph in warm gray — readable, recessive, never competing with
            the serif statement above it.
          </p>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">label</div>
          <div class="m">Inter 500 · 3px track · uppercase</div>
        </div>
        <div class="spec"><span class="label">Section · Eyebrow</span></div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">quote-mark</div>
          <div class="m">Playfair Display 400 · 50% taupe</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-size: 80px; color: var(--line)">&ldquo;</span>
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="n">03</span>
        <h2>Components</h2>
        <span class="sp"></span><span class="lab">1px Hairlines · No Shadow</span>
      </div>

      <div class="cgrid">
        <div class="s4">
          <div class="cnote">Tracing-paper card</div>
          <div class="c-card">
            <div class="ic">I</div>
            <div class="t">Hairline Border</div>
            <div class="x">
              1px taupe outline with a faint white-overlay fill that lets the canvas bleed through.
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Agenda rows</div>
          <div class="agenda">
            <div class="row">
              <span class="num">01</span><span class="t">Restraint</span
              ><span class="d">Stone on stone</span>
            </div>
            <div class="row">
              <span class="num">02</span><span class="t">Geometry</span
              ><span class="d">Compass arcs</span>
            </div>
            <div class="row" style="border-bottom: 0">
              <span class="num">03</span><span class="t">Breathing</span
              ><span class="d">Negative space</span>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Stats cluster</div>
          <div class="stats">
            <div class="st">
              <div class="f">5</div>
              <div class="l">Stones</div>
            </div>
            <div class="st">
              <div class="f">1px</div>
              <div class="l">Lines</div>
            </div>
            <div class="st">
              <div class="f">0</div>
              <div class="l">Shadows</div>
            </div>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s5">
          <div class="cnote">Timeline — 1px rule, no nodes</div>
          <div class="timeline">
            <div class="it">
              <div class="y">Phase 1</div>
              <div class="t">Survey</div>
              <div class="x">Establish the stone canvas.</div>
            </div>
            <div class="it">
              <div class="y">Phase 2</div>
              <div class="t">Draft</div>
              <div class="x">Lay the compass arcs.</div>
            </div>
            <div class="it">
              <div class="y">Phase 3</div>
              <div class="t">Frame</div>
              <div class="x">Hairline the structure.</div>
            </div>
          </div>
        </div>
        <div class="s3">
          <div class="cnote">Image placeholder</div>
          <div class="imgph"><span>Visual Reference</span></div>
        </div>
        <div class="s4">
          <div class="cnote">Team frames</div>
          <div class="team">
            <div class="p">
              <div class="ph">A</div>
              <div class="nm">Avery</div>
              <div class="ro">Editor</div>
            </div>
            <div class="p">
              <div class="ph">M</div>
              <div class="nm">Mori</div>
              <div class="ro">Design</div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <span class="n">04</span>
        <h2>Frame Compositions</h2>
        <span class="sp"></span><span class="lab">True 16:9 · cqw Scaled</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Cover</b><span>· identity · compass ring · left</span></div>
          <div class="frame tf1">
            <div class="fgeo ring"></div>
            <div class="body">
              <div class="lab">Cartesian — Frame System</div>
              <h3>Restraint, drawn in <em>one line.</em></h3>
              <div class="sub">A quiet museum-catalog system in motion.</div>
              <div class="ha"></div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Agenda</b><span>· index · sparse list · left</span></div>
          <div class="frame tf2">
            <div class="body">
              <div class="lab">Contents</div>
              <h3>Four Considerations</h3>
              <div class="row"><span class="nm">01</span><span class="t">On Measure</span></div>
              <div class="row"><span class="nm">02</span><span class="t">On Margin</span></div>
              <div class="row">
                <span class="nm">03</span><span class="t">On the Single Line</span>
              </div>
              <div class="row" style="border-bottom: 0">
                <span class="nm">04</span><span class="t">On Silence</span>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Pull Quote</b><span>· quote · compass ring · centered</span></div>
          <div class="frame tf3">
            <div class="fgeo ring dash"></div>
            <div class="body">
              <div class="qm">&ldquo;</div>
              <div class="q">Nothing is bold.<br />Nothing is loud.</div>
              <div class="at">— The System Voice</div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Closing Plate</b><span>· closer · centered ring</span></div>
          <div class="frame tf4">
            <div class="fgeo ring"></div>
            <div class="body">
              <div class="lab">Cartesian</div>
              <h3>Well-framed, <em>on stone.</em></h3>
              <div class="ha"></div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="n">05</span>
        <h2>Frame Rules</h2>
        <span class="sp"></span><span class="lab">Restraint</span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <ul>
            <li>Use a single 1px taupe line for every separator — the hairline is the identity.</li>
            <li>Set every Playfair headline at weight 400, in ink, sentence case.</li>
            <li>Render labels in taupe, uppercase, 2–3px tracked.</li>
            <li>Layer one or two compass rings (solid + dashed) at 20–50% behind content.</li>
            <li>Let frames breathe — sparse, generous negative space.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <ul>
            <li>No populist accent color — no red, blue, green; stone and ink only.</li>
            <li>No bold Playfair, no taupe headlines, no thick borders.</li>
            <li>No shadows, elevated cards, or rounded rectangles (circles only).</li>
            <li>Don't crowd the frame — packed layouts read as broken.</li>
            <li>Never more than two geo decorations per frame.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--bg);
        --cap-accent: var(--accent);
        --cap-accent-2: var(--line);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        border: 1px solid var(--cap-accent-2);
        border-radius: 0;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.1em 0.34em;
        font-family: var(--serif);
        font-weight: 400;
        font-size: 3.4cqw;
        line-height: 1.16;
        letter-spacing: -0.01em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.06em;
        border-bottom: 1px solid transparent;
        color: color-mix(in srgb, var(--cap-ink) 42%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: color-mix(in srgb, var(--cap-accent) 18%, var(--cap-canvas));
        border-bottom: 1px solid var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 1px dashed var(--line);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--sans);
        text-transform: uppercase;
        letter-spacing: 0.2cqw;
        font-size: 0.95cqw;
        color: var(--accent);
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="n">06</span>
        <h2>Captions</h2>
        <span class="sp"></span><span class="lab">Lower-third · Karaoke</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div
              class="fgeo ring dash"
              style="width: 30cqw; height: 30cqw; left: 8cqw; top: 10%; opacity: 0.3"
            ></div>
            <div class="bandline"></div>
            <div class="bandtag">Caption Band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · marker cleared</span></div>
          <div class="frame capdemo">
            <div
              class="fgeo ring"
              style="
                width: 34cqw;
                height: 34cqw;
                right: -8cqw;
                top: 50%;
                transform: translateY(-50%);
                opacity: 0.28;
              "
            ></div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Cartesian</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#EDE8E0" />
        <circle cx="68" cy="50" r="30" fill="none" stroke="#B8B0A4" />
        <circle cx="68" cy="50" r="20" fill="none" stroke="#B8B0A4" stroke-dasharray="3 3" />
        <rect x="16" y="44" width="40" height="5" fill="#1A1A1A" />
        <rect x="16" y="56" width="22" height="2" fill="#8A8178" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/cartesian/FRAME.md›
---
version: alpha
name: Cartesian — Frame (video / frame layer)
description: >
  Video-first companion to Cartesian's design.md. The unit is the frame (1920×1080). Atoms are
  identical and sacred — the five-tone warm-stone palette, Playfair Display 400 + Inter, the
  universal 1px taupe hairline as the only structural device, compass-drafted geometric rings,
  and zero shadow / zero fill. Composition, frame scale, and aspect-ratio behavior are rewritten
  for the frame. Restraint is the rule; motion is out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  bg-primary: "#EDE8E0"
  bg-secondary: "#E2DBD1"
  text-primary: "#1A1A1A"
  text-secondary: "#5A5A5A"
  accent: "#8A8178"
  line: "#B8B0A4"
  white-overlay: "rgba(255,255,255,0.3)"

typography:
  # — reading ramp (Inter) —
  body:        { fontFamily: "Inter", cqw: 1.0,  weight: 400, lineHeight: 1.6, color: "text-secondary" }
  body-sm:     { fontFamily: "Inter", cqw: 0.85, weight: 400, lineHeight: 1.6 }
  subtitle:    { fontFamily: "Inter", cqw: 1.3,  weight: 400, lineHeight: 1.5 }
  label:       { fontFamily: "Inter", px: 14, weight: 500, tracking: "3px", upper: true, color: "accent" }
  attribution: { fontFamily: "Inter", px: 15, weight: 400, tracking: "2px", upper: true, color: "accent" }
  micro:       { fontFamily: "Inter", px: 12, weight: 400, tracking: "2px", upper: true, color: "accent" }
  # — display / hero ramp (Playfair Display 400, sentence case) —
  h3:          { fontFamily: "Playfair Display", cqw: 1.8, weight: 400, lineHeight: 1.1 }
  timeline-headline:{ fontFamily: "Playfair Display", cqw: 1.9, weight: 400, lineHeight: 1.1 }
  card-headline:{ fontFamily: "Playfair Display", cqw: 2.0, weight: 400, lineHeight: 1.15 }
  stat-figure: { fontFamily: "Playfair Display", cqw: 3.0, weight: 400, lineHeight: 1.0 }
  quote-mark:  { fontFamily: "Playfair Display", cqw: 9.0, weight: 400, lineHeight: 0.5, color: "line" }
  h2:          { fontFamily: "Playfair Display", cqw: 4.0, weight: 400, lineHeight: 1.1 }
  h1:          { fontFamily: "Playfair Display", cqw: 6.2, weight: 400, lineHeight: 1.06 }
  display:     { fontFamily: "Playfair Display", cqw: 8.0, weight: 400, lineHeight: 1.04 }

spacing:
  pad-x: "7cqw"
  pad-y: "5cqw"
  gap-xl: "6cqw"
  gap-lg: "5cqw"

components:
  hairline:
    rule: "0.07cqw solid {colors.line}"
    description: "The universal structural device — every separator (agenda rule, timeline connector, card border, stats top) is this 1px taupe line. No thick borders exist."
  card:
    backgroundColor: "{colors.white-overlay}"
    border: "0.07cqw solid {colors.line}"
    rounded: "0"
    shadow: "none"
    description: "Faint white-overlay fill (canvas bleeds through) is what makes a card distinct from a bare region."
  card-icon:
    border: "0.07cqw solid {colors.line}"
    rounded: "50%"
    size: "40px circle"
    textColor: "{colors.accent}"
    typography: "Playfair numeral/letter"
    description: "Ringed circle mark."
  agenda-row:
    borderBottom: "0.07cqw solid {colors.line}"
    typography: "{typography.h3} (numeral {colors.accent}, label {colors.text-primary})"
    description: "Numeral left, label right over a taupe rule."
  timeline:
    borderTop: "0.07cqw solid {colors.line}"
    typography: "{typography.timeline-headline} + {typography.body-sm}"
    description: "A single taupe top rule across items — no nodes, no dots."
  stats-cluster:
    borderTop: "0.07cqw solid {colors.line}"
    typography: "{typography.stat-figure} + uppercase {colors.accent} labels"
    description: "Inline modest stat figures (no hero numeral)."
  geo-ring:
    border: "1px solid {colors.line} (inner dashed ::before ~70–80%)"
    rounded: "50%"
    size: "10–50cqw"
    opacity: "0.2–0.5"
    description: "Compass-construction rings behind content. 1–2 per frame, never more."
  horizontal-accent:
    backgroundColor: "{colors.text-primary}"
    size: "~18cqw × 1px"
    description: "The system's only INK-BLACK rule — a strong terminal accent on cover/closing, sparingly."
  vertical-line:
    backgroundColor: "{colors.line}"
    size: "1px × full height, ~5cqw from edge"
    opacity: "0.3–0.4"
    description: "Drafting-paper alignment guide. Optional."
  image-placeholder:
    backgroundColor: "{colors.bg-secondary}"
    mark: "crossed +30°/−30° 1px taupe diagonals (an X)"
    typography: "small uppercase {typography.micro}"
    description: "The signature image-not-wired mark."
  team-photo:
    backgroundColor: "{colors.bg-secondary}"
    border: "0.07cqw solid {colors.line}"
    rounded: "50%"
    typography: "Playfair initial in {colors.accent}"
    description: "Circular portrait frame."
---

# Cartesian — Frame (video / frame layer)

## Overview

Cartesian at frame scale is a **quiet museum-catalog editorial system** — restraint through 1px
lines. Every structural separator is a single 1px taupe hairline; there are no thick borders, no
fills (save the faint white-overlay card), no shadows, no rounded rectangles. Hierarchy comes from
**type contrast and negative space**, and atmosphere from **compass-drafted geometric rings**
drifting behind content.

The voice is a literary pairing: **Playfair Display** at weight 400 (the thin-stroke didone, never
bold, always sentence case) carries every headline, numeral, and quote mark in ink; **Inter**
carries body in warm gray and labels in uppercase taupe with 2–3px tracking. The palette is five
warm stones plus ink — no populist accent color exists. The correct density is **sparse and
breathing**: one clear idea, well-framed, on stone paper.

**Key characteristics at frame scale:**

- **1px taupe hairline** as the universal structural device — every separator is this one line.
- **Playfair Display 400** (ink, sentence case) for display; **Inter** body (gray) + labels (taupe, tracked).
- **Five warm stones + ink** — no red/blue/green; the only "color" is type contrast.
- **Compass-drafted geometric rings** (solid + dashed, 20–50% opacity) behind content for mood.
- **Flat** — zero shadow, zero rounded rectangle (circles only); the lone ink line is the `horizontal-accent`.
- **Sparse and breathing** — generous negative space; crowding reads as broken.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one Playfair element dominates at **3–6× its nearest neighbor**; the serif/sans + size contrast carries hierarchy, not weight.
- **Silence** — declarative frames read **55–60% empty**; Cartesian has **no dense frame** — even the agenda/index breathes (the system breaks when crowded).
- **Restraint** — **at most two geo rings** per frame; the single INK-BLACK `horizontal-accent` rule used sparingly; no populist accent color ever.
- **Reference** — aim at a **Vignelli editorial / Cooper Hewitt catalogue / pencil-and-tracing-paper plan**; failure looks like a **shadowed, rounded-card SaaS deck**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-x` (7cqw) generous gutters; geometry may bleed off an edge.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. The 1px hairlines and geo rings hold
their proportion against the frame at any render size.

## Colors

Tokens identical to the source. `{colors.bg-primary}` is the ground; `{colors.text-primary}` ink is
headlines and the one black accent rule; `{colors.text-secondary}` gray is body; `{colors.accent}`
taupe is labels, numerals, small text; `{colors.line}` taupe is every 1px structural border.
`{colors.bg-secondary}` is the only secondary fill (placeholders, photo frames). **No populist
accent** — when emphasis is needed, grow the type, switch sans→serif, or add a single
`horizontal-accent` ink line. Headlines are never taupe; small text is never ink.

## Typography

Two ramps. The **reading ramp** (Inter body 1.0cqw gray, labels in px taupe) carries copy + chrome;
the **display ramp** (Playfair `h3` 1.8cqw → `display` 8.0cqw, all weight 400) carries every headline.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`/`h1`; 4–6 → `h2`; 7+ → `h3`. Cartesian has no hero-stat numeral — stats stay modest (`stat-figure` 3cqw).
- **Playfair at 400, ink, sentence case** — never bold, never uppercase, never taupe. **Inter labels uppercase, 2–3px tracked, taupe.** Italic via Playfair italic for emphasis only.

## Depth & Surface

The flat plane is the only technique. Hierarchy from:

- **Type contrast** — Playfair serif vs Inter sans; the 8cqw→0.7cqw scale.
- **1px taupe hairlines** — every divider, card outline, timeline rule, photo ring.
- **Tone** — ink vs gray vs taupe.
- **Negative space** — generous padding.
- **Geometric atmosphere** — compass rings that suggest depth without creating it.

**Ceiling:** no box-shadow, no elevated card, no gradient, no rounded rectangle. The single ink line
(`horizontal-accent`) is the only non-taupe rule.

## Shapes

- **50% (circle)** — card-icon, team-photo, nav-dot, every geo ring.
- **0** — everything else; soft-rounded corners do not exist.

## Components

- **hairline** — the universal 1px taupe separator (the identity).
- **card** (1px taupe + white-overlay) / **card-icon** (ringed circle) / **agenda-row** / **timeline** (line, no nodes) / **stats-cluster** — all built on the hairline.
- **geo-ring** — compass decoration (solid + dashed), 1–2 per frame. **horizontal-accent** — the one ink line, sparingly. **vertical-line** — drafting guide.
- **image-placeholder** (crossed-X) / **team-photo** (ringed initial) — the stone-fill placeholders.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> Every frame is sparse and breathing; 1–2 geo decorations max.

### 1 · Cover (identity · move: serif + compass ring · left)

**Ground** `{colors.bg-primary}`, `pad-x`. **Composes** geo-ring (right, ~34cqw, solid+dashed), vertical-line (left), label, display/h1. **Focal** a 2–3 line Playfair `display`/`h1` headline in ink (italic on the key word), left-anchored, with a taupe `label` above and an Inter subtitle below. **Chrome** optional bottom meta row (Playfair value + taupe label). **Accent** the geo ring; optionally one `horizontal-accent` ink line (if it clears the meta). **Silence** ~55% empty. **Fixed** Playfair 400 ink sentence-case, ≤2 geo elements, 1px lines. **Free** title, ring placement, meta. **Density** sparse.

### 2 · Agenda / Index (index · move: hairline list · left)

**Ground** `{colors.bg-primary}`, `pad-x`. **Composes** label, h2, agenda-rows. **Focal** a Playfair `h2` over 4–6 agenda rows (Playfair numeral in taupe + Playfair label in ink), each on a 1px taupe rule. **Chrome** taupe `label` eyebrow. **Accent** none — taupe numerals carry it. **Silence** moderate; rows generously spaced. **Fixed** 1px taupe rules, Playfair 400. **Free** items, count. **Density** standard (sparse rows).

### 3 · Pull Quote (quote · move: centered statement · compass ring)

**Ground** `{colors.bg-primary}`, centered. **Composes** geo-ring (centered dashed, ~26cqw), quote-mark, h2/display-quote, attribution. **Focal** a 2-line Playfair quote in ink, centered, under a 50%-taupe Playfair quote-mark; a taupe uppercase attribution beneath. **Accent** the faint centered ring. **Silence** ~60% — deliberately open. **Fixed** Playfair 400, one ring, centered. **Free** quote, attribution. **Density** sparse.

### 4 · Closing Plate (closer · move: centered ring · centered)

**Ground** `{colors.bg-primary}`, centered. **Composes** geo-ring (centered, ~40cqw, solid+dashed), label, h1/display, horizontal-accent. **Focal** a 1–2 line Playfair sign-off in ink (italic key word), centered inside the largest compass ring; a short ink `horizontal-accent` beneath. **Accent** the ring + the one ink line. **Silence** ~60%. **Fixed** Playfair 400, centered, ≤2 geo. **Free** sign-off, ring scale. **Density** sparse.

### 5 · Two-Column Editorial (content · move: asymmetric split · left)

**Ground** `{colors.bg-primary}`, `pad-x`, two columns with `gap-xl`. **Composes** label, h2, body, image-placeholder (crossed-X) or card. **Focal** a Playfair `h2` + Inter body in the text column; an image-placeholder or card in the other. **Accent** none. **Silence** generous gutter. **Fixed** 1px taupe card/placeholder borders, white-overlay fill. **Free** which side is text, body copy. **Density** standard.

### 6 · Stats / Timeline (data · move: hairline rail · left)

**Ground** `{colors.bg-primary}`, `pad-x`. **Composes** label, h3, stats-cluster or timeline. **Focal** a modest Playfair stat row (or a 1px-rule timeline with year + headline + body, no nodes), framed by a 1px taupe top rule. **Accent** none. **Silence** moderate. **Fixed** modest stat scale, hairline rule, no nodes. **Free** figures, phases. **Density** standard.

## Composition Rules

### Do

- Use a **single 1px taupe line** for every separator — the hairline is the identity.
- Set every **Playfair headline at 400, ink, sentence case**; render labels taupe, uppercase, 2–3px tracked.
- Layer **one or two compass rings** (solid + dashed, 20–50% opacity) behind content for atmosphere.
- Let frames **breathe** — sparse, generous negative space; 55–60% empty on declarative frames.
- Lean centered on quote/closer; asymmetric/left on cover/agenda/editorial. Use `bg-secondary` for placeholder fills.

### Don't

- Don't introduce a populist accent color — stone and ink only.
- Don't bold Playfair, render headlines in taupe, or use thick (2px+) borders.
- Don't add shadows, elevated cards, or rounded rectangles (circles only).
- Don't crowd the frame — packed layouts read as broken.
- Never more than two geo decorations per frame; don't blow a headline edge-to-edge.

## Aspect-Ratio Behavior

| Treatment            | 16:9                       | 9:16                                | 1:1                         |
| -------------------- | -------------------------- | ----------------------------------- | --------------------------- |
| Cover                | headline left, ring right  | headline top, ring below            | headline upper, ring behind |
| Agenda / Index       | h2 + rows                  | h2 + rows (tighter)                 | h2 + rows                   |
| Pull Quote           | centered, ring behind      | centered, taller                    | centered                    |
| Closing Plate        | centered in ring           | centered, ring scaled               | centered                    |
| Two-Column Editorial | text + visual side-by-side | stacked (collapse)                  | stacked                     |
| Stats / Timeline     | horizontal rail            | vertical stack (drop timeline rule) | compact                     |

Generous `pad-x` holds on the short edge; re-step display per ratio above the 1.4cqw floor. On
9:16, the timeline rule loses meaning when stacked — switch to a vertical list (per source).

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder (the crossed-X image-placeholder, or a ringed initial for portraits).

## Numerals & Claims (hard rule)

Never invent figures, dates, or counts at frame scale. Render slots as `— figure —`, `{metric}`.
Stats and timeline years carry placeholders until the script supplies them. Agenda ordinals
(01, 02…) are decorative and may be sequential.

## Pre-Render Self-Audit

- **Squint** — one Playfair element dominates; the serif/sans contrast carries hierarchy.
- **Silence** — declarative frames 55–60% empty; nothing is crowded.
- **Palette** — five stones + ink only; no populist accent; headlines ink, labels taupe.
- **Lines** — every separator is a 1px taupe hairline; the only ink line is `horizontal-accent`.
- **Type** — Playfair 400 sentence-case, fit-to-measure; labels uppercase 2–3px; ≥1.4cqw floor.
- **Depth** — 0 shadow, 0 rounded rectangle; ≤2 geo rings per frame.
- **Anchor** — centered on quote/closer, left/asymmetric on cover/agenda/editorial; no 3 in a row alike.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the 0.6s fade in the source is a deck mechanic.
- **Playfair Display + Inter via Google Fonts.** CJK pairing (Noto Serif SC 700/400) carries over; Playfair has no Hanzi italic — substitute weight/taupe for emphasis.
- **9:16 / 1:1 are guidance**; verify the legibility floor and that the timeline collapses to a vertical list.
- Geo rings, the crossed-X placeholder, and the dashed inner ring are CSS-only; no external imagery is required.
assets/frame-presets/cobalt-grid/caption-skin.html›
<!--
  Cobalt Grid — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a cobalt literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a cream-paper card carrying the permanent graph-paper grid behind the line,
  framed by top + bottom 1.5px cobalt hairlines (cobalt's signature frame furniture) —
  square corners, NO drop shadow (cobalt's depth is structural and flat, never cast).
  Newsreader serif 400, sentence case, cobalt glyphs. Upcoming words sit in faint
  cobalt; the current word is a hard COBALT FLAT BLOCK — a risograph knockout: a solid
  cobalt fill (box-shadow spread = a square block, no blur, no layout shift) with the
  cream paper reading on top. Spoken words settle to solid cobalt ink, the block cleared.
  Strictly two-color throughout — cream + cobalt, never a second hue.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE cream card — graph-paper grid behind the line + top/bottom 1.5px cobalt
     hairlines (cobalt's frame furniture), square corners, NO drop shadow */
  .caption-pill {
    position: relative;
    max-width: 78%;
    padding: 20px 44px 24px;
    background: var(--cap-canvas, #f0ebde);
    background-image:
      linear-gradient(
        to right,
        color-mix(in srgb, var(--cap-ink, #1f2be0) 10%, transparent) 1px,
        transparent 1px
      ),
      linear-gradient(
        to bottom,
        color-mix(in srgb, var(--cap-ink, #1f2be0) 10%, transparent) 1px,
        transparent 1px
      );
    background-size: 16px 16px;
    border-top: 1.5px solid var(--cap-ink, #1f2be0);
    border-bottom: 1.5px solid var(--cap-ink, #1f2be0);
    border-radius: 0;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.1em 0.34em;
    font-family: var(--font-display, "Newsreader"), Georgia, serif;
    font-weight: 400;
    font-size: clamp(40px, 4.4vw, 58px);
    line-height: 1.16;
    letter-spacing: -0.01em;
  }

  /* upcoming — faint cobalt, no block */
  .caption-word {
    display: inline-block;
    padding: 0 0.06em;
    color: color-mix(in srgb, var(--cap-ink, #1f2be0) 42%, var(--cap-canvas, #f0ebde));
  }
  /* current word — THE cobalt flat block (risograph knockout): a solid cobalt fill
     via box-shadow spread = a hard square block w/o layout shift, no blur; the cream
     paper reads on top */
  .caption-word.is-active {
    color: var(--cap-canvas, #f0ebde);
    background: var(--cap-ink, #1f2be0);
    box-shadow: 0 0 0 0.06em var(--cap-ink, #1f2be0);
  }
  /* already spoken — solid cobalt ink, block cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #1f2be0);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — Newsreader italic, cobalt's only display inflection */
  .caption-word.cap-brand {
    font-style: italic;
  }
  /* numerals / data — the body sans voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Hanken Grotesk"), ui-monospace, monospace;
    color: color-mix(in srgb, var(--cap-ink, #1f2be0) 72%, var(--cap-canvas, #f0ebde));
  }
  /* emphasis — a cobalt hairline underline (no second hue; emphasis is the ink) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--cap-accent, #5560e5);
    text-underline-offset: 0.1em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.985 }, { scale: 1, duration: 0.1, ease: "power3.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/cobalt-grid/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Cobalt Grid</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;1,6..72,400&family=Hanken+Grotesk:wght@400;600&family=DM+Mono:wght@400&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --paper: #f0ebde;
        --paper-2: #e6e0ce;
        --ink: #1f2be0;
        --ink-soft: #5560e5;
        --grid: rgba(31, 43, 224, 0.1);
        --ink-faint: rgba(31, 43, 224, 0.18);
        --serif: "Newsreader", Georgia, serif;
        --sans: "Hanken Grotesk", sans-serif;
        --mono: "DM Mono", ui-monospace, monospace;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #0c0c14;
      }
      body {
        background: var(--paper);
        background-image:
          linear-gradient(to right, var(--grid) 1px, transparent 1px),
          linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
        background-size: 40px 40px;
        color: var(--ink);
        font-family: var(--sans);
        -webkit-font-smoothing: antialiased;
      }
      .serif {
        font-family: var(--serif);
        font-weight: 400;
      }
      .micro {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
      }
      .mono {
        font-family: var(--mono);
        letter-spacing: 0.05em;
      }

      section {
        padding: 110px 80px;
        position: relative;
      }
      .sec-head {
        display: flex;
        align-items: baseline;
        gap: 20px;
        border-bottom: 1.5px solid var(--ink);
        padding-bottom: 16px;
        margin-bottom: 56px;
      }
      .sec-head .tag {
        font-family: var(--mono);
        font-size: 13px;
        letter-spacing: 0.05em;
      }
      .sec-head h2 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 54px;
        line-height: 0.95;
        letter-spacing: -0.01em;
      }
      .sec-head .sp {
        flex: 1;
      }
      .sec-head .lab {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 12px;
      }

      /* pixel-glitch column (CSS, stair-stepped scanline blocks) */
      .glitch {
        position: absolute;
        top: 0;
        bottom: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: 0;
        pointer-events: none;
      }
      .glitch .step {
        background-image: repeating-linear-gradient(90deg, var(--ink) 0 2px, transparent 2px 8px);
        background-color: var(--paper);
      }
      /* QR block */
      .qr {
        display: grid;
        grid-template-columns: repeat(8, 1fr);
        grid-template-rows: repeat(8, 1fr);
        gap: 1.5px;
        background: var(--paper);
        padding: 4px;
        box-shadow: 0 0 0 1.5px var(--paper);
      }
      .qr i {
        background: var(--grid);
      }
      .qr i.on {
        background: var(--ink);
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        padding: 0;
        position: relative;
        overflow: hidden;
        display: flex;
        flex-direction: column;
      }
      .cover .hair-t {
        position: absolute;
        left: 80px;
        right: 80px;
        top: 28px;
        height: 1.5px;
        background: var(--ink);
      }
      .cover .hair-b {
        position: absolute;
        left: 80px;
        right: 80px;
        bottom: 30px;
        height: 1.5px;
        background: var(--ink);
      }
      .cover .top {
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        padding: 64px 80px 0;
      }
      .cover .top .lab {
        font-family: var(--mono);
        font-size: 14px;
        letter-spacing: 0.05em;
      }
      .cover .stage {
        flex: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 80px;
        position: relative;
        z-index: 4;
      }
      .cover .kick {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.18em;
        font-size: 15px;
        margin-bottom: 28px;
      }
      .cover h1 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: clamp(58px, 8.2vw, 142px);
        line-height: 0.92;
        letter-spacing: -0.01em;
        max-width: 66%;
      }
      .cover .ed {
        font-family: var(--serif);
        font-weight: 400;
        font-style: italic;
        font-size: clamp(22px, 2.5vw, 38px);
        line-height: 1.15;
        color: var(--ink-soft);
        margin-top: 22px;
        max-width: 52%;
      }
      .cover .foot {
        display: flex;
        justify-content: space-between;
        padding: 0 80px 64px;
        font-family: var(--mono);
        font-size: 13px;
        letter-spacing: 0.05em;
        position: relative;
        z-index: 4;
      }
      .cover .glitch {
        right: 0;
        width: 30vw;
        opacity: 0.7;
        z-index: 2;
      }
      .cover .qrwrap {
        position: absolute;
        top: 124px;
        right: 80px;
        width: 88px;
        height: 88px;
        z-index: 5;
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 0;
        border: 1.5px solid var(--ink);
      }
      .sw {
        border-right: 1.5px solid var(--ink);
        border-bottom: 1.5px solid var(--ink);
      }
      .sw:nth-child(3n) {
        border-right: 0;
      }
      .sw:nth-child(n + 4) {
        border-bottom: 0;
      }
      .sw .chip {
        height: 140px;
        border-bottom: 1.5px solid var(--ink);
      }
      .sw .meta {
        padding: 18px 22px;
        background: var(--paper);
      }
      .sw .name {
        font-family: var(--serif);
        font-size: 28px;
        line-height: 1;
      }
      .sw .hex {
        font-family: var(--mono);
        font-size: 12px;
        margin-top: 6px;
      }
      .sw .role {
        font-size: 13px;
        line-height: 1.5;
        color: var(--ink);
        margin-top: 10px;
        font-family: var(--sans);
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 240px 1fr;
        border-bottom: 1px solid var(--ink-faint);
        align-items: center;
        padding: 8px 0;
      }
      .type-row .lbl .tok {
        font-family: var(--mono);
        font-size: 13px;
      }
      .type-row .lbl .m {
        font-family: var(--sans);
        font-size: 11px;
        color: var(--ink-soft);
        margin-top: 6px;
        line-height: 1.5;
      }
      .type-row .spec {
        padding: 20px 0 20px 36px;
        border-left: 1px solid var(--ink-faint);
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 12px;
        margin-bottom: 18px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 36px;
        margin-bottom: 48px;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .s8 {
        grid-column: span 8;
      }
      /* topbar */
      .topbar {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        border-bottom: 1.5px solid var(--ink);
        padding-bottom: 14px;
      }
      .topbar .h {
        font-family: var(--serif);
        font-size: 40px;
        line-height: 1;
      }
      .topbar .t {
        font-family: var(--mono);
        font-size: 13px;
      }
      /* ledger */
      .ledger {
        margin-top: 8px;
      }
      .lrow {
        display: grid;
        grid-template-columns: 64px 0.7fr 1.5fr 0.6fr;
        gap: 24px;
        padding: 16px 0;
        border-bottom: 1px solid var(--ink-faint);
        align-items: baseline;
      }
      .lrow.head {
        border-bottom: 1.5px solid var(--ink);
      }
      .lrow .num {
        font-family: var(--mono);
        font-size: 14px;
      }
      .lrow .nm {
        font-family: var(--serif);
        font-size: 24px;
        line-height: 1.1;
      }
      .lrow .ds {
        font-family: var(--sans);
        font-size: 14px;
        color: var(--ink);
        line-height: 1.45;
      }
      .lrow .dl {
        font-family: var(--mono);
        font-size: 14px;
        text-align: right;
      }
      .lrow.head span {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 11px;
      }
      /* pixel stack chart */
      .pchart {
        display: flex;
        align-items: flex-end;
        gap: 14px;
        height: 200px;
        border-bottom: 1.5px solid var(--ink);
        padding-bottom: 0;
      }
      .pstack {
        display: flex;
        flex-direction: column-reverse;
        gap: 3px;
        flex: 1;
      }
      .pstack .cell {
        height: 12px;
        background: var(--grid);
      }
      .pstack .cell.on {
        background: var(--ink);
      }
      .pticks {
        display: flex;
        gap: 14px;
        margin-top: 10px;
      }
      .pticks span {
        flex: 1;
        text-align: center;
        font-family: var(--mono);
        font-size: 12px;
      }
      /* vertical stack labels */
      .vstack {
        display: flex;
        gap: 28px;
        font-family: var(--mono);
        font-size: 14px;
      }
      .vstack span {
        writing-mode: vertical-rl;
        text-orientation: mixed;
        letter-spacing: 0.04em;
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 12px;
        color: var(--ink-soft);
      }
      .fw .flabel b {
        color: var(--ink);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--paper);
        background-image:
          linear-gradient(to right, var(--grid) 1px, transparent 1px),
          linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
        background-size: 2cqw 2cqw;
        box-shadow: 0 0 0 1px var(--ink-faint);
      }
      .frame .ht {
        position: absolute;
        left: 4cqw;
        right: 4cqw;
        top: 3cqw;
        height: 0.12cqw;
        background: var(--ink);
        z-index: 5;
      }
      .frame .hb {
        position: absolute;
        left: 4cqw;
        right: 4cqw;
        bottom: 3cqw;
        height: 0.12cqw;
        background: var(--ink);
        z-index: 5;
      }
      .frame .pg {
        position: absolute;
        right: 4cqw;
        bottom: 4cqw;
        font-family: var(--mono);
        font-size: 1cqw;
        z-index: 6;
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 4;
      }

      /* f1 cover */
      .cf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .cf1 .kick {
        font-family: var(--sans);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.2cqw;
        font-size: 1.1cqw;
        margin-bottom: 2cqw;
      }
      .cf1 h3 {
        font-family: var(--serif);
        font-size: 11cqw;
        line-height: 0.9;
        letter-spacing: -0.1cqw;
        max-width: 62cqw;
      }
      .cf1 .ed {
        font-family: var(--serif);
        font-style: italic;
        font-size: 2.6cqw;
        color: var(--ink-soft);
        margin-top: 1.6cqw;
        max-width: 50cqw;
      }
      .cf1 .glitch {
        right: 0;
        width: 26cqw;
        z-index: 2;
        opacity: 0.7;
      }
      .cf1 .qr {
        position: absolute;
        top: 6cqw;
        right: 5cqw;
        width: 9cqw;
        height: 9cqw;
        z-index: 6;
        gap: 0.2cqw;
        padding: 0.4cqw;
      }

      /* f2 index ledger (dense) */
      .cf2 .body {
        padding: 7cqw 5cqw 6cqw;
        display: flex;
        flex-direction: column;
      }
      .cf2 .tb {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        border-bottom: 0.12cqw solid var(--ink);
        padding-bottom: 1cqw;
        margin-bottom: 2.2cqw;
      }
      .cf2 .tb .h {
        font-family: var(--serif);
        font-size: 3.8cqw;
        line-height: 1;
        white-space: nowrap;
      }
      .cf2 .tb .t {
        font-family: var(--mono);
        font-size: 1.1cqw;
      }
      .cf2 .r {
        display: grid;
        grid-template-columns: 5cqw 1fr;
        gap: 2cqw;
        padding: 1.1cqw 0;
        border-bottom: 0.06cqw solid var(--ink-faint);
        align-items: baseline;
      }
      .cf2 .r .n {
        font-family: var(--mono);
        font-size: 1.2cqw;
      }
      .cf2 .r .nm {
        font-family: var(--serif);
        font-size: 2.4cqw;
        line-height: 1;
      }
      .cf2 .r .dx {
        font-family: var(--sans);
        font-size: 1.1cqw;
        color: var(--ink);
        margin-top: 0.4cqw;
        line-height: 1.4;
      }

      /* f3 chapter (sparse) */
      .cf3 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 6cqw;
      }
      .cf3 .ix {
        font-family: var(--mono);
        font-size: 1.4cqw;
        margin-bottom: 2cqw;
      }
      .cf3 h3 {
        font-family: var(--serif);
        font-size: 9cqw;
        line-height: 0.98;
        letter-spacing: -0.05cqw;
      }
      .cf3 .lede {
        font-family: var(--sans);
        font-size: 1.5cqw;
        line-height: 1.5;
        color: var(--ink);
        max-width: 42cqw;
        margin-top: 2.4cqw;
      }
      .cf3 .glitch {
        left: 0;
        width: 14cqw;
        z-index: 2;
        opacity: 0.5;
      }

      /* f4 data chart */
      .cf4 .body {
        padding: 7cqw 5cqw 6cqw;
        display: flex;
        flex-direction: column;
      }
      .cf4 .tb {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        border-bottom: 0.12cqw solid var(--ink);
        padding-bottom: 1cqw;
        margin-bottom: 2.4cqw;
      }
      .cf4 .tb .h {
        font-family: var(--serif);
        font-size: 4.2cqw;
        line-height: 1;
      }
      .cf4 .tb .t {
        font-family: var(--mono);
        font-size: 1.1cqw;
      }
      .cf4 .chart {
        display: flex;
        align-items: flex-end;
        gap: 1.4cqw;
        flex: 1;
        border-bottom: 0.12cqw solid var(--ink);
      }
      .cf4 .st {
        display: flex;
        flex-direction: column-reverse;
        gap: 0.4cqw;
        flex: 1;
      }
      .cf4 .st .c {
        height: 1.4cqw;
        background: var(--grid);
      }
      .cf4 .st .c.on {
        background: var(--ink);
      }
      .cf4 .tk {
        display: flex;
        gap: 1.4cqw;
        margin-top: 1cqw;
      }
      .cf4 .tk span {
        flex: 1;
        text-align: center;
        font-family: var(--mono);
        font-size: 1cqw;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0;
        border: 1.5px solid var(--ink);
      }
      .do-card {
        padding: 40px;
      }
      .do-card.do {
        border-right: 1.5px solid var(--ink);
      }
      .do-card h4 {
        font-family: var(--serif);
        font-size: 36px;
        margin-bottom: 24px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-size: 15px;
        line-height: 1.55;
        padding-left: 24px;
        position: relative;
        margin-bottom: 14px;
        color: var(--ink);
        font-family: var(--sans);
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--mono);
      }
      .do-card.do li::before {
        content: "+";
      }
      .do-card.dont li::before {
        content: "\2715";
        opacity: 0.55;
      }

      .foot {
        padding: 56px 80px;
        border-top: 1.5px solid var(--ink);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--serif);
        font-size: 30px;
      }
      .foot .r {
        font-family: var(--mono);
        font-size: 13px;
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,400;0,500;0,700;1,400&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #f0ebde !important;
        --secondary: #1f2be0 !important;
        --tertiary: #e6e0ce !important;
        --accent: #5560e5 !important;
        --f-disp: "Newsreader", sans-serif !important;
        --f-body: "Hanken Grotesk", sans-serif !important;
        --paper: #f0ebde !important;
        --paper-2: #e6e0ce !important;
        --ink: #1f2be0 !important;
        --ink-soft: #5560e5 !important;
        --serif: "Newsreader", sans-serif !important;
        --sans: "Hanken Grotesk", sans-serif !important;
        --grid: rgba(31, 43, 224, 0.1) !important;
        --ink-faint: rgba(31, 43, 224, 0.18) !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="hair-t"></div>
      <div class="hair-b"></div>
      <div class="glitch">
        <div class="step" style="width: 60%; height: 18%; align-self: flex-end"></div>
        <div class="step" style="width: 100%; height: 16%; align-self: flex-end"></div>
        <div class="step" style="width: 44%; height: 14%; align-self: flex-end"></div>
        <div class="step" style="width: 78%; height: 20%; align-self: flex-end"></div>
        <div class="step" style="width: 34%; height: 12%; align-self: flex-end"></div>
      </div>
      <div class="qrwrap">
        <div class="qr">
          <i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
          ><i></i><i class="on"></i> <i></i><i class="on"></i><i></i><i class="on"></i
          ><i class="on"></i><i></i><i class="on"></i><i></i> <i class="on"></i><i></i
          ><i class="on"></i><i></i><i></i><i class="on"></i><i></i><i class="on"></i> <i></i
          ><i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
          ><i></i> <i class="on"></i><i></i><i class="on"></i><i></i><i class="on"></i><i></i
          ><i class="on"></i><i></i> <i></i><i class="on"></i><i></i><i class="on"></i><i></i
          ><i class="on"></i><i></i><i class="on"></i> <i class="on"></i><i></i><i class="on"></i
          ><i class="on"></i><i></i><i class="on"></i><i></i><i></i> <i></i><i class="on"></i><i></i
          ><i></i><i class="on"></i><i></i><i class="on"></i><i class="on"></i>
        </div>
      </div>
      <div class="top">
        <span class="lab">COBALT GRID / TREND REPORT</span>
        <span class="lab">VOL.01 — 2026</span>
      </div>
      <div class="stage">
        <div class="kick">Two-Color Risograph Monograph</div>
        <h1>The Grid Is the Canvas.</h1>
        <div class="ed">
          Cream paper, electric cobalt, and graph-paper drafting tone behind every frame.
        </div>
      </div>
      <div class="foot">
        <span>FRAME SYSTEM — NEWSREADER · HANKEN · DM MONO</span>
        <span>01 / 06</span>
      </div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="tag">01</span>
        <h2>Palette</h2>
        <span class="sp"></span><span class="lab">Strictly Two-Color</span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--paper)"></div>
          <div class="meta">
            <div class="name">Paper</div>
            <div class="hex">#F0EBDE</div>
            <div class="role">The canvas — warm newsprint cream.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="meta">
            <div class="name">Ink (Cobalt)</div>
            <div class="hex">#1F2BE0</div>
            <div class="role">The only ink — type, rules, grid, decoration.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink-soft)"></div>
          <div class="meta">
            <div class="name">Ink Soft</div>
            <div class="hex">#5560E5</div>
            <div class="role">Secondary cobalt for editorial subtitles.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--paper-2)"></div>
          <div class="meta">
            <div class="name">Paper 2</div>
            <div class="hex">#E6E0CE</div>
            <div class="role">Deeper cream for subtle region shifts.</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="
              background: var(--paper);
              background-image:
                linear-gradient(to right, var(--grid) 1px, transparent 1px),
                linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
              background-size: 22px 22px;
            "
          ></div>
          <div class="meta">
            <div class="name">Grid 10%</div>
            <div class="hex">cobalt · .10</div>
            <div class="role">Permanent graph paper + chart "off" cells.</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="
              background: var(--paper);
              box-shadow:
                inset 0 -1px 0 var(--ink-faint),
                inset 0 60px 0 var(--ink-faint);
            "
          ></div>
          <div class="meta">
            <div class="name">Ink Faint 18%</div>
            <div class="hex">cobalt · .18</div>
            <div class="role">Faint row dividers in dense lists.</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <span class="tag">02</span>
        <h2>Typography</h2>
        <span class="sp"></span><span class="lab">Serif · Sans · Mono</span>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">display-hero</div>
          <div class="m">Newsreader 400 · up to 18vh · -0.008em</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-size: 104px; line-height: 0.9; letter-spacing: -0.01em"
            >Trend Index</span
          >
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">vbig-numeral</div>
          <div class="m">Newsreader 400 · to 240px · -0.015em</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-size: 120px; line-height: 0.9; letter-spacing: -0.02em"
            >240</span
          >
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">headline</div>
          <div class="m">Newsreader 400 · section header</div>
        </div>
        <div class="spec"><span class="serif" style="font-size: 60px">The Cobalt Ledger</span></div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">body</div>
          <div class="m">Hanken Grotesk 400 · line 1.5</div>
        </div>
        <div class="spec">
          <p style="font-size: 16px; line-height: 1.5; max-width: 680px; font-family: var(--sans)">
            Hanken Grotesk carries every body paragraph and supporting line — the quiet sans against
            the serif statement.
          </p>
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">micro</div>
          <div class="m">Hanken 600 · 0.16em · uppercase</div>
        </div>
        <div class="spec">
          <span class="micro" style="font-size: 14px">Section Label / Lab-Tag</span>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div class="lbl">
          <div class="tok">mono-tag</div>
          <div class="m">DM Mono 400 · 0.05em</div>
        </div>
        <div class="spec">
          <span class="mono" style="font-size: 15px">CAT—001 · 2026.Q2 · ↑ 4.2</span>
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="tag">03</span>
        <h2>Components</h2>
        <span class="sp"></span><span class="lab">Flat · Structural Rules</span>
      </div>

      <div class="cgrid">
        <div class="s7">
          <div class="cnote">Topbar rule + ledger rows</div>
          <div class="topbar">
            <span class="h">Trend Ledger</span><span class="t">LAB—02 / N=08</span>
          </div>
          <div class="ledger">
            <div class="lrow head">
              <span>No.</span><span>Signal</span><span>Reading</span
              ><span style="text-align: right">Δ</span>
            </div>
            <div class="lrow">
              <span class="num">001</span><span class="nm">Risograph</span
              ><span class="ds">Two-color print revival in editorial design.</span
              ><span class="dl">↑ 4.2</span>
            </div>
            <div class="lrow">
              <span class="num">002</span><span class="nm">Graph Tone</span
              ><span class="ds">Drafting-paper grids as permanent canvas.</span
              ><span class="dl">↑ 3.1</span>
            </div>
            <div class="lrow">
              <span class="num">003</span><span class="nm">Pixel Glitch</span
              ><span class="ds">Stair-stepped scanline decoration.</span
              ><span class="dl" style="opacity: 0.6">— 0.0</span>
            </div>
          </div>
        </div>
        <div class="s5">
          <div class="cnote">Pixel-stack bar chart</div>
          <div class="pchart">
            <div class="pstack">
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell"></div>
              <div class="cell"></div>
              <div class="cell"></div>
            </div>
            <div class="pstack">
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell"></div>
            </div>
            <div class="pstack">
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell"></div>
              <div class="cell"></div>
              <div class="cell"></div>
              <div class="cell"></div>
            </div>
            <div class="pstack">
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell"></div>
              <div class="cell"></div>
            </div>
            <div class="pstack">
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell on"></div>
            </div>
            <div class="pstack">
              <div class="cell on"></div>
              <div class="cell on"></div>
              <div class="cell"></div>
              <div class="cell"></div>
              <div class="cell"></div>
              <div class="cell"></div>
            </div>
          </div>
          <div class="pticks">
            <span>Q1</span><span>Q2</span><span>Q3</span><span>Q4</span><span>Q5</span
            ><span>Q6</span>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s4">
          <div class="cnote">QR-block patch</div>
          <div class="qr" style="width: 120px; height: 120px">
            <i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
            ><i></i><i class="on"></i> <i></i><i class="on"></i><i></i><i class="on"></i
            ><i class="on"></i><i></i><i class="on"></i><i></i> <i class="on"></i><i></i
            ><i class="on"></i><i></i><i></i><i class="on"></i><i></i><i class="on"></i> <i></i
            ><i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
            ><i></i> <i class="on"></i><i></i><i class="on"></i><i></i><i class="on"></i><i></i
            ><i class="on"></i><i></i> <i></i><i class="on"></i><i></i><i class="on"></i><i></i
            ><i class="on"></i><i></i><i class="on"></i> <i class="on"></i><i></i><i class="on"></i
            ><i class="on"></i><i></i><i class="on"></i><i></i><i></i> <i></i><i class="on"></i
            ><i></i><i></i><i class="on"></i><i></i><i class="on"></i><i class="on"></i>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Pixel-glitch column</div>
          <div style="position: relative; height: 140px; background: var(--paper-2)">
            <div class="glitch" style="right: 0; width: 80%; opacity: 0.8">
              <div class="step" style="width: 50%; height: 22%; align-self: flex-end"></div>
              <div class="step" style="width: 90%; height: 20%; align-self: flex-end"></div>
              <div class="step" style="width: 38%; height: 18%; align-self: flex-end"></div>
              <div class="step" style="width: 70%; height: 24%; align-self: flex-end"></div>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Vertical-stack labels</div>
          <div class="vstack" style="height: 140px">
            <span>CATALOGUE · 2026</span><span>COBALT — GRID</span><span>EDITION No.01</span>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <span class="tag">04</span>
        <h2>Frame Compositions</h2>
        <span class="sp"></span><span class="lab">True 16:9 · cqw Scaled</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Hero Cover</b><span>· identity · glitch + QR · left</span></div>
          <div class="frame cf1">
            <div class="ht"></div>
            <div class="hb"></div>
            <div class="glitch">
              <div class="step" style="width: 55%; height: 18%; align-self: flex-end"></div>
              <div class="step" style="width: 100%; height: 16%; align-self: flex-end"></div>
              <div class="step" style="width: 40%; height: 14%; align-self: flex-end"></div>
              <div class="step" style="width: 74%; height: 20%; align-self: flex-end"></div>
            </div>
            <div class="qr">
              <i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
              ><i></i><i class="on"></i> <i></i><i class="on"></i><i></i><i class="on"></i
              ><i class="on"></i><i></i><i class="on"></i><i></i> <i class="on"></i><i></i
              ><i class="on"></i><i></i><i></i><i class="on"></i><i></i><i class="on"></i> <i></i
              ><i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
              ><i></i> <i class="on"></i><i></i><i class="on"></i><i></i><i class="on"></i><i></i
              ><i class="on"></i><i></i> <i></i><i class="on"></i><i></i><i class="on"></i><i></i
              ><i class="on"></i><i></i><i class="on"></i> <i class="on"></i><i></i
              ><i class="on"></i><i class="on"></i><i></i><i class="on"></i><i></i><i></i> <i></i
              ><i class="on"></i><i></i><i></i><i class="on"></i><i></i><i class="on"></i
              ><i class="on"></i>
            </div>
            <div class="body">
              <div class="kick">Trend Report</div>
              <h3>The grid is the canvas.</h3>
              <div class="ed">Cream + cobalt, drafting tone behind every frame.</div>
            </div>
            <div class="pg">01 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Index Ledger</b><span>· catalog · dense frame</span></div>
          <div class="frame cf2">
            <div class="ht"></div>
            <div class="hb"></div>
            <div class="body">
              <div class="tb">
                <span class="h">Six Signals</span><span class="t">LAB—02 / 2026</span>
              </div>
              <div class="r">
                <span class="n">01</span>
                <div>
                  <div class="nm">Risograph Revival</div>
                  <div class="dx">Two-color print as editorial register.</div>
                </div>
              </div>
              <div class="r">
                <span class="n">02</span>
                <div>
                  <div class="nm">Graph-Paper Tone</div>
                  <div class="dx">Drafting grids as permanent canvas.</div>
                </div>
              </div>
              <div class="r">
                <span class="n">03</span>
                <div>
                  <div class="nm">Pixel Glitch</div>
                  <div class="dx">Stair-stepped scanline decoration.</div>
                </div>
              </div>
              <div class="r">
                <span class="n">04</span>
                <div>
                  <div class="nm">QR Mosaic</div>
                  <div class="dx">8×8 patches as graphic punctuation.</div>
                </div>
              </div>
            </div>
            <div class="pg">02 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Chapter Opener</b><span>· section · sparse · left</span></div>
          <div class="frame cf3">
            <div class="ht"></div>
            <div class="hb"></div>
            <div class="glitch">
              <div class="step" style="width: 60%; height: 24%; align-self: flex-start"></div>
              <div class="step" style="width: 100%; height: 20%; align-self: flex-start"></div>
              <div class="step" style="width: 42%; height: 22%; align-self: flex-start"></div>
            </div>
            <div class="body">
              <div class="ix">CHAPTER 02</div>
              <h3>On Measure<br />&amp; Margin.</h3>
              <div class="lede">
                The grid wants to be filled — but the chapter opener lets it breathe.
              </div>
            </div>
            <div class="pg">03 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Data Frame</b><span>· chart · pixel-stack</span></div>
          <div class="frame cf4">
            <div class="ht"></div>
            <div class="hb"></div>
            <div class="body">
              <div class="tb">
                <span class="h">Signal Strength</span><span class="t">FIG.04 / INDEX</span>
              </div>
              <div class="chart">
                <div class="st">
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                </div>
                <div class="st">
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c"></div>
                </div>
                <div class="st">
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                </div>
                <div class="st">
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                </div>
                <div class="st">
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                </div>
                <div class="st">
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c on"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                  <div class="c"></div>
                </div>
              </div>
              <div class="tk">
                <span>01</span><span>02</span><span>03</span><span>04</span><span>05</span
                ><span>06</span>
              </div>
            </div>
            <div class="pg">04 / 06</div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="tag">05</span>
        <h2>Frame Rules</h2>
        <span class="sp"></span><span class="lab">Two-Color · Flat</span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <ul>
            <li>Keep the graph-paper grid behind every frame — it is the canvas tone.</li>
            <li>Frame every slide with top + bottom 1.5px cobalt hairlines.</li>
            <li>Set Newsreader at weight 400 in cobalt; size, not weight, makes hierarchy.</li>
            <li>Track Hanken labels uppercase at 0.16em; mono chrome at 0.05em.</li>
            <li>Render data as pixel-stack cells — cobalt on / 10% off.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <ul>
            <li>Never introduce a second ink color — cream + cobalt only.</li>
            <li>No bold Newsreader, no rounded corners, no drop shadows.</li>
            <li>Don't disable the grid or suppress the hairlines.</li>
            <li>Don't crowd chapter / quote / colophon — let the grid breathe.</li>
            <li>Don't blow a serif headline edge-to-edge — fit to measure.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--paper);
        --cap-accent: var(--ink-soft);
        --cap-accent-2: var(--paper-2);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 7;
      }
      .capdemo .pill {
        position: relative;
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        background-image:
          linear-gradient(to right, var(--grid) 1px, transparent 1px),
          linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
        background-size: 1.2cqw 1.2cqw;
        border-top: 0.16cqw solid var(--cap-ink);
        border-bottom: 0.16cqw solid var(--cap-ink);
        border-radius: 0;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.1em 0.34em;
        font-family: var(--serif);
        font-weight: 400;
        font-size: 3.4cqw;
        line-height: 1.16;
        letter-spacing: -0.01em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.06em;
        color: color-mix(in srgb, var(--cap-ink) 42%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      /* the cobalt flat block — risograph knockout: cobalt fill, cream paper on top */
      .capdemo .w.active {
        color: var(--cap-canvas);
        background: var(--cap-ink);
        box-shadow: 0 0 0 0.06em var(--cap-ink);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 1px dashed var(--ink-faint);
        z-index: 6;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 4cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        font-size: 0.95cqw;
        letter-spacing: 0.04em;
        color: var(--ink);
        opacity: 0.5;
        z-index: 7;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="tag">06</span>
        <h2>Captions</h2>
        <span class="sp"></span><span class="lab">Lower-third · Karaoke</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="ht"></div>
            <div class="hb"></div>
            <div class="glitch" style="right: 0; width: 16cqw; opacity: 0.5; z-index: 2">
              <div class="step" style="width: 60%; height: 18%; align-self: flex-end"></div>
              <div class="step" style="width: 100%; height: 16%; align-self: flex-end"></div>
              <div class="step" style="width: 42%; height: 14%; align-self: flex-end"></div>
            </div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
            <div class="pg">05 / 06</div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · block cleared</span></div>
          <div class="frame capdemo">
            <div class="ht"></div>
            <div class="hb"></div>
            <div
              class="qr"
              style="
                position: absolute;
                top: 6cqw;
                right: 5cqw;
                width: 7cqw;
                height: 7cqw;
                z-index: 6;
                gap: 0.2cqw;
                padding: 0.4cqw;
              "
            >
              <i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
              ><i></i><i class="on"></i> <i></i><i class="on"></i><i></i><i class="on"></i
              ><i class="on"></i><i></i><i class="on"></i><i></i> <i class="on"></i><i></i
              ><i class="on"></i><i></i><i></i><i class="on"></i><i></i><i class="on"></i> <i></i
              ><i class="on"></i><i></i><i class="on"></i><i class="on"></i><i></i><i class="on"></i
              ><i></i> <i class="on"></i><i></i><i class="on"></i><i></i><i class="on"></i><i></i
              ><i class="on"></i><i></i> <i></i><i class="on"></i><i></i><i class="on"></i><i></i
              ><i class="on"></i><i></i><i class="on"></i> <i class="on"></i><i></i
              ><i class="on"></i><i class="on"></i><i></i><i class="on"></i><i></i><i></i> <i></i
              ><i class="on"></i><i></i><i></i><i class="on"></i><i></i><i class="on"></i
              ><i class="on"></i>
            </div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
            <div class="pg">06 / 06</div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Cobalt Grid</span>
      <span class="r">FRAME SHOWCASE · ATOMS SACRED · COMPOSITION FREE</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#F0EBDE" />
        <g stroke="#1F2BE0" stroke-opacity="0.25" stroke-width="1">
          <line x1="0" y1="25" x2="100" y2="25" />
          <line x1="0" y1="50" x2="100" y2="50" />
          <line x1="0" y1="75" x2="100" y2="75" />
          <line x1="25" y1="0" x2="25" y2="100" />
          <line x1="50" y1="0" x2="50" y2="100" />
          <line x1="75" y1="0" x2="75" y2="100" />
        </g>
        <rect x="20" y="40" width="44" height="11" fill="#1F2BE0" />
        <rect x="70" y="20" width="14" height="14" fill="#1F2BE0" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/cobalt-grid/FRAME.md›
---
version: alpha
name: Cobalt Grid — Frame (video / frame layer)
description: >
  Video-first companion to Cobalt Grid's design.md. The unit is the frame (1920×1080). Atoms
  are identical and sacred — warm cream paper, electric cobalt ink (the only ink), the permanent
  graph-paper grid, Newsreader serif 400 + Hanken Grotesk + DM Mono, the top/bottom cobalt
  hairlines, and the pixel-glitch + QR-block signatures. Composition, frame scale, and
  aspect-ratio behavior are rewritten for the frame. Motion is out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  paper: "#F0EBDE"
  paper-2: "#E6E0CE"
  ink: "#1F2BE0"
  ink-soft: "#5560E5"
  grid: "rgba(31,43,224,0.10)"
  ink-faint: "rgba(31,43,224,0.18)"

typography:
  # — reading ramp —
  body:        { fontFamily: "Hanken Grotesk", cqw: 0.83, weight: 400, lineHeight: 1.5 }
  body-lede:   { fontFamily: "Hanken Grotesk", cqw: 0.95, weight: 400, lineHeight: 1.5 }
  micro:       { fontFamily: "Hanken Grotesk", px: 13, weight: 600, tracking: "0.16em", upper: true }
  micro-strong:{ fontFamily: "Hanken Grotesk", px: 16, weight: 600, tracking: "0.18em", upper: true }
  mono-tag:    { fontFamily: "DM Mono", cqw: 0.78, weight: 400, tracking: "0.05em" }
  mono-chrome: { fontFamily: "DM Mono", px: 13, weight: 400, tracking: "0.06em" }
  # — display / hero ramp (Newsreader 400, negative tracking) —
  table-name:  { fontFamily: "Newsreader", cqw: 1.5, weight: 400, lineHeight: 1.15 }
  row-headline:{ fontFamily: "Newsreader", cqw: 2.1, weight: 400, lineHeight: 1.05 }
  ed-callout:  { fontFamily: "Newsreader", cqw: 2.6, weight: 400, lineHeight: 1.1, italic: true }
  headline:    { fontFamily: "Newsreader", cqw: 4.6, weight: 400, lineHeight: 0.95 }
  headline-index:{ fontFamily: "Newsreader", cqw: 5.0, weight: 400, lineHeight: 0.95 }
  display-quote:{ fontFamily: "Newsreader", cqw: 5.7, weight: 400, lineHeight: 1.05, tracking: "-0.005em" }
  display-chapter:{ fontFamily: "Newsreader", cqw: 6.8, weight: 400, lineHeight: 1.0, tracking: "-0.005em" }
  display-closing:{ fontFamily: "Newsreader", cqw: 9.4, weight: 400, lineHeight: 0.96, tracking: "-0.005em" }
  display-hero:{ fontFamily: "Newsreader", cqw: 10.4, weight: 400, lineHeight: 0.9, tracking: "-0.008em" }
  vbig-numeral:{ fontFamily: "Newsreader", cqw: 12.5, weight: 400, lineHeight: 0.9, tracking: "-0.015em" }

spacing:
  edge: "4cqw"          # standard frame edge inset (~80px@1920)
  pad-top: "7cqw"
  pad-bottom: "6cqw"
  gap-md: "2cqw"

components:
  graph-grid:
    backgroundImage: "linear-gradient grid, ~2cqw cells, 10% {colors.ink} ({colors.grid})"
    placement: "behind EVERY frame on the ground"
    description: "Permanent graph-paper grid — never disabled; the canvas tone."
  hairlines:
    rule: "0.12cqw solid {colors.ink}"
    placement: "≈3cqw from top + bottom, inset {spacing.edge}"
    description: "Two persistent cobalt rules framing every composition."
  page-chrome:
    typography: "{typography.pagenum}"
    color: "{colors.ink}"
    placement: "page number bottom-right, nav/meta hint bottom-left, above the bottom hairline"
    description: "The only persistent chrome."
  pixel-glitch:
    backgroundColor: "{colors.paper}"
    fill: "repeating-linear-gradient(90deg, {colors.ink} 0 2px, transparent 2px 8px)"
    size: "14–30cqw wide, full height, stair-stepped"
    placement: "right on cover/data, left on chapter/colophon; z above grid, below headline"
    description: "Stair-stepped scanline column. Decorative."
  qr-block:
    backgroundColor: "{colors.paper}"
    cells: "8×8, {colors.ink} on / {colors.grid} off"
    size: "3–9cqw square"
    shadow: "0 0 0 0.12cqw {colors.paper} (anti-shadow for readability, not elevation)"
    description: "QR mosaic patch — corner punctuation."
  topbar-rule:
    typography: "{typography.headline-index} + {typography.mono-tag}"
    borderBottom: "0.12cqw solid {colors.ink}"
    description: "Section header on index/data/table frames."
  ledger-row:
    layout: "grid: mono num · Newsreader name · Hanken desc · mono delta"
    borderBottom: "0.06cqw solid {colors.ink-faint} (header 0.12cqw solid {colors.ink})"
    typography: "{typography.mono-tag} + {typography.table-name} + {typography.body}"
    description: "Dense matrix row with ↑/↓/— delta."
  pixel-stack-bar:
    cells: "column-reverse, {colors.grid} off / {colors.ink} on"
    baseline: "0.12cqw solid {colors.ink} + {typography.mono-tick} ticks"
    description: "Data as grid-unit cells; echoes the glitch."
  vstack-label:
    typography: "{typography.mono-tick}"
    transform: "writing-mode: vertical-rl"
    description: "Catalogue chrome along a frame edge."
---

# Cobalt Grid — Frame (video / frame layer)

## Overview

Cobalt Grid at frame scale is a **two-color risograph trend-report** — warm cream paper, electric
cobalt ink, and a **permanent graph-paper grid** behind every frame. Cobalt is the _only_ ink:
headlines, body, rules, the grid, the pixel-glitch decoration, the QR patches. There is no accent
color and no second surface.

The voice is a three-face conversation: **Newsreader** serif at weight 400 (size, not weight,
makes hierarchy) carries every display and headline; **Hanken Grotesk** carries body and
uppercase tracked labels; **DM Mono** carries all chrome — page numbers, tags, ticks, vertical
stacks. Every frame is framed by top + bottom cobalt hairlines; declarative frames carry the
**pixel-glitch column** and a **QR-block** patch.

**Key characteristics at frame scale:**

- **Strictly two-color** — cream paper + cobalt ink; no accent, no second surface.
- **Permanent ~2cqw graph-paper grid** (10% cobalt) behind every frame; cannot be disabled.
- **Top + bottom 1.5px cobalt hairlines** frame every composition, inset by `edge`.
- **Newsreader 400** for all display (negative tracking); **Hanken 600** uppercase 0.16em labels; **DM Mono** chrome.
- **Pixel-glitch column + QR-block** as the signature decorative patches on declarative frames.
- **Pixel-stack bars** render data as grid-unit cells (cobalt on / 10% off).

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one Newsreader element dominates at **3–6× its nearest neighbor** (`display-hero`/`vbig-numeral`); hierarchy is size, never weight.
- **Silence** — declarative frames (cover, chapter, quote, colophon) read **45–60% empty** with the grid showing; the **index ledger and data frame are the one dense exception** (density via quiet repetition, not richness).
- **Restraint** — **two-color only** (cream + cobalt, never a second hue); one pixel-glitch column and at most one QR-block per declarative frame.
- **Reference** — aim at a **WIRED Japan / Shift two-color risograph monograph**; failure looks like a **colorful dashboard with a second accent hue**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `edge` (4cqw) inset; hairlines + page chrome live on the safe line.

**The container law (load-bearing).** Every frame ground sets `container-type: size` AND carries
the graph-paper `background-image`; ALL frame-relative units are `cqw`/`cqh` against it — never
`vw`. The grid `background-size` is `~2cqw 2cqw` so cell density holds at any render size.

## Colors

Tokens identical to the source. `{colors.paper}` is the ground; `{colors.ink}` cobalt is the only
ink (type, rules, grid lines, glitch, QR). `{colors.ink-soft}` is a secondary cobalt for editorial
subtitles only; `{colors.grid}` (10%) is the permanent grid + chart "off" cells; `{colors.ink-faint}`
(18%) is faint row dividers. **Never a second hue** — emphasis comes from size, from switching
Hanken→Newsreader, from a mono delta arrow, or from opacity, never from color.

## Typography

Two ramps. The **reading ramp** (Hanken body 0.83cqw, mono chrome in px) carries copy + labels;
the **display/hero ramp** (Newsreader `headline` 4.6cqw → `vbig-numeral` 12.5cqw) carries every
statement and figure.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono chrome (px) is colophon only.
- **Fit-to-measure:** size the headline to its line length. Cap the block at **≤ 78cqw**; ≤3 words → `display-hero`/`vbig-numeral`; 4–6 → `display-chapter`; 7+ → `headline`.
- **Newsreader at 400 only**, in cobalt, with negative tracking (hero −0.008em, numeral −0.015em). **Hanken labels uppercase, 0.16–0.18em.** **DM Mono 0.04–0.08em.** No bold serif.

## Depth & Surface

Flat. Depth is structural only:

- **1.5px cobalt rules** — slide hairlines, topbar rule, chart baseline.
- **1px ink-faint dividers** — dense list / ledger rows.
- **The graph-paper grid** — measured-plane tone behind everything.
- **Pixel-glitch + QR** — texture and graphic punctuation, no z-axis.

**Ceiling:** no drop shadow (the QR's 1.5px paper outset is an anti-shadow for readability, not elevation), no gradient, no rounded corner, no second color.

## Shapes

- **0 radius everywhere** — frames, ledger rows, QR cells, glitch blocks, charts. Zero circular elements; this squareness is part of the identity.

## Components

- **graph-grid / hairlines / page-chrome** — the permanent frame furniture, inherited by every composition.
- **pixel-glitch** (edge column) + **qr-block** (corner patch) — the signature decoration on declarative frames.
- **topbar-rule** — the index/data/table section header. **ledger-row** — dense matrix row with delta arrows.
- **pixel-stack-bar** — data as grid-unit cells. **vstack-label** — vertical mono catalogue chrome.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> The grid + hairlines are present on every frame. Choose density by type: declarative = sparse, index/data = dense.

### 1 · Hero Cover (identity · move: serif + glitch · left)

**Ground** paper + grid, hairlines. **Composes** pixel-glitch (right, ~26cqw), qr-block (top-right), display-hero, ed-callout. **Focal** a 1–2 line `display-hero` Newsreader headline in cobalt, left-anchored, with an italic `ed-callout` subtitle in `{colors.ink-soft}`. **Chrome** Hanken kicker; mono meta top + page number. **Accent** none (cobalt is the only ink). **Silence** ~45% paper. **Fixed** Newsreader 400, glitch + QR present, grid + hairlines. **Free** title, glitch step pattern, QR placement. **Density** sparse.

### 2 · Index Ledger (catalog · move: dense matrix · left — the dense frame)

**Ground** paper + grid, hairlines, `pad-top`/`pad-bottom`. **Composes** topbar-rule, ledger-rows. **Focal** a topbar (`headline-index` + mono lab-tag, 1.5px rule) over 4–6 ledger rows (mono num · Newsreader name · Hanken desc), ink-faint dividers. **Chrome** page number. **Accent** mono delta arrows. **Silence** tight — the density exception (the grid wants filling). **Fixed** 1.5px topbar rule, 1px ink-faint dividers, Newsreader names. **Free** rows, lab-tag, deltas. **Density** dense-exception.

### 3 · Chapter Opener (section · move: scale · sparse · left)

**Ground** paper + grid, hairlines. **Composes** pixel-glitch (left, ~14cqw, low opacity), mono index, display-chapter, body-lede. **Focal** a `display-chapter` Newsreader title, with a small mono `CHAPTER NN` index above and an optional Hanken lede ≤42cqw. **Accent** none. **Silence** ~60% — let the grid breathe. **Fixed** Newsreader 400, glitch low-opacity, grid showing. **Free** title, lede, glitch side. **Density** sparse.

### 4 · Data Frame (chart · move: pixel-stack · left)

**Ground** paper + grid, hairlines, `pad-top`. **Composes** topbar-rule, pixel-stack-bar row. **Focal** a row of 6–8 pixel-stack bars (cobalt on / 10% off) over a 1.5px cobalt baseline with mono ticks. **Chrome** topbar headline + mono fig-tag; page number. **Accent** none — data is cobalt cells. **Silence** moderate. **Fixed** grid-unit cells, cobalt baseline, mono ticks. **Free** bar values (from script), tick labels. **Density** standard/dense.

### 5 · Manifesto / Quote (quote · move: centered statement · sparse)

**Ground** paper + grid, hairlines. **Composes** display-quote (or display-manifesto), attribution rule, optional compact glitch. **Focal** a 2–3 line Newsreader pull in cobalt, with a Hanken kicker above and a 1px cobalt attribution rule + mono byline beneath. **Accent** none. **Silence** ~55% — deliberately open. **Fixed** Newsreader 400, attribution rule, grid showing. **Free** quote, byline. **Density** sparse.

### 6 · Colophon (closer · move: right-aligned close · sparse)

**Ground** paper + grid, hairlines. **Composes** pixel-glitch (left edge, mirroring the cover), display-closing, mono credit columns. **Focal** a right-aligned `display-closing` Newsreader title with a Hanken kicker above. **Chrome** 3–4 column mono credit grid at the foot; page number. **Accent** none. **Silence** ~50%. **Fixed** glitch on left, Newsreader 400. **Free** closing line, credits. **Density** sparse.

## Composition Rules

### Do

- Keep the **graph-paper grid + top/bottom hairlines** on every frame — they are the system.
- Set **Newsreader at 400 in cobalt**; make hierarchy with size, not weight; negative-track display.
- Track **Hanken labels uppercase 0.16em**; **DM Mono chrome 0.04–0.08em**.
- Render data as **pixel-stack cells** (cobalt on / 10% off); echo the glitch language.
- Use the **pixel-glitch column + QR patch** on declarative frames (cover, chapter, quote, colophon).
- Lean: declarative frames sparse and breathing; index/data dense. Vary anchor (left for index/chapter, centered for quote/closer).

### Don't

- Don't introduce a second ink color — cream + cobalt only.
- Don't bold Newsreader, round any corner, or add a drop shadow (QR outset is an anti-shadow only).
- Don't disable the grid or suppress the hairlines.
- Don't crowd chapter / quote / colophon — those let the grid show.
- Don't blow a serif headline edge-to-edge — fit to measure.

## Aspect-Ratio Behavior

| Treatment         | 16:9                        | 9:16                                 | 1:1                          |
| ----------------- | --------------------------- | ------------------------------------ | ---------------------------- |
| Hero Cover        | headline left, glitch right | headline top, glitch full-width band | headline upper, glitch lower |
| Index Ledger      | topbar + rows               | topbar + fewer rows                  | 2-col compresses to 1        |
| Chapter Opener    | title left, glitch left     | title top, glitch side               | centered title               |
| Data Frame        | 6–8 bars                    | 4–5 bars taller                      | square chart                 |
| Manifesto / Quote | centered pull               | centered, taller                     | centered                     |
| Colophon          | right-aligned, glitch left  | stacked, glitch top                  | centered close               |

Grid + hairlines hold on the short edge for every ratio; re-step display so no load-bearing line
drops below 1.4cqw. Mono chrome stays Latin/digit-only.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Trend names, signals, and figures are content; the system supplies the grid and chrome.

## Numerals & Claims (hard rule)

Never invent figures, deltas, dates, or signal counts at frame scale. Render slots as `— figure —`,
`{metric}`, `↑ —`. Pixel-stack bar heights and ledger deltas especially carry placeholders until the
script supplies values. Mono catalogue ordinals (001, 002…) are decorative and may be sequential.

## Pre-Render Self-Audit

- **Squint** — one Newsreader element dominates at 3–5× its neighbor.
- **Silence** — declarative frames 45–60% empty; only index/data run dense.
- **Two-color** — cream + cobalt only; no second hue anywhere.
- **Furniture** — grid present, top/bottom hairlines present, page chrome above the hairline.
- **Type** — Newsreader 400 negative-tracked, fit-to-measure; Hanken labels 0.16em; ≥1.4cqw floor.
- **Depth** — 0 shadow (QR outset excepted), 0 rounded corner.
- **Anchor** — left on index/chapter/cover, centered on quote/closer; no 3 consecutive frames share an anchor.
- **Fabrication** — every numeral/delta traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the 280ms cross-fade in the source is a deck mechanic.
- **Pixel-glitch is rendered in CSS here** (stacked scanline blocks) rather than the source's inline SVG, to keep the showcase SVG-free; fidelity is preserved.
- **Three Google Fonts** (Newsreader, Hanken Grotesk, DM Mono); CJK pairing (Noto Serif SC 700/400) carries over from the source.
- **9:16 / 1:1 are guidance**; verify the legibility floor and grid density per ratio.
- The QR mosaic and glitch step patterns are hand-authored; there is no generative layer.
assets/frame-presets/code-editorial/caption-skin.html›
<!--
  Code editorial — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a code-editorial literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a warm cream card lifted off the page on a 1px hairline ink border + one soft
  warm shadow — code-editorial's only elevation language, no tilt, editorial and precise.
  EB Garamond sentence case, warm-ink glyphs. Upcoming words sit in faint ink; the current
  word reads in full ink under a 3px coral "voltage" underline (the brand's inline-link
  gesture, border-bottom = underline without layout shift); spoken words settle to solid
  ink, underline cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE lifted cream card — warm cream fill, 1px hairline ink border, one soft warm
     shadow (code-editorial's only elevation language), 12px radius, no tilt */
  .caption-pill {
    max-width: 78%;
    padding: 22px 44px 26px;
    background: var(--cap-canvas, #faf9f5);
    border: 1px solid color-mix(in srgb, var(--cap-ink, #141413) 12%, transparent);
    border-radius: 12px;
    box-shadow:
      0 1px 3px color-mix(in srgb, var(--cap-ink, #141413) 8%, transparent),
      0 4px 16px color-mix(in srgb, var(--cap-ink, #141413) 4%, transparent);
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.12em 0.3em;
    font-family: var(--font-display, "EB Garamond"), Georgia, serif;
    font-weight: 500;
    font-size: clamp(40px, 4.4vw, 56px);
    line-height: 1.24;
    letter-spacing: -0.02em;
  }

  /* upcoming — faint warm ink, no underline */
  .caption-word {
    display: inline-block;
    padding: 0 0.02em 0.06em;
    color: color-mix(in srgb, var(--cap-ink, #141413) 40%, var(--cap-canvas, #faf9f5));
    border-bottom: 3px solid transparent;
  }
  /* current word — full ink under the coral voltage underline (the brand's signature
     inline-link gesture; border-bottom = underline without layout shift) */
  .caption-word.is-active {
    color: var(--cap-ink, #141413);
    border-bottom: 3px solid var(--cap-accent, #cc785c);
  }
  /* already spoken — solid warm ink, underline cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #141413);
    border-bottom: 3px solid transparent;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — the expressive EB Garamond italic */
  .caption-word.cap-brand {
    font-style: italic;
    color: var(--cap-ink, #141413);
  }
  .caption-word.cap-brand.is-active {
    border-bottom: 3px solid var(--cap-accent, #cc785c);
  }
  /* numerals / data — slightly recessed warm ink */
  .caption-word.cap-num {
    color: color-mix(in srgb, var(--cap-ink, #141413) 72%, var(--cap-canvas, #faf9f5));
  }
  /* emphasis — a persistent coral voltage underline */
  .caption-word.cap-cta {
    border-bottom: 3px solid var(--cap-accent-2, #b5654a);
    color: var(--cap-ink, #141413);
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 46px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.98 }, { scale: 1, duration: 0.16, ease: "power1.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/code-editorial/fonts/OFL-eb-garamond.txt›
Copyright 2017 The EB Garamond Project Authors (https://github.com/octaviopardo/EBGaramond12) EBGaramond-Italic[wght].ttf: Copyright 2017 The EB Garamond Project Authors (https://github.com/octaviopardo/EBGaramond12)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
assets/frame-presets/code-editorial/fonts/OFL-inter.txt›
Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
assets/frame-presets/code-editorial/fonts/OFL-jetbrains-mono.txt›
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
assets/frame-presets/code-editorial/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Code editorial</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --ink: #141413;
        --cream: #faf9f5;
        --tile: #efe9de;
        --tile-strong: #ece3d4;
        --coral: #cc785c;
        --navy: #181715;
        --navy-soft: #1f1e1b;
        --navy-elev: #252320;
        --teal: #5db8a6;
        --amber: #e8a55a;
        --ok: #5db872;
        --warn: #c64545;
        --serif: "EB Garamond", Georgia, serif;
        --sans: "Inter", system-ui, sans-serif;
        --mono: "JetBrains Mono", ui-monospace, monospace;
        --hair: rgba(20, 20, 19, 0.12);
        --hair-soft: rgba(20, 20, 19, 0.06);
        --on-dark: rgba(250, 249, 245, 0.92);
        --on-dark-soft: rgba(250, 249, 245, 0.58);
        --shadow: 0 1px 3px rgba(20, 20, 19, 0.08), 0 4px 16px rgba(20, 20, 19, 0.04);
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #0e0d0c;
      }
      body {
        background: var(--cream);
        color: var(--ink);
        font-family: var(--sans);
        -webkit-font-smoothing: antialiased;
      }
      .serif {
        font-family: var(--serif);
        font-weight: 400;
        letter-spacing: -0.02em;
        line-height: 1;
      }
      .mono {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
      }
      .spike {
        color: var(--coral);
      }

      section {
        padding: 110px 120px;
        position: relative;
      }
      .topbar {
        display: flex;
        justify-content: space-between;
        align-items: flex-end;
        border-bottom: 1px solid var(--hair);
        padding-bottom: 18px;
        margin-bottom: 56px;
      }
      .topbar .lbl {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 15px;
        color: var(--coral);
      }
      .topbar h2 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 48px;
        letter-spacing: -0.02em;
        line-height: 1;
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        background: var(--cream);
        color: var(--ink);
        padding: 120px 140px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
      }
      .cover .top {
        position: absolute;
        top: 64px;
        left: 140px;
        right: 140px;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .cover .top .lbl {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 15px;
        color: var(--coral);
      }
      .cover .top .idx {
        font-family: var(--mono);
        font-weight: 500;
        letter-spacing: 0.06em;
        font-size: 14px;
        color: color-mix(in srgb, var(--ink) 56%, var(--cream));
      }
      .cover h1 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: clamp(88px, 13vw, 184px);
        line-height: 0.98;
        letter-spacing: -0.035em;
        color: var(--ink);
        max-width: 14ch;
      }
      .cover h1 .it {
        font-style: italic;
        color: var(--coral);
      }
      .cover .lede {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 28px;
        line-height: 1.5;
        color: color-mix(in srgb, var(--ink) 80%, var(--cream));
        max-width: 720px;
        margin-top: 38px;
      }
      .cover .footline {
        position: absolute;
        bottom: 64px;
        left: 140px;
        right: 140px;
        display: flex;
        justify-content: space-between;
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 14px;
        color: color-mix(in srgb, var(--ink) 50%, var(--cream));
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 24px;
      }
      .sw {
        border: 1px solid var(--hair);
        border-radius: 12px;
        overflow: hidden;
        background: var(--cream);
      }
      .sw .chip {
        height: 128px;
      }
      .sw .meta {
        padding: 18px 20px;
        border-top: 1px solid var(--hair);
      }
      .sw .name {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 24px;
      }
      .sw .hex {
        font-family: var(--mono);
        font-size: 12px;
        letter-spacing: 0.04em;
        color: var(--ink);
        opacity: 0.6;
        margin-top: 6px;
      }
      .sw .role {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 14px;
        line-height: 1.45;
        color: color-mix(in srgb, var(--ink) 70%, var(--cream));
        margin-top: 10px;
      }
      .syntax-note {
        margin-top: 28px;
        display: flex;
        gap: 28px;
        align-items: center;
        flex-wrap: wrap;
        font-family: var(--mono);
        font-size: 13px;
        letter-spacing: 0.02em;
        color: color-mix(in srgb, var(--ink) 60%, var(--cream));
      }
      .syntax-note .dot {
        display: inline-block;
        width: 14px;
        height: 14px;
        border-radius: 50%;
        vertical-align: -2px;
        margin-right: 8px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 240px 1fr;
        align-items: center;
        padding: 26px 0;
        border-bottom: 1px solid var(--hair-soft);
      }
      .type-row .tok {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 13px;
        color: var(--coral);
      }
      .type-row .m {
        font-family: var(--mono);
        font-size: 11px;
        color: var(--ink);
        opacity: 0.55;
        margin-top: 8px;
        line-height: 1.5;
        letter-spacing: 0.02em;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 12px;
        color: var(--coral);
        margin-bottom: 22px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 28px;
        margin-bottom: 52px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .s8 {
        grid-column: span 8;
      }
      .card {
        background: var(--cream);
        border: 1px solid var(--hair);
        border-radius: 12px;
        box-shadow: var(--shadow);
        padding: 30px 32px;
      }
      .card.tile {
        background: var(--tile);
      }
      .card .kt {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 30px;
        letter-spacing: -0.01em;
        line-height: 1.1;
      }
      .card .kb {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 16px;
        line-height: 1.5;
        color: color-mix(in srgb, var(--ink) 74%, var(--cream));
        margin-top: 10px;
      }
      .kicker {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 14px;
        color: color-mix(in srgb, var(--ink) 64%, var(--cream));
      }
      .callout {
        background: var(--coral);
        color: var(--cream);
        border-radius: 8px;
        padding: 22px 30px;
        display: inline-flex;
        align-items: center;
        gap: 14px;
      }
      .callout .b {
        font-family: var(--sans);
        font-weight: 500;
        font-size: 22px;
      }
      .callout .a {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 12px;
        opacity: 0.85;
      }
      .kpi .tag {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 13px;
        color: var(--coral);
        border-top: 1px solid var(--hair);
        padding-top: 16px;
        display: inline-block;
      }
      .kpi .fig {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 120px;
        line-height: 0.95;
        letter-spacing: -0.03em;
        color: var(--ink);
      }
      .kpi .fig .u {
        font-family: var(--mono);
        font-weight: 500;
        font-size: 36px;
        letter-spacing: 0;
        margin-left: 8px;
      }
      .kpi .d {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 17px;
        line-height: 1.4;
        color: color-mix(in srgb, var(--ink) 74%, var(--cream));
        margin-top: 12px;
        max-width: 300px;
      }
      .quote .q {
        font-family: var(--serif);
        font-style: italic;
        font-weight: 400;
        font-size: 38px;
        line-height: 1.18;
        letter-spacing: -0.018em;
        color: var(--ink);
      }
      .quote .cite {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 13px;
        color: color-mix(in srgb, var(--ink) 58%, var(--cream));
        margin-top: 18px;
      }
      /* code surface demo */
      .codewin {
        background: var(--navy);
        border: 1px solid var(--on-dark-soft);
        border-radius: 8px;
        overflow: hidden;
      }
      .codewin .bar {
        background: var(--navy-elev);
        padding: 12px 18px;
        display: flex;
        align-items: center;
        gap: 8px;
      }
      .codewin .bar .dot {
        width: 11px;
        height: 11px;
        border-radius: 50%;
      }
      .codewin .bar .fn {
        font-family: var(--mono);
        font-weight: 500;
        font-size: 13px;
        color: var(--on-dark-soft);
        margin-left: 12px;
        letter-spacing: 0.02em;
      }
      .codewin pre {
        margin: 0;
        padding: 22px 24px;
        font-family: var(--mono);
        font-weight: 400;
        font-size: 18px;
        line-height: 1.6;
        color: var(--on-dark);
      }
      .codewin .add {
        background: color-mix(in srgb, var(--ok) 16%, transparent);
        display: block;
        margin: 0 -24px;
        padding: 0 24px;
      }
      .codewin .del {
        background: color-mix(in srgb, var(--warn) 16%, transparent);
        display: block;
        margin: 0 -24px;
        padding: 0 24px;
        color: var(--on-dark-soft);
      }
      .codewin .k {
        color: var(--coral);
      }
      .codewin .s {
        color: var(--teal);
      }
      .codewin .n {
        color: var(--amber);
      }
      .codewin .g {
        color: var(--ok);
      }
      .codewin .r {
        color: var(--warn);
      }

      /* FRAMES */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 12px;
        color: color-mix(in srgb, var(--ink) 50%, var(--cream));
      }
      .fw .flabel b {
        color: var(--coral);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--cream);
        border-radius: 8px;
        box-shadow:
          0 0 0 1px var(--hair),
          0 10px 30px rgba(20, 20, 19, 0.06);
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 3;
      }
      .frame .tb {
        position: absolute;
        left: 5cqw;
        right: 5cqw;
        top: 3.4cqw;
        display: flex;
        justify-content: space-between;
        align-items: center;
        z-index: 4;
      }
      .frame .tb .l {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 0.95cqw;
        color: color-mix(in srgb, var(--ink) 56%, var(--cream));
      }
      .frame .tb .l.coral {
        color: var(--coral);
      }
      .frame .fl {
        position: absolute;
        left: 5cqw;
        right: 5cqw;
        bottom: 3cqw;
        display: flex;
        justify-content: space-between;
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 0.82cqw;
        color: color-mix(in srgb, var(--ink) 48%, var(--cream));
        z-index: 4;
      }

      /* f1 cover */
      .cf1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .cf1 .kick {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 1.1cqw;
        color: var(--coral);
        margin-bottom: 1.6cqw;
      }
      .cf1 h3 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 9.6cqw;
        line-height: 0.98;
        letter-spacing: -0.035em;
        color: var(--ink);
      }

      /* f2 statement */
      .cf2 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 7cqw;
      }
      .cf2 h3 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 7cqw;
        line-height: 1.04;
        letter-spacing: -0.028em;
        color: var(--ink);
        max-width: 74cqw;
      }
      .cf2 h3 .it {
        font-style: italic;
        color: var(--coral);
      }

      /* f3 code surface — the PR-critical frame */
      .cf3 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .cf3 .kick {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 0.95cqw;
        color: var(--coral);
        margin-bottom: 1.3cqw;
      }
      .cf3 .win {
        background: var(--navy);
        border: 0.08cqw solid var(--on-dark-soft);
        border-radius: 0.7cqw;
        overflow: hidden;
        box-shadow: 0 1cqw 3cqw rgba(20, 20, 19, 0.18);
      }
      .cf3 .win .bar {
        background: var(--navy-elev);
        padding: 0.9cqw 1.3cqw;
        display: flex;
        align-items: center;
        gap: 0.5cqw;
      }
      .cf3 .win .bar .dot {
        width: 0.8cqw;
        height: 0.8cqw;
        border-radius: 50%;
      }
      .cf3 .win .bar .fn {
        font-family: var(--mono);
        font-weight: 500;
        font-size: 1cqw;
        color: var(--on-dark-soft);
        margin-left: 0.8cqw;
        letter-spacing: 0.02em;
      }
      .cf3 .win pre {
        margin: 0;
        padding: 1.5cqw 1.8cqw;
        font-family: var(--mono);
        font-weight: 400;
        font-size: 1.55cqw;
        line-height: 1.62;
        color: var(--on-dark);
      }
      .cf3 .add {
        background: color-mix(in srgb, var(--ok) 16%, transparent);
        display: block;
        margin: 0 -1.8cqw;
        padding: 0 1.8cqw;
      }
      .cf3 .del {
        background: color-mix(in srgb, var(--warn) 16%, transparent);
        display: block;
        margin: 0 -1.8cqw;
        padding: 0 1.8cqw;
        color: var(--on-dark-soft);
      }
      .cf3 .k {
        color: var(--coral);
      }
      .cf3 .s {
        color: var(--teal);
      }
      .cf3 .n {
        color: var(--amber);
      }
      .cf3 .g {
        color: var(--ok);
      }
      .cf3 .r {
        color: var(--warn);
      }

      /* f4 number / impact */
      .cf4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .cf4 .tag {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 1cqw;
        color: var(--coral);
        border-top: 0.08cqw solid var(--hair);
        padding-top: 1.2cqw;
        display: inline-block;
      }
      .cf4 .fig {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 16cqw;
        line-height: 0.92;
        letter-spacing: -0.03em;
        color: var(--ink);
      }
      .cf4 .fig .u {
        font-family: var(--mono);
        font-weight: 500;
        font-size: 4.2cqw;
        letter-spacing: 0;
        margin-left: 0.6cqw;
        color: color-mix(in srgb, var(--ink) 64%, var(--cream));
      }
      .cf4 .d {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 1.7cqw;
        line-height: 1.4;
        color: color-mix(in srgb, var(--ink) 74%, var(--cream));
        max-width: 44cqw;
        margin-top: 1cqw;
      }

      /* f5 pull-quote */
      .cf5 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 8cqw;
      }
      .cf5 .q {
        font-family: var(--serif);
        font-style: italic;
        font-weight: 400;
        font-size: 5cqw;
        line-height: 1.16;
        letter-spacing: -0.018em;
        color: var(--ink);
      }
      .cf5 .cite {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 0.95cqw;
        color: color-mix(in srgb, var(--ink) 56%, var(--cream));
        margin-top: 2.2cqw;
      }

      /* f6 closing / cta */
      .cf6 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .cf6 h3 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 6.2cqw;
        line-height: 1.02;
        letter-spacing: -0.028em;
        color: var(--ink);
        margin-bottom: 2.4cqw;
      }
      .cf6 .ship {
        display: flex;
        align-items: center;
        gap: 1.4cqw;
        margin-bottom: 2.4cqw;
      }
      .cf6 .av {
        width: 4cqw;
        height: 4cqw;
        border-radius: 50%;
        border: 0.1cqw solid var(--hair);
        background: var(--tile);
        display: grid;
        place-items: center;
        font-family: var(--mono);
        font-weight: 500;
        font-size: 1.2cqw;
        color: var(--ink);
        margin-left: -1cqw;
      }
      .cf6 .av:first-child {
        margin-left: 0;
      }
      .cf6 .shiplbl {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 0.95cqw;
        color: color-mix(in srgb, var(--ink) 56%, var(--cream));
        margin-left: 0.8cqw;
      }
      .cf6 .cta {
        background: var(--coral);
        color: var(--cream);
        border-radius: 0.6cqw;
        padding: 1.2cqw 2cqw;
        display: inline-flex;
        align-self: flex-start;
        font-family: var(--sans);
        font-weight: 500;
        font-size: 1.8cqw;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .do-card h4 {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 34px;
        color: var(--ink);
        margin-bottom: 6px;
        letter-spacing: -0.02em;
      }
      .do-card .rule {
        height: 1px;
        background: var(--hair);
        margin-bottom: 22px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-family: var(--sans);
        font-weight: 400;
        font-size: 17px;
        line-height: 1.5;
        padding-left: 24px;
        position: relative;
        margin-bottom: 13px;
        color: color-mix(in srgb, var(--ink) 84%, var(--cream));
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--mono);
      }
      .do-card.do li::before {
        content: "+";
        color: var(--coral);
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: color-mix(in srgb, var(--ink) 40%, var(--cream));
      }

      .foot {
        padding: 60px 120px;
        border-top: 1px solid var(--hair);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 30px;
        color: var(--ink);
      }
      .foot .l .spike {
        color: var(--coral);
      }
      .foot .r {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 11px;
        color: color-mix(in srgb, var(--ink) 50%, var(--cream));
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="top">
        <span class="lbl"><span class="spike">✱</span> Frame System · Vol. 01</span
        ><span class="idx">EB Garamond · Inter · JetBrains Mono</span>
      </div>
      <h1>Cream, ink &amp; one <span class="it">coral.</span></h1>
      <div class="lede">
        A warm-editorial frame system — EB Garamond at optical sizes for display, Inter for body,
        JetBrains Mono for the index and code, and a single terracotta coral held as scarce voltage.
      </div>
      <div class="footline">
        <span>Warm cream paper · hairline elevation</span><span>1920 × 1080</span>
      </div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="topbar">
        <span class="lbl">01 — Palette</span>
        <h2>The trinity, plus a code surface</h2>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="meta">
            <div class="name">Ink</div>
            <div class="hex">#141413</div>
            <div class="role">
              Warm near-black — the voice. Every word, every hairline; never pure #000.
            </div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream)"></div>
          <div class="meta">
            <div class="name">Cream</div>
            <div class="hex">#FAF9F5</div>
            <div class="role">Warm paper — the ~60% floor. Never pure white, never cool.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--coral)"></div>
          <div class="meta">
            <div class="name">Coral</div>
            <div class="hex">#CC785C</div>
            <div class="role">
              Terracotta voltage — the one accent. CTA / inline link / band, once per frame.
            </div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--tile)"></div>
          <div class="meta">
            <div class="name">Tile</div>
            <div class="hex">#EFE9DE</div>
            <div class="role">Half-step content surface — where cards gather.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--tile-strong)"></div>
          <div class="meta">
            <div class="name">Tile Strong</div>
            <div class="hex">#ECE3D4</div>
            <div class="role">Pressed / deepest warm-paper step.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--navy)"></div>
          <div class="meta">
            <div class="name">Navy</div>
            <div class="hex">#181715</div>
            <div class="role">
              Warm code / terminal surface — structural anchor, not a brand hue.
            </div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--navy-elev)"></div>
          <div class="meta">
            <div class="name">Navy Elev</div>
            <div class="hex">#252320</div>
            <div class="role">Title bars + status strips on the code surface.</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="background: linear-gradient(90deg, var(--teal) 0 50%, var(--amber) 50% 100%)"
          ></div>
          <div class="meta">
            <div class="name">Syntax</div>
            <div class="hex">#5DB8A6 · #E8A55A</div>
            <div class="role">
              Teal strings · amber numbers — fixed code decoration, not remixable.
            </div>
          </div>
        </div>
      </div>
      <div class="syntax-note">
        <span><span class="dot" style="background: var(--coral)"></span>keywords</span>
        <span><span class="dot" style="background: var(--teal)"></span>strings</span>
        <span><span class="dot" style="background: var(--amber)"></span>numbers</span>
        <span><span class="dot" style="background: var(--ok)"></span>added</span>
        <span><span class="dot" style="background: var(--warn)"></span>removed</span>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="topbar">
        <span class="lbl">02 — Typography</span>
        <h2>EB Garamond display · Inter body · Mono index</h2>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display-cover</div>
          <div class="m">EB Garamond · 500 · 190px · −0.028em · sentence</div>
        </div>
        <div>
          <span class="serif" style="font-size: 108px; line-height: 0.98"
            >Streaming, end to end.</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display</div>
          <div class="m">EB Garamond · 500 · 140px · −0.022em</div>
        </div>
        <div><span class="serif" style="font-size: 80px">A serif that thinks.</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">number-hero</div>
          <div class="m">EB Garamond · 500 · 180px · figure + mono unit</div>
        </div>
        <div>
          <span class="serif" style="font-size: 96px; color: var(--ink)"
            >+1,204<span
              style="
                font-family: var(--mono);
                font-weight: 500;
                font-size: 36px;
                color: color-mix(in srgb, var(--ink) 64%, var(--cream));
              "
            >
              lines</span
            ></span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body / lead</div>
          <div class="m">Inter · 400 · line 1.5</div>
        </div>
        <div>
          <p
            style="
              font-family: var(--sans);
              font-weight: 400;
              font-size: 24px;
              line-height: 1.5;
              max-width: 680px;
              color: color-mix(in srgb, var(--ink) 82%, var(--cream));
            "
          >
            The body reads first and answers second — Inter at weight 400, sentence case, with its
            qualifiers kept. No hype, no exclamation.
          </p>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">kicker / code</div>
          <div class="m">JetBrains Mono · 500 uppercase 0.16em · code 400</div>
        </div>
        <div>
          <span class="mono" style="font-size: 16px; color: var(--coral)"
            >✱ Pull Request · #1842</span
          >
          &nbsp;
          <span style="font-family: var(--mono); font-size: 18px; color: var(--ink)"
            >async for tok in stream()</span
          >
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">quote-pull</div>
          <div class="m">EB Garamond · 400 · italic · the expressive register</div>
        </div>
        <div>
          <span class="serif" style="font-style: italic; font-size: 46px; color: var(--ink)"
            >Ship it when it reads like a person wrote it.</span
          >
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="topbar">
        <span class="lbl">03 — Components</span>
        <h2>Hairline elevation · scarce coral</h2>
      </div>

      <div class="cgrid">
        <div class="s5">
          <div class="cnote">card-hairline — 1px ink hairline + one soft warm shadow</div>
          <div class="card">
            <div class="kt">Reads first, answers second</div>
            <div class="kb">
              The editorial content card. Elevation is the hairline plus one soft warm shadow —
              never a heavy drop, glow, or gradient.
            </div>
          </div>
          <div class="card tile" style="margin-top: 24px">
            <div class="kt">Gathers on a half-step tile</div>
            <div class="kb">
              Content collects on a warm tile surface, half a step down from the cream floor.
            </div>
          </div>
        </div>
        <div class="s3">
          <div class="cnote">kicker-spike + coral-callout</div>
          <div class="kicker"><span class="spike">✱</span> For the considered worker</div>
          <div class="callout" style="margin-top: 22px">
            <span class="b">Start a project</span><span class="a">CTA</span>
          </div>
          <div
            class="kb"
            style="
              margin-top: 18px;
              font-family: var(--sans);
              font-size: 15px;
              line-height: 1.5;
              color: color-mix(in srgb, var(--ink) 70%, var(--cream));
            "
          >
            Coral is rationed — one moment per frame.
          </div>
        </div>
        <div class="s4">
          <div class="cnote">number-lockup — EB Garamond figure + mono unit</div>
          <div class="kpi">
            <div class="tag">Impact</div>
            <div class="fig">2.1<span class="u">× faster</span></div>
            <div class="d">Figure is serif; the unit is always mono, never the serif.</div>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s7">
          <div class="cnote">
            code-surface — the warm-navy code / terminal surface (code-* blocks render here)
          </div>
          <div class="codewin">
            <div class="bar">
              <span class="dot" style="background: #e8695a"></span
              ><span class="dot" style="background: #e8b35a"></span
              ><span class="dot" style="background: #5db872"></span
              ><span class="fn">transport.py</span>
            </div>
            <pre><span class="del">- <span class="k">def</span> respond(q):</span>
<span class="del">-     <span class="k">return</span> model.answer(q)</span>
<span class="add">+ <span class="k">async def</span> respond(q):</span>
<span class="add">+     <span class="k">async for</span> tok <span class="k">in</span> model.stream(q, n=<span class="n">3</span>):</span>
<span class="add">+         <span class="k">yield</span> tok</span></pre>
          </div>
        </div>
        <div class="s5">
          <div class="cnote">pull-quote — EB Garamond italic + mono cite</div>
          <div class="quote">
            <div class="q">
              Refactor the transport so streaming isn't a special case — it's the default.
            </div>
            <div class="cite">Commit a1b2c3d · a. chen</div>
          </div>
        </div>
      </div>
    </section>

    <!-- FRAMES -->
    <section style="background: var(--tile)">
      <div class="topbar">
        <span class="lbl">04 — Frames</span>
        <h2>Six treatments ↔ PR beats</h2>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Cover</b><span>· the PR title · cream · left</span></div>
          <div class="frame cf1">
            <div class="tb">
              <span class="l coral">✱ Pull Request · #1842</span><span class="l">example/sdk</span>
            </div>
            <div class="body">
              <div class="kick">✱ Feature</div>
              <h3>Streaming,<br />end to end.</h3>
            </div>
            <div class="fl">
              <span class="l">main ← stream-api</span><span class="l">01 / 06</span>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Statement</b><span>· the change in a sentence · cream</span></div>
          <div class="frame cf2">
            <div class="tb">
              <span class="l coral">The change</span><span class="l">02 / 06</span>
            </div>
            <div class="body">
              <h3>The API now streams tokens <span class="it">as they're written.</span></h3>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Code Surface</b><span>· the diff · navy · the PR-critical frame</span>
          </div>
          <div class="frame cf3">
            <div class="tb">
              <span class="l coral">The diff</span><span class="l">03 / 06</span>
            </div>
            <div class="body">
              <div class="kick">transport.py · +3 −2</div>
              <div class="win">
                <div class="bar">
                  <span class="dot" style="background: #e8695a"></span
                  ><span class="dot" style="background: #e8b35a"></span
                  ><span class="dot" style="background: #5db872"></span
                  ><span class="fn">transport.py</span>
                </div>
                <pre><span class="del">- <span class="k">def</span> respond(q):</span>
<span class="del">-     <span class="k">return</span> model.answer(q)</span>
<span class="add">+ <span class="k">async def</span> respond(q):</span>
<span class="add">+     <span class="k">async for</span> tok <span class="k">in</span> model.stream(q):</span>
<span class="add">+         <span class="k">yield</span> tok</span></pre>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Number / Impact</b><span>· +N/−M · cream · oversized figure</span>
          </div>
          <div class="frame cf4">
            <div class="tb">
              <span class="l coral">By the numbers</span><span class="l">04 / 06</span>
            </div>
            <div class="body">
              <span class="tag">Net change</span>
              <div class="fig">+1,204<span class="u">/ −318</span></div>
              <div class="d">
                Across 17 files — the transport layer rewritten, the special-case path deleted.
              </div>
            </div>
            <div class="fl"><span class="l">17 files</span><span class="l">04 / 06</span></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Pull-quote</b><span>· commit / review · EB Garamond italic</span>
          </div>
          <div class="frame cf5">
            <div class="tb">
              <span class="l coral">In the commit</span><span class="l">05 / 06</span>
            </div>
            <div class="body">
              <div class="q">"Streaming isn't a special case anymore — it's the default path."</div>
              <div class="cite">a. chen · author</div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Closing / CTA</b><span>· shipped-by · coral voltage</span></div>
          <div class="frame cf6">
            <div class="tb"><span class="l coral">Shipped</span><span class="l">06 / 06</span></div>
            <div class="body">
              <h3>Merged &amp; shipped.</h3>
              <div class="ship">
                <div class="av">AC</div>
                <div class="av">RK</div>
                <div class="av">JM</div>
                <span class="shiplbl">a. chen · r. kapoor · +1</span>
              </div>
              <span class="cta">Read the PR →</span>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="topbar">
        <span class="lbl">05 — Rules</span>
        <h2>Frame Rules</h2>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <div class="rule"></div>
          <ul>
            <li>Stand every frame on the warm cream floor; gather content on a half-step tile.</li>
            <li>Set display in EB Garamond 400, sentence case, optical size, negative-tracked.</li>
            <li>Ration coral to one moment per frame — CTA, inline link, or full-bleed band.</li>
            <li>Elevate with a 1px hairline + at most one soft warm shadow.</li>
            <li>Reserve warm navy for the code surface; pair every figure with a mono unit.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <div class="rule"></div>
          <ul>
            <li>No pure white, no cool gray, no pure black; no fourth brand hue.</li>
            <li>No heavy drop shadow, glow, gradient on content, or tilt.</li>
            <li>No uppercase or title-case EB Garamond; no sans headline; no serif label.</li>
            <li>No two corals in one frame; coral never sets a headline or body run.</li>
            <li>Don't cram edge-to-edge — the considered voice needs cream.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--cream);
        --cap-accent: var(--coral);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        border: 0.07cqw solid color-mix(in srgb, var(--cap-ink) 12%, transparent);
        border-radius: 0.7cqw;
        box-shadow:
          0 0.06cqw 0.18cqw rgba(20, 20, 19, 0.08),
          0 0.25cqw 1cqw rgba(20, 20, 19, 0.04);
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.12em 0.3em;
        font-family: var(--serif);
        font-weight: 500;
        font-size: 3.4cqw;
        line-height: 1.24;
        letter-spacing: -0.02em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.02em 0.06em;
        color: color-mix(in srgb, var(--cap-ink) 40%, var(--cap-canvas));
        border-bottom: 0.16cqw solid transparent;
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        border-bottom: 0.16cqw solid var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 0.12cqw dashed color-mix(in srgb, var(--ink) 28%, transparent);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 5cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 0.82cqw;
        color: color-mix(in srgb, var(--ink) 50%, var(--cream));
        z-index: 4;
      }
    </style>
    <section style="background: var(--tile)">
      <div class="topbar">
        <span class="lbl">06 — Captions</span>
        <h2>Lower-Third Karaoke</h2>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="tb"><span class="l coral">Captions</span><span class="l">active</span></div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Streaming</span>
                  <span class="w spoken">is</span>
                  <span class="w active">the</span>
                  <span class="w">default</span>
                  <span class="w">now.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · underline cleared</span></div>
          <div class="frame capdemo">
            <div class="tb">
              <span class="l coral">Captions</span><span class="l">settled</span>
            </div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Read</span>
                  <span class="w spoken">it</span>
                  <span class="w spoken">first.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l"><span class="spike">✱</span> Code editorial</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#faf9f5" />
        <text x="11" y="48" font-family="Georgia" font-size="34" fill="#141413">Aa</text>
        <text x="68" y="30" font-family="monospace" font-size="22" fill="#cc785c">✱</text>
        <rect x="11" y="62" width="58" height="1.5" fill="#141413" opacity="0.16" />
        <rect x="11" y="72" width="40" height="10" rx="2" fill="#181715" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/code-editorial/FRAME.md›
---
version: alpha
name: Code editorial — Frame (video / frame layer)
description: >
  Video-first companion to Code editorial's design.md. The unit is the frame (1920×1080). Atoms are
  identical and sacred — warm cream paper (never pure white, never cool), a single terracotta coral
  as scarce "voltage", hairline ink elevation (no heavy shadow), EB Garamond for all
  display + Inter body + JetBrains Mono for the index/code voice on a warm-navy code surface,
  sentence-case display, and the ✱ coral spike mark. Composition + frame scale rewritten. Motion out
  of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  ink: "#141413"
  cream: "#FAF9F5"
  tile: "#EFE9DE"
  tile-strong: "#ECE3D4"
  coral: "#CC785C"
  navy: "#181715"
  navy-soft: "#1F1E1B"
  navy-elev: "#252320"

borders: { hairline: "1px solid ink@12%", hairline-strong: "1px solid ink@20%", dark: "1px solid cream@14% (on navy)" }
shadows: { card: "0 1px 3px ink@8%, 0 4px 16px ink@4%", none: "none" }

typography:
  # — reading + chrome ramp —
  body:    { fontFamily: "Inter", cqw: 1.5, weight: 400, lineHeight: 1.5 }
  lead:    { fontFamily: "Inter", cqw: 2.08, weight: 400, lineHeight: 1.5 }
  card-title:{ fontFamily: "Inter", cqw: 2.3, weight: 500, lineHeight: 1.25, tracking: "-0.005em" }
  button:  { fontFamily: "Inter", cqw: 1.46, weight: 500, lineHeight: 1.0 }
  tag-upper:{ fontFamily: "Inter", cqw: 1.35, weight: 500, tracking: "0.18em", upper: true }
  kicker:  { fontFamily: "JetBrains Mono", px: 28, cqw: 1.46, weight: 500, tracking: "0.16em", upper: true }
  mono-label:{ fontFamily: "JetBrains Mono", px: 26, cqw: 1.35, weight: 500, tracking: "0.02em" }
  code:    { fontFamily: "JetBrains Mono", cqw: 1.67, weight: 400, lineHeight: 1.6 }
  # — display ramp (EB Garamond 400, sentence case, negative tracking. Renderer embeds only 400/700 — author at 400; italic is the synthesized slant) —
  headline:{ fontFamily: "EB Garamond", cqw: 4.6, weight: 400, lineHeight: 1.06, tracking: "-0.018em" }
  quote-pull:{ fontFamily: "EB Garamond", cqw: 5.0, weight: 400, lineHeight: 1.12, tracking: "-0.012em", italic: true }
  display-italic:{ fontFamily: "EB Garamond", cqw: 6.7, weight: 400, lineHeight: 1.05, tracking: "-0.012em", italic: true }
  display:{ fontFamily: "EB Garamond", cqw: 7.3, weight: 400, lineHeight: 1.02, tracking: "-0.022em" }
  number-hero:{ fontFamily: "EB Garamond", cqw: 9.4, weight: 400, lineHeight: 0.95, tracking: "-0.025em" }
  display-cover:{ fontFamily: "EB Garamond", cqw: 9.9, weight: 400, lineHeight: 0.98, tracking: "-0.028em" }
  number-unit:{ fontFamily: "JetBrains Mono", cqw: 2.08, weight: 500, lineHeight: 1.0 }

spacing:
  slide-pad: "4.2cqw"   # ~80px @1920
  gap-md: "1.7cqw"
  hairline: "1px"
  radius-sm: "6px"
  radius-md: "8px"
  radius-lg: "12px"
  radius-pill: "9999px"

components:
  card-hairline:
    backgroundColor: "{colors.cream} or {colors.tile}"
    border: "1px solid {colors.ink}@12%"
    rounded: "{spacing.radius-lg}"
    shadow: "{shadows.card}"
    typography: "{typography.card-title} + {typography.body}"
    description: "The editorial content card. Elevation is the hairline + ONE soft warm shadow — never a heavy drop, glow, or gradient."
  kicker-spike:
    typography: "{typography.kicker}"
    mark: "✱ coral spike prefix"
    description: "The eyebrow — JetBrains Mono uppercase, indexical (2–5 words), prefixed with the coral ✱. Never plain text, never a sentence."
  coral-callout:
    backgroundColor: "{colors.coral} (full-bleed) or {colors.cream} with a coral edge"
    textColor: "{colors.cream} on coral"
    rounded: "{spacing.radius-md}"
    typography: "{typography.button} / {typography.h2}"
    description: "The ONE voltage moment per frame — the CTA, the single inline link, OR the full-bleed band. Never two corals in one frame."
  number-lockup:
    typography: "{typography.number-hero} figure + {typography.number-unit} unit"
    description: "Hero stat — a EB Garamond figure paired with a JetBrains Mono unit (200K, +1,204, −318, 17 files). Figure is serif; the unit is ALWAYS mono, never the serif."
  pull-quote:
    typography: "{typography.quote-pull} (EB Garamond italic) + {typography.tag-upper} cite"
    description: "A commit message, a reviewer line, or the thesis. EB Garamond italic, small Inter uppercase cite beneath."
  section-rule:
    rule: "1px solid {colors.ink}@12% (or {colors.cream}@14% on navy)"
    description: "The only separator. A coral 1px rule may draw on to introduce a section. Never 2px+, never a heavy divider."
  code-surface:
    backgroundColor: "{colors.navy} body / {colors.navy-elev} title bar + status strip"
    textColor: "{colors.cream} (JetBrains Mono); syntax in coral (keywords) / teal #5DB8A6 (strings) / amber #E8A55A (numbers)"
    border: "1px solid {colors.cream}@14%"
    rounded: "{spacing.radius-md}"
    description: "The warm-navy code / terminal surface. The CODE ITSELF is rendered by the code-* registry blocks (code-diff / code-typing / code-snippet-*); this preset owns the surrounding surface, title bar, status strip, and mono chrome — not the code rendering."
  spike-mark:
    glyph: "✱ (U+2731), always {colors.coral}"
    description: "The brand mark. Fades + scales 0.92→1 on a single emphasis beat; never spins."
---

# Code editorial — Frame (video / frame layer)

## Overview

Code editorial at frame scale is a **warm-editorial brand book come to life** — the register of a literary
imprint or a research note. The thesis is three colors: **cream is the ground, ink is the voice,
coral is the voltage** — and a fourth (warm navy) only where code shows itself. Every surface is
**warm cream** (never pure white, never cool gray); content gathers on a **tile** surface half a
step darker — the demarcation is a half-step, never a hard contrast. Elevation is a **1px hairline**
ink border at low alpha plus, rarely, one soft warm shadow. There are no heavy drops, no glows, no
gradients on content.

Three editorial voices, each in its own face: **EB Garamond** carries every display moment — covers,
headlines, pull-quotes, big stat numerals — at large display sizes with gentle negative tracking;
its **italic** is the expressive register. **Inter** carries body, leads, card titles, buttons, and
UI chrome. **JetBrains Mono** carries the indexical layer — kickers, technical labels, the code
window, status strips. Switching a voice's face collapses the register: a sans headline or a serif
label reads as a different brand.

**Key characteristics at frame scale:**

- **Cream / ink / coral trinity** + a warm-navy code surface; cream is the default ground, ink the voice, coral the scarce voltage.
- **EB Garamond** (sentence case, negative-tracked) for all display; **Inter** body/chrome; **JetBrains Mono** index + code.
- **Hairline elevation** — a 1px low-alpha ink border + at most one soft warm shadow. No heavy drop, glow, or gradient.
- **Coral is rationed** — at most ONE coral moment per frame (CTA, inline link, OR full-bleed band); coral never sets a headline or a body run.
- **Density is free** — fill the frame as the content wants; a frame may stand on a single focal or carry a dense, layered composition.
- **The ✱ coral spike** opens kickers; warm navy is reserved for the code/terminal surface.

## The Frame

### Frame Craft Bar

Eyeball tests gate every frame before any structural check:

- **Squint** — one EB Garamond display moment dominates at 3–6× its neighbor.
- **Trinity** — cream/tile ground, ink text, coral exactly **once**; warm navy only on the code surface; no cool gray / pure white / fourth hue.
- **Type** — EB Garamond sentence-case display (negative-tracked); Inter 400 body; JetBrains Mono kickers (uppercase 0.16em, coral ✱) + code.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `slide-pad` ~4.2cqw; the kicker/mono chrome sit inside it.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. Hairlines stay 1px; card radii stay
6/8/12px; the warm-paper reading must survive every ratio.

## Colors

Tokens identical to the source. Default ground `{colors.cream}`; content gathers on
`{colors.tile}` / `{colors.tile-strong}` (half-step warm steps, never a hard contrast).
**Headlines & body:** `{colors.ink}` on cream/tile; `{colors.cream}` on navy. **Coral**
(`{colors.coral}`) is the scarce voltage — one moment per frame (CTA, inline link, or full-bleed
band), never body text, never a card fill. **Warm navy** (`{colors.navy}` / `navy-soft` /
`navy-elev`) is the code / terminal / dark-card surface — a structural anchor, not a fourth brand
hue. **No cool grays, no pure white, no pure black.**

**Fixed syntax colors (decoration, NOT remixable brand hues).** When a code line is hand-set rather
than rendered by a `code-*` block, keywords are coral, strings are **teal `#5DB8A6`**, numbers are
**amber `#E8A55A`**; status reads success `#5DB872` / warn `#C64545`. These track the code surface,
not the brand trinity — keep them out of the brand palette.

## Typography

Two ramps. The **reading/chrome ramp** (Inter `body` 1.5cqw / `lead` 2.08cqw weight 400; JetBrains
Mono `kicker`/`mono-label` in px) carries copy + chrome; the **display ramp** (EB Garamond `headline` 4.6cqw
→ `display-cover` 9.9cqw, weight 400, negative-tracked) carries every headline + stat.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono px labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display-cover`; 4–6 → `display`; 7+ → `headline`. Reserve the 7.3–9.9cqw tier for cover / statement / stat.
- **EB Garamond display is sentence case** (NOT title case, NOT uppercase), weight 400, negative-tracked (−0.012..−0.028em); reach for **italic** when the line is a stance or a definition. **Inter body** sentence case weight 400. **JetBrains Mono** kickers UPPERCASE 0.16em with the coral ✱. No uppercase serif, no sans headline, no serif label.

## Depth & Surface

Hairline elevation:

- **1px hairline** ink border at ~12% alpha is the primary lift (cream@14% on navy).
- **One soft warm shadow** (`0 1px 3px ink@8%, 0 4px 16px ink@4%`) — used rarely, never heavy.
- **Half-step surface** — a `{colors.tile}` block on cream reads elevated by the warm step, not by a cast shadow.

**Ceiling:** no heavy drop shadow, no glow, no gradient on content, no tilt. The system has no light
to emit; it reads by warmth and hairline.

## Shapes

- **6px** small chrome, **8px** cards / code surface, **12px** large cards / quote frames, **9999px** true pills only. No square corners, no heavy rounding; the editorial register is gently rounded, never hard.

## Components

- **card-hairline** — the editorial content card (hairline + one soft shadow). **section-rule** — the only separator (1px, coral may draw on).
- **kicker-spike** — the ✱ coral eyebrow. **coral-callout** — the one voltage moment (CTA / inline link / full-bleed band).
- **number-lockup** — EB Garamond figure + mono unit (the PR `+N / −M`, `200K`, file counts). **pull-quote** — EB Garamond italic + cite (a commit message / reviewer line).
- **code-surface** — the warm-navy code / terminal surface; the code itself comes from the **`code-*` registry blocks**, this owns the surface + mono chrome.
- **spike-mark** — the ✱ brand glyph, always coral.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · Fixed/Free · density.
> One coral moment per frame; open with a kicker-spike.

### 1 · Cover (identity · move: oversized EB Garamond · cream)

**Ground** `{colors.cream}`, `slide-pad`. **Composes** kicker-spike, display-cover, lead, mono-label index. **Focal** a 2–3 line EB Garamond `display-cover` (sentence case, ink) under a coral ✱ kicker. **Chrome** mono index strip (repo · branch). **Accent** the single coral ✱. **Fixed** EB Garamond 400 sentence case, hairline, cream ground. **Free** title, the mono index, layout + how full the frame runs. **Density** free.

### 2 · Statement (statement · move: single EB Garamond line · cream or navy)

**Ground** `{colors.cream}` (or `{colors.navy}` for gravity). **Composes** kicker-spike, display, optional lead. **Focal** one 2-line EB Garamond `display` carrying the change in a sentence — reach for **italic** if it's a stance. **Chrome** mono kicker. **Accent** none — the serif carries it (or one coral word). **Fixed** sentence-case serif. **Free** the line, ground, layout + density. **Density** free.

### 3 · Code Surface (code · move: warm-navy code window · the PR-critical frame)

**Ground** `{colors.cream}` framing a `{colors.navy}` **code-surface** (8px, cream@14% hairline, `navy-elev` title bar + filename in mono). **Composes** mono-label filename, the **`code-*` block** (code-diff / code-typing / code-snippet-_), optional `section-rule`. **Focal** the code panel — the diff / before→after / typed-on snippet. **Chrome** mono filename + status strip. **Accent** syntax coral/teal/amber inside the panel; one coral marker outside (e.g. a `+`/`−` gutter cue). **Fixed** warm-navy surface, mono code, hairline. **Free** which code-_ block, the code (from the diff), how large the panel runs. **Density** dense.

### 4 · Number / Impact (data · move: oversized figure · cream)

**Ground** `{colors.cream}`, `slide-pad`. **Composes** kicker-spike, number-lockup, lead/caption, optional `section-rule`. **Focal** a EB Garamond `number-hero` figure with a mono unit over a 1px rule — the PR impact (`+1,204 / −318`, `17 files`, `2.1× faster`). **Chrome** mono tag. **Accent** the figure in ink; at most one coral unit. **Fixed** serif figure + mono unit, hairline rule. **Free** the figures (from the script), tag, layout + density. **Density** free.

### 5 · Pull-quote (quote · move: EB Garamond italic · cream)

**Ground** `{colors.cream}`. **Composes** kicker-spike, pull-quote, tag-upper cite. **Focal** a EB Garamond **italic** `quote-pull` — a commit message, a reviewer line, or the thesis — with a small Inter uppercase cite (author · role) beneath. **Chrome** mono kicker. **Accent** none, or one coral mark. **Fixed** EB Garamond italic quote + uppercase cite. **Free** quote, attribution, layout + density. **Density** free.

### 6 · Closing / CTA (closer · move: coral voltage · cream or navy)

**Ground** `{colors.cream}` (or `{colors.navy}`). **Composes** display sign-off, coral-callout, optional contributor row (`assets/<login>.png` avatars + mono names). **Focal** a short EB Garamond sign-off with the one **coral-callout** (the CTA or full-bleed band) and, for a "shipped-by" close, a row of hairline-ringed avatar chips. **Chrome** mono index. **Accent** the single coral voltage. **Fixed** one coral moment, hairline avatar rings, sentence-case serif. **Free** sign-off, who ships, layout + density. **Density** free.

## Composition Rules

### Do

- Stand every frame on the **warm cream floor**; gather content on a **half-step tile** surface.
- Set all display in **EB Garamond, sentence case**, negative-tracked; **Inter 400** body; **JetBrains Mono** kickers (uppercase, 0.16em, coral ✱) + code.
- Ration **coral to one moment per frame** — CTA, inline link, OR full-bleed band.
- Elevate with a **1px hairline** + at most one soft warm shadow; reserve **warm navy** for the code/terminal surface.
- Lead with **one clear focal**; open regions with a **kicker-spike**. Fill the frame as the content wants.
- Pair a EB Garamond figure with a **mono unit** for every stat; render code via the **`code-*` blocks** on the navy surface.

### Don't

- No pure white, no cool gray, no pure black; no fourth brand hue (navy is structural, syntax colors are decoration).
- No heavy drop shadow, glow, gradient on content, or tilt — hairline elevation only.
- No uppercase or title-case EB Garamond display; no sans headline; no serif label; no serif-set numeric unit.
- No two coral moments in one frame; coral never sets a headline or body run.
- Don't blow a headline past the measure — step the ramp down.

## Aspect-Ratio Behavior

| Treatment     | 16:9                    | 9:16                        | 1:1                    |
| ------------- | ----------------------- | --------------------------- | ---------------------- |
| Cover         | display left, index top | display top, index below    | display, index corner  |
| Statement     | line left/centered      | line stacked                | centered               |
| Code Surface  | panel framed in cream   | panel taller, fewer lines   | panel centered, square |
| Number/Impact | figure left             | figure centered, taller     | centered               |
| Pull-quote    | quote left              | quote stacked               | centered               |
| Closing/CTA   | sign-off + avatar row   | sign-off top, avatars below | centered, avatars wrap |

`slide-pad` holds on the short edge; re-step display above the 1.4cqw floor. The code surface keeps
its hairline + mono chrome on every ratio; the avatar row wraps rather than shrinks below legibility.

## Approved Real Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Contributor avatars come from the project's `assets/<login>.png` (staged from the PR's
people graph); the ✱ spike, hairlines, and the code surface are CSS-only and need no external imagery.

## Numerals & Claims (hard rule)

Never invent figures, stats, diffs, or counts at frame scale. Render slots as `— figure —`,
`{metric}`, `+N / −M`. Number-lockups, code panels, and impact stats carry placeholders until the
script (from the PR ingest) supplies real values. Branch names, file counts, and `+/−` totals trace
to the diff; commit/issue numbers are chrome.

## Pre-Render Self-Audit

- **Squint** — one EB Garamond display moment dominates.
- **Trinity** — cream floor + tile step + ink voice; coral appears exactly once; warm navy only on the code surface; no cool gray / pure white / fourth hue.
- **Type** — EB Garamond sentence-case display (negative-tracked); Inter 400 body; JetBrains Mono kickers (uppercase 0.16em, coral ✱) + code; ≥1.4cqw floor.
- **Depth** — 1px hairline + at most one soft warm shadow; no heavy drop / glow / gradient / tilt; 6/8/12px radii.
- **Code** — code rendered by a `code-*` block on the warm-navy surface; syntax coral/teal/amber; figures paired with a mono unit.
- **Fabrication** — every numeral / diff traces to the PR, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only. Code editorial's motion register — short cross-dissolves, no overshoot/bounce/elastic, coral the only "draw-on", numbers count up, code types on line by line — lives in the skill's `references/motion.md`, not here.
- **EB Garamond + Inter + JetBrains Mono ship as licensed local WOFF2 assets with this preset.** Copy the six files in this preset's `fonts/` folder (weights 400 + 700) into the project's `assets/fonts/` and add one `@font-face` per file, so Studio, snapshots, and renders resolve them offline without a first-run Google Fonts fetch. Nothing stages them for you. Author display at **weight 400** (700 reads as a heavy bold, off-register), and treat italic as the browser-synthesized slant (acceptable for the pull-quote register; add a real italic face only if a project leans hard on it). EB Garamond is a warm old-style serif (low contrast, humanist); if it ever fails, fall to Georgia or another old-style serif — never to a sans. CJK: Noto Serif SC (display) / Noto Sans SC (body) / Noto Sans Mono CJK (code); the sentence-case warmth carries when the serif drops.
- **Syntax colors (teal `#5DB8A6` / amber `#E8A55A` / status) are fixed decoration**, declared in §Colors — they are NOT in the remixable `colors:` block, so a brand remix never repaints them.
- **The code itself is the `code-*` registry blocks**, not this preset — this preset owns only the surrounding warm-navy surface + mono chrome.
- **9:16 / 1:1 are guidance**; verify the legibility floor and that the cream/tile warmth + one-coral discipline hold per ratio.
assets/frame-presets/coral/caption-skin.html›
<!--
  Coral — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a coral literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a flat warm-cream block on a single ink hairline border — coral's hard-edge
  geometry, square corners, ZERO radius, ZERO shadow (coral's depth is hard color edges,
  never cast). Bebas Neue, uppercase + tracked, ink glyphs. Upcoming words sit in faint
  ink; the current word gets a solid coral-fire flat block (the brand's signature
  hard-edge fill — a square block, no blur, no radius) with ink reading on top; spoken
  words settle to solid ink, fill cleared. Brand/key terms carry a coral hairline
  underline (the counter inflection).
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE flat cream block — single ink hairline border, square corners (coral's hard-edge
     geometry), ZERO radius, ZERO shadow */
  .caption-pill {
    max-width: 78%;
    padding: 18px 44px 20px;
    background: var(--cap-canvas, #f5f0e8);
    border: 1px solid var(--cap-ink, #1a1a1a);
    border-radius: 0;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.1em 0.3em;
    font-family: var(--font-display, "Bebas Neue"), sans-serif;
    font-weight: 400;
    text-transform: uppercase;
    font-size: clamp(44px, 4.8vw, 64px);
    line-height: 1.04;
    letter-spacing: 0.06em;
  }

  /* upcoming — faint ink, no fill */
  .caption-word {
    display: inline-block;
    padding: 0 0.12em;
    color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 40%, var(--cap-canvas, #f5f0e8));
  }
  /* current word — THE coral-fire flat block (box-shadow spread = a hard square block
     w/o layout shift; no blur, no radius), ink reading on top */
  .caption-word.is-active {
    color: var(--cap-ink, #1a1a1a);
    background: var(--cap-accent, #e85d5d);
    box-shadow: 0 0 0 0.1em var(--cap-accent, #e85d5d);
  }
  /* already spoken — solid ink, fill cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #1a1a1a);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — a coral hairline underline (coral's only display inflection) */
  .caption-word.cap-brand {
    text-decoration: underline;
    text-decoration-thickness: 0.06em;
    text-decoration-color: var(--cap-accent, #e85d5d);
    text-underline-offset: 0.12em;
  }
  /* numerals / data — Inter body voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Inter"), ui-sans-serif, sans-serif;
    color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 72%, var(--cap-canvas, #f5f0e8));
  }
  /* emphasis — a coral-dark hairline underline (the counter accent) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--cap-accent-2, #d44a4a);
    text-underline-offset: 0.1em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(38px, 5.8vw, 54px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.97 }, { scale: 1, duration: 0.14, ease: "power2.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/coral/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Coral</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Inter:wght@300;400;600;700&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --coral: #e85d5d;
        --coral-dark: #d44a4a;
        --cream: #f5f0e8;
        --cream-dark: #e8e0d4;
        --ink: #1a1a1a;
        --gray: #6b6b6b;
        --light-gray: #b0b0b0;
        --white: #ffffff;
        --display: "Bebas Neue", sans-serif;
        --body: "Inter", sans-serif;
        --hatch: repeating-linear-gradient(
          45deg,
          transparent,
          transparent 20px,
          rgba(0, 0, 0, 0.06) 20px,
          rgba(0, 0, 0, 0.06) 40px
        );
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #0e0e0e;
      }
      body {
        background: var(--cream);
        color: var(--ink);
        font-family: var(--body);
        -webkit-font-smoothing: antialiased;
      }
      .beb {
        font-family: var(--display);
        text-transform: uppercase;
      }
      .eyebrow {
        font-family: var(--body);
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 4px;
        font-size: 13px;
        color: var(--coral);
      }

      /* doc rhythm */
      section {
        padding: 120px 100px;
        position: relative;
      }
      .sec-head {
        display: flex;
        align-items: flex-end;
        gap: 24px;
        margin-bottom: 64px;
        border-bottom: 3px solid rgba(26, 26, 26, 0.15);
        padding-bottom: 24px;
      }
      .sec-head .num {
        font-family: var(--display);
        font-size: 64px;
        line-height: 0.8;
        color: var(--coral);
      }
      .sec-head h2 {
        font-family: var(--display);
        font-size: 64px;
        line-height: 0.8;
        letter-spacing: 2px;
      }
      .sec-head .sp {
        flex: 1;
      }
      .sec-head .tag {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 3px;
        font-size: 12px;
        color: var(--gray);
      }

      /* COVER — region split */
      .cover {
        min-height: 100vh;
        display: grid;
        grid-template-rows: 38% 62%;
      }
      .cover .top {
        background: var(--coral);
        position: relative;
        overflow: hidden;
        padding: 56px 64px;
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
      }
      .cover .top::before {
        content: "";
        position: absolute;
        inset: 0;
        background: var(--hatch);
        pointer-events: none;
      }
      .cover .top .brand {
        font-family: var(--display);
        font-size: 40px;
        letter-spacing: 6px;
        color: var(--ink);
        position: relative;
        z-index: 2;
      }
      .cover .top .meta {
        text-align: right;
        position: relative;
        z-index: 2;
      }
      .cover .top .meta .ml {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 3px;
        font-size: 12px;
        color: var(--ink);
        opacity: 0.7;
      }
      .cover .top .meta .mf {
        font-family: var(--display);
        font-size: 38px;
        letter-spacing: 2px;
        color: var(--ink);
        margin-top: 4px;
      }
      .cover .top .bignum {
        position: absolute;
        right: 48px;
        bottom: -70px;
        font-family: var(--display);
        font-size: 340px;
        line-height: 0.7;
        color: rgba(0, 0, 0, 0.12);
        z-index: 1;
      }
      .cover .bot {
        background: var(--cream);
        padding: 64px;
        display: flex;
        flex-direction: column;
        justify-content: center;
      }
      .cover h1 {
        font-family: var(--display);
        font-size: clamp(80px, 13vw, 190px);
        line-height: 0.88;
        letter-spacing: 4px;
        color: var(--ink);
      }
      .cover h1 em {
        font-style: normal;
        color: var(--coral);
      }
      .cover .rule {
        height: 3px;
        background: rgba(26, 26, 26, 0.15);
        margin: 40px 0 28px;
        max-width: 900px;
      }
      .cover .strap {
        font-family: var(--body);
        font-weight: 300;
        font-size: 24px;
        line-height: 1.5;
        color: var(--gray);
        max-width: 760px;
      }
      .cover .strap b {
        font-weight: 600;
        color: var(--ink);
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 0;
        border: 1px solid rgba(26, 26, 26, 0.12);
      }
      .sw {
        padding: 0;
      }
      .sw .chip {
        height: 160px;
        border-bottom: 1px solid rgba(26, 26, 26, 0.12);
      }
      .sw:not(:last-child) {
        border-right: 1px solid rgba(26, 26, 26, 0.12);
      }
      .sw .meta {
        padding: 22px 24px;
      }
      .sw .name {
        font-family: var(--display);
        font-size: 30px;
        letter-spacing: 2px;
      }
      .sw .hex {
        font-family: var(--body);
        font-size: 13px;
        letter-spacing: 1px;
        color: var(--gray);
        margin-top: 4px;
      }
      .sw .role {
        font-size: 13px;
        line-height: 1.5;
        color: var(--gray);
        margin-top: 12px;
      }
      .palette-row {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
      }
      .palette-row + .palette-row .sw .chip {
        border-top: 0;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 260px 1fr;
        border-bottom: 1px solid rgba(26, 26, 26, 0.12);
        align-items: center;
      }
      .type-row .lbl {
        padding: 28px 0;
      }
      .type-row .tok {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 13px;
      }
      .type-row .meta {
        font-size: 12px;
        color: var(--gray);
        margin-top: 8px;
        line-height: 1.6;
      }
      .type-row .spec {
        padding: 28px 0 28px 40px;
        border-left: 1px solid rgba(26, 26, 26, 0.12);
        overflow: hidden;
      }

      /* COMPONENTS */
      .comp-note {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 12px;
        color: var(--gray);
        margin-bottom: 18px;
      }
      .comp-grid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 28px;
        margin-bottom: 56px;
      }
      .span3 {
        grid-column: span 3;
      }
      .span4 {
        grid-column: span 4;
      }
      .span6 {
        grid-column: span 6;
      }
      .c-card {
        background: var(--white);
        border-top: 5px solid var(--coral);
        padding: 32px;
        height: 100%;
      }
      .c-card .ico {
        width: 48px;
        height: 48px;
        background: var(--coral);
        color: var(--white);
        font-family: var(--display);
        font-size: 24px;
        display: grid;
        place-items: center;
        margin-bottom: 24px;
      }
      .c-card .t {
        font-family: var(--display);
        font-size: 34px;
        letter-spacing: 1px;
        line-height: 1.05;
      }
      .c-card .x {
        font-size: 14px;
        line-height: 1.6;
        color: var(--gray);
        margin-top: 14px;
      }
      .c-card .stat {
        font-family: var(--display);
        font-size: 48px;
        color: var(--coral);
        margin-top: 24px;
        line-height: 1;
      }
      .c-side {
        background: var(--white);
        border-left: 4px solid var(--coral);
        padding: 20px 24px;
        margin-bottom: 18px;
      }
      .c-side .v {
        font-family: var(--display);
        font-size: 44px;
        letter-spacing: 1px;
        line-height: 1;
      }
      .c-side .l {
        font-family: var(--body);
        font-size: 12px;
        letter-spacing: 1px;
        color: var(--gray);
        margin-top: 6px;
      }
      .c-accent {
        width: 80px;
        height: 4px;
        background: var(--coral);
      }
      .timeline {
        position: relative;
        height: 120px;
        margin-top: 20px;
      }
      .timeline .line {
        position: absolute;
        top: 20px;
        left: 0;
        right: 0;
        height: 4px;
        background: var(--ink);
      }
      .timeline .pt {
        position: absolute;
        top: 12px;
        width: 20px;
        height: 20px;
        border-radius: 50%;
        background: var(--coral);
        border: 4px solid var(--cream);
      }
      .timeline .lab {
        position: absolute;
        top: 44px;
        font-family: var(--display);
        font-size: 22px;
        letter-spacing: 1px;
        transform: translateX(-50%);
      }
      .infobar {
        background: var(--cream-dark);
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 28px 36px;
      }
      .infobar .bt {
        font-family: var(--display);
        font-size: 44px;
        letter-spacing: 2px;
      }
      .infobar .bm {
        font-family: var(--body);
        font-size: 12px;
        letter-spacing: 2px;
        text-transform: uppercase;
        color: var(--gray);
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 2px;
        font-size: 12px;
        color: var(--gray);
      }
      .fw .flabel b {
        color: var(--ink);
        letter-spacing: 1px;
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--cream);
        box-shadow: 0 2px 0 rgba(26, 26, 26, 0.1);
      }
      .frame .hatch {
        position: absolute;
        inset: 0;
        background: var(--hatch);
        pointer-events: none;
        z-index: 1;
      }
      .frame .z {
        position: relative;
        z-index: 2;
      }

      /* f1 cover region split */
      .f1 {
        display: grid;
        grid-template-rows: 36% 64%;
      }
      .f1 .t {
        background: var(--coral);
        position: relative;
        overflow: hidden;
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        padding: 2.6cqw 3cqw;
      }
      .f1 .t .brand {
        font-family: var(--display);
        font-size: 2cqw;
        letter-spacing: 0.4cqw;
        color: var(--ink);
      }
      .f1 .t .mf {
        font-family: var(--display);
        font-size: 1.9cqw;
        letter-spacing: 0.1cqw;
        color: var(--ink);
      }
      .f1 .t .bn {
        position: absolute;
        right: 2cqw;
        bottom: -3cqw;
        font-family: var(--display);
        font-size: 16cqw;
        line-height: 0.7;
        color: rgba(0, 0, 0, 0.12);
      }
      .f1 .b {
        background: var(--cream);
        padding: 0 3cqw;
        display: flex;
        flex-direction: column;
        justify-content: center;
      }
      .f1 .b h3 {
        font-family: var(--display);
        font-size: 8.5cqw;
        line-height: 0.86;
        letter-spacing: 0.3cqw;
        color: var(--ink);
      }
      .f1 .b h3 em {
        font-style: normal;
        color: var(--coral);
      }
      .f1 .b .ey {
        font-family: var(--body);
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.3cqw;
        font-size: 0.95cqw;
        color: var(--coral);
        margin-bottom: 1.2cqw;
      }

      /* f2 feature stat — coral full + giant numeral */
      .f2 {
        background: var(--coral);
        position: relative;
        overflow: hidden;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 7cqw;
      }
      .f2 .bn {
        position: absolute;
        right: 3cqw;
        top: -2cqw;
        font-family: var(--display);
        font-size: 34cqw;
        line-height: 0.8;
        color: rgba(0, 0, 0, 0.12);
        z-index: 1;
      }
      .f2 .ey {
        font-family: var(--body);
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.3cqw;
        font-size: 1cqw;
        color: var(--ink);
        margin-bottom: 1.4cqw;
      }
      .f2 h3 {
        font-family: var(--display);
        font-size: 7cqw;
        line-height: 0.9;
        letter-spacing: 0.2cqw;
        color: var(--ink);
      }
      .f2 .x {
        font-family: var(--body);
        font-weight: 300;
        font-size: 1.5cqw;
        line-height: 1.5;
        color: var(--ink);
        max-width: 44cqw;
        margin-top: 1.6cqw;
      }

      /* f3 quote — coral panel + ink panel */
      .f3 {
        display: grid;
        grid-template-columns: 40% 60%;
      }
      .f3 .l {
        background: var(--coral);
        position: relative;
        overflow: hidden;
        display: flex;
        align-items: flex-start;
        padding: 3cqw;
      }
      .f3 .l .mark {
        font-family: var(--display);
        font-size: 24cqw;
        line-height: 0.6;
        color: rgba(0, 0, 0, 0.35);
      }
      .f3 .r {
        background: var(--ink);
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .f3 .r .q {
        font-family: var(--body);
        font-weight: 300;
        font-size: 2.6cqw;
        line-height: 1.35;
        color: var(--cream);
      }
      .f3 .r .acc {
        width: 4cqw;
        height: 0.25cqw;
        background: var(--coral);
        margin: 2.4cqw 0 1.4cqw;
      }
      .f3 .r .at {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.25cqw;
        font-size: 0.95cqw;
        color: var(--cream);
      }
      .f3 .r .ro {
        font-family: var(--body);
        font-size: 0.85cqw;
        letter-spacing: 0.05cqw;
        color: var(--light-gray);
        margin-top: 0.4cqw;
      }

      /* f4 closing — cream + coral band */
      .f4 {
        background: var(--cream);
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        position: relative;
      }
      .f4 .ey {
        font-family: var(--body);
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.4cqw;
        font-size: 1cqw;
        color: var(--coral);
        margin-bottom: 1.6cqw;
      }
      .f4 h3 {
        font-family: var(--display);
        font-size: 9cqw;
        line-height: 0.86;
        letter-spacing: 0.3cqw;
        color: var(--ink);
      }
      .f4 .acc {
        width: 5cqw;
        height: 0.25cqw;
        background: var(--coral);
        margin-top: 2.4cqw;
      }
      .f4 .band {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 8cqw;
        background: var(--coral);
        overflow: hidden;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 4cqw;
      }
      .f4 .band::before {
        content: "";
        position: absolute;
        inset: 0;
        background: var(--hatch);
      }
      .f4 .band span {
        position: relative;
        font-family: var(--display);
        font-size: 2.2cqw;
        letter-spacing: 0.2cqw;
        color: var(--ink);
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 40px;
      }
      .do-card {
        padding: 40px;
      }
      .do-card.do {
        background: var(--ink);
        color: var(--cream);
      }
      .do-card.dont {
        background: var(--white);
        border-top: 5px solid var(--coral);
      }
      .do-card h4 {
        font-family: var(--display);
        font-size: 40px;
        letter-spacing: 2px;
        margin-bottom: 24px;
      }
      .do-card.do h4 {
        color: var(--coral);
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-size: 16px;
        line-height: 1.55;
        padding-left: 26px;
        position: relative;
        margin-bottom: 14px;
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--display);
        font-size: 18px;
      }
      .do-card.do li::before {
        content: "+";
        color: var(--coral);
      }
      .do-card.dont li::before {
        content: "\2715";
        color: var(--coral);
      }
      .do-card.dont li {
        color: var(--gray);
      }

      .foot {
        padding: 64px 100px;
        background: var(--ink);
        color: var(--cream);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--display);
        font-size: 30px;
        letter-spacing: 3px;
      }
      .foot .r {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 3px;
        font-size: 12px;
        color: var(--light-gray);
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches,
        .palette-row {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #f5f0e8 !important;
        --secondary: #1a1a1a !important;
        --tertiary: #6b6b6b !important;
        --accent: #e85d5d !important;
        --f-disp: "Bebas Neue", sans-serif !important;
        --f-body: "Inter", sans-serif !important;
        --coral: #e85d5d !important;
        --cream: #f5f0e8 !important;
        --ink: #1a1a1a !important;
        --gray: #6b6b6b !important;
        --display: "Bebas Neue", sans-serif !important;
        --body: "Inter", sans-serif !important;
        --coral-dark: #d44a4a !important;
        --cream-dark: #e8e0d4 !important;
        --light-gray: #b0b0b0 !important;
        --white: #ffffff !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover" style="padding: 0">
      <div class="top">
        <div class="brand">Coral</div>
        <div class="meta">
          <div class="ml">Frame System</div>
          <div class="mf">Vol. 01 / 1920×1080</div>
        </div>
        <div class="bignum">01</div>
      </div>
      <div class="bot">
        <h1>Solid planes,<br /><em>hard edges.</em></h1>
        <div class="rule"></div>
        <div class="strap">
          <b>A bold magazine-poster system in motion.</b> Three surfaces — coral fire, ink black,
          warm cream — meeting at hard color edges, animated by Bebas Neue caps and a constant 45°
          hatch.
        </div>
      </div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="num">01</span>
        <h2>Palette</h2>
        <span class="sp"></span><span class="tag">Three surfaces + ink + neutrals</span>
      </div>
      <div class="swatches palette-row">
        <div class="sw">
          <div class="chip" style="background: var(--coral)"></div>
          <div class="meta">
            <div class="name">Coral</div>
            <div class="hex">#E85D5D</div>
            <div class="role">The signature — accent and full-region environment.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--coral-dark)"></div>
          <div class="meta">
            <div class="name">Coral Dark</div>
            <div class="hex">#D44A4A</div>
            <div class="role">Gradient stop + chart comparison series.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream)"></div>
          <div class="meta">
            <div class="name">Cream</div>
            <div class="hex">#F5F0E8</div>
            <div class="role">The warm canvas — magazine paper.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="meta">
            <div class="name">Black</div>
            <div class="hex">#1A1A1A</div>
            <div class="role">Strongest surface; quote + statement grounds.</div>
          </div>
        </div>
      </div>
      <div class="swatches palette-row" style="border-top: 0">
        <div class="sw">
          <div class="chip" style="background: var(--cream-dark)"></div>
          <div class="meta">
            <div class="name">Cream Dark</div>
            <div class="hex">#E8E0D4</div>
            <div class="role">Subtle region differentiation; info bars.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--gray)"></div>
          <div class="meta">
            <div class="name">Gray</div>
            <div class="hex">#6B6B6B</div>
            <div class="role">Body paragraphs, labels, meta.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--light-gray)"></div>
          <div class="meta">
            <div class="name">Light Gray</div>
            <div class="hex">#B0B0B0</div>
            <div class="role">Tertiary text on ink surfaces.</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="background: var(--white); box-shadow: inset 0 0 0 1px rgba(26, 26, 26, 0.12)"
          ></div>
          <div class="meta">
            <div class="name">White</div>
            <div class="hex">#FFFFFF</div>
            <div class="role">Card fills against coral or cream.</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section style="background: var(--cream-dark)">
      <div class="sec-head">
        <span class="num">02</span>
        <h2>Typography</h2>
        <span class="sp"></span><span class="tag">Bebas Neue + Inter</span>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">hero-title</div>
          <div class="meta">Bebas Neue · 120px · 4px track<br />uppercase always · line 0.9</div>
        </div>
        <div class="spec">
          <span class="beb" style="font-size: 104px; letter-spacing: 4px; line-height: 0.9"
            >Coral Fire</span
          >
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">jumbo-feature</div>
          <div class="meta">Bebas Neue · up to 200px · 12px track</div>
        </div>
        <div class="spec">
          <span class="beb" style="font-size: 120px; letter-spacing: 12px; color: var(--coral)"
            >2026</span
          >
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">section-headline</div>
          <div class="meta">Bebas Neue · 80px · 2px track</div>
        </div>
        <div class="spec">
          <span class="beb" style="font-size: 72px; letter-spacing: 2px">Hard Color Edges</span>
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">body-light</div>
          <div class="meta">Inter · weight 300 · pull-quote voice</div>
        </div>
        <div class="spec">
          <p style="font-weight: 300; font-size: 28px; line-height: 1.5; max-width: 760px">
            The voice break — Inter Light takes over when content needs to feel personal rather than
            declarative.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div class="lbl">
          <div class="tok">section-label</div>
          <div class="meta">Inter · 700 · 4px track · uppercase</div>
        </div>
        <div class="spec"><span class="eyebrow">Fig. 02 — Type Specimen</span></div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="num">03</span>
        <h2>Components</h2>
        <span class="sp"></span><span class="tag">Flat · hard edges · coral borders</span>
      </div>

      <div class="comp-note">Column cards — white fill, 5px coral top border, 48px icon square</div>
      <div class="comp-grid">
        <div class="span4">
          <div class="c-card">
            <div class="ico">A</div>
            <div class="t">Region Split</div>
            <div class="x">Solid color planes meet at a hard edge. The boundary is the layout.</div>
            <div class="stat">01</div>
          </div>
        </div>
        <div class="span4">
          <div class="c-card">
            <div class="ico">B</div>
            <div class="t">Diagonal Hatch</div>
            <div class="x">A 45° 6%-opacity overlay gives coral regions texture without depth.</div>
            <div class="stat">45°</div>
          </div>
        </div>
        <div class="span4">
          <div class="c-card">
            <div class="ico">C</div>
            <div class="t">Wallpaper Numeral</div>
            <div class="x">Oversized Bebas at 12% opacity sits behind a region's title.</div>
            <div class="stat">12%</div>
          </div>
        </div>
      </div>

      <div class="comp-grid">
        <div class="span4">
          <div class="comp-note">Sidebar tiles — 4px coral left border</div>
          <div class="c-side">
            <div class="v">240%</div>
            <div class="l">Output Lift</div>
          </div>
          <div class="c-side">
            <div class="v">3.4×</div>
            <div class="l">Faster Cuts</div>
          </div>
          <div class="c-side" style="margin-bottom: 0">
            <div class="v">12K</div>
            <div class="l">Frames Shipped</div>
          </div>
        </div>
        <div class="span4">
          <div class="comp-note">Accent rule + info bar</div>
          <div class="c-accent" style="margin-bottom: 28px"></div>
          <div class="infobar">
            <span class="bt">Coverage</span><span class="bm">Coral · Ink · Cream</span>
          </div>
          <div style="margin-top: 28px" class="comp-note">Card icon</div>
          <div
            class="ico"
            style="
              width: 48px;
              height: 48px;
              background: var(--coral);
              color: #fff;
              font-family: var(--display);
              font-size: 24px;
              display: grid;
              place-items: center;
            "
          >
            →
          </div>
        </div>
        <div class="span4">
          <div class="comp-note">Timeline — ink line, coral nodes, cream halo</div>
          <div class="timeline">
            <div class="line"></div>
            <div class="pt" style="left: 4%"></div>
            <div class="lab" style="left: 6%">Plan</div>
            <div class="pt" style="left: 34%"></div>
            <div class="lab" style="left: 36%">Block</div>
            <div class="pt" style="left: 64%"></div>
            <div class="lab" style="left: 66%">Set</div>
            <div class="pt" style="left: 92%"></div>
            <div class="lab" style="left: 90%">Ship</div>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section style="background: var(--cream-dark)">
      <div class="sec-head">
        <span class="num">04</span>
        <h2>Frame Compositions</h2>
        <span class="sp"></span><span class="tag">True 16:9 · cqw scaled</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Region-Split Cover</b><span>· identity · coral / cream</span></div>
          <div class="frame f1">
            <div class="t">
              <div class="brand">Coral</div>
              <div class="mf">Vol. 01</div>
              <div class="bn">01</div>
            </div>
            <div class="b">
              <div class="ey">Frame System</div>
              <h3>Solid planes,<br /><em>hard edges.</em></h3>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Feature Stat</b><span>· coral environment · wallpaper numeral</span>
          </div>
          <div class="frame f2">
            <div class="bn">240</div>
            <div class="z">
              <div class="ey">By The Numbers</div>
              <h3>Two hundred<br />forty percent.</h3>
              <p class="x">
                Output lift across the system — the figure carries the region while the hatch gives
                it texture.
              </p>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Quote Layout</b><span>· coral panel + ink panel · giant mark</span>
          </div>
          <div class="frame f3">
            <div class="l"><div class="mark">&ldquo;</div></div>
            <div class="r">
              <div class="q">Bebas declares.<br />Inter explains.</div>
              <div class="acc"></div>
              <div class="at">The System Voice</div>
              <div class="ro">Typographic Principle</div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Closing Plate</b><span>· closer · cream + coral band · centered</span>
          </div>
          <div class="frame f4">
            <div class="ey">Coral — Frame System</div>
            <h3>Meet at<br />the edge.</h3>
            <div class="acc"></div>
            <div class="band"><span>Thank You</span><span>2026</span></div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="num">05</span>
        <h2>Frame Rules</h2>
        <span class="sp"></span><span class="tag">Flat · hard-edge · coral</span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <ul>
            <li>Compose as multi-surface region splits — coral, ink, cream at hard edges.</li>
            <li>Set every Bebas element uppercase with 1–12px letter-spacing.</li>
            <li>Render eyebrows in coral on cream/ink, in ink on coral.</li>
            <li>Apply the 45° hatch at 6% opacity on coral regions.</li>
            <li>Fill underweight coral regions with a 12%-opacity wallpaper numeral.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <ul>
            <li>Never render Bebas in sentence case or without tracking.</li>
            <li>No fourth surface; no drop shadows, elevations, or rounded rectangles.</li>
            <li>No white headlines on coral — Bebas on coral is always ink.</li>
            <li>No gray headlines; gray is body and meta only.</li>
            <li>Don't pair Bebas with a different body sans than Inter.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--cream);
        --cap-accent: var(--coral);
        --cap-accent-2: var(--coral-dark);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.6cqw;
        background: var(--cap-canvas);
        border: 1px solid var(--cap-ink);
        border-radius: 0;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.1em 0.3em;
        font-family: var(--display);
        font-weight: 400;
        text-transform: uppercase;
        font-size: 3.6cqw;
        line-height: 1.04;
        letter-spacing: 0.06em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.12em;
        color: color-mix(in srgb, var(--cap-ink) 40%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.1em var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 1px dashed rgba(26, 26, 26, 0.28);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        font-size: 0.85cqw;
        letter-spacing: 0.1cqw;
        color: var(--gray);
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="num">06</span>
        <h2>Captions</h2>
        <span class="sp"></span><span class="tag">Lower-third · Karaoke</span>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="hatch" style="background: var(--hatch); opacity: 0.5"></div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · fill cleared</span></div>
          <div class="frame capdemo">
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Coral</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#F5F0E8" />
        <rect width="100" height="42" fill="#E85D5D" />
        <rect x="20" y="60" width="60" height="9" fill="#1A1A1A" />
        <rect x="20" y="74" width="38" height="9" fill="#E85D5D" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/coral/FRAME.md›
---
version: alpha
name: Coral — Frame (video / frame layer)
description: >
  Video-first companion to Coral's design.md. The unit is the frame (1920×1080), not the
  slide-in-a-deck. Atoms are identical and sacred — the three-surface system (coral fire /
  ink black / warm cream), Bebas Neue uppercase tracked + Inter body, the 45° diagonal hatch,
  decorative wallpaper numerals, hard color-region splits, zero shadow, zero radius (save
  circles). Composition, frame scale, and aspect-ratio behavior are rewritten for the frame.
  Motion is out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  coral: "#E85D5D"
  coral-dark: "#D44A4A"
  cream: "#F5F0E8"
  cream-dark: "#E8E0D4"
  black: "#1A1A1A"
  gray: "#6B6B6B"
  light-gray: "#B0B0B0"
  white: "#FFFFFF"

typography:
  # — reading ramp (Inter) —
  body:          { fontFamily: "Inter", cqw: 1.0,  weight: 400, lineHeight: 1.7 }
  body-light:    { fontFamily: "Inter", cqw: 1.5,  weight: 300, lineHeight: 1.5, note: "pull-quote voice" }
  section-label: { fontFamily: "Inter", px: 12, weight: 700, tracking: "4px", upper: true }
  item-label:    { fontFamily: "Inter", px: 11, weight: 700, tracking: "3px", upper: true }
  quote-attribution: { fontFamily: "Inter", px: 14, weight: 600, tracking: "3px", upper: true }
  quote-role:    { fontFamily: "Inter", px: 12, weight: 400, tracking: "1px" }
  # — display / hero ramp (Bebas Neue, uppercase, tracked) —
  card-title:    { fontFamily: "Bebas Neue", cqw: 1.9, weight: 400, lineHeight: 1.1, tracking: "1px", upper: true }
  sidebar-value: { fontFamily: "Bebas Neue", cqw: 2.3, weight: 400, lineHeight: 1.0, upper: true }
  bar-title:     { fontFamily: "Bebas Neue", cqw: 2.3, weight: 400, lineHeight: 1.0, tracking: "2px", upper: true }
  card-stat:     { fontFamily: "Bebas Neue", cqw: 2.5, weight: 400, lineHeight: 1.0, upper: true }
  column-title:  { fontFamily: "Bebas Neue", cqw: 3.7, weight: 400, lineHeight: 1.0, tracking: "2px", upper: true }
  section-headline:{ fontFamily: "Bebas Neue", cqw: 4.2, weight: 400, lineHeight: 1.0, tracking: "2px", upper: true }
  stat-numeral:  { fontFamily: "Bebas Neue", cqw: 5.0, weight: 400, lineHeight: 1.0, upper: true }
  hero-title:    { fontFamily: "Bebas Neue", cqw: 6.5, weight: 400, lineHeight: 0.9, tracking: "4px", upper: true }
  jumbo-feature: { fontFamily: "Bebas Neue", cqw: 9.0, weight: 400, lineHeight: 1.0, tracking: "12px", upper: true }
  # — decorative —
  background-numeral: { fontFamily: "Bebas Neue", cqw: 10.0, weight: 400, color: "rgba(0,0,0,0.12)", note: "wallpaper numeral inside a coral region" }
  giant-mark:    { fontFamily: "Bebas Neue", cqw: 14.0, weight: 400, color: "rgba(0,0,0,0.35)", note: "decorative quote mark inside a coral region" }

spacing:
  pad-x: "5cqw"       # standard horizontal frame padding
  pad-y: "4cqw"
  pad-col: "3cqw"
  gap-grid: "1.7cqw"
  card-pad: "2cqw"

components:
  diagonal-hatch:
    backgroundImage: "repeating-linear-gradient(45deg, transparent 0 20px, rgba(0,0,0,.06) 20px 40px)"
    placement: "::before overlay on {colors.coral} regions"
    description: "Signature 45° hatch (6% ink). Variants −45° 30/60px, 90° vertical 60/62px 10% ink. Texture, never depth."
  region-split:
    layout: "two/three solid surfaces meeting at a hard edge; ratios 38/62 rows, 40/60 cols, 50/50"
    rounded: "0"
    description: "The primary layout device — no gradient, no rounded junction; the boundary is the layout."
  card:
    backgroundColor: "{colors.white}"
    borderTop: "0.26cqw solid {colors.coral}"
    rounded: "0"
    shadow: "none"
    typography: "{typography.card-title} + {typography.body}"
    description: "5px coral TOP border is the only chrome; holds a card-icon, title, body, coral card-stat."
  sidebar-item:
    backgroundColor: "{colors.white}"
    borderLeft: "0.2cqw solid {colors.coral}"
    rounded: "0"
    typography: "{typography.sidebar-value} + {typography.section-label}"
    description: "4px coral LEFT border is the only chrome."
  card-icon:
    backgroundColor: "{colors.coral}"
    textColor: "{colors.white}"
    size: "2.5cqw square"
    rounded: "0"
    description: "The card mark — one white Bebas glyph centered."
  accent-line:
    backgroundColor: "{colors.coral}"
    size: "4cqw × 0.25cqw (60×4 closing variant)"
    rounded: "0"
    description: "Sub-headline accent rule."
  background-numeral:
    typography: "{typography.background-numeral}"
    color: "rgba(0,0,0,0.12)"
    placement: "behind a {colors.coral} region's title"
    description: "The wallpaper-numeral signature (12% ink)."
  giant-mark:
    typography: "{typography.giant-mark}"
    color: "rgba(0,0,0,0.35)"
    placement: "inside a {colors.coral} region"
    description: "Oversized quote mark / character, half-decorative."
  timeline:
    line: "0.2cqw solid {colors.black} (gradient-dashed ::after)"
    node: "{colors.coral} circle, {colors.cream} halo, 50% radius"
    description: "Ink line with coral nodes + cream halos."
  info-bar:
    backgroundColor: "{colors.cream-dark}"
    typography: "{typography.bar-title} + uppercase {typography.section-label}"
    rounded: "0"
    shadow: "none"
    description: "Footer band beneath a feature region — Bebas title left, Inter meta right."
---

# Coral — Frame (video / frame layer)

## Overview

Coral at frame scale is a **bold magazine poster** built from three solid surfaces — coral fire,
ink black, warm cream — that meet at **hard color edges.** The region boundary IS the layout: a
frame splits into a coral plane + a cream plane, or a coral panel + an ink panel, each holding a
self-contained composition. No gradient transitions, no rounded junctions, no drop shadows.

The voice is a two-face hierarchy: **Bebas Neue** — tall condensed caps, always uppercase, always
tracked (1–12px) — carries every headline, stat, title, and meta figure; **Inter** carries every
body line, label, and attribution across weights 300–700. Bebas declares; Inter explains. The
signature atmospherics are the **45° diagonal hatch** (6% ink) over coral regions and the
**oversized wallpaper numeral** (12% ink) behind a region's title.

**Key characteristics at frame scale:**

- **Three surfaces, hard edges** — `{colors.coral}` / `{colors.black}` / `{colors.cream}` as solid regions.
- **Bebas uppercase + tracking** on every display element; **Inter** on every body/label.
- **45° hatch** (6% ink) on coral regions; **wallpaper numerals** (12%) and **giant marks** (35%) behind content.
- **Ink-on-fire** — Bebas on coral is always ink, never white. Eyebrows coral on cream/ink, ink on coral.
- **Flat** — no shadow, no elevation; radius only on circles (nav dots, timeline nodes).
- **Coral as accent AND environment** — 4–5px coral borders, 48px coral icon squares, and full coral regions.

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one element dominates at **3–6× its nearest neighbor**: the `hero-title`/`jumbo-feature` or a wallpaper numeral behind a region's title, never two rival headlines.
- **Silence** — coral/cream/ink regions read **40–55% empty**; the **three-column catalog is the one dense exception**. A coral region underfilled gets a wallpaper numeral, never more content.
- **Restraint** — coral fires as **either accent or one full region per frame** (not both at full strength); one giant-mark per quote; ink-on-fire (never white on coral).
- **Reference** — aim at a **sports-magazine cover / Saul Bass travel poster** (solid planes at hard edges, condensed caps as architecture); failure looks like a **soft drop-shadowed card deck**.

## The Frame

- **Primary:** 1920×1080 (16:9). Display sizes authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `5cqw` (pad-x) standard frame padding; region edges may bleed full-frame.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` resolved against it — **never `vw`.** A `vw`-sized frame
inflates whenever it isn't full-screen; `cqw` resolves against the frame at any render size.

## Colors

Tokens identical to the source. At frame scale the three surfaces are intermixed by composition —
coral/cream, coral/ink, ink/cream, or single-surface. `{colors.coral}` is both accent (borders,
icon squares, timeline nodes, eyebrows on cream/ink) and environment (full regions). Headlines:
ink on cream/coral, cream on ink — **never gray, never white-on-coral.** Eyebrows: coral on
cream/ink, ink on coral — coral-on-coral does not exist. The only sanctioned gradient is the rare
135° coral-dark→coral feature region; everything else is flat.

## Typography

Two ramps. The **reading ramp** (Inter body 1.0cqw, body-light 1.5cqw, labels in px) carries copy
and eyebrows; the **display/hero ramp** (Bebas, `card-title` 1.9cqw → `jumbo-feature` 9.0cqw, plus
the decorative `background-numeral` 10cqw and `giant-mark` 14cqw) carries every headline and stat.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
- **Fit-to-measure:** size the headline to its line length. Cap the block at **≤ 78cqw**; ≤3 words → `hero-title`/`jumbo-feature`; 4–6 → `section-headline`; 7+ → `column-title`.
- **Every Bebas element is uppercase with ≥1px tracking** (2px standard, 4px hero, 12px jumbo). **Every Inter label is uppercase, 1–4px tracked.** No italic, no underline, no sentence-case Bebas.

## Depth & Surface

Flat, with hard color edges. Depth signals only:

- **Hard region boundaries** — the primary structural device.
- **Accent borders** — 5px coral top (cards), 4px coral left (sidebar tiles), 4px ink (timeline).
- **45° hatch** — 6% ink texture on coral regions (no depth).
- **Wallpaper typography** — numerals at 12%, giant marks at 35%, layered behind content.

**Ceiling:** no box-shadow, no elevated card, no soft gradient (save the one 135° coral feature), no rounded rectangle.

## Shapes

- **0 radius** on every rectangle — regions, cards, sidebar tiles, icon squares, info bars, accent lines.
- **50%** on circles only — nav dots (10px), nav arrows (44px), timeline nodes (20px).

## Components

- **region-split** — the layout device; surfaces meet at a hard edge.
- **card** (5px coral top) / **sidebar-item** (4px coral left) / **card-icon** (48px coral square) — the only chrome on each is its single coral border.
- **diagonal-hatch** / **background-numeral** / **giant-mark** — the atmospheric + wallpaper signatures on coral regions.
- **accent-line** — coral sub-headline rule. **timeline** — ink line, coral nodes, cream halos. **info-bar** — cream-dark footer band.

## Frame Treatments

> Recipe per plate: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> Lean centered where the move allows; vary anchor; one idea per region.

### 1 · Region-Split Cover (identity · move: hard region edge · left)

**Ground** 38/62 split — `{colors.coral}` top band (hatch + wallpaper numeral) over `{colors.cream}`.
**Container** grid rows; brand + meta in the coral band, hero title in the cream field. **Composes**
region-split, diagonal-hatch, background-numeral, hero-title. **Focal** a 2-line `hero-title` in
ink, second line in `{colors.coral}`, left-anchored in the cream field. **Chrome** Bebas brand left

- Bebas meta right in the coral band. **Accent** coral band + coral second line. **Silence** the
  cream field ~45% empty. **Fixed** ink-on-fire in band, hatch on coral, hard edge. **Free** title
  copy, which line is coral, meta. **Density** sparse.

### 2 · Feature Stat (anchor · move: scale · coral environment · left)

**Ground** full `{colors.coral}` with hatch. **Composes** diagonal-hatch, background-numeral,
section-label, stat headline, body-light. **Focal** a `stat-numeral`/`jumbo-feature` figure or
2-line headline in ink, with a `background-numeral` (12% ink) behind it as wallpaper. **Chrome** an
ink `section-label` eyebrow; an optional Inter-300 support line ≤44cqw. **Accent** the coral ground
IS the environment; ink type, no white. **Silence** ~40% of the coral field empty. **Fixed**
ink-on-coral, hatch present, wallpaper numeral behind. **Free** the figure, headline, support copy.
**Density** sparse.

### 3 · Quote Layout (quote · move: panel split · giant mark)

**Ground** 40/60 split — `{colors.coral}` left panel (hatch + giant-mark) + `{colors.black}` right
panel. **Composes** region-split, giant-mark, body-light, accent-line, quote-attribution.
**Focal** a 2–3 line pull quote in `{colors.cream}` Inter **weight 300** on the ink panel.
**Chrome** a `giant-mark` (35% ink) on the coral panel; a `60×4` coral accent-line above the
attribution. **Accent** coral panel + coral accent-line. **Silence** the coral panel is mostly the
mark. **Fixed** Inter-300 quote, ink panel, ink-on-coral mark. **Free** quote, attribution, mark
glyph. **Density** sparse.

### 4 · Closing Plate (closer · move: cream field + coral band · centered)

**Ground** `{colors.cream}` field with a bottom `{colors.coral}` band (hatch). **Composes**
section-label, section-headline/hero-title, accent-line, info-bar. **Focal** a 2-line sign-off in
ink, centered, with a coral `accent-line` beneath. **Chrome** an ink eyebrow above; a coral band
footer carrying a Bebas sign-off + year. **Accent** coral band + coral accent-line. **Silence**
~55% empty cream. **Fixed** centered, ink type, one coral band. **Free** sign-off copy, band
contents. **Density** sparse.

### 5 · Three-Column Catalog (catalog · move: density — the dense frame · centered head)

**Ground** `{colors.cream}` (or `{colors.black}`), `pad-x`. **Composes** section-headline, 3× card.
**Focal** a centered `section-headline` over three white `card`s (5px coral top, 48px icon square,
Bebas title, Inter body, coral stat). **Accent** the three coral top borders + icon squares.
**Silence** tight — the density exception. **Fixed** 5px coral top as sole chrome, no shadow/radius.
**Free** the three cards' content. **Density** dense-exception.

### 6 · Timeline (process · move: horizontal rail · left)

**Ground** `{colors.cream}`, `pad-x`. **Composes** section-headline, timeline. **Focal** the ink
timeline-line with 4–5 coral nodes (cream halos) and Bebas labels. **Accent** coral nodes.
**Silence** moderate. **Fixed** ink line, coral nodes, cream halos. **Free** node count, labels.
**Density** standard.

## Composition Rules

### Do

- Compose as **multi-surface region splits** — coral / ink / cream meeting at hard edges; the boundary is the layout.
- Set every Bebas element **uppercase + tracked** (2px standard, 4px hero, 12px jumbo); every Inter label uppercase, 1–4px.
- Render eyebrows **coral on cream/ink, ink on coral**; headlines ink on cream/coral, cream on ink.
- Apply the **45° hatch** (6% ink) on coral regions; fill underweight coral regions with a **12% wallpaper numeral**.
- Use the **5px coral top** (cards) / **4px coral left** (tiles) as the only chrome on those elements.
- Lean centered on cover sign-offs and catalog heads; left/panel-split on features and quotes.

### Don't

- Don't render Bebas in sentence case or untracked; don't pair it with a non-Inter body sans.
- Don't add a fourth surface, a drop shadow, an elevation, or a rounded rectangle.
- Don't put white headlines on coral (always ink) or gray headlines anywhere (gray is body/meta).
- Don't soften a region boundary with a gradient (except the rare 135° coral feature).
- Don't fill a coral region with sparse fragments — fully populate it or add a wallpaper numeral / giant mark.
- Don't blow a headline edge-to-edge — step the ramp down for long lines.

## Aspect-Ratio Behavior

| Treatment            | 16:9                       | 9:16                                 | 1:1                  |
| -------------------- | -------------------------- | ------------------------------------ | -------------------- |
| Region-Split Cover   | 38/62 rows, title left     | taller coral band, title below       | 40/60, title lower   |
| Feature Stat         | figure left, numeral right | figure top, numeral behind           | centered figure      |
| Quote Layout         | 40/60 coral+ink            | stacked: coral mark top, quote below | stacked              |
| Closing Plate        | cream + bottom coral band  | cream + taller band                  | centered, band below |
| Three-Column Catalog | head over 3-up             | head top, 3 stacked                  | head top, 2+1        |
| Timeline             | horizontal rail            | vertical rail                        | compact horizontal   |

Safe area holds the `5cqw` padding on the short edge; re-step display per ratio so no load-bearing
line drops below the 1.4cqw floor. Bebas runs ~20% wider in CJK — adjust line breaks per ratio.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. The system supplies surfaces and geometry, not brands.

## Numerals & Claims (hard rule)

Never invent figures, stats, dates, or counts at frame scale. Render slots as `— figure —`,
`{metric}`, `N×`. Real numerals appear only when the script supplies them — the feature stat,
catalog, and timeline especially carry placeholders, not fabricated values. Wallpaper numerals
(01, 02…) are decorative and may be ordinal.

## Pre-Render Self-Audit

- **Squint** — one focal element per region dominates at 3–5× its neighbor.
- **Silence** — sparse frames 40–55% empty; only the catalog runs dense.
- **Surfaces** — two or three of coral/ink/cream, meeting at hard edges; no fourth surface.
- **Type** — Bebas uppercase + tracked, fit-to-measure; ink-on-coral; eyebrow color correct for surface; ≥1.4cqw floor.
- **Depth** — 0 shadow, 0 rounded rectangle; hatch + wallpaper carry texture.
- **Anchor** — centered on sign-offs/heads, panel/left on features/quotes; no 3 consecutive frames share an anchor.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; timing and transitions are a later stage. The 0.6s opacity fade in the source is a deck mechanic, not a frame spec.
- **Bebas Neue + Inter via Google Fonts.** CJK pairing (ZCOOL XiaoWei / Yozai) carries over from the source's CJK section; Bebas runs ~20% wider in CJK.
- **9:16 / 1:1 are guidance**, not pixel-locked; verify the legibility floor per ratio.
- The 45° hatch, wallpaper numerals, giant marks, and timeline dash are CSS-only; no external imagery is required.
assets/frame-presets/creative-mode/caption-skin.html›
<!--
  Creative Mode — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a creative-mode
  literal fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a warm-cream card on a thick 4px ink border with a HARD offset shadow (a solid
  same-direction ink+orange duplicate, no blur — creative-mode's only depth device),
  square corners. Archivo Black, UPPERCASE, 0.92 line-height, ink glyphs. Upcoming words
  sit in faint ink; the current word becomes a solid yellow inked block (the brand's
  signature hard-offset-shadow accent block, no blur) with ink reading on top; spoken
  words settle to solid ink, the accent block cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE cream card — thick 4px ink border + the signature HARD offset shadow
     (a solid same-direction orange+ink duplicate, no blur), square corners */
  .caption-pill {
    max-width: 80%;
    padding: 18px 40px 22px;
    background: var(--cap-canvas, #efe9d9);
    border: 4px solid var(--cap-ink, #0f0f0f);
    border-radius: 0;
    box-shadow:
      14px 14px 0 var(--cap-accent-2, #e85a1f),
      14px 14px 0 4px var(--cap-ink, #0f0f0f);
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.16em 0.3em;
    font-family: var(--font-display, "Archivo Black"), sans-serif;
    font-weight: 400;
    text-transform: uppercase;
    font-size: clamp(34px, 3.8vw, 52px);
    line-height: 0.92;
    letter-spacing: -0.01em;
  }

  /* upcoming — faint ink, no accent block */
  .caption-word {
    display: inline-block;
    padding: 0.02em 0.1em;
    color: color-mix(in srgb, var(--cap-ink, #0f0f0f) 38%, var(--cap-canvas, #efe9d9));
  }
  /* current word — THE hard yellow inked block (box-shadow spread = a hard square
     block w/o layout shift; no blur), ink reading on top */
  .caption-word.is-active {
    color: var(--cap-ink, #0f0f0f);
    background: var(--cap-accent, #f5c518);
    box-shadow: 0 0 0 0.08em var(--cap-accent, #f5c518);
  }
  /* already spoken — solid ink, accent block cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #0f0f0f);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — an inverted ink chip (the kicker-block voice) */
  .caption-word.cap-brand {
    color: var(--cap-canvas, #efe9d9);
    background: var(--cap-ink, #0f0f0f);
    box-shadow: 0 0 0 0.08em var(--cap-ink, #0f0f0f);
  }
  /* numerals / data — the mono voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Space Grotesk"), ui-monospace, monospace;
    color: color-mix(in srgb, var(--cap-ink, #0f0f0f) 72%, var(--cap-canvas, #efe9d9));
  }
  /* emphasis — a thick orange underline (the counter accent) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 3px;
    text-decoration-color: var(--cap-accent-2, #e85a1f);
    text-underline-offset: 0.12em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(30px, 5vw, 44px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.92 }, { scale: 1, duration: 0.16, ease: "back.out(2.2)" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/creative-mode/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Creative Mode</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Space+Grotesk:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --cream: #efe9d9;
        --cream-2: #e4dcc4;
        --ink: #0f0f0f;
        --ink-2: #2a2a2a;
        --green: #1f8a4c;
        --green-dark: #136636;
        --pink: #f06ca8;
        --pink-dark: #d14e8b;
        --orange: #e85a1f;
        --yellow: #f5c518;
        --display: "Archivo Black", sans-serif;
        --body: "Space Grotesk", sans-serif;
        --mono: "JetBrains Mono", monospace;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: var(--ink-2);
      }
      body {
        background: var(--cream);
        color: var(--ink);
        font-family: var(--body);
        -webkit-font-smoothing: antialiased;
      }
      .disp {
        font-family: var(--display);
        text-transform: uppercase;
        line-height: 0.92;
        letter-spacing: -0.01em;
      }
      .mono {
        font-family: var(--mono);
        text-transform: uppercase;
      }

      /* ---------- doc rhythm ---------- */
      section {
        padding: 120px 96px;
        border-bottom: 4px solid var(--ink);
      }
      .sec-head {
        display: flex;
        align-items: baseline;
        gap: 24px;
        margin-bottom: 64px;
      }
      .sec-head .idx {
        font-family: var(--mono);
        font-size: 18px;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        background: var(--ink);
        color: var(--cream);
        padding: 8px 14px;
      }
      .sec-head h2 {
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 52px;
        line-height: 0.92;
        letter-spacing: -0.01em;
      }
      .sec-head .rule {
        flex: 1;
        height: 4px;
        background: var(--ink);
        align-self: center;
      }

      /* ---------- COVER ---------- */
      .cover {
        min-height: 100vh;
        padding: 64px;
        display: grid;
        grid-template-rows: auto 1fr auto;
        gap: 40px;
        position: relative;
        overflow: hidden;
      }
      .cover .topbar {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 24px;
        font-family: var(--mono);
        font-size: 20px;
        letter-spacing: 0.08em;
        text-transform: uppercase;
      }
      .cover .topbar span:first-child {
        white-space: nowrap;
      }
      .cover .stage {
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
        z-index: 2;
      }
      .cover .pill {
        border: 2px solid var(--ink);
        border-radius: 999px;
        padding: 6px 18px;
        white-space: nowrap;
      }
      .cover h1 {
        font-family: var(--display);
        text-transform: uppercase;
        line-height: 0.86;
        letter-spacing: -0.02em;
        font-size: clamp(72px, 13vw, 210px);
      }
      .cover h1 .pink {
        color: var(--pink);
      }
      .cover .sub {
        display: flex;
        gap: 18px;
        align-items: center;
        margin-top: 48px;
        flex-wrap: wrap;
      }
      .cover .chip {
        font-family: var(--mono);
        font-size: 16px;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        border: 4px solid var(--ink);
        padding: 12px 18px;
        background: var(--cream);
        white-space: nowrap;
      }
      .cover .chip.g {
        background: var(--green);
        color: var(--cream);
      }
      .cover .chip.y {
        background: var(--yellow);
      }
      .cover .footer {
        display: flex;
        justify-content: space-between;
        align-items: flex-end;
        font-family: var(--mono);
        font-size: 20px;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        position: relative;
        z-index: 2;
        padding-left: 200px;
      }
      .cover .dot {
        display: inline-block;
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background: var(--ink);
        margin: 0 12px;
        vertical-align: middle;
      }
      /* cover decor */
      .cover .badge {
        position: absolute;
        top: 44%;
        right: 6%;
        width: 260px;
        height: 260px;
        background: var(--pink);
        border: 4px solid var(--ink);
        transform: rotate(-6deg);
        display: grid;
        place-items: center;
        z-index: 1;
        box-shadow:
          24px 24px 0 var(--orange),
          24px 24px 0 4px var(--ink);
      }
      .cover .badge .ring {
        width: 84%;
        height: 84%;
        border: 4px solid var(--ink);
        border-radius: 50%;
        display: grid;
        place-items: center;
        text-align: center;
      }
      .cover .badge .ring span {
        font-family: var(--display);
        text-transform: uppercase;
        line-height: 0.9;
        font-size: 34px;
      }
      .cover .circle {
        position: absolute;
        left: -120px;
        bottom: -120px;
        width: 300px;
        height: 300px;
        border-radius: 50%;
        background: var(--yellow);
        border: 4px solid var(--ink);
        z-index: 0;
      }

      /* ---------- PALETTE ---------- */
      .swatches {
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        gap: 24px;
      }
      .sw {
        border: 4px solid var(--ink);
      }
      .sw .chip {
        height: 150px;
      }
      .sw .meta {
        padding: 16px 18px;
        border-top: 4px solid var(--ink);
        background: var(--cream);
      }
      .sw .name {
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 22px;
        line-height: 1;
      }
      .sw .hex {
        font-family: var(--mono);
        font-size: 16px;
        letter-spacing: 0.04em;
        margin-top: 8px;
        color: var(--ink-2);
      }
      .sw .role {
        font-size: 15px;
        line-height: 1.4;
        margin-top: 10px;
        color: var(--ink-2);
      }

      /* ---------- TYPE ---------- */
      .type-row {
        border: 4px solid var(--ink);
        margin-bottom: 24px;
        display: grid;
        grid-template-columns: 280px 1fr;
        background: var(--cream);
      }
      .type-row .lbl {
        border-right: 4px solid var(--ink);
        padding: 24px;
        background: var(--cream-2);
      }
      .type-row .lbl .tok {
        font-family: var(--mono);
        font-size: 16px;
        letter-spacing: 0.06em;
        text-transform: uppercase;
      }
      .type-row .lbl .meta {
        font-family: var(--mono);
        font-size: 13px;
        color: var(--ink-2);
        margin-top: 10px;
        line-height: 1.6;
        text-transform: none;
        letter-spacing: 0;
      }
      .type-row .spec {
        padding: 28px 32px;
        display: flex;
        align-items: center;
        overflow: hidden;
      }

      /* ---------- COMPONENTS ---------- */
      .comp-grid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 28px;
      }
      .stat-cell {
        border: 4px solid var(--ink);
        padding: 28px 32px;
      }
      .stat-cell .n {
        font-family: var(--display);
        font-size: 84px;
        line-height: 0.9;
      }
      .stat-cell .l {
        font-family: var(--mono);
        font-size: 18px;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        margin-top: 12px;
      }
      .stat-cell.green {
        background: var(--green);
        color: var(--cream);
      }
      .stat-cell.pink {
        background: var(--pink);
        color: var(--ink);
      }
      .stat-cell.orange {
        background: var(--orange);
        color: var(--cream);
      }
      .stat-cell.cream {
        background: var(--cream);
      }
      .step-card {
        border: 4px solid var(--ink);
        padding: 28px;
        height: 300px;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
      }
      .step-card .num {
        font-family: var(--display);
        font-size: 120px;
        line-height: 0.85;
      }
      .step-card .t {
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 28px;
        line-height: 1;
      }
      .step-card .d {
        font-size: 18px;
        line-height: 1.4;
        margin-top: 10px;
      }
      .step-card.cream {
        background: var(--cream);
      }
      .step-card.yellow {
        background: var(--yellow);
      }
      .step-card.pink {
        background: var(--pink);
      }
      .step-card.green {
        background: var(--green);
        color: var(--cream);
      }
      .marker {
        background: var(--pink);
        border: 4px solid var(--ink);
        padding: 36px 44px;
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 46px;
        line-height: 1;
        box-shadow:
          24px 24px 0 var(--orange),
          24px 24px 0 4px var(--ink);
        align-self: flex-start;
      }
      .kicker-block {
        background: var(--ink);
        color: var(--cream);
        font-family: var(--mono);
        font-size: 20px;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        padding: 10px 18px;
        align-self: flex-start;
      }
      .badge-rot {
        background: var(--yellow);
        border: 4px solid var(--ink);
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 26px;
        padding: 14px 22px;
        transform: rotate(-4deg);
        align-self: flex-start;
      }
      .pill-badge {
        border: 2px solid var(--ink);
        border-radius: 999px;
        font-family: var(--mono);
        font-size: 18px;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        padding: 8px 18px;
        align-self: flex-start;
      }
      .span-3 {
        grid-column: span 3;
      }
      .span-4 {
        grid-column: span 4;
      }
      .span-6 {
        grid-column: span 6;
      }
      .span-12 {
        grid-column: span 12;
      }
      .comp-note {
        font-family: var(--mono);
        font-size: 14px;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--ink-2);
        margin-bottom: 14px;
      }

      /* table */
      .cm-table {
        border: 4px solid var(--ink);
        background: var(--cream-2);
        width: 100%;
        border-collapse: collapse;
      }
      .cm-table th,
      .cm-table td {
        border: 3px solid var(--ink);
        padding: 18px 26px;
        text-align: left;
        font-size: 20px;
      }
      .cm-table thead th {
        background: var(--ink);
        color: var(--cream);
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 22px;
        border-color: var(--cream);
      }
      .cm-table td.k {
        font-family: var(--mono);
        text-transform: uppercase;
        letter-spacing: 0.04em;
        font-size: 17px;
      }
      .cm-table .col-pink {
        background: var(--pink);
      }
      .cm-table .col-green {
        background: var(--green);
        color: var(--cream);
      }

      /* ---------- COMPOSITIONS (frame scale) ---------- */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .frame-wrap .flabel {
        display: flex;
        gap: 16px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--mono);
        font-size: 15px;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--ink-2);
      }
      .frame-wrap .flabel b {
        color: var(--ink);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        border: 4px solid var(--ink);
        background: var(--cream);
      }
      .frame .chrome-top,
      .frame .chrome-bot {
        position: absolute;
        left: 3.3cqw;
        right: 3.3cqw;
        display: flex;
        justify-content: space-between;
        align-items: center;
        font-family: var(--mono);
        text-transform: uppercase;
        font-size: 1.15cqw;
        letter-spacing: 0.08em;
        color: var(--ink);
        z-index: 5;
      }
      .frame .chrome-top {
        top: 2.5cqw;
      }
      .frame .chrome-bot {
        bottom: 2.5cqw;
      }
      .frame .chrome-top .pill {
        border: 0.2cqw solid var(--ink);
        border-radius: 999px;
        padding: 0.3cqw 0.9cqw;
        white-space: nowrap;
      }
      .frame > .f-cover,
      .frame > .f-claim,
      .frame > .f-stats,
      .frame > .f-close {
        position: absolute;
        inset: 0;
      }
      .frame .cdot {
        display: inline-block;
        width: 0.5cqw;
        height: 0.5cqw;
        border-radius: 50%;
        background: var(--ink);
        margin: 0 0.6cqw;
        vertical-align: middle;
      }

      /* frame 1 — cover */
      .f-cover {
        display: grid;
        place-items: center;
      }
      .f-cover h3 {
        font-family: var(--display);
        text-transform: uppercase;
        line-height: 0.84;
        letter-spacing: -0.02em;
        font-size: 15.5cqw;
        text-align: center;
      }
      .f-cover h3 .o {
        color: var(--orange);
      }

      /* frame 2 — big claim on pink */
      .f-claim {
        background: var(--pink);
        display: flex;
        align-items: center;
        padding: 0 8cqw;
      }
      .f-claim .stack h3 {
        font-family: var(--display);
        text-transform: uppercase;
        line-height: 0.9;
        letter-spacing: -0.01em;
        font-size: 11cqw;
        color: var(--ink);
      }
      .f-claim .ek {
        font-family: var(--mono);
        font-size: 1.3cqw;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        background: var(--ink);
        color: var(--cream);
        padding: 0.5cqw 1cqw;
        display: inline-block;
        margin-bottom: 2.4cqw;
      }

      /* frame 3 — stat grid */
      .f-stats {
        display: grid;
        place-content: center;
        gap: 3cqw;
        padding: 0 7cqw;
      }
      .f-stats .head {
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 4.2cqw;
        line-height: 0.92;
        text-align: center;
      }
      .f-stats .row {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 2cqw;
      }
      .f-stats .cell {
        border: 0.4cqw solid var(--ink);
        padding: 2.2cqw 2.4cqw;
      }
      .f-stats .cell .n {
        font-family: var(--display);
        font-size: 6.4cqw;
        line-height: 0.88;
      }
      .f-stats .cell .l {
        font-family: var(--mono);
        font-size: 1.2cqw;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        margin-top: 0.8cqw;
      }
      .f-stats .green {
        background: var(--green);
        color: var(--cream);
      }
      .f-stats .pink {
        background: var(--pink);
      }
      .f-stats .orange {
        background: var(--orange);
        color: var(--cream);
      }

      /* frame 4 — closing green */
      .f-close {
        color: var(--cream);
        display: grid;
        place-items: center;
        position: relative;
      }
      .f-close h3 {
        font-family: var(--display);
        text-transform: uppercase;
        line-height: 0.86;
        letter-spacing: -0.01em;
        font-size: 10cqw;
        text-align: center;
      }
      .f-close .stamp {
        position: absolute;
        right: 7cqw;
        top: 18cqw;
        width: 18cqw;
        height: 18cqw;
        background: var(--pink);
        border: 0.5cqw solid var(--cream);
        transform: rotate(-6deg);
        display: grid;
        place-items: center;
      }
      .f-close .stamp .ring {
        width: 84%;
        height: 84%;
        border: 0.4cqw solid var(--cream);
        border-radius: 50%;
        display: grid;
        place-items: center;
      }
      .f-close .stamp span {
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 2.2cqw;
        line-height: 0.9;
        text-align: center;
        color: var(--cream);
      }
      .frame.f-close {
        background: var(--green);
      }
      .frame.f-close .chrome-top,
      .frame.f-close .chrome-bot {
        color: var(--cream);
      }
      .f-close .chrome-top .pill {
        border-color: var(--cream);
      }
      .f-close .cdot {
        background: var(--cream);
      }

      /* ---------- principles ---------- */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 28px;
      }
      .do-card {
        border: 4px solid var(--ink);
        padding: 32px;
      }
      .do-card.do {
        background: var(--green);
        color: var(--cream);
      }
      .do-card.dont {
        background: var(--cream);
      }
      .do-card h4 {
        font-family: var(--display);
        text-transform: uppercase;
        font-size: 26px;
        margin-bottom: 18px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-size: 18px;
        line-height: 1.5;
        padding-left: 28px;
        position: relative;
        margin-bottom: 12px;
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--display);
      }
      .do-card.do li::before {
        content: "+";
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: var(--orange);
      }

      .foot {
        padding: 64px 96px;
        background: var(--ink);
        color: var(--cream);
        display: flex;
        justify-content: space-between;
        align-items: center;
        font-family: var(--mono);
        font-size: 16px;
        letter-spacing: 0.08em;
        text-transform: uppercase;
      }
      @media (max-width: 1100px) {
        .gallery,
        .swatches {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #efe9d9 !important;
        --secondary: #0f0f0f !important;
        --tertiary: #e4dcc4 !important;
        --accent: #f06ca8 !important;
        --f-disp: "Archivo Black", sans-serif !important;
        --f-body: "Space Grotesk", sans-serif !important;
        --cream: #efe9d9 !important;
        --cream-2: #e4dcc4 !important;
        --ink: #0f0f0f !important;
        --pink: #f06ca8 !important;
        --display: "Archivo Black", sans-serif !important;
        --body: "Space Grotesk", sans-serif !important;
        --ink-2: #2a2a2a !important;
        --green: #1f8a4c !important;
        --green-dark: #136636 !important;
        --pink-dark: #d14e8b !important;
        --orange: #e85a1f !important;
        --yellow: #f5c518 !important;
      }
    </style>
  </head>
  <body>
    <!-- ============ COVER ============ -->
    <section class="cover">
      <div class="topbar">
        <span>Creative Mode — Frame System</span>
        <span class="pill">Video / Frame Layer</span>
      </div>
      <div class="stage">
        <h1>Creative<br /><span class="pink">Mode.</span></h1>
        <div class="sub">
          <span class="chip g">Neo-Brutalist</span>
          <span class="chip">Archivo Black · JetBrains Mono</span>
          <span class="chip y">4 Accents · Hard Shadow</span>
        </div>
      </div>
      <div class="footer">
        <span>Editorial · Punk-Zine · Swiss Grid</span>
        <span>Frame Showcase<span class="dot"></span>1920 × 1080</span>
      </div>
      <div class="badge">
        <div class="ring">
          <span>Frame<br />Spec</span>
        </div>
      </div>
      <div class="circle"></div>
    </section>

    <!-- ============ PALETTE ============ -->
    <section>
      <div class="sec-head">
        <span class="idx">01</span>
        <h2>Palette</h2>
        <span class="rule"></span>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--cream)"></div>
          <div class="meta">
            <div class="name">Cream</div>
            <div class="hex">#EFE9D9</div>
            <div class="role">Universal canvas. Warm, never white.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="meta">
            <div class="name">Ink</div>
            <div class="hex">#0F0F0F</div>
            <div class="role">Borders, type, rules, chrome.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--green)"></div>
          <div class="meta">
            <div class="name">Green</div>
            <div class="hex">#1F8A4C</div>
            <div class="role">Dominant accent + closing ground.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--pink)"></div>
          <div class="meta">
            <div class="name">Pink</div>
            <div class="hex">#F06CA8</div>
            <div class="role">High-energy fills, markers, stamp.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--orange)"></div>
          <div class="meta">
            <div class="name">Orange</div>
            <div class="hex">#E85A1F</div>
            <div class="role">The hard-shadow color. Stat fills.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--yellow)"></div>
          <div class="meta">
            <div class="name">Yellow</div>
            <div class="hex">#F5C518</div>
            <div class="role">Warm punctuation. Badges, circles.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream-2)"></div>
          <div class="meta">
            <div class="name">Cream 2</div>
            <div class="hex">#E4DCC4</div>
            <div class="role">Recessed table fill.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink-2)"></div>
          <div class="meta">
            <div class="name">Ink 2</div>
            <div class="hex">#2A2A2A</div>
            <div class="role">Secondary body text.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--green-dark)"></div>
          <div class="meta">
            <div class="name">Green Dk</div>
            <div class="hex">#136636</div>
            <div class="role">Depth on decorative levers.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--pink-dark)"></div>
          <div class="meta">
            <div class="name">Pink Dk</div>
            <div class="hex">#D14E8B</div>
            <div class="role">Shadow-side accent.</div>
          </div>
        </div>
      </div>
    </section>

    <!-- ============ TYPOGRAPHY ============ -->
    <section>
      <div class="sec-head">
        <span class="idx">02</span>
        <h2>Typography</h2>
        <span class="rule"></span>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">display-hero</div>
          <div class="meta">Archivo Black · 160px<br />line 0.92 · upper · −.01em</div>
        </div>
        <div class="spec"><span class="disp" style="font-size: 128px">LOUD</span></div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">display-md</div>
          <div class="meta">Archivo Black · 96px<br />section headline</div>
        </div>
        <div class="spec"><span class="disp" style="font-size: 80px">MAKE IT MOVE</span></div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">stat-num</div>
          <div class="meta">Archivo Black · 96px<br />line 0.9</div>
        </div>
        <div class="spec">
          <span class="disp" style="font-size: 88px"
            >240<span style="color: var(--orange)">%</span></span
          >
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">body-lg</div>
          <div class="meta">Space Grotesk · 28px<br />line 1.4 · left-aligned</div>
        </div>
        <div class="spec">
          <p style="font-size: 24px; line-height: 1.4; max-width: 760px">
            Body copy sits in Space Grotesk — left-aligned, never centered. It explains while the
            display type declares.
          </p>
        </div>
      </div>
      <div class="type-row">
        <div class="lbl">
          <div class="tok">mono-kicker</div>
          <div class="meta">JetBrains Mono · 24px<br />.14em · upper</div>
        </div>
        <div class="spec">
          <span class="mono" style="font-size: 22px; letter-spacing: 0.14em"
            >FIG. 02 — TYPE SPECIMEN</span
          >
        </div>
      </div>
    </section>

    <!-- ============ COMPONENTS ============ -->
    <section>
      <div class="sec-head">
        <span class="idx">03</span>
        <h2>Components</h2>
        <span class="rule"></span>
      </div>

      <div class="comp-note">Stat cells — 4px ink border, flat accent fills</div>
      <div class="comp-grid" style="margin-bottom: 48px">
        <div class="stat-cell green span-3">
          <div class="n">240%</div>
          <div class="l">Output Lift</div>
        </div>
        <div class="stat-cell pink span-3">
          <div class="n">12K</div>
          <div class="l">Frames Shipped</div>
        </div>
        <div class="stat-cell orange span-3">
          <div class="n">3.4×</div>
          <div class="l">Faster Cuts</div>
        </div>
        <div class="stat-cell cream span-3">
          <div class="n">98</div>
          <div class="l">Templates</div>
        </div>
      </div>

      <div class="comp-note">Step cards — sequence ending on green</div>
      <div class="comp-grid" style="margin-bottom: 48px">
        <div class="step-card cream span-3">
          <div class="num">01</div>
          <div>
            <div class="t">Block</div>
            <div class="d">Lay the color planes.</div>
          </div>
        </div>
        <div class="step-card yellow span-3">
          <div class="num">02</div>
          <div>
            <div class="t">Set</div>
            <div class="d">Lock the display caps.</div>
          </div>
        </div>
        <div class="step-card pink span-3">
          <div class="num">03</div>
          <div>
            <div class="t">Stamp</div>
            <div class="d">Drop the hard shadow.</div>
          </div>
        </div>
        <div class="step-card green span-3">
          <div class="num">04</div>
          <div>
            <div class="t">Cut</div>
            <div class="d">Swap the ground.</div>
          </div>
        </div>
      </div>

      <div class="comp-grid">
        <div class="span-6" style="display: flex; flex-direction: column; gap: 28px">
          <div class="comp-note">Featured marker — 24px hard offset shadow</div>
          <div class="marker">Make It Loud</div>
          <div class="comp-note" style="margin-top: 14px">Chrome atoms</div>
          <div style="display: flex; gap: 18px; flex-wrap: wrap">
            <span class="kicker-block">Section / 01</span>
            <span class="badge-rot">New</span>
            <span class="pill-badge">Topbar Pill</span>
          </div>
        </div>
        <div class="span-6">
          <div class="comp-note">Comparison table — cream-2 fill, ink head row</div>
          <table class="cm-table">
            <thead>
              <tr>
                <th>Spec</th>
                <th class="col-pink">Frame</th>
                <th class="col-green">Slide</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td class="k">Border</td>
                <td>4px ink</td>
                <td>4px ink</td>
              </tr>
              <tr>
                <td class="k">Shadow</td>
                <td>24px hard</td>
                <td>none</td>
              </tr>
              <tr>
                <td class="k">Accents</td>
                <td>2–3 / frame</td>
                <td>2–3 / slide</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </section>

    <!-- ============ COMPOSITIONS ============ -->
    <section>
      <div class="sec-head">
        <span class="idx">04</span>
        <h2>Frame Compositions</h2>
        <span class="rule"></span>
      </div>
      <div class="gallery">
        <div class="frame-wrap">
          <div class="flabel"><b>Wordmark Cover</b><span>· identity · centered</span></div>
          <div class="frame">
            <div class="chrome-top">
              <span>Creative Mode</span><span class="pill">01 — Cover</span>
            </div>
            <div class="f-cover">
              <h3>Creative<br /><span class="o">Mode.</span></h3>
            </div>
            <div class="chrome-bot">
              <span>Editorial System</span><span>01<span class="cdot"></span>04</span>
            </div>
          </div>
        </div>

        <div class="frame-wrap">
          <div class="flabel"><b>Big Claim</b><span>· oversized statement · left</span></div>
          <div class="frame">
            <div class="chrome-top"><span>Statement</span><span class="pill">02 — Claim</span></div>
            <div class="f-claim">
              <div class="stack">
                <span class="ek">The Thesis</span>
                <h3>Make<br />it move.</h3>
              </div>
            </div>
            <div class="chrome-bot">
              <span>One Idea / Frame</span><span>02<span class="cdot"></span>04</span>
            </div>
          </div>
        </div>

        <div class="frame-wrap">
          <div class="flabel"><b>Stat Grid</b><span>· catalog · the density frame</span></div>
          <div class="frame">
            <div class="chrome-top">
              <span>By The Numbers</span><span class="pill">03 — Data</span>
            </div>
            <div class="f-stats">
              <div class="head">The Numbers Don't Whisper</div>
              <div class="row">
                <div class="cell green">
                  <div class="n">240%</div>
                  <div class="l">Output Lift</div>
                </div>
                <div class="cell pink">
                  <div class="n">3.4×</div>
                  <div class="l">Faster Cuts</div>
                </div>
                <div class="cell orange">
                  <div class="n">12K</div>
                  <div class="l">Frames Shipped</div>
                </div>
              </div>
            </div>
            <div class="chrome-bot">
              <span>Stat Cells</span><span>03<span class="cdot"></span>04</span>
            </div>
          </div>
        </div>

        <div class="frame-wrap">
          <div class="flabel">
            <b>Closing Plate</b><span>· closer · green ground · centered</span>
          </div>
          <div class="frame f-close">
            <div class="chrome-top">
              <span>Creative Mode</span><span class="pill">04 — End</span>
            </div>
            <h3>That's<br />the mode.</h3>
            <div class="stamp">
              <div class="ring">
                <span>Cut<br />Print</span>
              </div>
            </div>
            <div class="chrome-bot">
              <span>Thank You</span><span>04<span class="cdot"></span>04</span>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- ============ PRINCIPLES ============ -->
    <section style="border-bottom: none">
      <div class="sec-head">
        <span class="idx">05</span>
        <h2>Frame Rules</h2>
        <span class="rule"></span>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <ul>
            <li>One idea per frame — two things means two frames.</li>
            <li>Lean centered; reserve the green ground for the closing plate.</li>
            <li>Two or three accents per frame, never all four.</li>
            <li>4px ink borders, 24px hard offset on featured blocks only.</li>
            <li>Archivo Black uppercase, 0.92 line-height, always.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <ul>
            <li>No rounded corners except the topbar pill.</li>
            <li>No gradients, no blurred shadows, no glow.</li>
            <li>No Archivo Black in sentence case, ever.</li>
            <li>No fifth accent; no pure-white background.</li>
            <li>No headline blown edge-to-edge — size to the line.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- ============ CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) ============ -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--cream);
        --cap-accent: var(--yellow);
        --cap-accent-2: var(--orange);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 82cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        border: 0.4cqw solid var(--cap-ink);
        border-radius: 0;
        box-shadow:
          1cqw 1cqw 0 var(--cap-accent-2),
          1cqw 1cqw 0 0.3cqw var(--cap-ink);
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.16em 0.3em;
        font-family: var(--display);
        font-weight: 400;
        text-transform: uppercase;
        font-size: 3.2cqw;
        line-height: 0.92;
        letter-spacing: -0.01em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0.02em 0.1em;
        color: color-mix(in srgb, var(--cap-ink) 38%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.08em var(--cap-accent);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 0.15cqw dashed color-mix(in srgb, var(--ink) 38%, var(--cream));
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3.3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        font-size: 1cqw;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--ink-2);
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="idx">06</span>
        <h2>Captions</h2>
        <span class="rule"></span>
      </div>
      <div class="gallery">
        <div class="frame-wrap">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="chrome-top"><span>Captions</span><span class="pill">Karaoke</span></div>
            <div class="bandline"></div>
            <div class="bandtag">Caption Band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="frame-wrap">
          <div class="flabel">
            <b>Settled line</b><span>· all spoken · accent block cleared</span>
          </div>
          <div class="frame capdemo">
            <div class="chrome-top"><span>Captions</span><span class="pill">Lower-Third</span></div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span>Creative Mode · Frame Showcase</span>
      <span>Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#EFE9D9" />
        <rect
          x="30"
          y="30"
          width="40"
          height="40"
          fill="#F06CA8"
          stroke="#0F0F0F"
          stroke-width="4"
        />
        <rect x="36" y="36" width="40" height="40" fill="none" stroke="#0F0F0F" stroke-width="4" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/creative-mode/FRAME.md›
---
version: alpha
name: Creative Mode — Frame (video / frame layer)
description: >
  Video-first companion to Creative Mode's design.md. The unit is the frame (1920×1080),
  not the slide-in-a-deck. Atoms are identical and sacred — warm cream canvas, 4px ink
  borders, hard offset shadows (no blur), Archivo Black uppercase at 0.92 line-height,
  JetBrains Mono taxonomy, Space Grotesk body, the four-accent palette. Composition,
  frame scale, and aspect-ratio behavior are rewritten for the frame. Motion is out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  cream: "#EFE9D9"
  cream-2: "#E4DCC4"
  ink: "#0F0F0F"
  ink-2: "#2A2A2A"
  green: "#1F8A4C"
  green-dark: "#136636"
  pink: "#F06CA8"
  pink-dark: "#D14E8B"
  orange: "#E85A1F"
  yellow: "#F5C518"

typography:
  # — reading ramp (px @ 1920, with cqw) —
  body-lg:    { fontFamily: "Space Grotesk", px: 28, cqw: 1.46, weight: 400, lineHeight: 1.4 }
  body-md:    { fontFamily: "Space Grotesk", px: 24, cqw: 1.25, weight: 400, lineHeight: 1.3 }
  mono-label: { fontFamily: "JetBrains Mono", px: 24, cqw: 1.25, weight: 400, tracking: "0.06em", upper: true }
  mono-kicker:{ fontFamily: "JetBrains Mono", px: 24, cqw: 1.25, weight: 400, tracking: "0.14em", upper: true }
  table-head: { fontFamily: "Archivo Black", px: 28, cqw: 1.46, weight: 400, upper: true }
  # — display / hero ramp (frame-native, cqw-first) —
  step-title: { fontFamily: "Archivo Black", px: 34,  cqw: 1.77, weight: 400, lineHeight: 1.0, upper: true }
  badge-label:{ fontFamily: "Archivo Black", px: 28,  cqw: 1.46, weight: 400, upper: true }
  marker:     { fontFamily: "Archivo Black", cqw: 2.4, weight: 400, lineHeight: 1.0, upper: true }
  stamp-num:  { fontFamily: "Archivo Black", cqw: 2.2, weight: 400, lineHeight: 0.9 }
  stat-num:   { fontFamily: "Archivo Black", cqw: 6.4, weight: 400, lineHeight: 0.88 }
  step-num:   { fontFamily: "Archivo Black", cqw: 7.0, weight: 400, lineHeight: 0.85 }
  display-head: { fontFamily: "Archivo Black", cqw: 4.2, weight: 400, lineHeight: 0.92, tracking: "-0.01em", upper: true }
  display-lg: { fontFamily: "Archivo Black", cqw: 8.0,  weight: 400, lineHeight: 0.9,  tracking: "-0.01em", upper: true }
  display-xl: { fontFamily: "Archivo Black", cqw: 11.0, weight: 400, lineHeight: 0.9,  tracking: "-0.01em", upper: true }
  display-hero:{ fontFamily: "Archivo Black", cqw: 15.5,weight: 400, lineHeight: 0.84, tracking: "-0.02em", upper: true }

spacing:
  frame-pad: "3.3cqw"        # 64px chrome gutter @1920
  content-gutter: "5cqw"     # 96px content gutter @1920
  grid-gap: "1.5cqw"         # 28px
  cell-pad: "1.7cqw"         # 32px

components:
  frame-chrome:
    typography: "{typography.mono-label}"
    placement: "topbar 2.5cqw from top, meta 2.5cqw from bottom, both inset {spacing.frame-pad}"
    rounded: "0"
    shadow: "none"
    description: "Mono topbar (section label left + 999px ink-stroked pill right) + meta footer (descriptor left + NN • NN counter right, 0.5cqw ink dot divider). Present on most frames."
  stat-cell:
    backgroundColor: "{colors.green} · {colors.pink} · {colors.orange} · {colors.cream}"
    textColor: "{colors.cream} on accent · {colors.ink} on cream"
    border: "0.4cqw solid {colors.ink}"
    rounded: "0"
    padding: "{spacing.cell-pad}"
    typography: "{typography.stat-num} + {typography.mono-label}"
    shadow: "none"
    description: "Flat accent/cream stat tile, square corners, no shadow."
  step-card:
    backgroundColor: "{colors.cream} · {colors.pink} · {colors.yellow} · {colors.green}"
    border: "0.4cqw solid {colors.ink}"
    rounded: "0"
    padding: "{spacing.cell-pad}"
    typography: "{typography.step-num} + {typography.step-title} + {typography.body-md}"
    shadow: "none"
    description: "Ink-bordered card; giant step-num top. Sequence alternates cream with accents and ENDS on green."
  marker-block:
    backgroundColor: "{colors.pink}"
    textColor: "{colors.ink}"
    border: "0.4cqw solid {colors.ink}"
    rounded: "0"
    typography: "{typography.marker}"
    shadow: "1.25cqw 1.25cqw 0 {colors.orange}, 1.25cqw 1.25cqw 0 0.2cqw {colors.ink}"
    description: "The one hard-offset featured callout per frame."
  kicker-block:
    backgroundColor: "{colors.ink}"
    textColor: "{colors.cream}"
    typography: "{typography.mono-kicker}"
    padding: "0.5cqw 1cqw"
    rounded: "0"
    description: "Inverted eyebrow chip."
  badge-rotated:
    backgroundColor: "{colors.yellow}"
    border: "0.4cqw solid {colors.ink}"
    typography: "{typography.badge-label}"
    rounded: "0"
    transform: "rotate(-4deg)"
    description: "Deliberate-imperfection annotation."
  pill-badge:
    backgroundColor: "{colors.cream}"
    border: "0.2cqw solid {colors.ink}"
    rounded: "999px"
    typography: "{typography.mono-label}"
    description: "The ONLY rounded element; reads as a chip, not a card."
  stamp:
    backgroundColor: "{colors.pink}"
    border: "0.4cqw solid {colors.cream}"
    size: "~18cqw square"
    rounded: "0"
    transform: "rotate(-6deg)"
    typography: "{typography.stamp-num}"
    description: "Closing seal with a cream circular inner ring."
  comparison-table:
    backgroundColor: "{colors.cream-2}"
    border: "0.4cqw solid {colors.ink}"
    rule: "0.3cqw solid {colors.ink}"
    rounded: "0"
    typography: "{typography.table-head} + {typography.body-md}"
    shadow: "none"
    description: "Ink head row with cream Archivo labels; pink/green column-fill variants."
  decorative-circle:
    backgroundColor: "{colors.yellow}"
    border: "0.4cqw solid {colors.ink}"
    rounded: "50%"
    description: "Decorative figure; pairs with a green panel for shape contrast."
---

# Creative Mode — Frame (video / frame layer)

## Overview

Creative Mode at frame scale is a **neo-brutalist editorial poster in motion's clothing** — warm
cream paper, near-black ink, and four accents that collide at full saturation. Every frame is one
flat color-blocked composition: no gradients, no blurred shadows, no rounded cards (save the one
pill chip). Depth is **hard offset shadow** (a solid same-direction duplicate) or **color-block
contrast**, never light.

The display voice is **Archivo Black in strict uppercase at 0.92 line-height** — letters overlap
their own cap height; that tightness is the brand. **JetBrains Mono** carries every label, kicker,
counter, and axis as a "technical artifact" register. **Space Grotesk** carries the few body lines.
The frame is loud by construction and calm by restraint: two or three accents per frame, the green
ground reserved for a single closing plate, the hard shadow spent on one featured element only.

**Key characteristics at frame scale:**

- **Cream ground** (`{colors.cream}`) on nearly every frame; `{colors.green}` reserved for the closing plate.
- **0.4cqw (4px @1920) ink borders** on every structural element; 0.3cqw internal rules.
- **Hard offset shadow** (≈1.25cqw, orange+ink) on one featured block per frame — never blurred.
- **Archivo Black uppercase**, 0.92 line-height, always; sentence-case Archivo Black does not exist.
- **Two or three accents per frame**, never all four; collisions are the design.
- **One pill chip** (999px) per frame as the sole rounded element.

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — exactly one element dominates, at **3–6× its nearest neighbor** (a chasm, not a ramp): the `display-hero`/`display-xl` claim or the `stat-num` figure, never two rival headlines.
- **Silence** — sparse frames (cover, claim, closer) read **45–60% empty**; the **stat grid and comparison ledger are the one dense exception**. Never fill a sparse frame to look complete.
- **Restraint** — the scarce gestures fire **once per frame**: at most one hard-offset shadow, two-to-three accents (never all four), the green ground reserved for the single closing plate.
- **Reference** — aim at a **Risograph editorial poster / punk-zine spread** (flat ink-bordered blocks, one big claim, vast cream); failure looks like a **rounded, soft-shadowed SaaS feature grid**.

## The Frame

- **Primary:** 1920×1080 (16:9). All display sizes authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** chrome at `3.3cqw` (64px) inset; content at `5cqw` (96px) gutter. No load-bearing element crosses the `3.3cqw` line.

**The container law (load-bearing).** Every frame ground sets `container-type: size`. ALL
frame-relative units are `cqw`/`cqh` (1cqw = 1% of the frame's width), resolved against that
ground — **never `vw`.** `vw` measures the page viewport, so a frame inflates whenever it isn't
rendered full-screen; `cqw` resolves against the frame at any render size. This is why every size
in the display ramp is `cqw`.

## Colors

Tokens identical to the source. At frame scale: `{colors.cream}` is the **ground**, `{colors.ink}`
is borders + type, and the four accents (`green` / `pink` / `orange` / `yellow`) are **flat fills
rationed two-to-three per frame.** `{colors.orange}` is also the hard-shadow color. `{colors.green}`
doubles as the single closing-plate ground — its rarity is the impact. Never introduce a fifth
accent; never use pure white; never gradient.

## Typography

Two ramps. The **reading ramp** (body, mono labels, table heads) holds px+cqw for chrome and copy.
The **display/hero ramp** is frame-native and authored in `cqw` — from `display-head` (4.2cqw) up
to `display-hero` (15.5cqw) for a wordmark cover.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw (≈27px@1920)**. Mono chrome at 1.15cqw is colophon only.
- **Fit-to-measure:** a headline's size tracks its line length. Cap the headline block at **≤ 78cqw** and never touch the safe margin. ≤3 words → `display-hero`/`display-xl`; 4–6 words → `display-lg`; 7+ words → `display-head`. Short lines go big; long lines step down.
- **Uppercase + 0.92 line-height on all Archivo Black**, always. Mono carries 0.06–0.14em tracking. Never letter-space Archivo Black beyond the encoded −0.01em; never set body centered.

## Depth & Surface

Zero blur. Two depth devices only:

- **Hard offset shadow** — a solid duplicate offset ≈1.25cqw in X and Y (`box-shadow: 1.25cqw 1.25cqw 0 {colors.orange}, 1.25cqw 1.25cqw 0 0.2cqw {colors.ink}`). **One featured block per frame** (marker, stamp). Diagram stacks may use a 0.95cqw ink-only offset.
- **Color-block contrast** — cream on cream-2, ink on cream, accent on cream. No shadow needed when contrast carries.

**Ceiling:** no blurred shadow, no gradient, no glow, anywhere.

## Shapes

- **0 radius** on every structural element — stat cells, step cards, table cells, markers, panels.
- **50%** on decorative circles, stamp inner ring, the meta dot.
- **999px** on the topbar pill chip only — the single rounded exception.
- **Rotation** only at the fixed brand angles: badge −4deg, stamp −6deg.

## Components

- **frame-chrome** — the mono topbar + meta footer. Present on most frames; dropped only on a pure full-bleed wordmark beat if it competes.
- **stat-cell / step-card** — ink-bordered flat-fill blocks; step sequences end on green.
- **marker-block** — the one hard-shadow featured callout per frame.
- **kicker-block** — inverted ink eyebrow chip; **badge-rotated** — −4deg yellow annotation; **pill-badge** — the lone rounded chip.
- **stamp** — the −6deg closing seal. **comparison-table** — cream-2 ledger with ink head row.
- **decorative-circle** — yellow disc for shape contrast against a green panel.

## Frame Treatments

> Recipe per plate: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> Authored at 1920×1080. Lean centered; vary the anchor; one idea per frame.

### 1 · Wordmark Cover (identity · move: full-frame lockup · centered)

**Ground** `{colors.cream}`, `frame-pad`. **Container** grid, chrome top/bottom, focal centered.
**Composes** frame-chrome, the wordmark. **Focal** the two-line wordmark at `display-hero`
(15.5cqw), centered, second line in an accent (`{colors.pink}`/`orange`). **Chrome** mono topbar
(section label + pill) and meta footer (descriptor + 01•NN). **Accent** the second-line color only;
optionally one corner decorative-circle bleeding off an edge. **Silence** ~55% empty cream.
**Fixed** Archivo Black uppercase 0.84 lh, one accent on the wordmark, square corners. **Free** which
accent, line break, whether a circle bleeds a corner. **Density** sparse.

### 2 · Big Claim (oversized statement · move: scale · left)

**Ground** one full-bleed accent (`{colors.pink}`/`green`/`orange`), `content-gutter`. **Container**
flex, claim left-anchored and vertically centered. **Composes** kicker-block, the claim. **Focal**
a 2–3 line claim at `display-xl`/`display-lg`, ink on the accent (ink-on-fire — never white).
**Chrome** small ink kicker-block above the claim; mono meta footer. **Accent** the ground IS the
accent; no second accent competes. **Silence** ~45% of the colored field empty. **Fixed** ink type
on accent, fit-to-measure sizing, no shadow on type. **Free** the claim, which accent ground, which
word breaks. **Density** sparse — one idea.

### 3 · Stat Grid (catalog · move: density — the one dense frame · centered)

**Ground** `{colors.cream}`, `content-gutter`. **Container** grid: a centered `display-head` over a
3-up row of stat-cells. **Composes** frame-chrome, 3× stat-cell. **Focal** the row of three
ink-bordered cells (green / pink / orange), each a `stat-num` + mono label. **Chrome** mono topbar +
meta. **Accent** the three cell fills (the named density exception — three accents allowed here).
**Silence** ~25% — tight by design. **Fixed** 0.4cqw borders, square corners, no per-cell shadow,
mono labels. **Free** the three figures+labels, head copy, which three accents. **Density** dense-exception.

### 4 · Closing Plate (closer · move: ground-swap · centered)

**Ground** `{colors.green}` (the single green frame of the run), `frame-pad`. **Focal** a 2-line
sign-off at `display-lg` in `{colors.cream}`, centered. **Composes** frame-chrome (cream variant),
stamp. **Chrome** cream mono topbar + meta. **Accent** one `{colors.pink}` stamp rotated −6deg in
a corner, cream ring + stamp-num. **Silence** ~55% empty green. **Fixed** green ground reserved to
this beat, cream-on-green type, one stamp. **Free** sign-off copy, stamp text, stamp corner. **Density** sparse.

### 5 · Featured Marker (callout · move: hard-shadow focal · left/asymmetric)

**Ground** `{colors.cream}`. **Composes** frame-chrome, marker-block, optional body-md support line.
**Focal** the pink marker-block with the signature orange+ink hard offset shadow, set asymmetrically.
**Accent** pink block + orange shadow (two accents). **Silence** ~50%. **Fixed** exactly one hard
shadow on the frame, 0.4cqw borders. **Free** marker copy, block position, optional support line.
**Density** sparse.

### 6 · Comparison Ledger (data · move: matrix · left)

**Ground** `{colors.cream}`, `content-gutter`. **Composes** frame-chrome, comparison-table.
**Focal** the cream-2 table with ink head row; one column fill (pink or green) marks the winner.
**Accent** the single column fill. **Silence** tight — the second density exception. **Fixed**
cream-2 fill, 0.3cqw internal rules, ink head row with cream Archivo labels. **Free** rows, which
column fills, copy. **Density** dense-exception.

## Composition Rules

### Do

- Compose around **one idea per frame**, focal element **3–5× its neighbors** (squint test).
- **Lean centered** — cover, claim, stat grid, and closer all center their focal element; reserve left/asymmetric for the marker and ledger.
- Keep sparse frames **45–60% empty**; only the stat grid and ledger run dense.
- Use **two or three accents per frame**; reserve `{colors.green}` ground for the closing plate.
- Spend the hard offset shadow on **one featured block per frame**.
- Size headlines **fit-to-measure**; render Archivo Black uppercase at 0.92 lh.

### Don't

- Don't round corners (except the pill chip); don't gradient, blur, or glow.
- Don't set Archivo Black in sentence case or letter-space it beyond −0.01em.
- Don't use all four accents on one frame, a fifth accent, or pure white.
- Don't center body copy or set labels in anything but JetBrains Mono.
- Don't blow a headline edge-to-edge — step the ramp down for long lines.
- Don't put two hard shadows on one frame.

## Aspect-Ratio Behavior

| Treatment         | 16:9                            | 9:16                         | 1:1                      |
| ----------------- | ------------------------------- | ---------------------------- | ------------------------ |
| Wordmark Cover    | two lines centered              | stacked taller, circle top   | centered, tighter        |
| Big Claim         | claim left, full accent         | claim top, accent full       | claim centered           |
| Stat Grid         | head over 3-up row              | head top, 3 stacked          | head top, 2×2 (4th cell) |
| Closing Plate     | sign-off centered, stamp corner | stacked, stamp below         | centered, stamp corner   |
| Featured Marker   | marker asymmetric               | marker centered, shadow down | marker centered          |
| Comparison Ledger | full-width table                | table scrolls to fewer cols  | 2-col table              |

Safe area holds the `3.3cqw` chrome inset on the short edge for every ratio; re-step display per
ratio so no load-bearing line drops below the 1.4cqw floor.

## Approved Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Products/sections are content-agnostic; the system supplies geometry, not brands.

## Numerals & Claims (hard rule)

Never invent figures, percentages, counts, or dates at frame scale. Render data slots as
`— figure —`, `{metric}`, `N×`. Real numerals appear only when the script supplies them — the stat
grid and ledger especially carry placeholders, not fabricated values.

## Pre-Render Self-Audit

- **Squint** — one element dominates at 3–5× its neighbor, else rescale.
- **Silence** — sparse frames 45–60% empty; only stat grid / ledger run dense.
- **Accents** — two or three per frame, never all four; green ground only on the closer.
- **Depth** — 0 blur; at most one hard offset shadow; color-block contrast otherwise.
- **Geometry** — square corners except the pill; rotation only at −4/−6deg.
- **Type** — Archivo Black uppercase 0.92 lh, fit-to-measure, ≥1.4cqw on load-bearing lines.
- **Anchor** — centered default; left/asymmetric only on marker + ledger; no 3 consecutive frames share an anchor.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; timing and transitions are a later stage. The closing green is described as a _plate_, not a transition.
- **Archivo Black requires Google Fonts**; fallback is `sans-serif`. CJK pairing (Noto Serif SC 900 / NSC 400) carries over from the source's CJK section.
- **9:16 / 1:1 are guidance**, not pixel-locked; verify the legibility floor per ratio.
- Decorative geometry (circle, stamp, stacked blocks) is CSS-only; no external imagery is required.
assets/frame-presets/daisy-days/caption-skin.html›
<!--
  Daisy Days — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has a daisy-days literal
  fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: a cheerful picture-book card — a cream surface inside a 3px charcoal outline
  with a hard 6px charcoal offset shadow (no blur) and a generous 24px radius, the
  sticker-on-paper signature. Fredoka One glyphs, sentence case, charcoal ink. Upcoming
  words sit in muted charcoal; the current word pops into a rounded sunny-pastel candy
  block (coral fill, 3px charcoal outline, hard 3px offset shadow, 12px radius) with a
  bouncy scale pop; spoken words settle to solid charcoal, the candy block cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE picture-book card — cream surface, 3px charcoal outline, generous 24px radius,
     a hard 6px charcoal offset shadow (no blur): the sticker-on-paper signature */
  .caption-pill {
    max-width: 80%;
    padding: 18px 40px 22px;
    background: var(--cap-canvas, #f5f0e6);
    border: 3px solid var(--cap-ink, #2d2d2d);
    border-radius: 24px;
    box-shadow: 6px 6px 0 var(--cap-ink, #2d2d2d);
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.18em 0.32em;
    font-family: var(--font-display, "Fredoka One"), "Baloo 2", sans-serif;
    font-weight: 600;
    font-size: clamp(38px, 4.2vw, 56px);
    line-height: 1.22;
    letter-spacing: 0.02em;
  }

  /* upcoming — muted charcoal, no candy block */
  .caption-word {
    display: inline-block;
    padding: 0.02em 0.16em;
    border-radius: 12px;
    color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 52%, var(--cap-canvas, #f5f0e6));
  }
  /* current word — THE sunny-pastel candy block: a rounded coral chip with a 3px
     charcoal outline + a hard 3px charcoal offset shadow (no blur), charcoal on top */
  .caption-word.is-active {
    color: var(--cap-ink, #2d2d2d);
    background: var(--cap-accent, #f8635f);
    border-radius: 12px;
    box-shadow:
      0 0 0 3px var(--cap-ink, #2d2d2d),
      3px 5px 0 0 var(--cap-ink, #2d2d2d);
  }
  /* already spoken — solid charcoal, candy block cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #2d2d2d);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — a butter candy chip, the cheerful second pastel */
  .caption-word.cap-brand {
    color: var(--cap-ink, #2d2d2d);
    background: var(--cap-accent-2, #fde68a);
    border-radius: 12px;
    box-shadow: 0 0 0 3px var(--cap-ink, #2d2d2d);
  }
  /* numerals / data — Quicksand, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "Quicksand"), system-ui, sans-serif;
    font-weight: 700;
    color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 78%, var(--cap-canvas, #f5f0e6));
  }
  /* emphasis — a butter underline rule (the second sunny pastel) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 3px;
    text-decoration-color: var(--cap-accent-2, #fde68a);
    text-underline-offset: 0.12em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(32px, 5.2vw, 46px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.82 }, { scale: 1, duration: 0.2, ease: "back.out(2.6)" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/daisy-days/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Daisy Days</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;600;700&family=Quicksand:wght@400;500;600;700&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --cream: #f5f0e6;
        --turquoise: #7ecdc0;
        --soft-pink: #f7c8d4;
        --butter: #fde68a;
        --mint: #a8e6cf;
        --lavender: #d4a5e8;
        --peach: #ffcba4;
        --sky: #a8d8f0;
        --coral: #f8635f;
        --text-dark: #2d2d2d;
        --text-muted: #6b6b6b;
        --white: #ffffff;
        --disp: "Fredoka", "Fredoka One", "Baloo 2", sans-serif;
        --body: "Quicksand", sans-serif;
        --shadow: 6px 6px 0 var(--text-dark);
        --shadow-sm: 4px 4px 0 var(--text-dark);
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #1d1d1a;
      }
      body {
        background: var(--cream);
        color: var(--text-dark);
        font-family: var(--body);
        font-weight: 500;
        -webkit-font-smoothing: antialiased;
      }
      .disp {
        font-family: var(--disp);
        font-weight: 600;
        letter-spacing: 0.02em;
        line-height: 1.1;
      }
      .sec-head h2,
      .sec-head .badge,
      .cover h1,
      .cover .badge,
      .cover .counter,
      .sw .name,
      .card .ic,
      .card .t,
      .framed .cap,
      .step .c,
      .step .t,
      .chip-b,
      .do-card h4,
      .do-card .badge,
      .foot .l,
      .df1 .badge,
      .df1 h3,
      .df2 h3,
      .df2 .c .ic,
      .df2 .c .t,
      .df3 h3,
      .df3 .st .c,
      .df3 .st .t,
      .df3 .ar,
      .df4 .qm,
      .df4 .q {
        font-family: var(--disp);
        font-weight: 600;
      }
      .tshadow {
        text-shadow: 3px 3px 0 var(--text-dark);
      }
      .tshadow-soft {
        text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2);
      }

      section {
        padding: 90px 80px;
        position: relative;
        overflow: hidden;
      }
      .sec-head {
        display: flex;
        align-items: center;
        gap: 18px;
        margin-bottom: 48px;
        position: relative;
        z-index: 2;
      }
      .sec-head .badge {
        font-family: var(--disp);
        font-size: 0.95rem;
        background: var(--butter);
        border: 3px solid var(--text-dark);
        border-radius: 50px;
        padding: 8px 22px;
        box-shadow: var(--shadow-sm);
      }
      .sec-head h2 {
        font-family: var(--disp);
        font-size: 46px;
      }

      /* daisy/star CSS ornaments */
      .daisy {
        position: absolute;
        z-index: 1;
        pointer-events: none;
      }
      .daisy .p {
        position: absolute;
        width: 34%;
        height: 50%;
        background: var(--white);
        border: 2.4px solid var(--text-dark);
        border-radius: 50%;
        left: 33%;
        top: 0;
        transform-origin: 50% 100%;
      }
      .daisy .c {
        position: absolute;
        width: 38%;
        height: 38%;
        border-radius: 50%;
        background: var(--butter);
        border: 2.4px solid var(--text-dark);
        left: 31%;
        top: 31%;
        z-index: 2;
      }
      .star {
        position: absolute;
        z-index: 1;
        pointer-events: none;
        border: 2.2px solid var(--text-dark);
        clip-path: polygon(
          50% 0,
          63% 38%,
          100% 38%,
          69% 61%,
          82% 100%,
          50% 76%,
          18% 100%,
          31% 61%,
          0 38%,
          37% 38%
        );
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        background: var(--turquoise);
        padding: 60px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        position: relative;
        overflow: hidden;
      }
      .cover .badge {
        font-family: var(--disp);
        font-size: 0.95rem;
        background: var(--butter);
        border: 3px solid var(--text-dark);
        border-radius: 50px;
        padding: 10px 26px;
        box-shadow: var(--shadow-sm);
        margin-bottom: 32px;
        position: relative;
        z-index: 3;
        white-space: nowrap;
      }
      .cover h1 {
        font-family: var(--disp);
        font-size: clamp(64px, 10vw, 150px);
        line-height: 1.05;
        color: var(--white);
        text-shadow: 4px 4px 0 var(--text-dark);
        position: relative;
        z-index: 3;
      }
      .cover .sub {
        font-family: var(--body);
        font-weight: 600;
        font-size: 22px;
        line-height: 1.5;
        color: var(--text-dark);
        max-width: 680px;
        margin-top: 28px;
        position: relative;
        z-index: 3;
      }
      .cover .counter {
        position: fixed;
        bottom: 24px;
        left: 50%;
        transform: translateX(-50%);
        background: var(--white);
        border: 3px solid var(--text-dark);
        border-radius: 50px;
        padding: 6px 22px;
        font-family: var(--disp);
        font-size: 0.9rem;
        box-shadow: var(--shadow-sm);
        z-index: 5;
        white-space: nowrap;
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(6, 1fr);
        gap: 22px;
        position: relative;
        z-index: 2;
      }
      .sw {
        background: var(--white);
        border: 3px solid var(--text-dark);
        border-radius: 20px;
        overflow: hidden;
        box-shadow: var(--shadow-sm);
      }
      .sw .chip {
        height: 90px;
        border-bottom: 3px solid var(--text-dark);
      }
      .sw .meta {
        padding: 12px 14px;
      }
      .sw .name {
        font-family: var(--disp);
        font-size: 1rem;
      }
      .sw .hex {
        font-family: var(--body);
        font-weight: 600;
        font-size: 11px;
        color: var(--text-muted);
        margin-top: 4px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 220px 1fr;
        align-items: center;
        padding: 22px 0;
        border-bottom: 3px solid rgba(45, 45, 45, 0.12);
      }
      .type-row .tok {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 12px;
        color: var(--text-muted);
      }
      .type-row .m {
        font-family: var(--body);
        font-weight: 500;
        font-size: 11px;
        color: var(--text-muted);
        margin-top: 8px;
        line-height: 1.5;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 12px;
        color: var(--text-muted);
        margin-bottom: 20px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 28px;
        margin-bottom: 48px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .s8 {
        grid-column: span 8;
      }
      .card {
        background: var(--white);
        border: 3px solid var(--text-dark);
        border-radius: 20px;
        box-shadow: var(--shadow);
        padding: 30px;
      }
      .card .ic {
        width: 60px;
        height: 60px;
        border-radius: 50%;
        border: 3px solid var(--text-dark);
        display: grid;
        place-items: center;
        font-family: var(--disp);
        font-size: 1.6rem;
        margin-bottom: 18px;
      }
      .card .t {
        font-family: var(--disp);
        font-size: 1.5rem;
      }
      .card .x {
        font-family: var(--body);
        font-weight: 500;
        font-size: 14px;
        line-height: 1.6;
        color: var(--text-muted);
        margin-top: 8px;
      }
      .framed {
        border: 3px solid var(--text-dark);
        border-radius: 28px;
        overflow: hidden;
        box-shadow: var(--shadow);
      }
      .framed .cap {
        padding: 18px 28px;
        font-family: var(--disp);
        font-size: 1.4rem;
        color: var(--white);
        text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2);
      }
      .framed .bd {
        background: var(--white);
        padding: 28px;
        font-family: var(--body);
        font-weight: 500;
        font-size: 15px;
        line-height: 1.6;
      }
      .steps {
        display: flex;
        gap: 20px;
        align-items: flex-start;
      }
      .step {
        flex: 1;
        text-align: center;
      }
      .step .c {
        width: 90px;
        height: 90px;
        border-radius: 50%;
        border: 3px solid var(--text-dark);
        display: grid;
        place-items: center;
        font-family: var(--disp);
        font-size: 2.2rem;
        color: var(--white);
        box-shadow: var(--shadow-sm);
        margin: 0 auto;
      }
      .step .t {
        font-family: var(--disp);
        font-size: 1.2rem;
        margin-top: 14px;
      }
      .step .x {
        font-family: var(--body);
        font-weight: 500;
        font-size: 13px;
        color: var(--text-muted);
        margin-top: 6px;
      }
      .bullets {
        list-style: none;
      }
      .bullets li {
        font-family: var(--body);
        font-weight: 500;
        font-size: 15px;
        line-height: 1.6;
        padding-left: 32px;
        position: relative;
        margin-bottom: 12px;
      }
      .bullets li::before {
        content: "";
        position: absolute;
        left: 0;
        top: 5px;
        width: 20px;
        height: 20px;
        border-radius: 50%;
        border: 2px solid var(--text-dark);
        background: var(--butter);
      }
      .chips {
        display: flex;
        gap: 14px;
        flex-wrap: wrap;
      }
      .chip-b {
        font-family: var(--disp);
        font-size: 0.85rem;
        border: 3px solid var(--text-dark);
        border-radius: 50px;
        padding: 8px 20px;
        box-shadow: var(--shadow-sm);
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
        position: relative;
        z-index: 2;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 12px;
        color: var(--text-muted);
      }
      .fw .flabel b {
        color: var(--text-dark);
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        border: 3px solid var(--text-dark);
        border-radius: 16px;
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 3;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
        padding: 0 6cqw;
      }

      /* f1 cover */
      .df1 {
        background: var(--turquoise);
      }
      .df1 .badge {
        font-family: var(--disp);
        font-size: 1.1cqw;
        background: var(--butter);
        border: 0.22cqw solid var(--text-dark);
        border-radius: 50px;
        padding: 0.6cqw 1.6cqw;
        box-shadow: 0.3cqw 0.3cqw 0 var(--text-dark);
        margin-bottom: 2cqw;
        white-space: nowrap;
      }
      .df1 h3 {
        font-family: var(--disp);
        font-size: 9cqw;
        line-height: 1.05;
        color: var(--white);
        text-shadow: 0.4cqw 0.4cqw 0 var(--text-dark);
      }
      .df1 .sub {
        font-family: var(--body);
        font-weight: 600;
        font-size: 1.5cqw;
        color: var(--text-dark);
        margin-top: 2cqw;
        max-width: 60cqw;
      }

      /* f2 info cards */
      .df2 {
        background: var(--cream);
      }
      .df2 .body {
        justify-content: flex-start;
        align-items: stretch;
        text-align: left;
        padding: 6cqw 5cqw;
      }
      .df2 h3 {
        font-family: var(--disp);
        font-size: 4.4cqw;
        text-align: center;
        margin-bottom: 2.4cqw;
      }
      .df2 .row {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 2cqw;
        flex: 1;
      }
      .df2 .c {
        background: var(--white);
        border: 0.25cqw solid var(--text-dark);
        border-radius: 1.6cqw;
        box-shadow: 0.5cqw 0.5cqw 0 var(--text-dark);
        padding: 2cqw;
        display: flex;
        flex-direction: column;
      }
      .df2 .c .ic {
        width: 4.5cqw;
        height: 4.5cqw;
        border-radius: 50%;
        border: 0.22cqw solid var(--text-dark);
        display: grid;
        place-items: center;
        font-family: var(--disp);
        font-size: 1.8cqw;
        color: var(--white);
        margin-bottom: 1.2cqw;
      }
      .df2 .c .t {
        font-family: var(--disp);
        font-size: 1.8cqw;
      }
      .df2 .c .x {
        font-family: var(--body);
        font-weight: 500;
        font-size: 1cqw;
        line-height: 1.5;
        color: var(--text-muted);
        margin-top: 0.6cqw;
      }

      /* f3 process steps */
      .df3 {
        background: var(--peach);
      }
      .df3 .body {
        padding: 6cqw 5cqw;
      }
      .df3 h3 {
        font-family: var(--disp);
        font-size: 4.4cqw;
        color: var(--white);
        text-shadow: 0.3cqw 0.3cqw 0 var(--text-dark);
        margin-bottom: 3cqw;
      }
      .df3 .row {
        display: flex;
        gap: 2cqw;
        align-items: flex-start;
        justify-content: center;
      }
      .df3 .st {
        flex: 1;
        text-align: center;
        max-width: 18cqw;
      }
      .df3 .st .c {
        width: 7cqw;
        height: 7cqw;
        border-radius: 50%;
        border: 0.3cqw solid var(--text-dark);
        display: grid;
        place-items: center;
        font-family: var(--disp);
        font-size: 2.6cqw;
        color: var(--white);
        box-shadow: 0.4cqw 0.4cqw 0 var(--text-dark);
        margin: 0 auto;
      }
      .df3 .st .t {
        font-family: var(--disp);
        font-size: 1.5cqw;
        margin-top: 1cqw;
        color: var(--text-dark);
      }
      .df3 .ar {
        font-family: var(--disp);
        font-size: 2.4cqw;
        color: var(--text-dark);
        align-self: center;
        margin-top: 2cqw;
      }

      /* f4 quote */
      .df4 {
        background: var(--soft-pink);
      }
      .df4 .qbox {
        background: var(--white);
        border: 0.25cqw solid var(--text-dark);
        border-radius: 2cqw;
        box-shadow: 0.6cqw 0.6cqw 0 var(--text-dark);
        padding: 4cqw 5cqw;
        max-width: 72cqw;
      }
      .df4 .qm {
        font-family: var(--disp);
        font-size: 6cqw;
        line-height: 0.6;
        color: var(--soft-pink);
        -webkit-text-stroke: 0.15cqw var(--text-dark);
      }
      .df4 .q {
        font-family: var(--disp);
        font-size: 3.2cqw;
        line-height: 1.35;
        color: var(--text-dark);
        margin-top: 0.5cqw;
      }
      .df4 .at {
        font-family: var(--body);
        font-weight: 700;
        font-size: 1.2cqw;
        color: var(--text-muted);
        margin-top: 1.6cqw;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 28px;
        position: relative;
        z-index: 2;
      }
      .do-card {
        background: var(--white);
        border: 3px solid var(--text-dark);
        border-radius: 24px;
        box-shadow: var(--shadow);
        padding: 34px;
      }
      .do-card h4 {
        font-family: var(--disp);
        font-size: 1.7rem;
        margin-bottom: 8px;
      }
      .do-card .badge {
        display: inline-block;
        font-family: var(--disp);
        font-size: 0.8rem;
        border: 3px solid var(--text-dark);
        border-radius: 50px;
        padding: 5px 16px;
        margin-bottom: 18px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-family: var(--body);
        font-weight: 500;
        font-size: 14px;
        line-height: 1.55;
        padding-left: 26px;
        position: relative;
        margin-bottom: 12px;
        color: var(--text-dark);
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        top: 4px;
        width: 16px;
        height: 16px;
        border-radius: 50%;
        border: 2px solid var(--text-dark);
        content: "";
      }
      .do-card.do li::before {
        background: var(--mint);
      }
      .do-card.dont li::before {
        background: var(--coral);
      }

      .foot {
        padding: 48px 80px;
        background: var(--lavender);
        border-top: 3px solid var(--text-dark);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--disp);
        font-size: 1.7rem;
        color: var(--white);
        text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2);
      }
      .foot .r {
        font-family: var(--body);
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        font-size: 11px;
        color: var(--text-dark);
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;600;700&family=Fredoka+One&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #f5f0e6 !important;
        --secondary: #2d2d2d !important;
        --tertiary: #7ecdc0 !important;
        --accent: #f8635f !important;
        --f-disp: "Fredoka", sans-serif !important;
        --f-body: "Quicksand", sans-serif !important;
        --cream: #f5f0e6 !important;
        --turquoise: #7ecdc0 !important;
        --coral: #f8635f !important;
        --text-dark: #2d2d2d !important;
        --disp: "Fredoka", sans-serif !important;
        --body: "Quicksand", sans-serif !important;
        --soft-pink: #f7c8d4 !important;
        --butter: #fde68a !important;
        --mint: #a8e6cf !important;
        --lavender: #d4a5e8 !important;
        --peach: #ffcba4 !important;
        --sky: #a8d8f0 !important;
        --text-muted: #6b6b6b !important;
        --white: #ffffff !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="daisy" style="width: 140px; height: 140px; top: -30px; left: -20px">
        <div class="p" style="transform: rotate(0deg)"></div>
        <div class="p" style="transform: rotate(60deg)"></div>
        <div class="p" style="transform: rotate(120deg)"></div>
        <div class="p" style="transform: rotate(180deg)"></div>
        <div class="p" style="transform: rotate(240deg)"></div>
        <div class="p" style="transform: rotate(300deg)"></div>
        <div class="c"></div>
      </div>
      <div
        class="star"
        style="
          width: 70px;
          height: 70px;
          background: var(--soft-pink);
          top: 18%;
          right: 10%;
          transform: rotate(12deg);
        "
      ></div>
      <div
        class="star"
        style="
          width: 50px;
          height: 50px;
          background: var(--butter);
          bottom: 20%;
          left: 12%;
          transform: rotate(-8deg);
        "
      ></div>
      <div class="daisy" style="width: 100px; height: 100px; bottom: -26px; right: -16px">
        <div class="p" style="transform: rotate(0deg)"></div>
        <div class="p" style="transform: rotate(60deg)"></div>
        <div class="p" style="transform: rotate(120deg)"></div>
        <div class="p" style="transform: rotate(180deg)"></div>
        <div class="p" style="transform: rotate(240deg)"></div>
        <div class="p" style="transform: rotate(300deg)"></div>
        <div class="c"></div>
      </div>
      <div class="badge">Frame System · Vol. 01</div>
      <h1>Hello,<br />sunshine!</h1>
      <div class="sub">
        A cheerful childlike frame system — chunky Fredoka One headlines, friendly Quicksand body, a
        sunny garden palette, charcoal outlines, hard offset shadows, and hand-drawn ornaments.
      </div>
      <div class="counter">01 / 06</div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="sec-head">
        <span class="badge">Palette</span>
        <h2>A Sunny Garden</h2>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--turquoise)"></div>
          <div class="meta">
            <div class="name">Turquoise</div>
            <div class="hex">#7ECDC0</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--soft-pink)"></div>
          <div class="meta">
            <div class="name">Soft Pink</div>
            <div class="hex">#F7C8D4</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--butter)"></div>
          <div class="meta">
            <div class="name">Butter</div>
            <div class="hex">#FDE68A</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--mint)"></div>
          <div class="meta">
            <div class="name">Mint</div>
            <div class="hex">#A8E6CF</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--lavender)"></div>
          <div class="meta">
            <div class="name">Lavender</div>
            <div class="hex">#D4A5E8</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--peach)"></div>
          <div class="meta">
            <div class="name">Peach</div>
            <div class="hex">#FFCBA4</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--sky)"></div>
          <div class="meta">
            <div class="name">Sky</div>
            <div class="hex">#A8D8F0</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--coral)"></div>
          <div class="meta">
            <div class="name">Coral</div>
            <div class="hex">#F8635F · accent</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="background: var(--cream); border-bottom: 3px solid var(--text-dark)"
          ></div>
          <div class="meta">
            <div class="name">Cream</div>
            <div class="hex">#F5F0E6 · canvas</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--text-dark)"></div>
          <div class="meta">
            <div class="name">Charcoal</div>
            <div class="hex">#2D2D2D · outline</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--text-muted)"></div>
          <div class="meta">
            <div class="name">Muted</div>
            <div class="hex">#6B6B6B</div>
          </div>
        </div>
        <div class="sw">
          <div
            class="chip"
            style="background: var(--white); border-bottom: 3px solid var(--text-dark)"
          ></div>
          <div class="meta">
            <div class="name">White</div>
            <div class="hex">#FFFFFF · cards</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="sec-head">
        <span class="badge" style="background: var(--soft-pink)">Type</span>
        <h2>Fredoka × Quicksand</h2>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display</div>
          <div class="m">Fredoka One · chunky · on-color gets text-shadow</div>
        </div>
        <div class="spec">
          <span
            class="disp"
            style="
              font-size: 72px;
              color: var(--turquoise);
              -webkit-text-stroke: 2px var(--text-dark);
            "
            >Hooray!</span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">headline</div>
          <div class="m">Fredoka One · primary slide voice</div>
        </div>
        <div class="spec"><span class="disp" style="font-size: 54px">Let's Play</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Quicksand 500 · line 1.6 · never uppercase</div>
        </div>
        <div class="spec">
          <p
            style="
              font-family: var(--body);
              font-weight: 500;
              font-size: 17px;
              line-height: 1.6;
              max-width: 640px;
            "
          >
            Quicksand carries every paragraph and label — friendly, rounded, and calm against the
            chunky display.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">badge</div>
          <div class="m">Fredoka One · pill chip · butter fill</div>
        </div>
        <div class="spec"><span class="chip-b" style="background: var(--butter)">New!</span></div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="sec-head">
        <span class="badge" style="background: var(--mint)">Components</span>
        <h2>Outlined & Shadowed</h2>
      </div>

      <div class="cgrid">
        <div class="s4">
          <div class="cnote">Info card + circle icon</div>
          <div class="card">
            <div class="ic" style="background: var(--turquoise)">A</div>
            <div class="t">Bordered Card</div>
            <div class="x">3px charcoal outline + 6px hard offset shadow, generous radius.</div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Framed header — cap + body</div>
          <div class="framed">
            <div class="cap" style="background: var(--lavender)">A Tonal Cap</div>
            <div class="bd">
              A colored header strip sits flush above a white body — one unit, one shadow.
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Dot bullets + chips</div>
          <ul class="bullets">
            <li>Outlined disc bullets, never glyphs</li>
            <li>Butter fill by default</li>
            <li>4px from the line top</li>
          </ul>
          <div class="chips" style="margin-top: 18px">
            <span class="chip-b" style="background: var(--mint)">Mint</span
            ><span class="chip-b" style="background: var(--peach)">Peach</span
            ><span class="chip-b" style="background: var(--sky)">Sky</span>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s8">
          <div class="cnote">Process steps — circles rotate the pastel set</div>
          <div class="steps">
            <div class="step">
              <div class="c" style="background: var(--coral)">1</div>
              <div class="t">Sprout</div>
              <div class="x">Plant the idea.</div>
            </div>
            <div class="step">
              <div class="c" style="background: var(--mint)">2</div>
              <div class="t">Grow</div>
              <div class="x">Water it daily.</div>
            </div>
            <div class="step">
              <div class="c" style="background: var(--sky)">3</div>
              <div class="t">Bloom</div>
              <div class="x">Watch it open.</div>
            </div>
            <div class="step">
              <div class="c" style="background: var(--lavender)">4</div>
              <div class="t">Share</div>
              <div class="x">Pass it on.</div>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Quote mark + avatar</div>
          <div style="display: flex; align-items: center; gap: 20px">
            <span
              class="disp"
              style="
                font-size: 60px;
                color: var(--soft-pink);
                -webkit-text-stroke: 2px var(--text-dark);
                line-height: 0.6;
              "
              >&ldquo;</span
            >
            <span
              style="
                width: 80px;
                height: 80px;
                border-radius: 50%;
                border: 3px solid var(--text-dark);
                background: var(--butter);
                box-shadow: var(--shadow-sm);
                display: grid;
                place-items: center;
                font-family: var(--disp);
                font-size: 1.8rem;
              "
              >:)</span
            >
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section>
      <div class="sec-head">
        <span class="badge" style="background: var(--peach)">Frames</span>
        <h2>Frame Compositions</h2>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel">
            <b>Cover</b><span>· identity · turquoise · ornament wreath</span>
          </div>
          <div class="frame df1">
            <div class="daisy" style="width: 11cqw; height: 11cqw; top: -2.5cqw; left: -2cqw">
              <div class="p" style="transform: rotate(0)"></div>
              <div class="p" style="transform: rotate(60deg)"></div>
              <div class="p" style="transform: rotate(120deg)"></div>
              <div class="p" style="transform: rotate(180deg)"></div>
              <div class="p" style="transform: rotate(240deg)"></div>
              <div class="p" style="transform: rotate(300deg)"></div>
              <div class="c"></div>
            </div>
            <div
              class="star"
              style="
                width: 5cqw;
                height: 5cqw;
                background: var(--soft-pink);
                top: 16%;
                right: 9%;
                transform: rotate(12deg);
              "
            ></div>
            <div
              class="star"
              style="
                width: 4cqw;
                height: 4cqw;
                background: var(--butter);
                bottom: 16%;
                left: 9%;
                transform: rotate(-8deg);
              "
            ></div>
            <div class="body">
              <div class="badge">Frame System</div>
              <h3>Hello,<br />sunshine!</h3>
              <div class="sub">A cheerful frame system in a sunny garden palette.</div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Info Cards</b><span>· catalog · cream · 3-up · the dense frame</span>
          </div>
          <div class="frame df2">
            <div
              class="star"
              style="
                width: 4cqw;
                height: 4cqw;
                background: var(--mint);
                top: 5%;
                right: 6%;
                transform: rotate(10deg);
              "
            ></div>
            <div class="body">
              <h3>Three Little Things</h3>
              <div class="row">
                <div class="c">
                  <div class="ic" style="background: var(--turquoise)">A</div>
                  <div class="t">Outline</div>
                  <div class="x">3px charcoal on all.</div>
                </div>
                <div class="c">
                  <div class="ic" style="background: var(--coral)">B</div>
                  <div class="t">Shadow</div>
                  <div class="x">Hard offset, no blur.</div>
                </div>
                <div class="c">
                  <div class="ic" style="background: var(--lavender)">C</div>
                  <div class="t">Round</div>
                  <div class="x">Generous radius.</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Process Steps</b><span>· sequence · peach · rotating dots</span>
          </div>
          <div class="frame df3">
            <div class="body">
              <h3>How It Grows</h3>
              <div class="row">
                <div class="st">
                  <div class="c" style="background: var(--coral)">1</div>
                  <div class="t">Sprout</div>
                </div>
                <div class="ar">&rarr;</div>
                <div class="st">
                  <div class="c" style="background: var(--mint)">2</div>
                  <div class="t">Grow</div>
                </div>
                <div class="ar">&rarr;</div>
                <div class="st">
                  <div class="c" style="background: var(--sky)">3</div>
                  <div class="t">Bloom</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Quote</b><span>· quote · soft-pink · quote-mark anchor</span></div>
          <div class="frame df4">
            <div
              class="star"
              style="
                width: 4cqw;
                height: 4cqw;
                background: var(--butter);
                top: 10%;
                left: 8%;
                transform: rotate(-10deg);
              "
            ></div>
            <div class="body">
              <div class="qbox">
                <div class="qm">&ldquo;</div>
                <div class="q">Every day is a daisy day.</div>
                <div class="at">— The Garden Club</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="sec-head">
        <span class="badge" style="background: var(--sky)">Rules</span>
        <h2>Frame Rules</h2>
      </div>
      <div class="dos">
        <div class="do-card do">
          <span class="badge" style="background: var(--mint)">Do</span>
          <ul>
            <li>Fredoka headlines, Quicksand body — strict by role.</li>
            <li>Outline every shape 3px charcoal + hard offset shadow (6/4px, no blur).</li>
            <li>Headlines on saturated surfaces get a 3px charcoal text-shadow + white text.</li>
            <li>Bullets are outlined butter discs; cluster 3–7 ornaments per frame.</li>
            <li>One content container per frame; rotate marker colors through the pastels.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <span class="badge" style="background: var(--coral)">Don't</span>
          <ul>
            <li>No square corners; no blurred or rgba shadows.</li>
            <li>No colored borders (charcoal only); no coral surface.</li>
            <li>No third font; no Quicksand headline or Fredoka body.</li>
            <li>No glyph bullets; no empty corners (ornaments fill them).</li>
            <li>Don't blow a headline edge-to-edge — fit to measure.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--text-dark);
        --cap-canvas: var(--cream);
        --cap-accent: var(--coral);
        --cap-accent-2: var(--butter);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.6cqw 3.4cqw 1.9cqw;
        background: var(--cap-canvas);
        border: 0.3cqw solid var(--cap-ink);
        border-radius: 2.2cqw;
        box-shadow: 0.6cqw 0.6cqw 0 var(--cap-ink);
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.18em 0.32em;
        font-family: var(--disp);
        font-weight: 600;
        font-size: 3.2cqw;
        line-height: 1.22;
        letter-spacing: 0.02em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0.02em 0.16em;
        border-radius: 1.1cqw;
        color: color-mix(in srgb, var(--cap-ink) 52%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        border-radius: 1.1cqw;
        box-shadow:
          0 0 0 0.3cqw var(--cap-ink),
          0.3cqw 0.5cqw 0 0 var(--cap-ink);
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 0.2cqw dashed rgba(45, 45, 45, 0.4);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 3cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--body);
        font-weight: 600;
        font-size: 1cqw;
        letter-spacing: 0.04em;
        color: var(--text-muted);
        z-index: 4;
      }
    </style>
    <section>
      <div class="sec-head">
        <span class="badge" style="background: var(--coral)">Captions</span>
        <h2>Lower-Third Karaoke</h2>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo df1">
            <div class="daisy" style="width: 9cqw; height: 9cqw; top: -2cqw; left: -1.6cqw">
              <div class="p" style="transform: rotate(0)"></div>
              <div class="p" style="transform: rotate(60deg)"></div>
              <div class="p" style="transform: rotate(120deg)"></div>
              <div class="p" style="transform: rotate(180deg)"></div>
              <div class="p" style="transform: rotate(240deg)"></div>
              <div class="p" style="transform: rotate(300deg)"></div>
              <div class="c"></div>
            </div>
            <div
              class="star"
              style="
                width: 4cqw;
                height: 4cqw;
                background: var(--soft-pink);
                top: 14%;
                right: 8%;
                transform: rotate(12deg);
              "
            ></div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Settled line</b><span>· all spoken · candy block cleared</span>
          </div>
          <div class="frame capdemo df1">
            <div
              class="star"
              style="
                width: 4cqw;
                height: 4cqw;
                background: var(--butter);
                bottom: 18%;
                left: 8%;
                transform: rotate(-8deg);
              "
            ></div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Daisy Days</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#7ECDC0" />
        <g stroke="#2D2D2D" stroke-width="2.4">
          <circle cx="30" cy="34" r="7" fill="#fff" />
          <circle cx="44" cy="34" r="7" fill="#fff" />
          <circle cx="37" cy="27" r="7" fill="#fff" />
          <circle cx="37" cy="41" r="7" fill="#fff" />
          <circle cx="37" cy="34" r="6" fill="#FDE68A" />
        </g>
        <rect
          x="20"
          y="60"
          width="56"
          height="9"
          rx="4"
          fill="#fff"
          stroke="#2D2D2D"
          stroke-width="2"
        />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/daisy-days/FRAME.md›
---
version: alpha
name: Daisy Days — Frame (video / frame layer)
description: >
  Video-first companion to Daisy Days' design.md. The unit is the frame (1920×1080). Atoms are
  identical and sacred — the sunny-garden pastel palette (cream + turquoise/pink/butter/mint/
  lavender/peach/sky + coral accent), charcoal 3px outlines, hard offset shadows (6/4px, no blur),
  the Fredoka + Quicksand pairing, generous radii, headline text-shadow on color, dot bullets, and
  the hand-drawn SVG ornament layer. Composition + frame scale rewritten. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  cream: "#F5F0E6"
  turquoise: "#7ECDC0"
  soft-pink: "#F7C8D4"
  butter: "#FDE68A"
  mint: "#A8E6CF"
  lavender: "#D4A5E8"
  peach: "#FFCBA4"
  sky: "#A8D8F0"
  coral: "#F8635F"
  text-dark: "#2D2D2D"
  text-muted: "#6B6B6B"
  white: "#FFFFFF"

borders: { primary: "3px solid text-dark", thin: "2px solid text-dark" }
shadows: { default: "6px 6px 0 text-dark", small: "4px 4px 0 text-dark", text-headline: "3px 3px 0 text-dark", text-headline-soft: "3px 3px 0 rgba(0,0,0,0.2)" }

typography:
  # — reading ramp (Quicksand) —
  body:    { fontFamily: "Quicksand", cqw: 0.95, weight: 500, lineHeight: 1.6 }
  body-strong:{ fontFamily: "Quicksand", cqw: 0.95, weight: 600, lineHeight: 1.5 }
  meta:    { fontFamily: "Quicksand", cqw: 0.78, weight: 600, lineHeight: 1.45 }
  # — display ramp (Fredoka One / Fredoka 600 — single weight, never italic) —
  badge:   { fontFamily: "Fredoka One", cqw: 0.9, tracking: "0.02em" }
  label-display:{ fontFamily: "Fredoka One", cqw: 1.3, lineHeight: 1.3, tracking: "0.02em" }
  subtitle:{ fontFamily: "Fredoka One", cqw: 1.8, lineHeight: 1.2, tracking: "0.02em" }
  quote:   { fontFamily: "Fredoka One", cqw: 2.6, lineHeight: 1.35 }
  title:   { fontFamily: "Fredoka One", cqw: 3.0, lineHeight: 1.15, tracking: "0.02em" }
  headline:{ fontFamily: "Fredoka One", cqw: 4.5, lineHeight: 1.1, tracking: "0.02em" }
  display: { fontFamily: "Fredoka One", cqw: 6.5, lineHeight: 1.1, tracking: "0.02em" }

spacing:
  pad-slide: "3cqw"
  radius: "20px"
  radius-lg: "28px"
  radius-pill: "50px"
  radius-round: "50%"

components:
  card:
    backgroundColor: "{colors.white}"
    border: "3px solid {colors.text-dark}"
    rounded: "{spacing.radius} (28px featured)"
    shadow: "6px 6px 0 {colors.text-dark}"
    description: "The universal container; white-on-pastel is standard."
  framed-header:
    backgroundColor: "pastel cap + {colors.white} body"
    border: "3px solid {colors.text-dark} (one continuous)"
    rounded: "{spacing.radius-lg}"
    shadow: "6px 6px 0 {colors.text-dark}"
    description: "Pastel header strip flush above a white body — one unit, one shadow."
  badge-pill:
    backgroundColor: "{colors.butter}"
    border: "3px solid {colors.text-dark}"
    rounded: "{spacing.radius-pill}"
    typography: "{typography.badge}"
    shadow: "4px 4px 0 {colors.text-dark}"
    description: "Section tag. white-space:nowrap."
  circle-marker:
    backgroundColor: "any pastel"
    textColor: "{colors.white} (dark on butter)"
    border: "3px solid {colors.text-dark}"
    rounded: "50%"
    size: "bullet 20 / icon 44 / dot 48 / step 90"
    typography: "Fredoka numeral"
    description: "Steps carry a small shadow."
  bullet-dot:
    backgroundColor: "{colors.butter}"
    border: "2px solid {colors.text-dark}"
    rounded: "50%"
    size: "20px (::before, 4px from line top)"
    description: "Lists never use glyph bullets."
  ornament:
    type: "hand-drawn SVG (daisy, star, sun, cloud, rainbow)"
    stroke: "2.1px {colors.text-dark}"
    placement: "z-index:1 behind content (z-index:2), cropping past the frame edge"
    description: "3–7 per frame, clustered at corners/edges."
  quote-mark:
    typography: "oversized Fredoka quote glyph"
    color: "{colors.soft-pink} (charcoal stroke)"
    description: "Above a quote body."
  text-headline-shadow:
    shadow: "3px 3px 0 {colors.text-dark} (soft 20% variant on pink/mint)"
    appliesTo: "Fredoka headlines on saturated surfaces (cream headlines sit flat)"
    description: "Makes the headline read 'outlined' like the shapes."
---

# Daisy Days — Frame (video / frame layer)

## Overview

Daisy Days at frame scale is a **cheerful, childlike system** — picture-book illustration meets
sticker-sheet kawaii. Every shape carries a **3px charcoal outline**, every elevated element a
**solid hard offset shadow** (no blur), every surface a sunny-garden pastel. The voice is one
pairing: **Fredoka One** (chunky rounded, single weight) for every headline, **Quicksand** for
every body and meta line. The signature is the **hand-drawn SVG ornament layer** — daisies, stars,
suns, clouds, rainbows clustering at corners and cropping past the edge.

The palette is **multi-pastel with one warm pop**: cream canvas, seven pastel surfaces, and
`{colors.coral}` reserved for small high-attention markers only. Headlines on a saturated surface
get a 3px charcoal text-shadow (so they read "outlined" like the shapes) and switch to white;
headlines on cream sit flat in charcoal. Depth is 2D and graphic — thick outline + hard offset =
sticker-on-paper.

**Key characteristics at frame scale:**

- **Cream default + rotating pastel surfaces**; `{colors.coral}` is a marker accent, never a surface.
- **Fredoka One** headlines + **Quicksand 500/600** body — strict by role, never crossed.
- **3px charcoal outline + hard offset shadow** (6/4px, zero blur) on every elevated shape.
- **Generous radii** — 20px cards, 28px featured, pill badges, full-circle markers; no square corners.
- **Headline text-shadow on saturated surfaces** (white text); flat charcoal on cream.
- **Dot bullets** (outlined butter discs, never glyphs) + a **3–7 ornament wreath** per frame.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one Fredoka headline or content card dominates at 3–6× its neighbor.
- **Silence** — **one content container per frame** surrounded by an ornament wreath; the **info-card grid is the one dense exception**. Empty corners read as broken.
- **Restraint** — cream or **one** pastel surface; coral is a small-marker accent (never a surface); charcoal borders + hard offset shadows only; no ninth color.
- **Reference** — aim at a **children's picture-book / sticker-sheet kawaii zine**; failure looks like a **flat, square-cornered, blurred-shadow corporate slide**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-slide` ~3cqw; ornaments deliberately bleed past the edge.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. Borders stay 3px/2px; radii stay
20/28/50px; shadow offsets scale in `cqw` so the sticker offset holds proportionally.

## Colors

Tokens identical to the source. Default ground `{colors.cream}`; rotate saturated pastels
(turquoise / soft-pink / butter / mint / lavender / peach / sky) for tonal mood. **Cards are white**
on any surface. **Borders + shadows are always `{colors.text-dark}` charcoal** — never colored,
never blurred, never `rgba` (save the soft text-shadow variant). `{colors.coral}` is the lone
high-saturation accent — small markers (step circles, dots, headers) **only**, never a surface.
Body is charcoal/muted; pastels carry **no semantic meaning**. No ninth color.

## Typography

Two ramps. The **reading ramp** (Quicksand 500 body 0.95cqw, 600 emphasis, meta) carries copy; the
**display ramp** (Fredoka One `label-display` 1.3cqw → `display` 6.5cqw, single weight) carries
every headline, title, quote, and marker numeral.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; meta is chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`; 4–6 → `headline`; 7+ → `title`.
- **Fredoka One for all display, Quicksand for all body — never crossed.** Fredoka is single-weight (no italic, no underline, no alt weights); Quicksand stays 500/600/700. Fredoka tracking 0.02em; Quicksand body never uppercase.

## Depth & Surface

2D graphic depth — hard offset shadow, solid charcoal, zero blur, bottom-right:

- **`shadows.default` 6px** cards, frames, badges, chart containers.
- **`shadows.small` 4px** small cards, step circles, avatars.
- **Text-headline shadow** — 3px charcoal on Fredoka headlines over saturated surfaces (20% soft variant on pink/mint); cream headlines flat.
- **Outline + offset** together are the sticker-on-paper signature.

**Ceiling:** no blurred shadow, no `rgba` (except the soft text-shadow), no gradient, no glow; an element either casts a hard charcoal offset or none.

## Shapes

- **20px** cards, **28px** featured, **50px** pill (badges, counter), **50%** all circles, **4px** legend swatch. Zero square corners — every region is rounded.

## Components

- **card / framed-header** — the white-fill bordered containers (one shadow). **badge-pill** — butter section tag.
- **circle-marker** family (bullet 20 / icon 44 / dot 48 / step 90) + **bullet-dot** — outlined pastel discs, Fredoka numerals.
- **ornament** — the hand-drawn SVG sticker layer (3–7 per frame). **quote-mark** — soft-pink Fredoka anchor. **text-headline-shadow** — the on-color headline treatment.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> One content container per frame + a 3–7 ornament wreath; empty corners read as broken.

### 1 · Cover (identity · move: ornament wreath · saturated · centered)

**Ground** a saturated pastel (e.g. `{colors.turquoise}`). **Composes** badge-pill, display, body sub, 3–7 ornaments, counter. **Focal** a 1–2 line Fredoka `display` in **white with the 3px charcoal text-shadow**, centered, under a butter badge-pill. **Chrome** counter pill. **Accent** the ornament wreath (daisies + stars at corners, cropping past edges). **Silence** content centered; ornaments fill the edges. **Fixed** text-shadow on color, 3px outlines, charcoal shadows. **Free** surface color, ornament mix/positions, copy. **Density** full-but-not-crowded.

### 2 · Info Cards (catalog · move: 3-up white cards · cream · the dense frame)

**Ground** `{colors.cream}`, `pad-slide`. **Composes** headline (flat charcoal), 3× card (circle-icon + Fredoka title + Quicksand body), a couple ornaments. **Focal** three white 3px-bordered cards with 6px shadows. **Chrome** flat headline. **Accent** the pastel circle-icons (rotate turquoise/coral/lavender). **Silence** tight — the density exception (fewer ornaments here). **Fixed** white cards, 3px + 6px, 20px radius. **Free** card content, icon hues. **Density** dense-exception.

### 3 · Process Steps (sequence · move: rotating circle markers · peach · centered)

**Ground** `{colors.peach}` (or another pastel). **Composes** headline (white + text-shadow), 3–4 step circles + `→` arrows, ornaments. **Focal** a row of 90px outlined step circles, fills **rotating coral → mint → sky → lavender**, Fredoka white numerals, linked by Fredoka arrows. **Chrome** white headline with text-shadow. **Accent** the rotating circle fills. **Silence** moderate. **Fixed** 3px circles + small shadow, rotating fills, arrow glyphs. **Free** step count, labels. **Density** standard.

### 4 · Quote (quote · move: quote-mark anchor · soft-pink · centered)

**Ground** `{colors.soft-pink}`. **Composes** a white quote card (28px radius, 6px shadow), quote-mark, Fredoka quote, Quicksand attribution, ornaments. **Focal** a Fredoka `quote` in charcoal inside the white card, under an oversized soft-pink quote-mark. **Chrome** Quicksand 700 attribution. **Accent** the quote-mark + a star or two. **Silence** card centered, ornaments at corners. **Fixed** quote-mark anchor, white card. **Free** quote, attribution. **Density** moderate.

### 5 · Framed Section (feature · move: cap+body card · cream)

**Ground** `{colors.cream}`. **Composes** framed-header (pastel cap + white body), bullet-dot list, ornaments. **Focal** a framed-header — a pastel cap strip (Fredoka title, optional text-shadow) above a white body with a butter-dot bullet list. **Accent** the cap color + bullet dots. **Silence** moderate. **Fixed** one continuous 3px border + one shadow, butter dot bullets. **Free** cap color, list. **Density** standard.

### 6 · Closing (closer · move: ornament wreath · saturated · centered)

**Ground** a saturated pastel (e.g. `{colors.lavender}`). **Composes** badge-pill, display (white + text-shadow), 3–7 ornaments. **Focal** a Fredoka `display` sign-off in white with the charcoal text-shadow, centered. **Accent** the ornament wreath. **Silence** content centered. **Fixed** text-shadow on color, ornaments fill corners. **Free** sign-off, surface, ornaments. **Density** full.

## Composition Rules

### Do

- Pair **Fredoka One headlines + Quicksand body** strictly by role.
- Outline every shape **3px charcoal + a hard offset shadow** (6/4px, no blur); white card fills on any surface.
- Give Fredoka headlines on **saturated surfaces a 3px charcoal text-shadow + white text**; flat charcoal on cream.
- Use **outlined butter-disc bullets** (never glyphs); cluster **3–7 hand-drawn ornaments** per frame (corners, cropping past edges).
- Keep **one content container per frame**; rotate marker colors (coral → mint → sky → lavender → butter); reserve coral for small markers.
- Center most frames; lean cream for content-dense frames, saturated for cover/closer/quote.

### Don't

- No square corners; no blurred or `rgba` shadows (save the soft text-shadow).
- No colored borders (charcoal only); no coral surface; no ninth color.
- No third font; no Quicksand headline or Fredoka body; no italic/underline Fredoka; no uppercase Quicksand body.
- No glyph bullets; no empty corners (ornaments fill them); no two competing content panels.
- Don't blow a headline edge-to-edge — fit to measure.

## Aspect-Ratio Behavior

| Treatment      | 16:9                       | 9:16                             | 1:1        |
| -------------- | -------------------------- | -------------------------------- | ---------- |
| Cover          | centered, corner ornaments | centered, taller, more ornaments | centered   |
| Info Cards     | 3 across                   | stacked                          | 2+1        |
| Process Steps  | horizontal + arrows        | vertical, arrows rotate down     | 2×2        |
| Quote          | centered card              | centered, taller                 | centered   |
| Framed Section | cap + body                 | cap + body taller                | cap + body |
| Closing        | centered, wreath           | centered, wreath                 | centered   |

`pad-slide` holds on the short edge; re-step display above the 1.4cqw floor. On tighter ratios keep
the ornament count toward the upper end (5–7) so corners never read empty.

## Approved Real Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Ornaments and pastels are content-agnostic; counters/badges carry per-deck text.

## Numerals & Claims (hard rule)

Never invent figures, dates, or counts at frame scale. Render slots as `— figure —`, `{metric}`,
`N`. Step numbers and any chart values carry placeholders until the script supplies them; the slide
counter is decorative chrome.

## Pre-Render Self-Audit

- **Squint** — one Fredoka headline or content card dominates per frame.
- **Silence** — one container per frame surrounded by an ornament wreath; only the info-card grid runs dense.
- **Color** — cream or one pastel surface; coral markers only; charcoal borders/shadows; no ninth hue.
- **Type** — Fredoka headlines (text-shadow + white on saturated, flat charcoal on cream), Quicksand body; ≥1.4cqw floor.
- **Depth** — 3px outline + hard offset (no blur, no rgba save soft text-shadow); rounded corners only.
- **Ornaments** — 3–7 per frame, cropping past edges; no empty corners. **Bullets** — outlined discs, never glyphs.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the source uses scroll-snap nav, no transition spec.
- **Fonts:** the source names _Fredoka One_; Google now serves the **Fredoka** variable family — request `Fredoka:wght@500;600;700` and set display weight 600 (visually equal to Fredoka One), with `Fredoka One` kept first in the stack for environments that still serve it. Quicksand loads normally. CJK: ZCOOL XiaoWei (display) / Yozai (body).
- **9:16 / 1:1 are guidance**; keep ornament count high so corners stay filled per ratio.
- Ornaments (daisy/star/sun/cloud/rainbow), markers, and framed headers are CSS/SVG-only; recoloring SVG ornaments requires editing their stroke values.
- **Contrast:** keep `{colors.text-muted}` off pastel surfaces (cream/white cards only); small text on saturated grounds should be charcoal or white.
assets/frame-presets/editorial-forest/caption-skin.html›
<!--
  Editorial Forest — caption skin (preset-local source).

  This is the preset's own lower-third karaoke caption look: a caption skin to read or
  copy, not to mount as is. A copy used as compositions/captions.html fills its three
  reserved holes and wraps the fragment in a <template>:
    · the empty GROUPS array            → the engine's scene-aware word groups
    · the 0 duration + 0 width/height   → real total + canvas size
    · the empty data-brand-tokens style → :root tokens derived from the project's frame.md

  Token contract — reference ONLY this fixed vocab (set it from the
  project's frame.md; each var has an editorial-forest
  literal fallback for standalone preview):
    --cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
    --font-body · --cap-band-top · --cap-band-height
  Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
  root data-composition-id="captions", timeline at window.__timelines["captions"].
  State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
  never tl.call() callbacks (seek does not fire them → wrong state on render).

  Visual: an oat-cream card on a single 2px green hairline border — the system's only
  separator — with a 6px card radius and NO shadow (editorial-forest is flat, paper-based;
  depth is color-block + hairline, never cast). Source Serif 4 weight 500, sentence case,
  warm-ink glyphs. Upcoming words sit in faint ink; the current word gets a quiet
  dusty-rose panel (a soft 6px-radius block, no blur — editorial restraint, not a loud
  block) with ink reading on top; spoken words settle to solid ink, panel cleared. The
  per-word emphasis accent is a 2px green hairline underline, never heavy.
-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>

<!-- the project's :root tokens go here -->
<style data-brand-tokens></style>

<style>
  #captions-root {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .caption-layer {
    position: absolute;
    inset: 0;
    z-index: 20;
    pointer-events: none;
  }
  /* the keep-out band, from captionBand(H) — frame content stays above it */
  .caption-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--cap-band-top, 900px);
    height: var(--cap-band-height, 180px);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .caption-group {
    /* every group is absolutely stacked over the SAME band — exactly one shown at a
       time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
  }

  /* THE oat-cream card — single 2px green hairline border (the system's only
     separator), 6px card radius, NO shadow (flat, paper-based) */
  .caption-pill {
    max-width: 78%;
    padding: 20px 44px 24px;
    background: var(--cap-canvas, #efe7d4);
    border: 2px solid var(--cap-accent, #d27e96);
    border-radius: 6px;
  }
  .caption-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.1em 0.34em;
    font-family: var(--font-display, "Source Serif 4"), Georgia, serif;
    font-weight: 500;
    font-size: clamp(40px, 4.4vw, 58px);
    line-height: 1.16;
    letter-spacing: -0.02em;
  }

  /* upcoming — faint warm ink, no panel */
  .caption-word {
    display: inline-block;
    padding: 0 0.06em;
    color: color-mix(in srgb, var(--cap-ink, #1a1a17) 42%, var(--cap-canvas, #efe7d4));
  }
  /* current word — a quiet dusty-rose panel (box-shadow spread = a soft 6px block w/o
     layout shift; no blur — editorial restraint, not a loud block), warm ink on top */
  .caption-word.is-active {
    color: var(--cap-ink, #1a1a17);
    background: var(--cap-accent, #d27e96);
    box-shadow: 0 0 0 0.05em var(--cap-accent, #d27e96);
    border-radius: 6px;
  }
  /* already spoken — solid warm ink, panel cleared */
  .caption-word.is-spoken {
    color: var(--cap-ink, #1a1a17);
    background: transparent;
    box-shadow: none;
  }

  /* optional per-word accents (dormant until a word-classer tags them) */
  /* brand / key term — heavier serif weight, editorial-forest's only display inflection */
  .caption-word.cap-brand {
    font-weight: 600;
  }
  /* numerals / data — the mono chrome voice, slightly recessed */
  .caption-word.cap-num {
    font-family: var(--font-body, "JetBrains Mono"), ui-monospace, monospace;
    color: color-mix(in srgb, var(--cap-ink, #1a1a17) 72%, var(--cap-canvas, #efe7d4));
  }
  /* emphasis — a 2px green hairline underline (the system's only separator, never heavy) */
  .caption-word.cap-cta {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--cap-accent-2, #e89cb1);
    text-underline-offset: 0.1em;
  }

  @media (max-aspect-ratio: 9/16) {
    .caption-pill {
      max-width: 92%;
    }
    .caption-line {
      font-size: clamp(34px, 5.4vw, 48px);
    }
  }
</style>

<div
  id="captions-root"
  data-composition-id="captions"
  data-timeline-locked
  data-start="0"
  data-duration="0"
  data-fps="30"
  data-width="0"
  data-height="0"
>
  <div class="caption-layer" aria-hidden="true">
    <div id="caption-stage" class="caption-stage"></div>
  </div>
</div>

<script>
  // the scene-aware word groups + real duration go here.
  var GROUPS = [];
  var DURATION = 0;

  (function () {
    var stage = document.getElementById("caption-stage");

    // build DOM: one .caption-group per group, .caption-word per word
    GROUPS.forEach(function (group, g) {
      var groupEl = document.createElement("div");
      groupEl.className = "caption-group";
      groupEl.id = "caption-group-" + g;
      var pill = document.createElement("div");
      pill.className = "caption-pill";
      var line = document.createElement("div");
      line.className = "caption-line";
      (group.words || []).forEach(function (w, i) {
        var span = document.createElement("span");
        span.className = "caption-word";
        span.id = "caption-word-" + g + "-" + i;
        span.textContent = String(w.text);
        line.appendChild(span);
      });
      pill.appendChild(line);
      groupEl.appendChild(pill);
      stage.appendChild(groupEl);
    });

    window.__timelines = window.__timelines || {};
    var tl = gsap.timeline({ paused: true });

    GROUPS.forEach(function (group, g) {
      var groupEl = document.getElementById("caption-group-" + g);
      var words = group.words || [];
      var next = GROUPS[g + 1];
      var isLast = g === GROUPS.length - 1;
      var start = Math.max(0, Number(group.start));
      var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
      if (end <= start) end = start + 0.01;

      // group on/off — exactly one group visible at a time
      tl.set(groupEl, { opacity: 1 }, start);
      tl.set(groupEl, { opacity: 0 }, end);

      // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
      // engine's frame-by-frame seek; .call callbacks would NOT fire).
      words.forEach(function (w, i) {
        var el = document.getElementById("caption-word-" + g + "-" + i);
        var at = Math.max(start, Number(w.start));
        tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
        tl.set(el, { className: "caption-word is-active" }, at); // current
        tl.fromTo(el, { scale: 0.985 }, { scale: 1, duration: 0.18, ease: "power1.out" }, at);
        if (i + 1 < words.length) {
          var nextAt = Math.max(start, Number(words[i + 1].start));
          tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
        }
      });
      if (words.length) {
        var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
        var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
        tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
      }
    });

    // full-span anchor so the sub-comp timeline spans the whole video
    tl.to({}, { duration: DURATION }, 0);
    window.__timelines["captions"] = tl;
  })();
</script>
assets/frame-presets/editorial-forest/frame-showcase.html›
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Editorial Forest</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,[email protected],400;8..60,500;8..60,600&family=JetBrains+Mono:wght@400;500&display=swap"
      rel="stylesheet"
    />
    <style id="ds-tokens">
      :root {
        --green: #2e4a2a;
        --green-deep: #243a21;
        --green-lite: #3a5a36;
        --pink: #e89cb1;
        --pink-deep: #d27e96;
        --cream: #efe7d4;
        --cream-2: #e6dcc4;
        --ink: #1a1a17;
        --serif: "Source Serif 4", "Source Serif Pro", Georgia, serif;
        --mono: "JetBrains Mono", ui-monospace, monospace;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        background: #14140f;
      }
      body {
        background: var(--cream);
        color: var(--ink);
        font-family: var(--serif);
        -webkit-font-smoothing: antialiased;
      }
      .serif {
        font-family: var(--serif);
        font-weight: 500;
        letter-spacing: -0.02em;
        line-height: 0.96;
      }
      .mono {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
      }

      section {
        padding: 110px 120px;
        position: relative;
      }
      .topbar {
        display: flex;
        justify-content: space-between;
        align-items: center;
        border-bottom: 2px solid var(--green);
        padding-bottom: 18px;
        margin-bottom: 56px;
      }
      .topbar .lbl {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.18em;
        font-size: 15px;
        color: var(--green);
      }
      .topbar h2 {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 48px;
        letter-spacing: -0.02em;
        line-height: 1;
      }

      /* COVER */
      .cover {
        min-height: 100vh;
        background: var(--green);
        color: var(--cream);
        padding: 100px 140px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        position: relative;
      }
      .cover .top {
        position: absolute;
        top: 64px;
        left: 140px;
        right: 140px;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .cover .top .lbl {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.18em;
        font-size: 15px;
        color: var(--pink);
      }
      .cover .mono-circle {
        width: 120px;
        height: 120px;
        border-radius: 50%;
        border: 2px solid var(--pink);
        display: grid;
        place-items: center;
        font-family: var(--mono);
        font-weight: 500;
        letter-spacing: 0.1em;
        font-size: 24px;
        color: var(--pink);
      }
      .cover h1 {
        font-family: var(--serif);
        font-weight: 500;
        font-size: clamp(96px, 15vw, 210px);
        line-height: 0.92;
        letter-spacing: -0.02em;
        color: var(--pink);
      }
      .cover h1 .cr {
        color: var(--cream);
      }
      .cover .lede {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 28px;
        line-height: 1.4;
        color: var(--cream);
        max-width: 760px;
        margin-top: 36px;
      }
      .cover .footline {
        position: absolute;
        bottom: 64px;
        left: 140px;
        right: 140px;
        display: flex;
        justify-content: space-between;
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 14px;
        color: var(--pink);
      }

      /* PALETTE */
      .swatches {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 0;
        border: 2px solid var(--green);
      }
      .sw {
        border-right: 2px solid var(--green);
        border-bottom: 2px solid var(--green);
      }
      .sw:nth-child(4n) {
        border-right: 0;
      }
      .sw:nth-child(n + 5) {
        border-bottom: 0;
      }
      .sw .chip {
        height: 130px;
        border-bottom: 2px solid var(--green);
      }
      .sw .meta {
        padding: 18px 20px;
        background: var(--cream);
      }
      .sw .name {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 24px;
      }
      .sw .hex {
        font-family: var(--mono);
        font-size: 12px;
        letter-spacing: 0.04em;
        color: var(--ink);
        opacity: 0.65;
        margin-top: 6px;
      }
      .sw .role {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 14px;
        line-height: 1.4;
        color: var(--ink);
        margin-top: 10px;
      }

      /* TYPE */
      .type-row {
        display: grid;
        grid-template-columns: 240px 1fr;
        align-items: center;
        padding: 26px 0;
        border-bottom: 2px solid rgba(46, 74, 42, 0.25);
      }
      .type-row .tok {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 13px;
        color: var(--green);
      }
      .type-row .m {
        font-family: var(--mono);
        font-size: 11px;
        color: var(--ink);
        opacity: 0.6;
        margin-top: 8px;
        line-height: 1.5;
        letter-spacing: 0.02em;
      }
      .type-row .spec {
        overflow: hidden;
      }

      /* COMPONENTS */
      .cnote {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 12px;
        color: var(--green);
        margin-bottom: 22px;
      }
      .cgrid {
        display: grid;
        grid-template-columns: repeat(12, 1fr);
        gap: 28px;
        margin-bottom: 52px;
      }
      .s3 {
        grid-column: span 3;
      }
      .s4 {
        grid-column: span 4;
      }
      .s5 {
        grid-column: span 5;
      }
      .s6 {
        grid-column: span 6;
      }
      .s7 {
        grid-column: span 7;
      }
      .s8 {
        grid-column: span 8;
      }
      .tile {
        border-radius: 6px;
        padding: 34px 32px;
        min-height: 240px;
        display: flex;
        flex-direction: column;
      }
      .tile .ord {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 14px;
      }
      .tile .ti {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 40px;
        line-height: 0.98;
        letter-spacing: -0.01em;
        margin-top: auto;
      }
      .tile .ft {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 12px;
        margin-top: 14px;
      }
      .tile.green {
        background: var(--green);
        color: var(--cream);
      }
      .tile.green .ord,
      .tile.green .ft {
        color: var(--pink);
      }
      .tile.pink {
        background: var(--pink);
        color: var(--green-deep);
      }
      .tile.pink .ord,
      .tile.pink .ft {
        color: var(--green-deep);
      }
      .tile.lite {
        background: var(--green-lite);
        color: var(--pink);
      }
      .tile.lite .ti {
        color: var(--cream);
      }
      .tile.cream2 {
        background: var(--cream-2);
        border: 2px solid var(--green);
        color: var(--green);
      }
      .tile.cream2 .ord,
      .tile.cream2 .ft {
        color: var(--green);
      }
      .kpi {
        border-top: 2px solid var(--green);
        padding-top: 22px;
      }
      .kpi .tag {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 13px;
        color: var(--green);
      }
      .kpi .fig {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 128px;
        line-height: 0.92;
        letter-spacing: -0.03em;
        color: var(--ink);
      }
      .kpi .fig .u {
        font-size: 64px;
      }
      .kpi .d {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 18px;
        line-height: 1.35;
        color: var(--ink);
        margin-top: 10px;
        max-width: 280px;
      }
      .monobig {
        width: 130px;
        height: 130px;
        border-radius: 50%;
        border: 2px solid var(--pink);
        display: grid;
        place-items: center;
        font-family: var(--mono);
        font-weight: 500;
        letter-spacing: 0.1em;
        font-size: 26px;
        color: var(--green);
      }
      .bars {
        display: flex;
        align-items: flex-end;
        gap: 24px;
        height: 200px;
        border-bottom: 2px solid var(--green);
        border-left: 2px solid var(--green);
        padding: 0 8px;
      }
      .bars .bar {
        width: 56px;
        border-radius: 3px 3px 0 0;
        position: relative;
      }
      .bars .bar .v {
        position: absolute;
        top: -30px;
        left: 50%;
        transform: translateX(-50%);
        font-family: var(--mono);
        font-weight: 500;
        font-size: 14px;
        color: var(--ink);
      }

      /* COMPOSITIONS */
      .gallery {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .fw .flabel {
        display: flex;
        gap: 14px;
        align-items: baseline;
        margin-bottom: 16px;
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 12px;
        color: var(--ink);
        opacity: 0.55;
      }
      .fw .flabel b {
        color: var(--green);
        opacity: 1;
      }
      .frame {
        aspect-ratio: 16/9;
        container-type: size;
        position: relative;
        overflow: hidden;
        background: var(--cream);
        box-shadow: 0 0 0 1px rgba(46, 74, 42, 0.2);
      }
      .frame > .body {
        position: absolute;
        inset: 0;
        z-index: 3;
      }
      .frame .tb {
        position: absolute;
        left: 5cqw;
        right: 5cqw;
        top: 3cqw;
        display: flex;
        justify-content: space-between;
        align-items: center;
        z-index: 4;
      }
      .frame .tb .l {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 0.95cqw;
      }
      .frame .fl {
        position: absolute;
        left: 5cqw;
        right: 5cqw;
        bottom: 2.8cqw;
        display: flex;
        justify-content: space-between;
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.14em;
        font-size: 0.85cqw;
        z-index: 4;
      }

      /* f1 cover green */
      .ef1 {
        background: var(--green);
      }
      .ef1 .tb .l {
        color: var(--pink);
      }
      .ef1 .mc {
        width: 7cqw;
        height: 7cqw;
        border-radius: 50%;
        border: 0.13cqw solid var(--pink);
        display: grid;
        place-items: center;
        font-family: var(--mono);
        font-weight: 500;
        letter-spacing: 0.1em;
        font-size: 1.3cqw;
        color: var(--pink);
      }
      .ef1 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .ef1 h3 {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 13cqw;
        line-height: 0.9;
        letter-spacing: -0.02em;
        color: var(--pink);
      }
      .ef1 h3 .cr {
        color: var(--cream);
      }
      .ef1 .fl .l {
        color: var(--pink);
      }

      /* f2 topic tiles */
      .ef2 .body {
        padding: 7cqw 5cqw 6cqw;
        display: flex;
        flex-direction: column;
      }
      .ef2 h3 {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 5cqw;
        line-height: 0.96;
        letter-spacing: -0.02em;
        color: var(--green);
        margin-bottom: 2.4cqw;
      }
      .ef2 .row {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 1.5cqw;
        flex: 1;
      }
      .ef2 .t {
        border-radius: 0.6cqw;
        padding: 1.8cqw;
        display: flex;
        flex-direction: column;
      }
      .ef2 .t .o {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 0.85cqw;
      }
      .ef2 .t .ti {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 2.6cqw;
        line-height: 0.98;
        letter-spacing: -0.01em;
        margin-top: auto;
      }
      .ef2 .green {
        background: var(--green);
        color: var(--cream);
      }
      .ef2 .green .o {
        color: var(--pink);
      }
      .ef2 .pink {
        background: var(--pink);
        color: var(--green-deep);
      }
      .ef2 .pink .o {
        color: var(--green-deep);
      }
      .ef2 .cream2 {
        background: var(--cream-2);
        border: 0.13cqw solid var(--green);
        color: var(--green);
      }
      .ef2 .cream2 .o {
        color: var(--green);
      }

      /* f3 KPI green */
      .ef3 {
        background: var(--green);
      }
      .ef3 .tb .l {
        color: var(--pink);
      }
      .ef3 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 5cqw;
      }
      .ef3 .tag {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 1cqw;
        color: var(--pink);
        border-top: 0.12cqw solid var(--pink);
        padding-top: 1.2cqw;
        display: inline-block;
      }
      .ef3 .fig {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 18cqw;
        line-height: 0.9;
        letter-spacing: -0.03em;
        color: var(--pink);
      }
      .ef3 .fig .u {
        font-size: 9cqw;
      }
      .ef3 .d {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 1.7cqw;
        line-height: 1.35;
        color: var(--cream);
        max-width: 40cqw;
        margin-top: 1cqw;
      }
      .ef3 .fl .l {
        color: var(--pink);
      }

      /* f4 statement quote */
      .ef4 .body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 0 8cqw;
      }
      .ef4 h3 {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 6.4cqw;
        line-height: 1.02;
        letter-spacing: -0.02em;
        color: var(--green);
      }
      .ef4 .at {
        font-family: var(--serif);
        font-weight: 600;
        font-size: 1.9cqw;
        color: var(--ink);
        margin-top: 2cqw;
      }
      .ef4 .ro {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 0.95cqw;
        color: var(--green);
        margin-top: 0.5cqw;
      }

      /* PRINCIPLES */
      .dos {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 48px;
      }
      .do-card h4 {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 34px;
        color: var(--green);
        margin-bottom: 6px;
        letter-spacing: -0.02em;
      }
      .do-card .rule {
        height: 2px;
        background: var(--green);
        margin-bottom: 22px;
      }
      .do-card ul {
        list-style: none;
      }
      .do-card li {
        font-family: var(--serif);
        font-weight: 400;
        font-size: 17px;
        line-height: 1.5;
        padding-left: 24px;
        position: relative;
        margin-bottom: 13px;
      }
      .do-card li::before {
        position: absolute;
        left: 0;
        font-family: var(--mono);
      }
      .do-card.do li::before {
        content: "+";
        color: var(--green);
      }
      .do-card.dont li::before {
        content: "\00d7";
        color: var(--pink-deep);
      }

      .foot {
        padding: 60px 120px;
        border-top: 2px solid var(--green);
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .foot .l {
        font-family: var(--serif);
        font-weight: 500;
        font-size: 30px;
        color: var(--green);
      }
      .foot .r {
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.16em;
        font-size: 11px;
        color: var(--ink);
        opacity: 0.55;
      }
      @media (max-width: 1100px) {
        .gallery {
          grid-template-columns: 1fr;
        }
        .swatches {
          grid-template-columns: 1fr 1fr;
        }
      }
    </style>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,[email protected],400;8..60,500;8..60,600&display=swap"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap"
    />
    <style id="ft-override">
      :root {
        --primary: #efe7d4 !important;
        --secondary: #2e4a2a !important;
        --tertiary: #e6dcc4 !important;
        --accent: #e89cb1 !important;
        --f-disp: "Source Serif 4", sans-serif !important;
        --f-body: "JetBrains Mono", sans-serif !important;
        --green: #2e4a2a !important;
        --pink: #e89cb1 !important;
        --cream: #efe7d4 !important;
        --cream-2: #e6dcc4 !important;
        --serif: "Source Serif 4", sans-serif !important;
        --mono: "JetBrains Mono", sans-serif !important;
        --green-deep: #243a21 !important;
        --green-lite: #3a5a36 !important;
        --pink-deep: #d27e96 !important;
        --ink: #1a1a17 !important;
      }
    </style>
  </head>
  <body>
    <!-- COVER -->
    <section class="cover">
      <div class="top">
        <span class="lbl">Frame System · Vol. 01</span>
        <div class="mono-circle">EF</div>
      </div>
      <h1>Paper,<br />ink &amp; <span class="cr">leaf.</span></h1>
      <div class="lede">
        A serif-led editorial frame system — Source Serif 4 at weight 500, a forest-green /
        dusty-rose / oat-cream triad, and JetBrains Mono as the editorial chrome.
      </div>
      <div class="footline">
        <span>Source Serif 4 · JetBrains Mono</span><span>1920 × 1080</span>
      </div>
    </section>

    <!-- PALETTE -->
    <section>
      <div class="topbar">
        <span class="lbl">01 — Palette</span>
        <h2>Three Surfaces</h2>
      </div>
      <div class="swatches">
        <div class="sw">
          <div class="chip" style="background: var(--green)"></div>
          <div class="meta">
            <div class="name">Green</div>
            <div class="hex">#2e4a2a</div>
            <div class="role">Forest primary — surface, fill, rule.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--pink)"></div>
          <div class="meta">
            <div class="name">Pink</div>
            <div class="hex">#e89cb1</div>
            <div class="role">Dusty rose — the accent + on-green text.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream)"></div>
          <div class="meta">
            <div class="name">Cream</div>
            <div class="hex">#efe7d4</div>
            <div class="role">Oat paper — the default ground.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--ink)"></div>
          <div class="meta">
            <div class="name">Ink</div>
            <div class="hex">#1a1a17</div>
            <div class="role">Warm near-black body on cream.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--green-deep)"></div>
          <div class="meta">
            <div class="name">Green Deep</div>
            <div class="hex">#243a21</div>
            <div class="role">Text on pink surfaces.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--green-lite)"></div>
          <div class="meta">
            <div class="name">Green Lite</div>
            <div class="hex">#3a5a36</div>
            <div class="role">Second green for tile pairs.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--pink-deep)"></div>
          <div class="meta">
            <div class="name">Pink Deep</div>
            <div class="hex">#d27e96</div>
            <div class="role">Pink-on-pink borders.</div>
          </div>
        </div>
        <div class="sw">
          <div class="chip" style="background: var(--cream-2)"></div>
          <div class="meta">
            <div class="name">Cream 2</div>
            <div class="hex">#e6dcc4</div>
            <div class="role">Tiles on the cream ground.</div>
          </div>
        </div>
      </div>
    </section>

    <!-- TYPOGRAPHY -->
    <section>
      <div class="topbar">
        <span class="lbl">02 — Typography</span>
        <h2>Serif 500 · Mono Chrome</h2>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">display-hero</div>
          <div class="m">Source Serif 4 · 500 · 220px · −0.02em · opsz</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-size: 120px; line-height: 0.92">Quarterly</span>
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">headline-xl</div>
          <div class="m">Source Serif 4 · 500 · 96px</div>
        </div>
        <div class="spec"><span class="serif" style="font-size: 76px">A Quiet Annual</span></div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">stat-figure</div>
          <div class="m">Source Serif 4 · 500 · 220px · −0.03em</div>
        </div>
        <div class="spec">
          <span class="serif" style="font-size: 108px; color: var(--green)"
            >94<span style="font-size: 54px">%</span></span
          >
        </div>
      </div>
      <div class="type-row">
        <div>
          <div class="tok">body</div>
          <div class="m">Source Serif 4 · 400 · line 1.38</div>
        </div>
        <div class="spec">
          <p
            style="
              font-family: var(--serif);
              font-weight: 400;
              font-size: 24px;
              line-height: 1.38;
              max-width: 680px;
            "
          >
            Body runs the serif at weight 400 — the drop from 500 display to 400 body is the
            system's reading rhythm.
          </p>
        </div>
      </div>
      <div class="type-row" style="border-bottom: 0">
        <div>
          <div class="tok">label</div>
          <div class="m">JetBrains Mono · 500 · uppercase · 0.18em</div>
        </div>
        <div class="spec">
          <span class="mono" style="font-size: 16px; color: var(--green)"
            >Editorial Chrome · Section</span
          >
        </div>
      </div>
    </section>

    <!-- COMPONENTS -->
    <section>
      <div class="topbar">
        <span class="lbl">03 — Components</span>
        <h2>Flat · Paper-Based</h2>
      </div>

      <div class="cgrid">
        <div class="s8">
          <div class="cnote">Topic tiles — fills rotate green / pink / green-lite / cream-2</div>
          <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px">
            <div class="tile green">
              <span class="ord">01</span>
              <div class="ti">Paper</div>
              <span class="ft">Ground</span>
            </div>
            <div class="tile pink">
              <span class="ord">02</span>
              <div class="ti">Ink</div>
              <span class="ft">Type</span>
            </div>
            <div class="tile lite">
              <span class="ord">03</span>
              <div class="ti">Leaf</div>
              <span class="ft">Surface</span>
            </div>
            <div class="tile cream2">
              <span class="ord">04</span>
              <div class="ti">Rose</div>
              <span class="ft">Accent</span>
            </div>
          </div>
        </div>
        <div class="s4">
          <div class="cnote">Monogram circle + KPI</div>
          <div class="monobig">EF</div>
          <div class="kpi" style="margin-top: 28px">
            <div class="tag">Retention</div>
            <div class="fig">94<span class="u">%</span></div>
            <div class="d">Net revenue, trailing twelve months.</div>
          </div>
        </div>
      </div>

      <div class="cgrid" style="margin-bottom: 0">
        <div class="s6">
          <div class="cnote">Bar chart — pink / cream / green, mono ticks</div>
          <div class="bars">
            <div class="bar" style="height: 55%; background: var(--pink)">
              <span class="v">55</span>
            </div>
            <div class="bar" style="height: 72%; background: var(--cream-2)">
              <span class="v">72</span>
            </div>
            <div class="bar" style="height: 90%; background: var(--green)">
              <span class="v">90</span>
            </div>
            <div class="bar" style="height: 64%; background: var(--pink)">
              <span class="v">64</span>
            </div>
          </div>
        </div>
        <div class="s6">
          <div class="cnote">Meta definition list — 2px green top rule</div>
          <div
            style="
              border-top: 2px solid var(--green);
              display: grid;
              grid-template-columns: repeat(3, 1fr);
              gap: 36px;
              padding-top: 20px;
            "
          >
            <div>
              <div class="mono" style="font-size: 13px; color: var(--green)">Editor</div>
              <div class="serif" style="font-weight: 500; font-size: 30px; margin-top: 8px">
                A. Wren
              </div>
            </div>
            <div>
              <div class="mono" style="font-size: 13px; color: var(--green)">Issue</div>
              <div class="serif" style="font-weight: 500; font-size: 30px; margin-top: 8px">
                No. 12
              </div>
            </div>
            <div>
              <div class="mono" style="font-size: 13px; color: var(--green)">Season</div>
              <div class="serif" style="font-weight: 500; font-size: 30px; margin-top: 8px">
                Spring
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- COMPOSITIONS -->
    <section style="background: var(--cream-2)">
      <div class="topbar">
        <span class="lbl">04 — Frames</span>
        <h2>Frame Compositions</h2>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel">
            <b>Cover</b><span>· identity · green · monogram · centered-left</span>
          </div>
          <div class="frame ef1">
            <div class="tb">
              <span class="l">Frame System · Vol. 01</span>
              <div class="mc">EF</div>
            </div>
            <div class="body">
              <h3>Paper, ink<br />&amp; <span class="cr">leaf.</span></h3>
            </div>
            <div class="fl">
              <span class="l">Source Serif 4 · Mono</span><span class="l">01 / 06</span>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel">
            <b>Topic Tiles</b><span>· catalog · rotating fills · the dense frame</span>
          </div>
          <div class="frame ef2">
            <div class="tb">
              <span class="l" style="color: var(--green)">Contents</span
              ><span class="l" style="color: var(--green)">02 / 06</span>
            </div>
            <div class="body">
              <h3>Four Strands</h3>
              <div class="row">
                <div class="t green">
                  <span class="o">01</span>
                  <div class="ti">On Paper</div>
                </div>
                <div class="t pink">
                  <span class="o">02</span>
                  <div class="ti">On Ink</div>
                </div>
                <div class="t cream2">
                  <span class="o">03</span>
                  <div class="ti">On Measure</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>KPI Stat</b><span>· data · green · oversized figure</span></div>
          <div class="frame ef3">
            <div class="tb">
              <span class="l">By The Numbers</span><span class="l">03 / 06</span>
            </div>
            <div class="body">
              <span class="tag">Retention</span>
              <div class="fig">94<span class="u">%</span></div>
              <div class="d">
                Net revenue retention across the year — the figure carries the frame.
              </div>
            </div>
            <div class="fl"><span class="l">Annual Report</span><span class="l">03 / 06</span></div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Statement</b><span>· quote · cream · centered-left</span></div>
          <div class="frame ef4">
            <div class="tb">
              <span class="l" style="color: var(--green)">In Their Words</span
              ><span class="l" style="color: var(--green)">04 / 06</span>
            </div>
            <div class="body">
              <h3>Set it slowly,<br />and let it breathe.</h3>
              <div class="at">Avery Wren</div>
              <div class="ro">Editor-in-Chief</div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- PRINCIPLES -->
    <section>
      <div class="topbar">
        <span class="lbl">05 — Rules</span>
        <h2>Frame Rules</h2>
      </div>
      <div class="dos">
        <div class="do-card do">
          <h4>Do</h4>
          <div class="rule"></div>
          <ul>
            <li>
              Run every display in Source Serif 4 weight 500, negative-tracked, tight line-height.
            </li>
            <li>Set every label/caption in JetBrains Mono 500 uppercase, 0.14–0.18em.</li>
            <li>Give every frame a topbar (mono label + monogram or counter).</li>
            <li>Pick one dominant surface per frame; rotate tile fills, don't repeat one.</li>
            <li>Separate sections with 2px hairlines in the region's accent.</li>
          </ul>
        </div>
        <div class="do-card dont">
          <h4>Don't</h4>
          <div class="rule"></div>
          <ul>
            <li>No drop shadows, gradients, or glows — flat, paper-based depth only.</li>
            <li>No third typeface; no italic or underline; no serif body at 500.</li>
            <li>No fourth color family; no 1px or 4px rules (2px / 2.5px only).</li>
            <li>No two competing content blocks — one subject per frame.</li>
            <li>Don't blow a headline edge-to-edge — fit to measure.</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- CAPTIONS — preview of caption-skin.html (the preset's lower-third karaoke look) -->
    <style>
      /* mirrors caption-skin.html; the skin's --cap-* vocab mapped to this preset's tokens */
      .capdemo {
        --cap-ink: var(--ink);
        --cap-canvas: var(--cream);
        --cap-accent: var(--pink-deep);
        --cap-accent-2: var(--pink);
      }
      .capdemo .stage {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 16.67cqh;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 4;
      }
      .capdemo .pill {
        max-width: 80cqw;
        padding: 1.4cqw 3cqw 1.7cqw;
        background: var(--cap-canvas);
        border: 0.21cqw solid var(--cap-accent);
        border-radius: 0.6cqw;
      }
      .capdemo .line {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.1em 0.34em;
        font-family: var(--serif);
        font-weight: 500;
        font-size: 3.4cqw;
        line-height: 1.16;
        letter-spacing: -0.02em;
      }
      .capdemo .w {
        display: inline-block;
        padding: 0 0.06em;
        color: color-mix(in srgb, var(--cap-ink) 42%, var(--cap-canvas));
      }
      .capdemo .w.spoken {
        color: var(--cap-ink);
      }
      .capdemo .w.active {
        color: var(--cap-ink);
        background: var(--cap-accent);
        box-shadow: 0 0 0 0.05em var(--cap-accent);
        border-radius: 0.6cqw;
      }
      .capdemo .bandline {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 16.67cqh;
        border-top: 2px dashed rgba(46, 74, 42, 0.32);
        z-index: 3;
      }
      .capdemo .bandtag {
        position: absolute;
        right: 5cqw;
        bottom: calc(16.67cqh + 0.7cqw);
        font-family: var(--mono);
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        font-size: 0.85cqw;
        color: var(--green);
        opacity: 0.6;
        z-index: 4;
      }
    </style>
    <section style="background: var(--cream-2)">
      <div class="topbar">
        <span class="lbl">06 — Captions</span>
        <h2>Lower-Third Karaoke</h2>
      </div>
      <div class="gallery">
        <div class="fw">
          <div class="flabel"><b>Active line</b><span>· spoken · current · upcoming</span></div>
          <div class="frame capdemo">
            <div class="tb">
              <span class="l" style="color: var(--green)">Captions</span
              ><span class="l" style="color: var(--green)">05 / 06</span>
            </div>
            <div class="bandline"></div>
            <div class="bandtag">caption band · 16.7%</div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Built</span>
                  <span class="w spoken">for</span>
                  <span class="w active">teams</span>
                  <span class="w">who</span>
                  <span class="w">ship.</span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="fw">
          <div class="flabel"><b>Settled line</b><span>· all spoken · panel cleared</span></div>
          <div class="frame capdemo">
            <div class="tb">
              <span class="l" style="color: var(--green)">Captions</span
              ><span class="l" style="color: var(--green)">06 / 06</span>
            </div>
            <div class="stage">
              <div class="pill">
                <div class="line">
                  <span class="w spoken">Ship</span>
                  <span class="w spoken">faster</span>
                  <span class="w spoken">today.</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="foot">
      <span class="l">Editorial Forest</span>
      <span class="r">Frame Showcase · Atoms Sacred · Composition Free</span>
    </div>

    <template id="__bundler_thumbnail">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="100" fill="#2e4a2a" />
        <text x="12" y="46" font-family="Georgia" font-weight="500" font-size="30" fill="#e89cb1">
          Aa
        </text>
        <circle cx="80" cy="26" r="13" fill="none" stroke="#e89cb1" stroke-width="2" />
        <rect x="12" y="60" width="60" height="2" fill="#e89cb1" />
      </svg>
    </template>
  </body>
</html>
assets/frame-presets/editorial-forest/FRAME.md›
---
version: alpha
name: Editorial Forest — Frame (video / frame layer)
description: >
  Video-first companion to Editorial Forest's design.md. The unit is the frame (1920×1080). Atoms
  are identical and sacred — the green/pink/cream editorial triad, Source Serif 4 at weight 500
  (optical-size axis) for all display + JetBrains Mono 500 uppercase chrome, flat paper depth (no
  shadows), 2px hairline rules, 6/8px card radii, and the monogram circle stamp. Composition + frame
  scale rewritten. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script

colors:
  green: "#2e4a2a"
  green-deep: "#243a21"
  green-lite: "#3a5a36"
  pink: "#e89cb1"
  pink-deep: "#d27e96"
  cream: "#efe7d4"
  cream-2: "#e6dcc4"
  ink: "#1a1a17"

typography:
  # — reading + chrome ramp —
  body:    { fontFamily: "Source Serif 4", cqw: 1.56, weight: 400, lineHeight: 1.38 }
  body-card:{ fontFamily: "Source Serif 4", cqw: 1.35, weight: 400, lineHeight: 1.34 }
  label:   { fontFamily: "JetBrains Mono", px: 26, cqw: 1.35, weight: 500, tracking: "0.18em", upper: true }
  caption-mono:{ fontFamily: "JetBrains Mono", px: 24, cqw: 1.25, weight: 500, tracking: "0.14em", upper: true }
  # — display ramp (Source Serif 4 weight 500, opsz, negative tracking) —
  title-card-sm:{ fontFamily: "Source Serif 4", cqw: 2.9, weight: 500, lineHeight: 0.98, tracking: "-0.01em" }
  title-card:{ fontFamily: "Source Serif 4", cqw: 3.5, weight: 500, lineHeight: 0.96, tracking: "-0.01em" }
  headline:{ fontFamily: "Source Serif 4", cqw: 4.4, weight: 500, lineHeight: 1.0, tracking: "-0.02em" }
  headline-xl:{ fontFamily: "Source Serif 4", cqw: 5.0, weight: 500, lineHeight: 0.96, tracking: "-0.02em" }
  display:{ fontFamily: "Source Serif 4", cqw: 7.3, weight: 500, lineHeight: 1.02, tracking: "-0.02em" }
  display-hero:{ fontFamily: "Source Serif 4", cqw: 11.5, weight: 500, lineHeight: 0.92, tracking: "-0.02em" }
  stat-figure:{ fontFamily: "Source Serif 4", cqw: 11.5, weight: 500, lineHeight: 0.92, tracking: "-0.03em" }
  stat-figure-unit:{ fontFamily: "Source Serif 4", cqw: 5.7, weight: 500, lineHeight: 0.92 }
  name:    { fontFamily: "Source Serif 4", cqw: 2.3, weight: 600, lineHeight: 1.0 }

spacing:
  slide-pad: "5cqw"
  rule: "2px"
  rule-card: "2.5px"
  radius-card: "6px"
  radius-step: "8px"

components:
  topbar:
    typography: "{typography.label} (JetBrains Mono) + monogram-circle or counter"
    placement: "top edge, label left, mark right"
    description: "On EVERY frame — the system's spine; a frame without it reads untreated."
  footline:
    typography: "{typography.caption-mono} ×2, space-between"
    placement: "absolute bottom edge"
    description: "On cover/data/summary frames."
  monogram-circle:
    border: "2px solid {colors.pink}"
    rounded: "50%"
    size: "130px"
    typography: "mono monogram"
    description: "The identity stamp. Cover/summary only."
  topic-tile:
    backgroundColor: "{colors.green} (pink text) / {colors.pink} (green-deep) / {colors.green-lite} (pink) / {colors.cream-2} + 2px {colors.green} border (green)"
    rounded: "{spacing.radius-card}"
    shadow: "none"
    typography: "{typography.caption-mono} ordinal + {typography.title-card-sm} + mono foot"
    description: "Fills rotate; never repeat one across a grid."
  step-tile:
    backgroundColor: "{colors.cream} + green border / {colors.green} / {colors.pink}"
    border: "2.5px solid"
    rounded: "{spacing.radius-step}"
    typography: "mono ordinal + {typography.title-card} + {typography.body-card} + mono marker over a top rule"
    description: "Framework/process card."
  kpi-block:
    typography: "{typography.caption-mono} tag → {typography.stat-figure} (+{typography.stat-figure-unit}) → {typography.body}"
    rule: "2px accent rule above"
    description: "Oversized serif figure."
  meta-dl:
    borderTop: "2px solid {colors.green}"
    typography: "{typography.caption-mono} dt + {typography.meta-value} dd"
    description: "3-column dt/dd grid."
  bar:
    backgroundColor: "{colors.pink} / {colors.cream} / {colors.green}"
    rounded: "3px 3px 0 0"
    size: "56px wide"
    typography: "mono value above"
    description: "Vertical chart bar."
  rule-thin:
    rule: "2px solid (green on cream / pink on green / green-deep on pink)"
    description: "The only separator. Never 1px, never 3px+."
---

# Editorial Forest — Frame (video / frame layer)

## Overview

Editorial Forest at frame scale is a **serif-led literary-editorial system** — the register of a
Penguin classic or a quiet annual report. One confident voice, **Source Serif 4 at weight 500**
(optical-size axis engaged), carries every headline and stat up to ~12cqw; **JetBrains Mono** at
weight 500 uppercase is the editorial chrome (labels, captions, axis ticks, footlines). Three
surfaces — forest green, dusty rose, oat cream — and nothing else. Depth is **flat and paper-based**:
no shadows, no gradients; elevation is color-block contrast + 2px hairlines + border-vs-fill.

The unmistakable signature is **weight 500** (never 400 display, never 700) and the **mono/serif
role inversion** (mono for chrome, serif for body and display). Body text drops to serif weight
400 — that 500→400 step is the reading rhythm. Every frame carries a mono topbar; the monogram
circle is the identity stamp.

**Key characteristics at frame scale:**

- **Green / pink / cream triad** — two surface tones per frame is typical, three is loud.
- **Source Serif 4 weight 500** (opsz, negative tracking) for all display; **serif 400** body; **JetBrains Mono 500** uppercase chrome.
- **Flat — no shadows, no gradients**; elevation is color-block + 2px hairline + border-vs-fill.
- **2px hairline rules** are the only separator (2.5px on step tiles); 6/8px card radii; the monogram circle is the only full round.
- **Topbar on every frame** (mono label + monogram/counter); footline on data/cover frames.
- **Spacious & committed** — one subject per frame in deep negative space; fewer elements at larger sizes.

## The Frame

### Frame Craft Bar

Three eyeball tests gate every frame before any structural check:

- **Squint** — one Source Serif 4 moment dominates at 3–6× its neighbor; the surface block centers the eye.
- **Silence** — one subject per frame in deep negative space; the **topic-tile and step grids are the one dense exception**.
- **Restraint** — **two surface tones max** per frame (three is loud); serif **weight 500** display / **400** body; green ground reserved for gravity moments.
- **Reference** — aim at a **Penguin classic / quiet annual report / art-book spread**; failure looks like a **shadowed, multi-color web dashboard**.

- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `slide-pad` 5cqw (the source's generous 96–140px); the topbar/footline sit inside it.

**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw` (the source is a fixed 1920 canvas, so
its px map cleanly: `px ÷ 1920 × 100`). Hairlines stay 2px; card radii stay 6/8px.

## Colors

Tokens identical to the source. Default ground `{colors.cream}` for content; `{colors.green}` for
cover/statement/summary gravity. **Headlines:** green on cream, cream (or pink at hero scale) on
green, green-deep on pink. **Body:** ink on cream, cream on green, green-deep on pink. Labels/rules
take the region's accent (pink on green, green on cream, green-deep on pink). Tile fills rotate
green / pink / green-lite / cream-2-with-green-border. **No fourth color family** — the triad +
near-duplicates is the whole palette; never `rgba` transparency on a surface.

## Typography

Two ramps. The **reading/chrome ramp** (Source Serif 4 **400** body 1.56cqw; JetBrains Mono labels
in px) carries copy + chrome; the **display ramp** (Source Serif 4 **500** `title-card-sm` 2.9cqw →
`display-hero`/`stat-figure` 11.5cqw, opsz, negative-tracked) carries every headline + figure.

- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono px labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display-hero`; 4–6 → `headline-xl`; 7+ → `headline`. Reserve the 7.3–11.5cqw tier for statement/stat/cover.
- **Serif is weight 500 for display, 400 for body** (the rhythm), 600 only for an attribution name. Negative tracking (−0.01..−0.03em), tight line-height (0.92–1.02). **Mono uppercase, 0.08–0.18em.** No italic, no underline, no third face, no serif body at 500.

## Depth & Surface

Flat, paper-based. Elevation from:

- **Color-block contrast** — a green tile on cream reads elevated by ink-block separation.
- **2px hairline rules** — section separators in the region's accent.
- **Border vs fill** — a cream-2 tile + 2px green border reads a different elevation than a solid tile.

**Ceiling:** zero shadow (none — adding `box-shadow` shatters the paper feel), no gradient, no glow; surfaces are solid ink-on-paper.

## Shapes

- **6px** topic tiles, **8px** step tiles, **2px** legend swatch, **3px 3px 0 0** bar tops, **50%** monogram circle. No square corners, no heavy rounding.

## Components

- **topbar** (the spine) + **footline** + **monogram-circle** (identity stamp) — the chrome set.
- **topic-tile** (6px, rotating fills) / **step-tile** (8px, 2.5px border) — the catalog + framework patterns.
- **kpi-block** (oversized serif figure) / **meta-dl** (3-col, 2px rule) / **bar** / **rule-thin** — data + structure.

## Frame Treatments

> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> Topbar on every frame; one subject per frame in deep negative space.

### 1 · Cover (identity · move: oversized serif · green · left)

**Ground** `{colors.green}`, `slide-pad`. **Composes** topbar (mono label + monogram-circle), display-hero, body lede, footline. **Focal** a 2-line Source Serif 4 `display-hero` in pink (one word in cream), left. **Chrome** mono topbar; mono footline. **Accent** pink display + the monogram circle. **Silence** ~50%. **Fixed** serif 500, flat, monogram 2px pink. **Free** title, which word is cream. **Density** low.

### 2 · Topic Tiles (catalog · move: rotating-fill grid · cream · the dense frame)

**Ground** `{colors.cream}`, `slide-pad`. **Composes** topbar, headline-xl, 3–4× topic-tile. **Focal** a row of 6px tiles, fills rotating green / pink / cream-2-with-border, each a mono ordinal + serif title. **Chrome** mono topbar. **Accent** the tile fills (mix 3 of 4, never repeat one). **Silence** tight — the density exception. **Fixed** 6px radius, rotating fills, no shadow. **Free** tile titles, which fills. **Density** dense-exception.

### 3 · KPI Stat (data · move: oversized figure · green)

**Ground** `{colors.green}`, `slide-pad`. **Composes** topbar, kpi-block (mono tag + 220px stat-figure + serif description), footline. **Focal** a Source Serif 4 `stat-figure` (~11.5cqw, +unit) in pink over a 2px pink rule. **Chrome** mono topbar + footline. **Accent** the pink figure. **Silence** ~55%. **Fixed** serif 500 figure, 2px accent rule, flat. **Free** the figure (from script), tag, description. **Density** low.

### 4 · Statement (quote · move: display serif · cream · left)

**Ground** `{colors.cream}`, generous pad. **Composes** topbar, display, name (600), caption-mono role. **Focal** a 2-line Source Serif 4 `display` (~7.3cqw) in green; a 600-weight attribution name + mono role beneath. **Chrome** mono topbar. **Accent** none — the serif carries it. **Silence** ~55%. **Fixed** serif 500 display + 600 name only, no italic. **Free** quote, name, role. **Density** low.

### 5 · Step Framework (process · move: 8px step tiles · cream/green)

**Ground** `{colors.cream}` (or green), `slide-pad`. **Composes** topbar, headline, 3–4× step-tile. **Focal** a row of 8px step tiles (2.5px border) — mono ordinal + serif 68px title + body + mono marker over a top rule; fills rotate cream-with-green-border / green / pink. **Accent** the tile fills. **Silence** moderate. **Fixed** 8px radius, 2.5px border, mono markers. **Free** steps, fills. **Density** standard.

### 6 · Chart (data · move: bars + meta · green)

**Ground** `{colors.green}`, `slide-pad`. **Composes** topbar, headline, bars + 2px axis rules, meta-dl. **Focal** 56px pink/cream/green bars on inner-edge 2px rules with mono ticks; an optional 3-col meta-dl beneath. **Chrome** mono topbar. **Accent** the bar fills. **Silence** moderate. **Fixed** 56px bars, 3px tops, 2px axes, mono ticks. **Free** values (from script), labels. **Density** standard.

## Composition Rules

### Do

- Run every display in **Source Serif 4 weight 500**, opsz, negative-tracked, tight line-height; **body at 400**.
- Set every label/caption/axis/footline in **JetBrains Mono 500 uppercase, 0.08–0.18em**.
- Give every frame a **topbar** (mono label + monogram or counter); footline on data/cover.
- Pick **one dominant surface per frame** (max two tones); **rotate tile fills**, never repeat one across a grid.
- Separate sections with **2px hairlines** in the region's accent; reserve the monogram circle for cover/summary.
- Scale display aggressively (7.3–11.5cqw) for statement/stat/cover; lean left/editorial, one subject per frame.

### Don't

- No box-shadow, gradient, or glow — flat, paper-based depth only.
- No third typeface; no italic/underline; no serif body at 500; no mono at headline scale.
- No fourth color family; no `rgba` surfaces; no 1px or 4px+ rules (2px / 2.5px only).
- No two competing content blocks; don't omit the topbar.
- Don't blow a headline edge-to-edge — step the ramp down.

## Aspect-Ratio Behavior

| Treatment      | 16:9                             | 9:16                        | 1:1                      |
| -------------- | -------------------------------- | --------------------------- | ------------------------ |
| Cover          | display left, monogram top-right | display top, monogram below | display, monogram corner |
| Topic Tiles    | 3–4 across                       | stacked                     | 2×2                      |
| KPI Stat       | figure left                      | figure centered, taller     | centered                 |
| Statement      | quote left                       | quote stacked               | centered                 |
| Step Framework | 3–4 across                       | stacked                     | 2×2                      |
| Chart          | bars + meta                      | bars taller, meta stacks    | square chart             |

`slide-pad` holds on the short edge; re-step display above the 1.4cqw floor. Topbar spans the top
on every ratio; the monogram may drop to a corner.

## Approved Real Entities

No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. The monogram, footline strings, and tile counters carry per-deck identity; figures are content.

## Numerals & Claims (hard rule)

Never invent figures, KPIs, dates, or counts at frame scale. Render slots as `— figure —`,
`{metric}`, `— %`. KPI blocks, bars, and meta values especially carry placeholders until the script
supplies them. Issue numbers / counters are decorative chrome.

## Pre-Render Self-Audit

- **Squint** — one serif moment dominates; the surface block centers the eye.
- **Silence** — one subject per frame in deep negative space; only the topic-tile/step grids run dense.
- **Triad** — two surface tones max; headlines/body/labels use the correct per-surface color; no fourth family.
- **Type** — Source Serif 4 **500** display (opsz, negative-tracked) / **400** body; mono uppercase 0.08–0.18em; ≥1.4cqw floor.
- **Depth** — 0 shadow, 0 gradient; 2px hairlines; 6/8px radii; monogram the only full round.
- **Chrome** — topbar present on every frame; footline on data/cover.
- **Fabrication** — every numeral traces to the script, else placeholder.

## Known Gaps

- **Motion intentionally out of scope.** frame.md specifies composition only; the source relies on `deck-stage.js` for scaling/nav, no transition spec.
- **Source Serif 4 (opsz 8..60) + JetBrains Mono via Google Fonts** — the optical-size axis is critical; a non-opsz fallback flattens the size-aware letterforms. CJK: LXGW WenKai (display) / Noto Serif SC (body) / Noto Sans Mono CJK (chrome).
- **9:16 / 1:1 are guidance**; verify the floor and that two-tone discipline holds per ratio.
- Bars, hairlines, tiles, and the monogram circle are CSS-only; no external imagery is required.
- **Contrast:** green-on-pink / pink-on-green only at display scale (84px+); keep small text cream-on-green or ink-on-cream.
references/captions.md›
# Captions

## Words and timings

- `npx hyperframes transcribe <file> --json` and a HeyGen voiceover (`/media-use`) give every word's start and end. Work from those as `[{ "id": "w0", "text": "Hello", "start": 0.0, "end": 0.5 }, …]`.
- Local transcription defaults to `small.en`, which **translates** non-English speech into English. Pass the model: `npx hyperframes transcribe <file> --model small.en` for English, `--model small --language <code>` otherwise, `--model medium.en` for speech over music. It writes `assets/transcript.json` (its JSON names the path) and overwrites it on the next run; `--to srt` or `--to vtt` writes a sidecar.
- For Chinese, Japanese and other unspaced scripts, local transcription returns a whole sentence as one "word", sometimes in the other script (Traditional for Simplified speech). Split each segment's text into short phrases and spread its time across them by character count; check the characters against what is said.
- Whisper invents "Thank you." over silence and repeats lines over a silent tail: drop words that start inside a final silence (`silencedetect=noise=-35dB:d=0.6`; real speech sits around −16 to −26 dB).
- Never put two words in one timed entry, and match transcript words to caption words by position, not text, or a repeated word takes the wrong time.
- Read the transcript before using it. More than 20% `♪` or `�` tokens, or nonsense words, means it failed: retry with a larger model, or take lyrics from the person. Drop non-word tokens before building groups.

## Structure

- One sub-composition host with `data-track-kind="captions"` holds every caption group, so Studio shows them on one captions track.
- Ready-made styles: `npx hyperframes catalog --tag caption-style --json`. An installed one is source you can restyle.

## Groups

- 2–3 words for high energy, 3–5 conversational, 4–6 calm. Break at a sentence end, a pause of 150 ms or more, or the word limit.
- One group visible at a time. A group opens at or before its first word's start and closes at or after its last word's end. A caption at exactly 0 s looks pre-existing: start 0.1–0.3 s in.
- Every group ends with a hard kill after its exit, or a stale group can stay on screen:

```js
tl.to(group, { opacity: 0, duration: 0.12, ease: "power2.in" }, end - 0.12);
tl.set(group, { opacity: 0, visibility: "hidden" }, end);
```

`tl.set` is an instant flip, so setting `visibility` on the group is fine; never tween it on a `.clip`.

## Fit and place

- Size each group to fit: `window.__hyperframes.fitTextFontSize(text, { fontFamily, fontWeight, maxWidth })` returns `{ fontSize }`. Defaults: `maxWidth` 1600 landscape, 900 portrait; base 78, minimum 42, step 2. When a word scales up for emphasis, divide `maxWidth` by that scale.
- A full-width absolute container, centred with text alignment. Never `left: 50%; transform: translateX(-50%)`, which clips at the frame edges.
- `overflow: visible`, not `hidden`, which cuts off scaled words and glows.
- Landscape: 80–120 px from the bottom. Portrait: about 600–700 px from the bottom. Never over the speaker's face; inside the 80% title-safe box.
- Position each group absolutely. In a flex column a group hidden with `visibility: hidden` still takes space, so the next one lands lower. In a stack that accumulates, groups in one block share one exit time, or the stack reflows.
- Fade the group or its words, not both: the opacities multiply and read as a pop. A gradient or `background-clip: text` fill goes on the inner text span, not the transformed container.
- Over footage, choose the blend by the brightness (0–255) behind the text: under 60 `screen`, 60–180 `overlay`, over 180 `normal` with opaque colour. Plain subtitles above brightness 60 get a scrim sized to the text (30–40%), never a full-width bar.
- Footage may already carry burned-in subtitles only mid-clip: scan a 1 fps contact sheet before placing captions.

## Text behind the speaker

- Stack the video, then the captions, then a cutout of the person from `npx hyperframes remove-background` (`/media-use`), ordered by `z-index`. Shake or punch in the video and the cutout together in one wrapper.
- `check` treats a `<video>` as opaque across its whole box, so a WebM cutout fails every caption behind it with `text_occluded`: put `data-layout-allow-occlusion` on the cutout and judge from `snapshot`.
- A big word behind the head reads best 30–55% covered: under about 15% it floats, over about 65% it is eaten. Re-lay out when a content word is 80% covered, or two adjacent words 50% each. Measure the rendered word boxes against the matte over each caption's own time window (heads move: one clip went from y=224 to y=96 in 8 s); estimates from character widths were wrong both ways.
- The matte model drops mic arms, lets large rigs near the body leak in, and flickers on held objects. Its first run downloads 168 MB, and it runs at about 2 fps at 1080p on CPU.
references/film.md›
# Films from a source: measured facts

What is specific to HyperFrames, or was measured on shipped films, when one video is made from a website, a script, a pull request, a music track, a real place or a short motion brief. The story comes first, from `story.md`; the shape of each kind of film is in `types/`. Motion craft is in `motion.md`, look in `look.md`, sound in `sound.md`, captions in `captions.md`.

## Script and pacing

- TTS reads about 2.2 words a second. Keep a scene to about 19 words (9 s); at most two scenes may reach 26 words (12 s). 30–90 s is about 155 words or fewer; a whole film about 400 at most.
- A voiceover longer than its visuals makes the visuals pad with idle drift. Trim the script; never stretch the motion.
- Everything on screen in the first quarter of a scene, then frozen, reads as a slideshow. Reveal each piece when the voiceover names it, across the back half of the scene; write the voiceover as separate phrases so it has cue points.
- Up to about six short scenes, build them yourself in one context (about 9 min for five, against about 21 min split across sub-agents). Beyond that, give each worker two or three scenes and start them all at once; a second wave nearly doubles the time.

## From a website

- `npx hyperframes capture <url> --json` writes `screenshots/full-page.png`: a 1x plate of the whole page, taken after the scroll pass with sticky and fixed elements made static, pixel-exact for a 1920-wide viewport travelling down it. It is skipped above 16384 px tall (use `scroll-*.png`); pushing in past 1:1 needs its own 2x capture of that region.
- To show the site as it is, use its screenshots. Rebuilding the page in HTML drifts from the real layout; rebuild only the part that moves, or overlay captured assets at measured positions.
- `--capture-budget` (default 120000 ms) bounds work after navigation; `--timeout` bounds navigation only. Videos are not downloaded: `capture --video <dir> --list`, then `--index N`. `asset-descriptions.md` has vision captions only when `OPENROUTER_API_KEY`, `GEMINI_API_KEY` or `GOOGLE_API_KEY` is set.
- "very little text content" with an empty asset catalog means the capture is unusable even when it says `ok: true`. A `logo-*.svg` name is a guess from page structure, not proof.
- Brand tokens: link blue `#0000EE` and status reds and greens are never the accent; the accent is the colour that recurs across the most roles. Icon fonts (Font Awesome, icomoon, webflow-icons) are never the display or body face; a captured monospace goes to the mono role. Use only weights the brand font ships, or 600/700 render as fake bold. Every preset has a light ground: for a dark brand, swap ground and ink.
- Capture keeps each `@font-face` rule, `unicode-range` subsets included, in `extracted/page.html`; copying the font files alone loses which characters each covers. Name copied fonts by family, weight and style, or an upright and an italic "Regular" collide.

## From a pull request

- `gh pr view --json files` stops at about 100 files: page `gh api repos/{owner}/{repo}/pulls/N/files`.
- The PR author only opened it: credit commit authors, by commit count (`commits[].authors[]`); skip bots, lockfiles and `dist/`. Reviewers come as logins; names via `gh api users/<login> --jq .name`. TTS reads `@handles` aloud, so speak names.
- Never invent a version. Use the first non-draft release published at or after the merge; else the default branch's `package.json` version as "unreleased"; else only the PR URL.
- Length from size (additions + deletions), as a ceiling: ≲50 lines 20–40 s, 50–200 40–70 s, 200–600 70–110 s, ≳600 lines or 25+ files 110–180 s.
- Show 2–4 hunks of 4–12 lines; counts like +N/−M go on a stat card, not into the voiceover.
- The `code-*` registry blocks (`npx hyperframes catalog --query code --json`) are 1920×1080 scenes driven by `window.__TOKENS` (Shiki tokens, `github-dark`; `fontStyle` bit 1 italic, bit 2 bold) and `window.__BLOCK` (`{id, effect, seq, line?, duration}`). `code-diff` and `code-morph` need at least two states; `code-highlight` counts lines from 0 and `code-scroll` from 1; `code-typing` types at a fixed speed, so check characters × speed against the scene's duration. They reserve no caption band: inset the panel when captions are on.

## On music

- Library music, HeyGen's included, comes as 15–30 s loops: loop the bed to the film with a 0.4 s fade-in and a 1.5 s fade-out. A track whose opening is a quiet build drains a short film: start from a stronger later section.
- Trust beat times only on rhythmic stretches; on calm music the tracker invents a metronome, often at double tempo (`npx hyperframes beats` reports `bpmConfidence`). Cut there on phrase and energy changes; `npx hyperframes audio --only sections,onsets,moments --json` finds them.
- A move that must land on a hit starts 40–190 ms early; a reaction starts 0–45 ms after. The hit itself is a 0 ms `tl.set`: easing a hit kills it. Scenes tile the track with no gaps.
- A word meant to be read holds at least one beat. Any single treatment goes stale after about 6 s: lengthen a section with a hold or a new treatment, never by slowing tweens. On rhythmic sections give footage one clip per strong beat; on calm ones one slow push from 1.0 to 1.08.

## Maps and real places

- A real place (a city, a building, a route, a zoom from the globe to a street) is shown on real imagery. Vector outlines from Natural Earth or GeoJSON read as a diagram and lose the place; keep them for borders and labels drawn on top.
- Live tiles change between renders, so bake the camera move to video with `scripts/bake-basemap.mjs` (MapLibre over Esri satellite or Carto tiles; writes an all-intra MP4 and `<NAME>-coords.json`). From space down to a street: `PROJECTION=globe FROM="lng,lat" CENTER="lng,lat" ZSTART=1.85 ZEND=16` (Earth in a starfield turns from FROM to CENTER, then dives); `--help` lists the rest. It needs network for the tiles and takes well under a second a frame, so bake once, keep the MP4, and run one bake at a time. Draw borders, pins and labels as a live SVG from those coordinates, animating them only while the camera holds.
- Credit the imagery on screen ("Esri, Maxar, Earthstar Geographics" or "© CARTO, © OpenStreetMap") in small type on a soft dark backing, so it holds contrast over bright tiles; mark it `data-layout-ignore` so the type floor in `check` skips it.

## Short motion pieces

- Kinetic type takes 3–7 English words or 4–12 Chinese characters per scene. A count-up runs 1.2–1.6 s, then holds, landing on a downbeat or a hard stop.
- A highlighter that wraps across lines: tween a custom property on a `background-size` band with `box-decoration-break: clone`. `getTotalLength()` can return 0 before layout; give line draw-ons a fallback.
- A point in an image guessed directly misses by 16–24% of the frame. `scripts/locate.mjs` narrows it with a numbered 9×9 grid, then 6×6 on the crop, then a check render (about 2% error).
references/format.md›
# Composition format

The detail behind the contract in `SKILL.md`. Full references: `npx hyperframes docs compositions`, `docs data-attributes`, `docs troubleshooting`.

## Shape of a project

```html
<!-- index.html: a normal document; declares variables, mounts scenes -->
<html
  data-composition-variables='[{"id":"headline","type":"string","label":"Headline","default":"…"}]'
>
  <body>
    <div
      id="root"
      data-composition-id="main"
      data-start="0"
      data-duration="12"
      data-width="1920"
      data-height="1080"
    >
      <div
        id="intro"
        data-composition-id="intro"
        data-composition-src="compositions/intro.html"
        data-start="0"
        data-duration="4"
        data-track-index="1"
        data-width="1920"
        data-height="1080"
      ></div>
    </div>
    <script>
      window.__timelines["main"] = gsap.timeline({ paused: true });
    </script>
  </body>
</html>
```

```html
<!-- compositions/intro.html: everything inside <template> -->
<template>
  <style>
    #root {
      position: absolute;
      inset: 0;
    }
  </style>
  <div id="root" data-composition-id="intro" data-width="1920" data-height="1080">
    <h1 id="headline" class="clip" data-start="0.4" data-duration="3" data-var-text="headline">
      …
    </h1>
  </div>
  <script>
    const tl = gsap.timeline({ paused: true });
    tl.fromTo(
      "#headline",
      { opacity: 0, filter: "blur(8px)" },
      { opacity: 1, filter: "blur(0px)", duration: 0.18, ease: "power4.out" },
      0.4,
    );
    window.__timelines["intro"] = tl;
  </script>
</template>
```

- Give the host, the scene root and the timeline key the same id. A host id that differs from its scene's makes each render wait 45 s per scene, then show static frames. Styles and scripts outside `<template>` are dropped.
- Size the scene root with `inset: 0`, never hard-coded pixels; the canvas is `data-width` × `data-height`.
- Keep ids unique across the assembled page (prefix them: `#intro-title`). A scene's timeline can't reach elements outside its file.
- Use `fromTo`, not `from`, inside a scene: the host re-seeks the scene each time it shows, and `from` records its start state once, so it drifts on seek-back.
- Never `add()` a scene's timeline to the root's; the runtime drives each scene itself, and nesting seeks it twice.

## Attributes you'll use most

| Attribute                                      | Meaning                                                                                             |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `data-start`, `data-duration`                  | Seconds, local to the file that declares the element                                                |
| `data-track-index`                             | Studio lane only; paint order is CSS `z-index`                                                      |
| `data-track-kind`                              | `graphics` or `captions` on scene hosts (video and audio come from the tag)                         |
| `data-composition-src`, `data-variable-values` | Mount a scene file; per-mount variable values (a JSON object)                                       |
| `data-media-start`                             | Offset into the source media (a trim)                                                               |
| `data-playback-rate`                           | Constant speed, 0.1–10                                                                              |
| `data-has-audio="true"`                        | A `<video>` keeps its own sound; a timed `<video>` with neither this nor `muted` fails lint         |
| `data-volume`, `data-fade-in`, `data-fade-out` | Gain (1 = 0 dB, up to 3.98 = +12 dB) and edge fades; envelopes use `data-automation` (`/media-use`) |
| `data-link`, `data-sync-origin`                | A video and audio edited as one (see Linked clips)                                                  |
| `data-hidden`                                  | Hidden in preview and render; reversible                                                            |
| `data-no-timeline`                             | On the root of a still composition: no timeline to wait for                                         |

## Variables

- Declare an array of `{id, type, label, default}`: the video's own on `index.html`'s `<html>`, a scene or block file's on that file's `<html>` (or root element), where they stay inside each mount. Types: `string` (`placeholder`, `maxLength`), `number` (`min`, `max`, `step`, `unit`), `color`, `boolean`, `enum` (`options: [{value, label}]`).
- Bind without script: `data-var-text="id"`, `data-var-src="id"` (keep a real fallback `src` on media with audio), `var(--id)` in CSS; for logic, `window.__hyperframes.getVariables()` once at init.
- Values are an object keyed by id. A host's `data-variable-values` sets one mount; Studio's Variables panel and batch renders set only the root's variables, so to vary a block, change its host's values. Variables can't change the root's `data-duration`.

## Fonts

- Bundled, so they resolve by name: Inter, Montserrat, Outfit, Nunito, Oswald, League Gothic, Archivo Black, Space Mono, IBM Plex Mono, JetBrains Mono, EB Garamond, Playfair Display, Source Code Pro, Noto Sans JP, Roboto, Open Sans, Lato, Poppins. Common system fonts (Helvetica, Arial, Times, Menlo and the like) are swapped for the nearest of these.
- Any other family needs `@font-face` in the file that uses it, or lint fails `font_family_without_font_face`: `src: url('assets/fonts/x.woff2')` for a file in the project (a capture's own fonts, or one you download there), or `local('Exact Name')` for a font installed on this machine.

## Timing and the timeline

- A clip shows over `[start, start + duration)`: an end state landing exactly on its end is never rendered.
- Only a composition's root timeline is paused; timelines nested inside it are not. Never `tl.play()`.
- A scene's clock starts at 0 at its host's `data-start`; media `data-start` is local to its composition. `<video>` / `<audio>` without `data-duration` play their full length; an `<img>` without one lasts 3 s.
- A host's `data-duration` is the scene's slot: a shorter scene timeline holds its last frame, and a slot shorter than the video goes blank when it ends.
- Root children with `data-start` are laid out full-frame by the runtime; an untimed full-bleed background needs `position: absolute; inset: 0` or it collapses to nothing.
- A fill on the composition root is dropped when the video has shader transitions or HDR media; put it on a full-bleed child.
- `data-start="intro + 0.5"` starts after `intro` ends (spaces required). A typo, a missing id or a cycle silently resolves to 0, so snapshot what you time this way.
- A clip nested inside another timed element counts its `data-start` from the composition, not from that element, and shows only while that element shows. `timeline` lists it under its parent and warns `never visible` when the windows miss (`lint`: `nested_clip_never_visible`).
- `npx hyperframes timeline --json` lists every clip by kind with `absStart`, `absEnd`, `file`, `src`, `playbackRate`, `nested`, `host` and `durationSource`: read it instead of opening every file. It reads one level of nesting (a deeper scene shows `children=unread`); `laneError` means a `data-automation` or `data-fx-chain` that doesn't parse.

## Adding media

Write what a Studio drop writes, so the clip behaves like one the person dropped:

- `id`, `class="clip"`, `data-start` (the playhead or the requested time, never a silent 0) and `data-track-index`. Video with sound: `playsinline data-has-audio="true"`; silent footage: `muted playsinline`. Audio: `data-volume="1"`.
- Images and video fill the frame: `position: absolute; left: 0; top: 0`, width and height equal to the canvas, `object-fit: contain`.
- `z-index` is the number of top-level clips already in that file plus one.
- Raise the root's `data-duration` to cover the new clip's end, or it never plays.

## Linked clips

Studio's Detach audio writes a muted `<video>` and an `<audio>` of the same file sharing `data-link` and `data-sync-origin`; Studio moves, trims, splits and deletes them as one.

- Prefer one `<video data-has-audio="true">`; link only when the sound needs its own track, volume or effects.
- Every member keeps the same `data-start`, `data-duration`, `data-media-start` and `data-playback-rate`; retime them all together.
- Unlink by removing `data-link` from every member. Leave `data-sync-origin` alone while they are still one source.

## Layout audit opt-outs

`check` measures boxes at sampled times; `overflow: hidden` does not silence a finding. Use the narrowest opt-out:

| Attribute                        | Silences                                                                                                                 |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `data-layout-allow-overflow`     | Overflow on the element and every descendant, including text-clipping checks: keep it on the smallest decorative wrapper |
| `data-layout-bleed="true"`       | One intentional crop of primary text                                                                                     |
| `data-layout-allow-overlap`      | Deliberate text layering, on that block only (not inherited)                                                             |
| `data-layout-allow-occlusion`    | An element meant to cover text                                                                                           |
| `data-layout-allow-caption-zone` | Intentional copy in the caption band                                                                                     |
| `data-layout-ignore`             | A decorative element never audited                                                                                       |

## Lint errors you'll meet first

| Rule                                                   | Fix                                                                                                    |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `video_missing_muted`                                  | A timed `<video>` gets `data-has-audio="true"` or `muted`                                              |
| `gsap_css_transform_conflict`                          | Set the start inside the tween (`gsap.fromTo`) instead of a CSS `transform`                            |
| `gsap_animates_clip_element`                           | Animate a child of the `.clip`, or its `opacity`                                                       |
| `video_nested_in_timed_element`                        | Time the `<video>` or its wrapper, not both                                                            |
| `media_missing_id`, `media_crossorigin_breaks_preview` | Give every `<audio>` an `id`; drop `crossorigin`                                                       |
| `gsap_timeline_registered_before_async_build`          | Register the timeline at the end of the async callback                                                 |
| `standalone_composition_wrapped_in_template`           | Only scene files use `<template>`                                                                      |
| `subcomposition_root_styled_by_class`                  | Style a scene's root as `#root`                                                                        |
| `subcomposition_blanks_before_host`                    | Make the scene's slot span the time it should show                                                     |
| `clip_ends_past_root_duration`                         | Raise the root's `data-duration`                                                                       |
| `linked_clips_out_of_sync`, `linked_clip_orphan`       | Retime every linked member together; unlink from all of them                                           |
| `sweep_static`                                         | A 3 s+ video with no visible change: spread the motion, or mark a still composition `data-no-timeline` |
| `missing_data_no_timeline`                             | A root or host with no timeline: add `data-no-timeline`, or every render waits 45 s                    |
references/look.md›
# The look

Left alone, the same brief comes out with the same look run after run, and banning that look only moves it to the next default. Outside input breaks it, in this order: the project's design spec, a real brand, a reference image or film the person gave you, the subject's own world. Settle it before the first scene, as numbers in the LOOK line (below).

## The project's design spec

Read the first of `frame.md`, `design.md`, `DESIGN.md` at the project root that exists; ignore the rest.

- **Frontmatter is normative**: `colors`, `typography`, `spacing`, `components`. Quote hex values, families and weights exactly; never round or invent them.
- **The prose is intent**: what the look is for, Do's and Don'ts. Follow it for judgment, not values.
- **Brand, not layout**: hex, fonts, weight relationships and Do's and Don'ts are strict; type sizes, spacing, border weight and decoration are yours to scale for video. A 1px border or a 6% shadow from a web page vanishes on screen.

## From a brand

- A website: `npx hyperframes capture <url> --json` writes screenshots, the copy (`extracted/visible-text.txt`), images, logos, `extracted/tokens.json` (colours, fonts) and `extracted/design-styles.json` under its output folder. Look at the screenshots before choosing anything. A capture that exits non-zero, prints `ok: false` or writes `BLOCKED.md` failed: take no brand data from its partial files.
- Figma: `npx hyperframes figma tokens <fileKey>` (`/figma`).
- Images the person attached: take palette and type from what you see.

Use the brand's own fonts when the capture shipped their files or they are bundled (`format.md`); otherwise download the family's `.woff2` into `assets/fonts/` with an `@font-face`, or pick the closest bundled family and say so.

## From a reference

A reference film or image gives its grammar, never its content: shot lengths, how titles enter and leave, the transitions, the palette roles, the type's scale and weight. Snapshot it next to yours (`snapshot --at <t,…> --against ref.mp4`) and write the grammar down, marking what you measured apart from what you guessed.

## Presets

The presets are slide-deck looks: use one when the person names it, or for a deck (`/slideshow`), never as the fallback for a film without a brand. Each folder in `../assets/frame-presets/` holds a `FRAME.md` spec, a `caption-skin.html` and a `frame-showcase.html` contact sheet. To use one, copy its `FRAME.md` to the project root as `frame.md`, then read it as above; its fonts that aren't bundled need `@font-face` files. The two HTML files are reference code: read them for treatments, never mount them.

| Preset              | Look                                                                                           |
| ------------------- | ---------------------------------------------------------------------------------------------- |
| `biennale-yellow`   | Museum catalogue: parchment, deep indigo ink, solar-yellow blooms, Instrument Serif, hairlines |
| `blockframe`        | Loud neobrutalist: 4px black borders, hard shadows, candy pastels, Inter 900 caps              |
| `blue-professional` | Consulting restraint: cream canvas, one cobalt accent, Space Grotesk + Inter, tinted cards     |
| `bold-poster`       | Vintage editorial poster: Shrikhand tilted display, Baskerville, tomato red, double borders    |
| `broadside`         | Protest poster: ink-black and fire-orange planes, giant lowercase Barlow 900, mono chrome      |
| `capsule`           | Friendly editorial: every container a pill, cream, nine candy accents, Bodoni Moda             |
| `cartesian`         | Quiet catalogue: warm-stone palette, Playfair + Inter, taupe hairline grid, compass rings      |
| `cobalt-grid`       | Two-colour risograph: cream paper, cobalt ink, graph-paper grid, Newsreader serif              |
| `code-editorial`    | Developer brand book: warm paper, scarce terracotta, EB Garamond, navy code surface            |
| `coral`             | Bold magazine: coral, ink and cream planes at hard edges, Bebas Neue, giant numerals           |
| `creative-mode`     | Sparse neobrutalist: cream, 4px ink borders, Archivo Black caps, rationed accents              |
| `daisy-days`        | Picture book: charcoal outlines, sunny pastels, Fredoka One, hand-drawn ornaments              |
| `editorial-forest`  | Literary: green, pink and cream, Source Serif display, mono chrome, flat paper                 |

## The LOOK line

With no design spec, the look is the LOOK line of `SPEC.md` (`spec.md`), taken from the brand, the reference or the subject itself: its era, its materials, where it would be shown. Every part is a number (hex, px, %, a named face and weight), the ground and the hero come from a medium rather than from effects, and the BANNED line keeps it off the defaults. Inter, Space Grotesk, Instrument Serif, Geist and JetBrains Mono are where generated design lands; use them when the brand does, not by default. Copy the palette and the faces into the root variables below.

## Video facts

- Full-screen linear gradients on dark grounds band under H.264; use a radial gradient, a solid fill or a solid plus a local glow.
- An accent at 5% opacity disappears in compression: 15–25% for atmosphere, full saturation for the focal element.

## Make it re-brandable

Declare palette and fonts as variables on the root `<html>` and use them everywhere, so the person can re-brand from Studio's Variables panel:

```html
<html
  data-composition-variables='[
  {"id":"color-ground","type":"color","label":"Background","default":"<ground from the plan>"},
  {"id":"color-ink","type":"color","label":"Text","default":"<ink from the plan>"},
  {"id":"color-accent","type":"color","label":"Accent","default":"<accent from the plan>"},
  {"id":"font-display","type":"string","label":"Display font","default":"<display face from the plan>"}
]'
></html>
```

Reference them as `var(--color-accent)`; point a registry item's colour and font variables at the same values.
references/motion.md›
# Motion

## Motion craft

Measured frame by frame from the official HyperFrames launch films. These are defaults: a reference the person gave, or a Moment that does otherwise on purpose, wins for its own beat.

- **Moves**: a fast move takes 0.3–0.6 s and carries motion blur on its own ease, peaking at its fastest frame: 6–12 px on text-sized elements, 15–20 px full-frame, horizontal-only on a horizontal move. One move is one tween.
- **Arrive slow, leave clean**: long-tailed ease-outs on arrivals; landing takes longer than leaving. An exit is one smooth motion, never slide, stop, then leave. Entrances that are too snappy read as glitches; slowing the whole video by about a second often fixes it.
- **Ease by role**: an arrival lands on a long tail (`expo.out`, `power4.out`), a camera move or a colour flood eases both ways (`power2.inOut`), a zoom through or an exit accelerates (`power3.in`). The same `power2.out` at 0.5 s on everything is the flat default that makes a film feel generated. Linear only for rotation, progress and constant speed that is the point.
- **Springs** are named by damping ratio: 1.0 never bounces, 0.7-0.85 reads premium, 0.4-0.6 playful, under 0.3 a cartoon. Position and size may bounce; opacity and colour never do. GSAP's `elastic.out(1, 0.5)` and `back.out(1.4)` are the nearest built-ins; a closed-form spring of `t` keeps any frame renderable on its own.
- **Exits** are 40-50% shorter than their entrances. **Overlap**: the next element starts when the previous is 30-50% done; fully sequential looks robotic, all at once looks cheap. **Stagger**: letters a frame apart, words 60-120 ms, list items 40-80 ms.
- **Beat grid**: frames per beat = fps × 60 / BPM; compute each beat from its index and round it, never add a rounded beat repeatedly (at 128 BPM and 30 fps that drifts 2 frames in 32 beats). Cut on the beat, change the layout on the bar.
- **Cuts**: cut at the fastest frame of a move, with matching direction and speed on both sides. A click into a new screen is a match cut on the click's rebound; the new screen comes in slightly large and settles.
- **Titles**: a landed title is dead still; the motion moves to the next element, the camera or the world behind. Vary the entrance within a film, never a fade and slide-up for each:
  - per word: `blur(6px)` → 0 and opacity 0 → 1 in 0.13 s, 0.067 s apart, no translate;
  - per word from light grey to ink, 0.07–0.1 s apart, no position change;
  - the whole line blurring in, 8 px → 0 in 0.13–0.2 s;
  - per letter for a name, 0.1 s apart, opacity 0.3 → 1 with a slight blur;
  - behind the subject: the title between the plate and a matte of the person;
  - one word of the sentence rolling through options like a slot machine, about 0.1 s a roll.
- **Holds**: a line the viewer must read holds about 0.5 s plus 0.3 s a word, the film's last line at least 2 s; a one- or two-word chapter line may hold 0.4 s. Then exit with a blur-out and fade of 0.13–0.25 s, word by word, or with the scene's move.
- **Title size**: 4–9% of the frame height, one or two lines; quiet lines at 2.5–4% against hero words that fill 60–90% of the width. One highlighted word in the accent colour, which carries meaning.
- **Rhythm**: contrast speeds. Montages cut every 0.13–0.8 s; a demo holds one framing for 7–10 s while the UI acts; shots average 1.8–3 s with motion inside them. Hold 0.3–0.75 s before the payoff, which plays at normal speed.
- **Continuous motion that works**: the product's UI working, footage playing, a character acting, a 3D world turning, a slow orbit of a background stage (3–5° a second).
- **Defaults that look generated**: breathing, floating or pulsing loops standing in for motion on titles and UI; titles that keep sliding or scaling after they land; a slow creeping scale on a title or a slow push late in a scene; bounce on UI; click rings; every scene entering the same way; every element fading and sliding up. A character's idle and overshoot are acting, not these (`types/character.md`).

## Transitions with a cause

Use 3–5 kinds in a film, each caused by something on screen. Hard cuts belong inside a scene (punch-ins); at most a third of the seams between beats are hard cuts, and worlds never crossfade into each other. Measured on the official films:

| Transition               | How it is built                                                                                                                                                                     |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Zoom through a button    | the camera tracks to the button with blur (0.6 s), then the button scales from ~8% to ~250% of the frame in 0.35 s, strong ease-in; its fill becomes the next frame                 |
| Object becomes the world | a pill or card grows past the frame in 0.65 s, ease-in-out, its radius shrinking; what was around it blurs ~15 px and fades in 0.3 s; the new content fades in while it still grows |
| Morph                    | one container widens into the next (icon card → prompt box, ~110 → ~880 px in 0.2 s `power3.out`) with a horizontal blur 12 → 0                                                     |
| Carry across             | the result shrinks 1 → 0.6 with blur peaking at 6 px mid-move and lands inside the next app as it builds around it                                                                  |
| Colour flood             | the hero object stays while the whole ground changes colour in ~0.4 s `power2.inOut`                                                                                                |
| Whip                     | a horizontal or vertical smear of 0.13–0.3 s; the arriving element lands blurred and sharpens in ~0.13 s                                                                            |
| Iris from the click      | `clip-path: circle()` opens from the clicked point onto the next world in 0.15 s                                                                                                    |
| Dip cut                  | the outgoing frame darkens ~40% for 3 frames before a hard cut, so the cut reads as meant                                                                                           |
| Blur swap on one stage   | out: opacity → 0 with ~8 px blur in 0.13–0.2 s; in: blur → 0 in 0.13 s; the ground never cuts                                                                                       |

Registry versions: `npx hyperframes catalog --tag transition --json`.

| The person says        | Change                                                                         |
| ---------------------- | ------------------------------------------------------------------------------ |
| "too fast"             | Slow every zoom and pan to 0.7× of its speed, nothing else                     |
| "speed this up"        | Speed the content and shorten the beat by the same amount; later beats move up |
| "the titles move"      | Land them, then hold them dead still until they leave                          |
| "it feels like slides" | Enter the next scene through an object on screen; cut a move mid-flight        |

## The right mechanism

| Request                                 | Mechanism                                                                                                                  |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Punch-in, zoom, pan, Ken Burns, reframe | Tween `scale` and `x`/`y` on a wrapper inside the clip, never on the timed media element; chain the states on one timeline |
| Crop or mask reveal                     | Tween `clip-path` or a mask on an inner wrapper; source time is untouched                                                  |
| Constant speed change                   | `data-playback-rate` on the media, 0.1–10 (picture and pitch-preserved sound)                                              |
| Speed ramp                              | A `rate` lane in `data-automation` on the media (`t` in clip seconds, `v` 0.1–10); it overrides the constant rate          |
| Hold or freeze                          | Hold a final frame or a finished scene; a freeze mid-source needs a still exported first and placed as its own clip        |

Ready-made moves: `npx hyperframes catalog --tag camera --json`, or `catalog --query "<the move>" --tag motion-primitive --json`.

## Seeing motion

The whole-film pass (beat sheet, cuts, phone width, the craft warnings) is `review.md`. For one move:

- `npx hyperframes keyframes --json [--selector '#id'] [--runtime all]` lists keyframes and motion paths (GSAP, CSS, anime.js): property, time, value, ease.
- `npx hyperframes keyframes --shot motion.png --selector '#id'` draws the real element's samples over time with its path (9 samples; `--samples N`). `--layout strip` for motion in place, `--from`/`--to` for one stretch, `--angle iso|top|side|'yaw,pitch'` for 3D, `--ghost` for a `<canvas>`.
- `npx hyperframes snapshot --range 2,4 --frames 8` samples one stretch; `--composition <id>` samples one scene; `--at <t,…>` exact instants; `--zoom '#id'` a sharp crop; `--angle iso` depth and overlap.
- Against a reference video: `snapshot --at <t,…> --against ref.mp4` writes a side-by-side sheet.
- Assert intent instead of eyeballing it: a `<name>.motion.json` next to the composition makes `check` verify it on the seeked timeline, as errors (`motion_appears_late`, `motion_out_of_order`, `motion_off_frame`, `motion_frozen`):

```json
{
  "assertions": [
    { "kind": "appearsBy", "selector": "#headline", "bySec": 0.5 },
    { "kind": "before", "a": "#headline", "b": "#cta" },
    { "kind": "staysInFrame", "selector": ".card" },
    { "kind": "keepsMoving", "withinSelector": ".scene" }
  ]
}
```

## Common faults

| Symptom                          | Usual cause                                                                                                                                    |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Blank or frozen scene            | Timeline registered before an async build finished, `visibility` tweened on a `.clip`, or a host id that differs from its scene's timeline key |
| Stutter or drift between renders | Layout measured in `onUpdate`, `requestAnimationFrame` or clock time; two timelines on one property                                            |
| Jump at a cut                    | Outgoing and incoming states differ at the seam: snapshot just before and after it                                                             |
| Ease looks off at a handoff      | Speeds don't match across the seam: mirror the ease (`power3.in` out, `power3.out` in)                                                         |

Studio's keyframe diamonds and motion-path handles rewrite the GSAP script in the composition file, so the person may have changed motion since you wrote it.
references/overlays.md›
# Cards over talking-head footage

Designed cards, lower thirds and split layouts over a person speaking. Placement numbers are for 1920×1080 and 1080×1920; scale others.

## Where the video sits

| Layout                       | Landscape (x, y, w×h) | Portrait (x, y, w×h) |
| ---------------------------- | --------------------- | -------------------- |
| Split, video beside the card | 960, 0, 960×1080      | 0, 960, 1080×960     |
| Stack, video above           | 14, 14, 1892×548      | 0, 0, 1080×844       |
| Overlay                      | full frame            | full frame           |

Picture-in-picture, 28 px from the edges (bottom-right: y = H − h − 28):

| Canvas    | Source | Rectangle         |
| --------- | ------ | ----------------- |
| Landscape | 16:9   | 1432, 28, 460×258 |
| Landscape | 9:16   | 1644, 28, 248×440 |
| Portrait  | 16:9   | 690, 28, 360×203  |
| Portrait  | 9:16   | 738, 28, 312×555  |

- Card zones: a lower third is the bottom 30%; a side panel the right 42% (landscape) or bottom 40% (portrait). Glass card slots: 48, 460, 660×540 landscape; 24, 1280, 1032×564 portrait.
- Portrait type is landscape × 1.3 (titles × 1.35, stats × 1.4); padding drops from 40–64 px to 24–36 px.
- Size a rectangle to its source's aspect: `object-fit: cover` crops a portrait source in a landscape box.

## Timing

- Cards fade in over 0.4 s (`power2.out`) and out over 0.35 s (`power2.in`). Re-frame the video (0.5–0.7 s) only in a gap between cards; move it with transforms on a wrapper, not `left`/`top`/`width`/`height`.
- A static card goes dead after about 8 s; past about 15 s it needs a staged reveal. As a starting density: one card per 6–8 s under a minute, 8–12 s up to 3 min, 12–20 s up to 10 min.
- A transcript's last word can end past the media: clamp card ends and the root `data-duration` to the file's ffprobe length, or the render ends in black.

## Pitfalls

- Lower-third, overlay and picture-in-picture scenes need a transparent root, or they hide the speaker.
- Glass over bright footage goes white on white: put a dark scrim behind it (about `rgba(0,0,0,.55)` fading out from the top, `.72` at the bottom) and a dark glass base such as `rgba(15,16,22,.78)` with `backdrop-filter: blur(22px)`.
- More than 3 timed elements on one track warns `timeline_track_too_dense`: give each chapter its own sub-composition.
- Footage with keyframes more than about 1 s apart freezes on seek: re-encode with `-g <fps>`.

Text behind the speaker (a caption between the footage and a cutout of the person): `captions.md`.
references/plugin-installation.md›
# Running from an installed plugin

Check the directory two levels above the loaded `SKILL.md` for `plugin.json`,
`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, or
`.cursor-plugin/plugin.json` identifying `hyperframes`, or a Gemini extension manifest.
That directory is `<PLUGIN_ROOT>`. If none exists, this is a standalone skill;
follow the normal installation and freshness instructions.

For a plugin installation, these rules replace the standalone update commands
in every skill and reference:

- Do not run `hyperframes skills update`, `skills check`, or `npx skills add`.
  The agent's plugin manager owns installation and updates. Read the skill at
  `<PLUGIN_ROOT>/skills/<name>/SKILL.md`; report a missing bundled skill instead
  of downloading a different release. Resolve all skill references inside this
  bundle, even if a standalone copy is also installed. Claude exposes the router
  as `/hyperframes:hyperframes`; other clients may expose `/hyperframes`.
- Replace `npx hyperframes` (or bare `hyperframes`) in command examples with:

  ```bash
  node "<PLUGIN_ROOT>/skills/hyperframes/scripts/plugin-cli.mjs" <command> <args...>
  ```

  Run in the user's project directory, never in the plugin directory. The launcher
  selects the manifest's CLI version and disables automatic standalone skill
  installation, including during `init`. The first call may download that exact
  CLI version through npm. A download failure is an error, not permission to use
  latest. Existing project dependency files and lockfiles must not be overwritten
  to match the plugin; surface a runtime incompatibility before changing them.

- For bundled Node helpers, use the same
  launcher with `--script <absolute-script-path> <args...>`. This passes the
  release version to their dependency loader. External providers, registry
  downloads, and existing project dependencies have their own versions; a plugin
  version alone does not freeze those inputs.
- Treat the plugin directory as read-only. Put outputs and temporary work in the
  project or a temporary directory. Pass this plugin root and launcher convention
  to any delegated agent so it does not fall back to global skills.

To get newer skills, use the client's plugin update flow and reload the session.
Do not silently update an installed plugin during a video task.

The launcher suppresses CLI update notices as well as standalone skill refreshes, including when running an older CLI with a stale notice cache. It passes the release as `HYPERFRAMES_PLUGIN_VERSION` for plugin-aware helpers and as `HYPERFRAMES_SKILL_PKG_VERSION` for the existing helper-package bootstrap. Our release process versions those packages together.
references/review.md›
# Review

You wrote the timeline, so you know what happens; the viewer sees it once, at full speed, often on a phone. Review is how you see the film as they will: as a harsh motion director, not a proud author. `check` catches what is broken; this pass catches what is weak. Run it after `check` passes, on every new film and on any edit that changes timing or layout, before you reply or render.

## 1. Measure, then look

1. `npx hyperframes check --json`. Besides errors it warns on craft it can measure: a line on screen too briefly to read (`line_unread`), more than 3 s with nothing changing (`dead_stretch`), text under 2.6% of the frame's short side (`text_below_floor`), an empty first frame (`first_frame_empty`, the thumbnail on social players), a film over 15 s with no sound (`film_silent`). A warning is a question, not a rule: fix it, or keep it on purpose and say why in the note (a deliberate hold carries `data-hold`).
2. `npx hyperframes snapshot --review` writes three sheets: each scene just after it starts and at its middle, plus each `data-beat`, a strip across every cut (−0.2 s to +0.2 s), and the beat frames at phone width (360 px). Open all three.
3. Motion between the beats: `snapshot --range START,END --frames 8` around the hero move and the payoff; `keyframes --shot` for a path that must arc or land.
4. The rendered file: `npx hyperframes audio <video> --json` shows whether sound is there, where it hits and how loud it is. You cannot hear it; say so.

Trust the numbers over your eye where they disagree: a clipped label or a 0.6 s line looks fine on a contact sheet.

## 2. What to look for

First, describe each beat frame in one sentence as a stranger would, naming every object; an object you would have to explain ("those bars are mirrors") is a P0. Then read the beat frames in order: do they tell a story a stranger follows, each one causing the next? Compare each with its shot in `SPEC.md`; what does not match is a note. Then name every default the frames show (`spec.md` "BANNED by default", or one not on the list yet), add each to the BANNED line, and fix them before anything else.

Then go through the sheets once per question:

| Question                      | What it catches                                                                                                                                    |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Does it read as what it is?   | A shape that reads as another object, a material that reads as plastic, a product screen that reads as a mockup, a face or mascot that reads wrong |
| Does the subject separate?    | Subject and ground too close in value, two focal points in one frame, the eye with nowhere to go                                                   |
| Framing and contact           | Crops, text at the edge or under platform UI, collisions at a seam, things floating that should touch                                              |
| Timing as a first-time viewer | A read shorter than its words (`story.md` §6), something that pops on with no move, a jump at a cut, everything moving at once on one curve        |
| Tells                         | Which items of `story.md` §8 and `motion.md` "Defaults that look generated" are on screen                                                          |
| The pictures alone            | With the words covered, do the beat frames still tell the storyline?                                                                               |
| The phone sheet               | Can every line meant to be read be read at 360 px?                                                                                                 |

## 3. Notes and fixes

Write each problem as one line: `t=4.2s · where · problem → exact change · what stays`. Rank them:

- **P0** breaks the read or the contract: unreadable, wrong, broken, a tell in the hero shot.
- **P1** weakens the film: a dead stretch, a soft payoff, a seam that jumps.
- **P2** polish.

Fix every P0 and the P1s worth their time: text first, then timing, then detail. Then snapshot only what you changed, once. A second pass only when a P0 is still open. Never score the film out of 10: a score finds nothing to fix. When a fix does not hold, snapshot the frames on either side of the problem, list every difference, and change only the line that causes it. A film that feels busy: remove a third of the motion and keep the moves that lead the eye.

A fresh pair of eyes, when your host can start a sub-agent: give it only `STORYBOARD.md` and the three sheets, ask for P0 and P1 lines in the format above, and keep editing to yourself. Commit or copy the project before you fix, so a fix that makes things worse can be rolled back.

## 4. The delivery note

Write `REVIEW.md` at the project root and put its short form in your reply:

- the creative calls you made, and the storylines you did not build;
- what you measured and what you only looked at; what nobody checked (the sound was measured, not heard);
- the weakest shot, and what you would change with more time;
- warnings you kept on purpose, and why.

The next turn reads it before changing anything.
references/runtimes.md›
# Animation runtimes

HyperFrames seeks each frame on its own, so motion must be a function of time, never of the previous frame. Full GSAP reference: `npx hyperframes docs gsap`.

## GSAP facts that bite

- Register the timeline last. Building inside `document.fonts.ready.then(…)` or another async callback is fine if you register at the end of it; registering first renders blank (`gsap_timeline_registered_before_async_build`).
- Place tweens at absolute seconds (`tl.to(el, {…}, 2.4)`). Set start states in the tween (`fromTo`), not in CSS.
- Measure layout once before building tweens, never inside `onUpdate`.
- Finite repeats only: `repeat: Math.max(0, Math.floor(duration / cycle) - 1)`.
- Seed randomness with a small PRNG (mulberry32).
- A scene's timeline can't reach elements outside its file, and two timelines must not animate the same property of one element.
- Transforms on a `<span>` need `display: inline-block` or `block`.
- Text that must fit a width: `window.__hyperframes.fitTextFontSize(text, { fontFamily, fontWeight, maxWidth })` returns `{ fontSize }`; `window.__hyperframes.pretext` measures text without a reflow.

## Other runtimes

| Runtime                      | Use for                           | How HyperFrames drives it                                                                        |
| ---------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------ |
| CSS `@keyframes`             | Simple loops and decoration       | Found automatically; finite iterations, `animation-fill-mode: both`, `data-start` on the element |
| WAAPI                        | Native keyframes                  | `element.animate(…, { fill: "both" })` then `pause()`; times are document time, so use `delay`   |
| Lottie / dotLottie           | After Effects exports, characters | `autoplay: false`, always set `loop`, push the instance to `window.__hfLottie`                   |
| anime.js v4                  | Existing anime code               | `autoplay: false`, push to `window.__hfAnime`, `anime.createSeededRandom`                        |
| Three.js, WebGPU, p5, canvas | 3D and generative                 | Draw from `e.detail.time` in an `hf-seek` listener; set `data-duration` on the root              |

Charts and map overlays: drive them from a GSAP tween's `onUpdate`, not the library's own transitions. A real place is shown on baked imagery, not drawn (`film.md` "Maps and real places"). Heavy setup (big meshes, shader compiles) registers a `window.__hf.buildReady["<key>"]` promise.

## The seek protocol

```html
<script>
  function renderAt(t) {
    /* draw the frame for t seconds from state derived from t alone */
  }
  window.addEventListener("hf-seek", (e) => {
    renderAt(e.detail.time);
    // async work (GPU submit, image decode): e.detail.waitUntil(promise), called synchronously
  });
  renderAt(0);
</script>
```

No `requestAnimationFrame` loop or timer as the clock.
references/skill-lifecycle.md›
# Skill installation and freshness

Read this reference when installing or updating skills, diagnosing unexpected skill behavior, or running HyperFrames setup in CI.

For a plugin installation, follow [plugin execution rules](plugin-installation.md); the plugin manager owns updates and every skill is bundled. The commands below apply only to standalone skills.

- **Core set, installed eagerly:** `/hyperframes`, `/hyperframes-cli`, `/hyperframes-registry` and `/media-use`.
- **On demand:** `/figma`, `/slideshow` and `/remotion-to-hyperframes`, installed with `npx hyperframes skills update <name>`.

## What `init` does

`npx hyperframes init` checks GitHub and refreshes the core set plus other skills already installed. A current install is a no-op. Offline or rate-limited checks degrade gracefully and do not fail project scaffolding.

The `--skip-skills` CLI flag is temporarily ignored. CI and tests may opt out with `HYPERFRAMES_SKIP_SKILLS=1`.

`HYPERFRAMES_SKIP_SKILLS=1` also makes `skills update` (bare or named) and bare `skills` exit 0 without installing or pruning, so an install pinned to one commit stays pinned.

## Diagnose and update

```bash
npx hyperframes skills check
npx hyperframes skills check --json
npx hyperframes skills update
npx hyperframes skills update <name>
npx hyperframes skills
```

- `skills check` exits non-zero when an installed skill is stale or the core set is incomplete. Skills available on demand but not installed are not failures.
- Bare `skills update` refreshes the core set and everything already installed, and removes skills that are no longer published.
- Named `skills update <name...>` also installs those named skills.
- Bare `skills` installs the full published set explicitly.

If the HyperFrames CLI is unavailable, use `npx skills add heygen-com/hyperframes --skill <name>` for one skill or `npx skills add heygen-com/hyperframes --all` for the full published set.

The CLI may print a one-line stale-skill reminder during `render`, `lint`, or `check`. Treat a failed update as a visible tool failure; do not continue from a remembered skill.

## Keep the project's CLI current

A scaffolded project pins `hyperframes@<version>` in its `package.json` scripts so renders stay reproducible; the pin never advances on its own, and a pinned run of an older CLI prints no warning about it. When resuming a project whose scripts carry a pin, probe once before the first render-affecting command:

```bash
npx hyperframes@latest upgrade --project . --check
```

The probe is read-only and reports the pin against the latest release; keep the explicit `.` — on older CLI releases a bare `--project` followed by another flag consumes that flag as its directory value. When it reports the project behind — or any CLI output already shows it (the stderr notice `This project pins hyperframes@… (latest …)`, or `_meta.updateAvailable: true` in a `--json` result from a pinned script) — apply with `npx hyperframes@latest upgrade --project .`, then verify with `npx hyperframes check`. A passing check confirms the project's compositions still validate on the new version — not that rendered output is frame-identical to the old pin — so a successful bump is never silent: name the old and new version in the run's summary. A project with no composition yet needs no verification. If the check fails, revert the `package.json` change, continue on the pinned version, and report which version the project stays on and why. Act on the signal rather than relaying it to the user; never leave a bumped pin unverified.
references/sound.md›
# Sound

Sound is half of how a film feels, and the viewer notices a silent one before any frame. Every judge of frames misses it, so plan it on purpose.

## 1. The default

- **A film over about 15 s that tells something** (launch, explainer, recap, PR video) gets a music bed and sounds on its visible actions, unless the person asked for silence or it is made to play muted (a looping background, a feed that autoplays without sound: then the type and the pictures carry it alone).
- **A voiceover** only when the request or the source calls for one (`SKILL.md`).
- **Each storyline says its sound** in one line (`story.md` §2): the bed's feel and tempo, the one moment the music changes, which actions get effects, or silence and why.
- **Short pieces** (a title card, a sting under 10 s, captions or overlays on footage that has its own sound) follow the request and the footage.

## 2. The clock

- **Narrated**: the voice is the clock. Time each beat's start to its anchor word; text lands on the word that says it.
- **Music only**: the music is the clock. Land the payoff and the mark on its strong beats (`npx hyperframes beats`, then `film.md` "On music"); cuts follow the motion, not the drums.
- Let the sound act out the story when it fits: the old way sits near silence, the music opens when the product appears; the music holds back until the payoff.

## 3. Music

The person's own track first, as given. Else search the catalog (`/media-use`, `bgm`) with the film's feel and tempo, not its subject ("tense minimal pulse building to a bright drop, 120 bpm"), and read a few results before keeping one. Enter at the track's strongest bar, not a quiet intro; end on a bar line with a short fade under the end card.

With no music source (no `heygen` sign-in and no local model), deliver silent and say so in one line with how to add one. Do not spend the run building an audio chain or writing music in code: nobody can hear whether it is good.

## 4. Effects

Few and exact: every effect has a cause on screen, and too many read as a template.

| Sound             | Only on                                        |
| ----------------- | ---------------------------------------------- |
| click             | a real cursor click on screen                  |
| whoosh            | a scene cut, or a large card moving fast       |
| soft key tick     | text arriving letter by letter or line by line |
| pop               | a badge, chip or number landing                |
| riser into impact | the payoff or the mark, once in the film       |

- Place a clip so its peak lands one frame after its picture (light arrives before sound; a sound early feels wrong sooner than one late): `npx hyperframes audio <file> --json` reports where it hits, and most files peak after their start.
- Each hit sits 3-6 dB over the bed; a bed with most of its energy under 150 Hz plays as nothing on a phone speaker.
- Rotate two or three takes of a repeated sound; the same whoosh on every cut is the most common tell.
- Over strong music, a typing-heavy film can drop the clicks and keys. No synthesised tones, chimes or dings.

## 5. Levels

Music under a voice sits around 0.14 volume; nothing clips (AI audio often does). Keep each file's true peak at −1 dBTP or lower and the mix around −16 to −14 LUFS for social, `npx hyperframes normalize-audio` to match levels (`/media-use`). Check the rendered file with `npx hyperframes audio <video> --json`: sound present, hits where the picture hits, loudness in range. Say in the delivery note that it was measured, not heard (`review.md` §4).
references/spec.md›
# The spec

Adjectives are where the default look lives. Given "premium, modern, clean", the model fills every gap with its own defaults, and they look the same in every run: a dark void, a glow, small type floating in the middle, everything fading in. Every adjective you turn into a number is a decision taken back from those defaults.

So before any scene, write `SPEC.md` at the project root: the film as a director's brief to yourself, with a number wherever you would have written an adjective. Build from it, check against it, and change it when a review changes the film.

The spec serves the story, never the other way round (`story.md`). A look that turns the story's things into abstract shapes has failed, however striking the frame.

## The template

```text
GOAL:    what the viewer feels or does at the end, one sentence
OUTPUT:  HyperFrames, W x H, fps
TIME:    seconds x fps = frames; BPM and frames per beat when there is music
LOOK:    ground (hex, texture and its amount), ink, one accent and where it may appear;
         display face + weight + size in px + tracking, body face the same;
         the medium (paper and ink, photographs, product UI, glass, clay, 3D studio) with its numbers;
         light and depth: shadow offset, blur and opacity, grain %, vignette %
OBJECTS: every thing the story needs the viewer to recognise (a mirror, a clock, a phone, a person),
         drawn with the material and details that make it read without a label: a mirror is
         silvered glass with a bright edge and a mount, not a bar
MOTION:  eases by role as named curves (enter, move, exit, camera); springs as damping ratio;
         durations and staggers in frames
SHOTS:   one line per shot: frame range, the one hero move, what the viewer reads (with its window),
         the sound, and how it hands off to the next shot
BANNED:  the defaults below, plus anything this film must not become
QA:      the frames to check, and what each must show
```

## Turning words into numbers

| The word  | Becomes                                                                                                                                |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| premium   | a material with numbers: satin paper with 2% grain, a product on a seamless sweep with a 0.55 contact shadow, chrome at roughness 0.05 |
| modern    | a camera and a type system: a 30-frame push on one ease, a display face at 9% of the frame height with -2% tracking                    |
| clean     | a grid and a ration: 12 columns, 96 px margins, the accent on one word per shot                                                        |
| dynamic   | cuts on the beat, a layout change on the bar (every 4 beats), one hero move per shot                                                   |
| playful   | springs at damping 0.45-0.6 on position and scale (never on opacity or colour), 10-30% exaggerated poses                               |
| cinematic | a lens and a light: a 35 mm framing, a key from upper left, a rim from behind, 6% vignette                                             |

## Composition numbers

- The hero of a shot fills at least 40% of the frame's height or width. A small object in the middle of an empty frame is never a shot; push in, crop, or give it a world.
- One hero move per shot. Secondary moves travel 50-70% as far, start 100-200 ms after it, and everything else holds still.
- In a story film every shot moves the story: something happens that causes the next shot. Lines read as cause and effect ("so…", "which means…"), not slogans.
- An information card (B-roll over a talking head, a data beat, a vertical explainer card) is different: its information is on screen within about 2.5 s, then it holds, so any paused frame reads as a finished poster.
- Something new every 2-4 s across the film; a new layout every bar when there is music.
- Text meant to be read: at least 28 px at 1080 (2.6% of the short side), held 0.5 s + 0.3 s a word. Too many words: cut words, never shrink type.

## BANNED by default

These are where generated video lands when nothing else is decided. Each is banned unless the brand's own material or the person's reference uses it, and then only as they use it:

- a black or dark navy void as the ground, with small elements floating in its middle;
- glow, bloom, neon, lens flares, glowing orbs and rings; floating particles as decoration;
- purple or blue gradients, mesh gradients, glass cards;
- a cream or off-white paper ground with a serif display, italic accent words, numbered "01 / 02" labels, small monospace caps labels, pill-shaped chips and buttons;
- everything centred; everything fading in at once; every element fading and sliding up;
- text that moves while it is read; titles that keep drifting after they land;
- corner labels, frame borders, corner brackets, HUD readouts as decoration;
- three cards in a row, KPI grids, a title over a dimmed screenshot;
- the logo docked in a corner.

After the first render, name every default it used anyway, from this list or not, add each to the film's BANNED line, and fix them. The list grows with every film.

## A look built from a medium

A look holds together when it is the rules of one medium, not a palette. A risograph look, for example, is five parts and nothing else:

- **paper**: one flat colour with a fibre noise, no gradients;
- **ink A**: every word that must be read and every structural line;
- **ink B**: one fluorescent accent, one focus per frame (the number, the bar, the stamp);
- **overprint**: where the inks overlap a third colour appears, and the B plate sits 3 px off register;
- **halftone**: every gradient is dots.

Motion comes from the same medium: the frame is printed, plate by plate, not faded in. Write any look this way: its parts, what each may carry, and how the medium itself moves (paper is cut and lifted, ink is printed, glass refracts, clay is pressed).
references/story.md›
# Story

Read before building any video that tells something over about 15 s: a launch or promo, an explainer, a pull-request or changelog video, a recap, a music video with a narrative. Most weak films are good motion around no story: a list of features, three cards in a row, a logo, a URL.

Skip it for an edit to an existing project, captions or overlays on footage, and a short single-idea piece (one kinetic line, a map move, a highlight on an image), where the request is the story. When the person gave a script, a storyboard or a reference film, follow theirs. The shape of each kind of film (launch, explainer, music, data, vertical, 3D, character) is in `types/`.

## 1. The one thing

- **What it is**, in one line: "You can now …" for a launch, the one idea the viewer must understand for an explainer, what changed and why it matters for a pull request. A whole release ("2.0", "what's new") is not one thing: take its headline.
- **The one thing to remember** after the film ends, in the viewer's words, not the brand slogan.
- **One viewer doing one real task**, from the moment they need it to the result. That task is the spine, and one real piece of data (a query, a file, a prompt, a number) travels through it from the hook to the payoff. An explainer's spine is one concrete example worked through.
- Keep facts (what it does, real numbers, real labels) apart from creative choices (angle, tone, look). Every claim on screen traces to the source: the site, the PR, the docs or the person's words. Use the person's own copy verbatim.
- What the source does not say stays unsaid: never derive a claim it did not make ("40 min to 2 min" is not "20× faster"), mark an example number as illustrative, and show a screen you cannot get as a labelled placeholder, not a drawn one.

## 2. Three storylines, then one

Write three. They are three different films, not three titles on one film: each has its own **world** (where it happens and what it looks like), its own **device** (what the story is) and its own **form** (how the camera and the cuts carry it). Sketch five or more ideas first, a line each pairing a device with a form (tables below); drop those the subject cannot carry well, and of the rest keep the three that differ most. Make one of the three the bold one you would not have picked first. Each says:

- its name, its device and form in a few words, and in two sentences what the viewer sees and feels;
- the first two seconds, exactly;
- its beats in one line (`hook → … → end card`);
- its look in one line: ground, type, the one material or treatment, taken from the subject or the brand (`look.md`);
- its sound in one line (`sound.md` §1).

The storylines are words: build nothing for any of them before one is picked. Pick a storyline for its story: the one a stranger would follow and remember, with people or a situation they know, ahead of the most striking picture. Picking a storyline picks its look and its sound. Then write the picked one as `SPEC.md`.

Put your recommendation first, with the reason in one clause, and save all three in the project as `STORYLINES.md` so a later turn can switch.

- **The person is there to answer:** show the three and stop once until they pick. A mix ("A's opening with C's middle") is one storyline. Ask nothing else unless only they can answer it (real screens you cannot capture, narrated or music only). State everything else in one assumption line: the length as a range, the aspect, music or voice, and every paid item (a HeyGen voice, an avatar, catalog music) with the free option beside it.
- **They asked for the file or for no stops:** take your recommendation, build it, and name the other two in one line each in your reply.
- With a storyboard (`storyboard.md`), the picked storyline becomes the board's frames.

| Device                        | What the viewer sees                                                                    |
| ----------------------------- | --------------------------------------------------------------------------------------- |
| A moment in someone's day     | a person, a time stamp, the message that creates the need ("launch is tomorrow")        |
| A character does the work     | a mascot or a cursor carries, drags, draws and reacts; every cut has a cause            |
| The feature as the hook       | the feature happening to a recognisable image in the first half second                  |
| The result first              | the finished output plays, then the camera pulls back to show where it came from        |
| One object across apps        | the thing being made travels from one app into the product, which keeps working on it   |
| Life before and after         | the old way as a scene (one person drowning in tabs), then the new way                  |
| Type carries the argument     | giant kinetic type, colour fields and grids that cut on the beat                        |
| A number lands                | one real number earned by a scene, then made physical                                   |
| A question with the input     | "What if you could make a video from your ⟨website⟩?", the blank filled by a real input |
| The object is the explanation | a machine or model whose working is the idea; the camera tours through it               |

| Form                       | What the viewer sees                                                                         |
| -------------------------- | -------------------------------------------------------------------------------------------- |
| One continuous take        | the camera never cuts; it pushes, pulls and travels from one world into the next             |
| Object-to-object chain     | every shot ends on the thing that opens the next: a button becomes a card becomes a phone    |
| An endless zoom            | the camera dives into a detail that turns out to be the next world, again and again          |
| A flight through one world | a 3D set, a map or a desk the camera travels across; each idea is a stop on the route        |
| Cut on the beat            | hard cuts on the music, type and colour fields changing every half beat                      |
| Match cuts                 | one shape holds while everything around it changes: a circle becomes a lens, a wheel, a logo |
| Split screen to one        | several panes play at once, then collapse into the one that matters                          |
| One frame that rebuilds    | a single composition keeps rearranging itself and never leaves the frame                     |

A pitch where all three are plain cuts between scenes has not tried the forms. Before and after: the new side moves calmly while the old side churns; never mock a named competitor. A recap asks one question first so each piece answers it.

## 3. The hook

The first two seconds are the densest of the film, and frame 0 is the thumbnail: never black, never a logo alone. Open on the feature working on something recognisable, a real moment that creates the need, a question with the input inside it, a sentence whose last word becomes the picture, or the bug itself. Then let the old way fail on screen (the wait, the wall of text, the wrong result) before the product brings relief; the proof is often the same shot improved in front of the viewer. Typing into a prompt box as the opener, with nothing else on screen, is the tell of a generated film; as a story step a few seconds in, it is fine.

## 4. Beats

A common shape for a launch of about 35 s; scale it, let the device bend it, and take another type's shape from its file in `types/`:

| Beat     | Share | Job                                                                                       |
| -------- | ----- | ----------------------------------------------------------------------------------------- |
| Hook     | ~8%   | the world and the need, or the feature already working                                    |
| Tension  | ~12%  | the old way failing on screen, or a gag                                                   |
| Reveal   | ~8%   | the product or idea named as a title, about 20–30% in                                     |
| Proof    | ~40%  | the real task in the real UI as 3–6 verbs (_paste, pick, watch it build, ship_), one each |
| Payoff   | ~20%  | the output arrives, already moving: the biggest visual moment, once, 60–80% in            |
| Claim    | ~8%   | the one thing, in one line                                                                |
| End card | ~8%   | the mark carried in, the name and the call to action land and hold 2–3 s                  |

In each verb beat keep only what the cursor touches and what proves the line; delete the rest of the screen. The process runs fast; the payoff plays at normal speed. Length follows the story (a feature launch is usually 20–45 s); a length in the request is a range, never padded.

## 5. Worlds

A film visits several worlds, each with its own background, light and scale (a desktop, the product on a brand gradient, a white void with a character, live action), never one dark canvas with small UI floating in it. Enter each world through an object already on screen: the thing clicked becomes the next shot. Keep one home stage to return to, one carrier the eye follows, and the palette and type constant across worlds.

## 6. Copy and voice

- Titles take at most 6 words a line, one line on screen at a time, no trailing periods; an explainer's lines may run to about 10. A line holds about 0.3 s a word plus 0.5 s, and the last line at least 2 s.
- Show, don't list: no bullet slides or three-card rows. When similar things repeat, each gets its own idea and layout.
- Real numbers only, from the source. The first line is never "Introducing", "Meet" or the logo; after the pain, "Introducing X" is fine. Never "unlock", "seamless", "supercharge", "game-changer", "revolutionize", "effortless", or "in seconds" unless measured.
- Read the lines alone: a stranger gets the pitch. Watch the pictures alone: they tell the same story.
- No voiceover unless the request or the source calls for one; then the screen never repeats the spoken sentence word for word. Without a voice, large titles and the product's own text carry the words, over music and effects (`sound.md`).

## 7. The brand

The mark lands once, with weight, usually on the end card; before it the brand shows only inside the product and the story, never docked small in a corner. The end card is built, not a still: the mark is carried in, typed or wiped in. The accent colour is rationed: one highlighted word, the payoff, the end card.

## 8. Tells of a generated film

The defaults a film falls into when nothing is decided are banned by name in `spec.md` ("BANNED by default"): a dark void with small things floating in it, glow and particles, the cream-paper-serif-mono-label look, everything fading and sliding up, three cards in a row, the docked logo. Beyond those, for a story:

- every scene entering the same way, or the same move beat after beat;
- side-by-side panels and floating cards standing in for a scene;
- titles that keep sliding or shrinking after they land; breathing or floating loops standing in for motion on titles and UI (a character's idle breath is acting, `types/character.md`).

When a first render shows any of them, name which, take the replacement from the subject, the brand or the medium, and add it to the BANNED line of `SPEC.md`.
references/storyboard-format.md›
# Storyboard format — `STORYBOARD.md` + parsed manifest

Defines the storyboard's **base data format** only: the `STORYBOARD.md` file shape and the `StoryboardManifest` it parses into. Planning the film itself is in [story.md](story.md); the optional narration/TTS file (`SCRIPT.md`) is a separate concern, not here.

A storyboard is the **plan layer** for a video — an ordered set of **frames** (key moments) in one markdown file. Parser: `@hyperframes/core/storyboard` → `StoryboardManifest`.

## Frontmatter (global direction)

YAML block at the top. Unknown keys are kept under `globals.extra`.

| Key        | Meaning                                                      | Example                                   |
| ---------- | ------------------------------------------------------------ | ----------------------------------------- |
| `format`   | Canvas size                                                  | `1920x1080`                               |
| `duration` | The length the person asked for (advisory, not a hard limit) | `22s`                                     |
| `message`  | One-line thesis                                              | `Ship a launch video in an afternoon`     |
| `arc`      | Narrative arc                                                | `Hook → Problem → Solution → Proof → CTA` |
| `audience` | Who it's for                                                 | `indie devs on X`                         |
| `mode`     | Interaction mode (default collaborative)                     | `autonomous`                              |

Set `duration` when the storyboard is first written. It is an expectation, not a gate: compare where the cut actually lands against it — judge whether the drift serves the piece, and update the value when the intended length genuinely changes.

## Per-frame sections

One `## Frame N — Title` heading per frame (`Frame` / `Beat` / `Scene` accepted at H2/H3). Metadata as `- key: value` bullets; everything below them until the next heading is the free-form **narrative**.

| Key             | Meaning                                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------------------- |
| `status`        | `outline` → `built` → `animated` (defaults `outline`)                                                               |
| `src`           | project-relative path to the frame's HTML sub-composition (the tile poster renders from it)                         |
| `duration`      | e.g. `4s`                                                                                                           |
| `transition_in` | `crossfade` / `cut` / `wipe` … (alias `transition`); a note only, build it yourself (overlap clips for a crossfade) |
| `scene`         | one-line contact-sheet caption (aliases `description` / `summary` / `caption`)                                      |
| `voiceover`     | the frame's narration _guide_ (aliases `vo` / `voice_over` / `narration`)                                           |
| `poster`        | seconds to seek for the tile poster (past the intro animation)                                                      |
| _any other key_ | kept verbatim under the frame's `extra` — carry your own per-frame data (effects, assets, …) here                   |

Two `extra` keys every film plan carries, so the build and the review check the same thing:

| Key     | Meaning                                                                                                                                                                                                                          |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reads` | what the viewer must understand in this frame, in order, each with its window: `the cursor finds the button (0.4-1.2s); "Export" lands (1.2-2.6s)`. Windows never overlap, and each is long enough for its words (`story.md` §6) |
| `sound` | the bed, the effects on visible actions, or silence: `bed enters strong; click on Export; whoosh on the cut`                                                                                                                     |

## Parsed manifest

The parser is **lenient**: it never throws and records anything surprising as a `warning`.

```
StoryboardManifest {
  globals: { format?, message?, arc?, audience?, extra: {…} }
  frames: Array<{
    index, number?, title?,
    status,                       // "outline" | "built" | "animated"
    src?, duration? / durationSeconds?, transitionIn?,
    scene?, voiceover?, poster?,
    narrative,                    // markdown below the metadata
    extra: {…}                    // unknown keys, preserved
  }>
  warnings: Array<{ message, line?, frameIndex? }>
}
```

## `SCRIPT.md` (out of scope here)

Optional, free-form, **not parsed into the manifest** — the locked-narration file that drives TTS, absent for videos with no narration/TTS. The per-frame `voiceover` above is the storyboard's own narration guide.

## Example

```markdown
---
format: 1920x1080
message: "Ship a launch video in an afternoon"
arc: Hook → Problem → Solution → Proof → CTA
audience: indie devs on X
---

## Frame 1 — Hook

- scene: Big type punches in on the beat
- duration: 3s
- poster: 2s
- transition_in: cut
- status: animated
- voiceover: "Ship a launch video in an afternoon."
- reads: "Ship a launch video" lands (0.3-1.6s); "in an afternoon" (1.6-3.0s)
- sound: bed enters on its strongest bar; impact on "afternoon"
- src: compositions/frames/01-hook.html

Open cold on the promise. This is the thesis — everything after pays it off.

## Frame 2 — The problem

- scene: A 20-minute timer spins on a stack of rejected takes
- duration: 4s
- transition_in: crossfade
- status: built
- voiceover: "The old way? Prompt, wait twenty minutes, get something that misses."
- src: compositions/frames/02-problem.html

The old way: prompt, wait, get something that misses. Establish the pain we remove.
```

## Notes

- A frame with `status: outline` and no built `src` renders as an outline placeholder.
- `built` is the middle rung: the frame's HTML exists and its **layout is confirmed** (a wireframe sketch or better) — motion not yet added. Nothing is drawn yet beyond the approved layout.
- Multi-line `voiceover` values collapse to one line on save.
references/storyboard.md›
# Storyboard recipe — plan a video as stills the person approves

Use it whenever a run makes or changes a storyboard: when the person asks for one, or when you want their go on the shots before animating. This file owns **how to make the board**; `storyboard-format.md` owns the `STORYBOARD.md` file the tools parse.

A film's shots are planned in the SHOTS line of `SPEC.md` (`spec.md`); a storyboard turns those shots into stills when the person wants to approve them first, one frame per shot, carrying its `reads` and `sound` (`storyboard-format.md`).

The person sees the plan as stills on the board, comments or draws on a frame, and says go. The board is `boards/storyboard.html`, and tools that show it find its frames by the classes in § 2; boxes marked any other way (`cell` boxes, `id="frame-NN"` markers) are not found.

## 1. The run

1. **Pose each shot** in the composition the project renders, the root `index.html`, at its key moment: real words, real fonts, real colors, the focal point big enough to read on a phone. A shot posed in its own file (`compositions/frames/NN-*.html`) shows only once it is mounted in `index.html` (`data-composition-src`) at its planned start; `snapshot` renders `index.html` and nothing else. Before the first pose, copy `index.html` to `boards/.film.html`: until the go, the preview plays that film, never the stills (step 4).
2. **Snapshot every still in one run** with your host's snapshot tool when one is offered (each shot's seconds, out `boards/stills`); otherwise `npx hyperframes snapshot --at <every shot's seconds, in film order> --no-end --describe false -o boards/stills`. Each still is named `frame-NN-at-<seconds>s.png`, NN its place in the film. Every `snapshot` run clears its `-o` folder and numbers from `00`, so never snapshot a single moment into `boards/stills/` itself (step 5). Look at every still yourself; fix and snapshot again until each one holds.
3. **Write `boards/storyboard.html`** (§ 2), styled as a finished board: Inter or the system sans-serif, a light page, each frame a rounded card with its still above its words, even gaps.
4. **Put the film back.** Copy the posed `index.html` to `boards/.posed.html`, then `boards/.film.html` back over `index.html`. The board stays on its own page: held stills in the preview read as a slideshow.
5. **Stop.** Hand the person the board and wait for their go. Notes on frames change only those frames. For each one: copy `boards/.posed.html` over `index.html`, re-pose it, snapshot just its moment into `boards/stills/redo/` (`--at <its seconds>`), move that one file over the frame's old still under the old name (its new time in the name if the time changed, and the page's `src` with it), then put the film back (step 4), rewrite the page and stop again. The other stills stay as approved.
6. **Once they say go**, copy `boards/.posed.html` over `index.html` and animate from the approved stills.

## 2. The page

One `class="frame"` box per still, in film order, holding its image, a `class="title"` line "<two-digit number>: <name>" and a `class="time"` range in seconds. Optional: a `class="words"` line under it, and a `class="act"` heading before the first frame of each act.

```html
<section class="act">Act 1: The hook</section>
<div class="frame">
  <img src="stills/frame-00-at-1.5s.png" alt="" />
  <div class="title">01: The hook</div>
  <div class="time">0.0 to 3.0s</div>
  <p class="words">The cursor drops the photo on the timeline; the title lands on the drop.</p>
</div>
```

Stills are written next to the page (`boards/stills/`), so `src` is relative to `boards/` and names the file `snapshot` wrote for that shot. A still written after the page shows by itself; never reopen or rename the page for it.

## 3. The plan beside it

`STORYBOARD.md` at the project root (format: `storyboard-format.md`) names the same frames in the same order, and its words win over the page's: one heading per frame like `## Frame 1: The hook (0-3s)`, a `- act:` bullet for its act, and `- status: outline` until its still is posed.
references/types/3d.md›
# 3D, simulations and generative pieces

Use 3D when the film earns it: an object or a set as the payoff, a product, a simulation that explains. Loading Three.js, WebGPU, p5 or canvas and the seek protocol are in `../runtimes.md`.

## Deterministic by construction

- Draw every frame from timeline time in the `hf-seek` listener; frames render out of order.
- Simulate once at load (physics, agents, particles) into arrays, then look up the state at `t`: `render(t)` stays a pure function. Heavy setup (meshes, shader compiles, the simulation) registers a `window.__hf.buildReady` promise.
- Seed every random choice; a seeded hash of the index, never `Math.random()`.

## Camera and story

- One hero object beats a crowd. Fly through or into it rather than orbiting it: one dive is worth ten turns.
- Cutaways, exploded views and chapters with one caption line each, plus labels with real numbers, turn a simulation into an explanation instead of eye candy.
- Product UI is never tilted into 3D; only generated content tilts.

## Light and materials

- Light it with a warm key, a cool rim and a dark environment with two or three soft strips; turn the environment, not the object, so highlights sweep across it; a contact shadow under the object.
- Bloom eats detail: keep it to the brightest points. Glossy metal under a low light gives white streaks: roughness about 0.45 or more, environment intensity about 0.5.
- One NaN pixel turns a whole frame black through bloom: clamp values that can divide by zero.

## Check it

Snapshot the dive and the payoff (`../review.md`); a frame that takes too long to draw slows every render, so profile the heaviest moment once.
references/types/character.md›
# Characters, mascots and story shorts

A character gives every transition a cause and gives the film its jokes: it carries the result into the player, rejects its own first try, reads the manual. After Effects characters come in through Lottie (`../runtimes.md`).

## Acting

- Mood changes go through a take: anticipation, a squash, the new face, a settle. A face never snaps from one state to the next.
- Squash and stretch, anticipation, overshoot and follow-through belong to the character. Here an idle breath or blink is life, not a tell; the tells about breathing and bounce in `../motion.md` are about titles and UI.
- Code moves every part at once, on one curve, by the same amount. Offset the parts (head leads, arms follow), vary the amounts, and never move both arms, both eyes or a crowd in unison.
- Contact sells it: feet on the ground, props held by the hand, eyes on what they look at.

## Reads

Each action is one read. Lead the eye to where it will happen, play the action fast, and let its meaning land slowly: hold the reaction. A sign or caption that repeats what the action already shows is the classic failure.

## Build

- A rig is a set of parameters over time: position, squash, arm angles, an eye state and a mouth state per moment. Write mood changes as keyed states (`[[t0,"normal"],[t1,"scared"]]`) the rig eases between.
- Hand-drawn life: redraw the outlines about 12 times a second with a jitter seeded by `Math.floor(t * 12)`, so the line boils while the character holds still, and every render matches.
- A long piece is one chapter per sub-composition, all drawing from the same rig and helpers.
references/types/data.md›
# Numbers, charts and data stories

## One number at a time

- A number is earned by the scene before it, then made physical: a count that becomes a skyline, a share that becomes a crowd.
- The number is the biggest thing on screen. Tabular figures (`font-variant-numeric: tabular-nums`) so a count-up does not jitter; a count-up runs 1.2–1.6 s, then holds.
- Real data only, from the source, rounded the same way everywhere, with a small source line on screen. One data module drives every figure in the film.

## Charts as pictures

- Build a chart in reading order: the frame and axis, a reference line, then the data drawing in, then one annotation on the point that matters. Annotate on the mark; no legends.
- Highlight one series, grey the rest. A chart with one thing to say says it in its title.
- The same object carries across beats: a bar becomes the ground of the next scene, a line becomes the road the camera follows.
- The camera can dive into the one value that matters, then the rest falls away.
- Registry charts first (`npx hyperframes catalog --query chart --json`); drive any chart library from a GSAP tween's `onUpdate`, never its own transitions (`../runtimes.md`).

## Tells for this type

A grid of KPI cards; a dashboard; a pie chart; every number counting up at once; numbers with no source.
references/types/explainer.md›
# Explainers, tutorials and "how it works"

One idea the viewer understands by the end (`../story.md` §1). The spine is one concrete example worked through, start to finish, not a tour of facts. Usually 30–90 s.

## Ground it first

Start where the viewer already is: a person, a place or a situation they know, and a question they would ask about it ("why does the astronaut come home younger than her twin?"). The abstract device (a light clock, a graph, a field) enters inside that situation, as the thing that answers the question, and the film returns to the person at the end with the answer. A film that opens on the device alone is a diagram, not an explainer.

## Devices that explain

- **The object is the explanation.** Build a thing whose working is the idea: a machine, a model, a map, a light clock, drawn so it reads as what it is (its material, its mount, where it sits in the scene). Every number on screen comes from it, and the camera tours through it: cutaways, an exploded view, a dive into the part that matters.
- **One diagram that grows.** Start from the simplest true picture and add one part per beat, each appearing when it is named; never a finished diagram dropped on screen at once.
- **Continuous transformation.** Each step morphs out of the last (a bar becomes an axis, a dot becomes a population), so the viewer never loses the thread to a cut.
- **The worked example.** One real input (a sentence, a number, a photo) travels through every step to the answer.

## Shapes of information

Most explainable lines are one of a few shapes, and each has its own picture: a number that lands (`data.md`); a myth turned over (the wrong idea struck through, the right one printed over it); before and after; many things converging into a few groups; a network that densifies; one chosen out of many; steps in order; a real screenshot with the detail circled; a quote set as a poster; a chapter card. Pick the shape first, then the picture, so similar lines get different pictures and every line gets the one that shows it.

## Words

- Narrated: the voice is the clock (`../sound.md` §2). Reveal each part when the voice names it, across the back half of the scene; keep a scene to about 19 spoken words (`../film.md` "Script and pacing").
- Not narrated: large lines carry it, up to about 10 words, each held 0.5 s + 0.3 s a word.
- Labels sit on the part they name, with a leader line when needed, and appear with it. No legends, no bullet slides.

## Getting it right

- Every claim, value and step traces to the source; a number the source did not give is left out or marked illustrative. One data module drives every figure so the numbers agree with each other.
- Check the science or the logic of each step before animating it; a beautiful wrong diagram is worse than none.
- End on the one idea in one line. No brand end card unless asked.

## Tells for this type

A slide of bullets; icons in circles; three cards in a row; the whole diagram at once; a narrator repeating the text on screen word for word.
references/types/launch.md›
# Launch, promo and feature films

The shape is `../story.md` §4 (hook → tension → reveal → proof → payoff → claim → end card), usually 20–45 s. What a viewer shares is real people and real output: in the official films, effects-heavy pieces drew about a tenth of the views of ones built on a real result.

## Real UI, never a mockup

Everything that claims to be the product is the product. One invented button loses everyone who uses it.

1. Where it comes from, best first: the person's recording or screenshots (ask, naming the screen per beat, when the proof needs one you cannot capture); Figma frames, rebuilt as states of one app so the change between two frames is a real click (`/figma`); walking the flow yourself when no sign-in is needed; `npx hyperframes capture` of the feature's own page; the product's code, PR or docs to confirm today's labels.
2. Missing a screen: a clearly labelled placeholder ("Screenshot: Settings → Motion"), said in the reply. A feature with nothing visible (speed, an API): the real UI that exposes it, a real analogue the viewer knows, real terminal output, or a checklist of real facts. Never a made-up dashboard.
3. Logo, icons and fonts are the brand's own files (`/media-use` for logos); never redrawn from memory, never a coloured square for an app icon.

## The product on screen

- Rebuild only the piece the beat uses (the panel, the prompt box, the row that changes) and show it at 1.6–2× its real size; text stays at least 26 px at 1080p after any push. Never a small window in the middle of an empty canvas, never product UI tilted in 3D.
- The camera cycles between the window on its ground, one pane, and a punch-in on one control. Moves take 0.4–0.6 s with motion blur, arrive at the control the cursor needs, then hold while the UI acts.
- Containers morph instead of cutting: the button becomes the menu, the message becomes the bubble.
- Pull back to reveal that the result was playing inside the product, or zoom through the player until the result fills the frame.
- The cursor is big (4–7% of the frame), enters from off-screen, and its click causes the next thing on the same frame.

## Worlds and transitions

Visit several worlds (`../story.md` §5) and enter each through an object on screen, with 3–5 kinds of transition in a film (`../motion.md` "Transitions with a cause").

## The end card

The mark lands in the last 5–10% of the film, centred, the lockup 15–30% of the frame width, performed rather than faded in: the cursor clicks the mark and a fill rises through it, or a character carries it in, then the wordmark wipes in beside it. Then the call to action (a real button, the command to type), held 1.5–3 s.

## Tells for this type

Typing into a prompt box as the opener; a feature list as three cards; a title over a dimmed screenshot; an invented dashboard; the logo docked in a corner.
references/types/music.md›
# Music videos and films cut to a track

The song is the structure. Timing facts (hits, beat trust, loops) are in `../film.md` "On music"; this file is the shape.

## Song structure becomes edit structure

- Find the sections first: `npx hyperframes audio <track> --only sections,onsets,moments --json`. Each section is a chapter: intro, verse, chorus, bridge, outro.
- Cut density follows energy: slower shots in verses, one picture per strong beat in the chorus, a held image on a breakdown.
- The chorus returns to one home stage and escalates each time it comes back; the ending rhymes with the opening.
- Chapter breaks land on section boundaries, with a transition that belongs to the film's medium (a brush wipe, a paper tear, a match cut).

## Pictures for the words

- Each lyric line or phrase gets its own picture (shots of about 1.4–4 s): show what the line means, not the line itself. Lyrics on screen only when asked; then each line holds at least a beat and is set as type, not subtitles.
- Something happens in every shot, and every cut has a cause on screen.
- A song about something real gets real material (footage, photos, places); an abstract track gets one world with a strong medium.

## The look

A physical medium carries a music film further than effects: watercolour with ink outlines, cut paper, risograph print, film grain on footage. Characters, if any, act (`character.md`).

## Tells for this type

The same treatment for more than about 6 s; a spectrum bar or waveform as the main picture; every cut on every beat; words about the music rather than the song's own world.
references/types/vertical.md›
# Vertical and social clips (9:16)

Made at 1080×1920 when the request or the platform says vertical; a landscape film is not squeezed into it but recomposed.

## The platform's own UI

The app draws over the frame. On 1080×1920: nothing in the top 168 px (status and search bar); every word and the product inside the band from 168 to 1330 px; below 1330 px (caption, handle, buttons) only texture or decoration; no text under the right-hand action rail. Ads and some apps reserve more at the bottom; check the platform the clip is for. Burned-in captions sit at the bottom of the band (`../captions.md`).

## The first second

- Frame 0 is the thumbnail and the hook: a face, the result, or one bold line already on screen. Never black, never a logo.
- The viewer decides in about a second whether to stay: the change or the question arrives in the first beat.
- Many viewers watch muted: speech is captioned, and the picture carries the hook without sound.

## Layout

- One subject, centred, stacked vertically; landscape layouts (side-by-side panels, wide charts) are rebuilt as a vertical sequence.
- Type is larger than in landscape (titles about ×1.35, `../overlays.md`); nothing meant to be read under 28 px, and a fixed word limit per line (about 8 Chinese characters or 5 English words for a claim): cut words, never shrink.
- Talking heads: punch-ins on emphasis, B-roll cut on the sentence, the face never under a card (`../overlays.md`).

## Pacing

Faster than landscape: shots of 1–2 s, a new beat every 2–4 s, the payoff before the swipe. A loop that cuts from the last frame back to the first is a choice, not a default.
scripts/bake-basemap.mjs›
// bake-basemap.mjs — bake a real-imagery map camera move for a composition (references/film.md, "Maps").
//
// WHAT IT DOES (and why baking at all): drives MapLibre in headless Chrome to record ONLY the
// real-imagery basemap as an MP4 (camera zoom→hold), and projects each requested country's border
// to SCREEN coordinates at the held view. The HF composition then plays the MP4 on track 0 and
// animates a *live* SVG overlay (border draw-on, colour-block / flag fills, labels, pins) from the
// exported `<NAME>-coords.json`. Borders/fills are NOT baked into the video — they stay editable in HF.
//
// Why bake the imagery at all (this is the real reason, not "smoothness"): HF forbids render-time
// network and requires deterministic output. Live raster tiles re-fetch every render and can change
// → non-deterministic. Baking FREEZES the imagery into pixels = deterministic + offline-reproducible.
// (Exposing the engine's per-frame `onBeforeCapture` hook would let MapLibre run live and smooth, but
// it would NOT remove the need to freeze tiles for determinism — so this bake step stays relevant.)
//
// PARAMETRIC — drive everything by env. Example (Brazil + Argentina on satellite):
//   NAME=br-ar STYLE=satellite COUNTRIES="Brazil:#22d3ee,Argentina:#f59e0b" \
//   CENTER="-60,-25" ZSTART=2.4 ZEND=3.4 FPS=30 DUR=5 node bake-basemap.mjs
// From space down to a street (globe, starfield, a turn from FROM to CENTER, then the dive):
//   NAME=mbs PROJECTION=globe FROM="18,16" CENTER="103.8607,1.2834" ZSTART=1.85 ZEND=16.2 \
//   PITCH=40 DUR=10 HOLD=0.8 node bake-basemap.mjs
// It encodes frames-<NAME>/f%04d.png → <NAME>.mp4 (all-intra: -g 1) and writes <NAME>-coords.json
// for the HF composition. One bake at a time: several headless Chromes on a small machine stall.
import { join } from "node:path";
import { mkdirSync, writeFileSync, readdirSync, existsSync } from "node:fs";
import { homedir } from "node:os";
import { spawnSync } from "node:child_process";

if (process.argv.includes("--help") || process.argv.includes("-h")) {
  console.log(`usage: [NAME=..] [STYLE=satellite|dark|light|<{z}/{x}/{y} url>] [CENTER="lng,lat"]
       [PROJECTION=mercator|globe] [FROM="lng,lat"] [PAN=0.35] [ZSTART=..] [ZEND=..] [PITCH=..]
       [BEARING=..] [FPS=30] [DUR=5] [HOLD=0.5] [COUNTRIES="Name:#hex,Name:#hex"] [OUT=dir]
       [CHROME=path] node bake-basemap.mjs
PROJECTION=globe starts from Earth in space (starfield, atmosphere) and flattens as it zooms in.
FROM pans the camera from that point to CENTER over the first PAN of the move, while the zoom
runs from PAN/2 to HOLD; ZSTART about 1.8 frames the whole globe, ZEND 16 is a building.
Writes <OUT>/<NAME>.mp4 (all-intra basemap) and <OUT>/<NAME>-coords.json (projected borders).
Needs puppeteer-core, ffmpeg, a Chrome or chrome-headless-shell, and network for the tiles.
Run one bake at a time; it prints its pace and an estimate as it goes.`);
  process.exit(0);
}

// Loaded after the --help check so usage prints without puppeteer-core installed.
const { default: puppeteer } = await import("puppeteer-core");

// --- resolve Chrome dynamically (no hardcoded machine path) ---
function resolveChrome() {
  if (process.env.CHROME && existsSync(process.env.CHROME)) return process.env.CHROME;
  const exe = process.platform === "win32" ? "chrome-headless-shell.exe" : "chrome-headless-shell";
  const base = join(homedir(), ".cache", "puppeteer", "chrome-headless-shell");
  if (existsSync(base)) {
    for (const v of readdirSync(base).sort().reverse()) {
      // lexical sort; any working binary is fine
      try {
        for (const inner of readdirSync(join(base, v))) {
          const bin = join(base, v, inner, exe);
          if (existsSync(bin)) return bin;
        }
      } catch {
        /* skip */
      }
    }
  }
  for (const c of [
    "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
    "/usr/bin/google-chrome",
    "/usr/bin/chromium",
    "/usr/bin/chromium-browser",
  ])
    if (existsSync(c)) return c;
  throw new Error(
    "Chrome not found. Set CHROME=/path/to/chrome-headless-shell, or install one:\n" +
      "  npx puppeteer browsers install chrome-headless-shell",
  );
}

// --- params (all overridable by env) ---
const NAME = process.env.NAME || "basemap";
const STYLE = process.env.STYLE || "satellite"; // satellite | dark | light | raw {z}/{x}/{y} template
const CENTER = (process.env.CENTER || "2.6,46.6").split(",").map(Number);
const ZSTART = +(process.env.ZSTART || 4.2);
const ZEND = +(process.env.ZEND || 5.4);
const PITCH = +(process.env.PITCH || 0);
const BEARING = +(process.env.BEARING || 0);
const FPS = +(process.env.FPS || 30),
  DUR = +(process.env.DUR || 5),
  N = Math.max(1, Math.round(FPS * DUR));
const HOLD = +(process.env.HOLD || 0.5); // p∈[0,1] at which the zoom finishes; camera holds after
const PROJECTION = process.env.PROJECTION === "globe" ? "globe" : "mercator";
const FROM = process.env.FROM ? process.env.FROM.split(",").map(Number) : null; // pan start; null = no pan
const PAN = +(process.env.PAN || 0.35); // p at which the pan from FROM reaches CENTER
const MARGIN = (process.env.KEEPMARGIN || "16,13").split(",").map(Number); // [lon°,lat°] keep-box around each country's mainland
// COUNTRIES="Name:#hex,Name:#hex" — borders to project (optional; omit for a pure zoom-to / pin shot)
const COUNTRIES = (process.env.COUNTRIES || "")
  .split(",")
  .map((s) => s.trim())
  .filter(Boolean)
  .map((s) => {
    const [name, color] = s.split(":");
    return { name, color: color || "#38bdf8" };
  });

// fail fast on bad numeric env — otherwise NaN silently bakes zero/garbage frames and still prints "done"
for (const [k, v] of Object.entries({
  "CENTER.lng": CENTER[0],
  "CENTER.lat": CENTER[1],
  ZSTART,
  ZEND,
  PITCH,
  BEARING,
  FPS,
  DUR,
  HOLD,
  PAN,
  ...(FROM ? { "FROM.lng": FROM[0], "FROM.lat": FROM[1] } : {}),
}))
  if (!Number.isFinite(v))
    throw new Error(
      `bad numeric env: ${k}=${v} — check CENTER="lng,lat" / ZSTART / ZEND / FPS / DUR`,
    );

// IMPORTANT: tileSize:256 matches Esri/CARTO raster endpoints. MapLibre's INTERNAL world width is
// 512·2^zoom regardless — a 512px (@2x/retina/vector) tile source needs tileSize:512 or every zoom
// level is off by one. Keep 256 for these raster sources.
const TILES =
  {
    satellite:
      "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
    dark: "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
    light: "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
  }[STYLE] || STYLE; // STYLE may also be a raw {z}/{x}/{y} template

const OUT = process.env.OUT || process.cwd(); // artifacts → workspace (cwd), NOT the installed skill dir
const framesDir = join(OUT, "frames-" + NAME);
mkdirSync(framesDir, { recursive: true });

// A seeded star field behind the globe, so every bake of the same move is identical.
let seed = 7;
const rnd = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const STARS = `<svg id="stars" width="1920" height="1080">${Array.from({ length: 260 }, () => {
  const x = (rnd() * 1920).toFixed(0),
    y = (rnd() * 1080).toFixed(0);
  const r = (rnd() * 1.3 + 0.3).toFixed(2),
    a = (rnd() * 0.6 + 0.2).toFixed(2);
  return `<circle cx="${x}" cy="${y}" r="${r}" fill="#fff" opacity="${a}"/>`;
}).join("")}</svg>`;

// Deps PINNED exact (mutable @5/@2 majors would drift the bake over time).
const PAGE = `<!doctype html><html><head>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/maplibre-gl.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/maplibre-gl.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/topojson-client.min.js"></script>
<style>*{margin:0}html,body{width:1920px;height:1080px;overflow:hidden;background:radial-gradient(ellipse at 50% 55%,#0b1630 0%,#050812 55%,#020308 100%)}#stars{position:absolute;inset:0}#map{position:absolute;inset:0;width:1920px;height:1080px}.maplibregl-control-container{display:none!important}</style>
</head><body>${PROJECTION === "globe" ? STARS : ""}<div id="map"></div><script>
var CENTER=${JSON.stringify(CENTER)}, ZSTART=${ZSTART}, ZEND=${ZEND}, PITCH=${PITCH}, BEARING=${BEARING}, HOLD=${HOLD};
var FROM=${JSON.stringify(FROM)}, PAN=${PAN};
var MARGIN=${JSON.stringify(MARGIN)}, WANT=${JSON.stringify(COUNTRIES)};
var GLOBE=${PROJECTION === "globe"};
// On the globe the page behind the map shows through (stars), and the atmosphere fades out as the camera nears the ground.
var map=new maplibregl.Map({container:"map",style:{version:8,projection:{type:GLOBE?"globe":"mercator"},
  sky:GLOBE?{"atmosphere-blend":["interpolate",["linear"],["zoom"],0,1,5,1,7,0]}:undefined,
  sources:{s:{type:"raster",tiles:[${JSON.stringify(TILES)}],tileSize:256,maxzoom:19}},
  layers:(GLOBE?[]:[{id:"bg",type:"background",paint:{"background-color":"#05070d"}}]).concat([{id:"s",type:"raster",source:"s"}])},
  center:FROM||CENTER,zoom:ZSTART,pitch:0,bearing:0,interactive:false,attributionControl:false,fadeDuration:0,preserveDrawingBuffer:true,maxTileCacheSize:6000,
  canvasContextAttributes:{alpha:GLOBE,preserveDrawingBuffer:true}});
function ease(x){return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;} // easeInOutCubic (= Remotion interpolate+Easing)
function sine(x){return -(Math.cos(Math.PI*x)-1)/2;}
function seg(p,a,b){return b<=a?1:Math.min(1,Math.max(0,(p-a)/(b-a)));}
// With FROM, the camera turns to CENTER over [0,PAN] while the zoom runs over [PAN/2,HOLD]; without it, the zoom runs over [0,HOLD].
function camAt(p){
  var s=FROM?sine(seg(p,0,PAN)):1, t=ease(seg(p,FROM?PAN/2:0,HOLD));
  var c=FROM?[FROM[0]+(CENTER[0]-FROM[0])*s, FROM[1]+(CENTER[1]-FROM[1])*s]:CENTER;
  return {center:c, zoom:ZSTART+(ZEND-ZSTART)*t, pitch:PITCH*t, bearing:BEARING*t}; }
function ringCentroid(r){ var sx=0,sy=0; for(var k=0;k<r.length;k++){sx+=r[k][0];sy+=r[k][1];} return [sx/r.length, sy/r.length]; }
// Keep the polygons in a lon/lat box around the country's MAINLAND (the vertex-richest polygon),
// dropping far-flung overseas territories that would blow up the bbox. Generalizes per subject —
// no continent-specific constant. Keeps near islands (Corsica, Sicily); drops Guiana, Alaska, Hawaii.
function mainland(f){
  if(!f) return f;
  if(f.geometry.type!=="MultiPolygon"){ f.__anchorRing=f.geometry.coordinates[0]; return f; } // Polygon: outer ring
  var polys=f.geometry.coordinates, anchor=polys[0], amax=-1;
  polys.forEach(function(poly){ if(poly[0].length>amax){amax=poly[0].length;anchor=poly;} });
  var ac=ringCentroid(anchor[0]);
  var kept=polys.filter(function(poly){ var c=ringCentroid(poly[0]); return Math.abs(c[0]-ac[0])<=MARGIN[0] && Math.abs(c[1]-ac[1])<=MARGIN[1]; });
  var nf={type:"Feature",properties:f.properties,geometry:{type:"MultiPolygon",coordinates:kept}}; nf.__anchorRing=anchor[0]; return nf;
}
function lonSpan(f){ var mn=1e9,mx=-1e9; (f.geometry.type==="Polygon"?[f.geometry.coordinates]:f.geometry.coordinates).forEach(function(poly){poly[0].forEach(function(c){if(c[0]<mn)mn=c[0];if(c[0]>mx)mx=c[0];});}); return mx-mn; }
// ANTIMERIDIAN unwrap (Russia/Fiji/NZ): make all lon contiguous around the camera-center ref so a
// feature touching ±180° doesn't smear when map.project() runs per-vertex (mercatorX is linear and
// accepts out-of-range lon, so 181 sits just east of center, not far-west at -179). Mutates in place;
// __anchorRing shares the same arrays so it's covered.
function unwrapLon(f, ref){ if(!f) return; function fix(r){ for(var i=0;i<r.length;i++){ var lon=r[i][0]; while(lon-ref>180)lon-=360; while(lon-ref<-180)lon+=360; r[i][0]=lon; } }
  var g=f.geometry; if(g.type==="Polygon") g.coordinates.forEach(fix); else g.coordinates.forEach(function(poly){ poly.forEach(fix); }); }
var FEATS=[]; window.__warn=[];
window.__ready=new Promise(function(res){ map.on("load", function(){
  if(!WANT.length){ res(); return; }
  fetch("https://cdn.jsdelivr.net/npm/[email protected]/countries-110m.json").then(function(r){return r.json();}).then(function(w){
    var fc=topojson.feature(w,w.objects.countries);
    WANT.forEach(function(want){
      var f=fc.features.filter(function(x){return x.properties.name===want.name;})[0];
      if(!f){ window.__warn.push("country not found in world-atlas: "+want.name); return; }
      f=mainland(f);
      unwrapLon(f, CENTER[0]); // antimeridian: unwrap lons around the camera ref (CENTER must be near the subject) before projecting
      if(lonSpan(f)>180) window.__warn.push(want.name+" spans >180° lon even after unwrap — projection may still smear.");
      FEATS.push({name:want.name, color:want.color, f:f});
    });
    res();
  });
});});
window.__setCam=function(p){ map.jumpTo(camAt(p)); };
// Resolves false once every tile for this camera is loaded and painted, true if ms passed first.
// Polls rather than waiting for "idle": that event can fire before a listener is attached, which
// left every cached frame sitting out the whole timeout.
window.__waitIdle=function(ms){ return new Promise(function(res){ var t0=performance.now();
  (function poll(){ if(map.loaded()&&map.areTilesLoaded()){ map.triggerRepaint(); requestAnimationFrame(function(){requestAnimationFrame(function(){res(false);});}); return; }
    if(performance.now()-t0>(ms||9000)){ res(!map.areTilesLoaded()); return; } setTimeout(poll,40); })(); }); };
function featurePath(f){ function ring(r){ return r.map(function(c,i){ var p=map.project(c); return (i?"L":"M")+p.x.toFixed(1)+" "+p.y.toFixed(1); }).join(" ")+"Z"; }
  var g=f.geometry,d=""; if(g.type==="Polygon") g.coordinates.forEach(function(r){d+=ring(r);}); else g.coordinates.forEach(function(poly){poly.forEach(function(r){d+=ring(r);});}); return d; }
function bboxOf(f){ var mnx=1e9,mny=1e9,mxx=-1e9,mxy=-1e9;
  function eat(r){ r.forEach(function(c){ var p=map.project(c); if(p.x<mnx)mnx=p.x; if(p.y<mny)mny=p.y; if(p.x>mxx)mxx=p.x; if(p.y>mxy)mxy=p.y; }); }
  var g=f.geometry; if(g.type==="Polygon")g.coordinates.forEach(eat); else g.coordinates.forEach(function(poly){poly.forEach(eat);});
  return {x:+mnx.toFixed(1),y:+mny.toFixed(1),w:+(mxx-mnx).toFixed(1),h:+(mxy-mny).toFixed(1)}; }
window.__project=function(){ return {
  view:{center:CENTER, zoom:ZEND, pitch:PITCH, bearing:BEARING},
  countries: FEATS.map(function(e){ var lc=ringCentroid(e.f.__anchorRing); var lp=map.project(lc);
    return { name:e.name, color:e.color, d:featurePath(e.f), bbox:bboxOf(e.f), label:{x:+lp.x.toFixed(1),y:+lp.y.toFixed(1)} }; }),
}; };
</script></body></html>`;

// --no-sandbox is intentional: trusted Source-time bake, headless, often root/CI; deps are version-pinned above.
const browser = await puppeteer.launch({
  executablePath: resolveChrome(),
  headless: true,
  args: [
    "--no-sandbox",
    "--hide-scrollbars",
    "--use-gl=angle",
    "--use-angle=swiftshader",
    "--enable-unsafe-swiftshader",
    "--enable-webgl",
    "--window-size=1920,1080",
  ],
});
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 });
  await page.setContent(PAGE, { waitUntil: "load" });
  await page.evaluate(() => window.__ready);
  for (const w of await page.evaluate(() => window.__warn)) console.warn(`[${NAME}] WARN: ${w}`);
  console.log(
    `[${NAME}] ready (${STYLE}, ${PROJECTION}); baking ${N} frames, zoom ${ZSTART}→${ZEND} hold@p=${HOLD}, ${COUNTRIES.length} border(s)`,
  );
  const t0 = Date.now();
  let coords = null;
  const timeouts = [];
  for (let i = 0; i < N; i++) {
    const p = N === 1 ? 1 : i / (N - 1);
    await page.evaluate((pp) => window.__setCam(pp), p);
    const timedOut = await page.evaluate((ms) => window.__waitIdle(ms), 9000);
    if (timedOut) {
      timeouts.push(i);
      console.warn(`[${NAME}] idle TIMEOUT at frame ${i} — tiles may be incomplete`);
    }
    await page.screenshot({
      path: join(framesDir, `f${String(i).padStart(4, "0")}.png`),
      clip: { x: 0, y: 0, width: 1920, height: 1080 },
      optimizeForSpeed: true,
    });
    if (p >= HOLD && !coords && COUNTRIES.length)
      coords = await page.evaluate(() => window.__project()); // capture at first hold frame
    if (i % 20 === 0 || i === N - 1) {
      const per = (Date.now() - t0) / 1000 / (i + 1);
      console.log(
        `  [${NAME}] ${i + 1}/${N} · ${per.toFixed(2)} s/frame · about ${Math.ceil((per * (N - i - 1)) / 60)} min left`,
      );
    }
  }
  // encode frames → all-intra MP4 (every frame seekable for HF); fall back to printing the command if ffmpeg is absent
  const mp4 = join(OUT, NAME + ".mp4"),
    pat = join(framesDir, "f%04d.png");
  const ff = spawnSync(
    "ffmpeg",
    [
      "-y",
      "-framerate",
      String(FPS),
      "-i",
      pat,
      "-c:v",
      "libx264",
      "-pix_fmt",
      "yuv420p",
      "-g",
      "1",
      "-crf",
      "16",
      "-movflags",
      "+faststart",
      mp4,
    ],
    { stdio: "ignore" },
  );
  if (ff.status === 0) console.log(`[${NAME}] encoded → ${mp4}`);
  else
    console.warn(
      `[${NAME}] ffmpeg unavailable (status ${ff.status}) — encode manually:\n  ffmpeg -y -framerate ${FPS} -i ${pat} -c:v libx264 -pix_fmt yuv420p -g 1 -crf 16 -movflags +faststart ${mp4}`,
    );
  if (coords) {
    writeFileSync(join(OUT, NAME + "-coords.json"), JSON.stringify(coords));
    console.log(
      `[${NAME}] coords written: ${coords.countries.map((c) => c.name + "(" + c.d.length + "ch)").join(", ")}`,
    );
  }
  if (timeouts.length) {
    // FAIL LOUD: the asset exists but is suspect — don't let a half-loaded bake pass silently
    console.error(
      `[${NAME}] ${timeouts.length}/${N} frame(s) hit the idle timeout (frames ${timeouts.slice(0, 8).join(",")}${timeouts.length > 8 ? "…" : ""}). The basemap MP4 may have INCOMPLETE tiles. Re-run with a slower zoom / larger timeout / check the tile server.`,
    );
    process.exitCode = 1;
  } else {
    console.log(`[${NAME}] done — all ${N} frames reached map idle (complete tiles).`);
  }
} finally {
  await browser.close();
}
scripts/lib/main-module.mjs›
import { realpathSync } from "node:fs";
import { pathToFileURL } from "node:url";

// Node resolves symlinks in the main module's import.meta.url but argv[1] keeps the invoked
// spelling (a symlinked skills dir, or /tmp vs /private/tmp on macOS), so compare real paths.
export function isMainModule(importMetaUrl) {
  if (!process.argv[1]) return false;
  try {
    return pathToFileURL(realpathSync(process.argv[1])).href === importMetaUrl;
  } catch {
    return false;
  }
}
scripts/lib/main-module.test.mjs›
import { strict as assert } from "node:assert";
import { test } from "node:test";
import { mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { dirname, join, relative, resolve } from "node:path";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";

const SKILLS = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
const CLI_MEDIA_USE = resolve(SKILLS, "../packages/cli/src/media-use");

// Agents install skills by symlinking the skills dir, so each script must still run its CLI.
test("skill CLIs run when invoked through a symlinked skills dir", () => {
  const dir = mkdtempSync(join(tmpdir(), "skills-link-"));
  try {
    symlinkSync(SKILLS, join(dir, "skills"), "dir");
    for (const script of ["media-use/audio/scripts/heygen-voice.mjs"]) {
      const run = spawnSync(process.execPath, [join(dir, "skills", script), "not-a-command"], {
        encoding: "utf8",
      });
      assert.notEqual(run.status, 0, `${script} exited ${run.status} without running its CLI`);
      assert.match(run.stderr, /usage/, script);
    }
  } finally {
    rmSync(dir, { recursive: true, force: true });
  }
});

function skillScripts(dir) {
  return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
    const path = join(dir, entry.name);
    if (entry.isDirectory()) return entry.name === "node_modules" ? [] : skillScripts(path);
    return /\.(mjs|js)$/.test(entry.name) && !/\.test\.mjs$/.test(entry.name) ? [path] : [];
  });
}

test("skill scripts detect their CLI entry through lib/main-module.mjs only", () => {
  const handRolled = [...skillScripts(SKILLS), ...skillScripts(CLI_MEDIA_USE)].filter(
    (path) =>
      !path.endsWith("main-module.mjs") && readFileSync(path, "utf8").includes("process.argv[1]"),
  );
  assert.deepEqual(
    handRolled.map((path) => relative(SKILLS, path)),
    [],
  );
});
scripts/locate.mjs›
#!/usr/bin/env node
/* locate.mjs — find "X" in an image WITHOUT any detector API or key.
 *
 * You (the author model) are weak at regressing pixel coordinates but strong at
 * "which numbered strip is the target in?" — so localization = RSVP grid-index
 * (ACL 2025 long.715): pick strips on a numbered grid, reconstruct the box
 * geometrically. Zero npm deps — node + ffmpeg (both already required by
 * hyperframes). Accuracy notes: references/film.md.
 *
 * THE LOOP (you read images between steps):
 *   1) node locate.mjs overlay <img> --out DIR
 *        → DIR/gv.png (vertical strips 1..9) + DIR/gh.png (horizontal 1..9).
 *        READ both, pick the strip numbers the target spans.
 *   2) node locate.mjs region <img> --vids 4,5 --hids 6,7 --out DIR
 *        → prints coarse region + writes DIR/gc.png (region cropped+upscaled,
 *        finer 6×6 grid). READ it, pick the finer strips.
 *   3) node locate.mjs final <img> --region x0,y0,x1,y1 --vids 3,4 --hids 3,4
 *        → {box, center} normalized to the FULL image.
 *   4) node locate.mjs mark <img> --box x0,y0,x1,y1 --out DIR/check.png
 *        → VERIFY: read check.png — is the red box on the target? If off,
 *        redo step 2/3 with corrected strips. Never skip this.
 */
import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, copyFileSync } from "node:fs";

const N1 = 9,
  PAD1 = 0.4; // stage 1: strips per axis, padding in STRIP units
const N2 = 6,
  PAD2 = 0.25; // stage 2 (crop)
const GREEN = "0x3CDC5A",
  BLUE = "0x3C82FF",
  RED = "0xFF3232";

const ff = (args) =>
  execFileSync("ffmpeg", ["-v", "error", "-y", ...args], {
    stdio: ["ignore", "ignore", "inherit"],
  });
function probe(img) {
  const out = execFileSync("ffprobe", [
    "-v",
    "error",
    "-select_streams",
    "v:0",
    "-show_entries",
    "stream=width,height",
    "-of",
    "csv=p=0",
    "--",
    img,
  ])
    .toString()
    .trim();
  const [w, h] = out.split(",").map(Number);
  return { w, h };
}
function font() {
  const dst = "/tmp/locate-font.ttf";
  if (!existsSync(dst)) {
    for (const c of [
      "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
      "/System/Library/Fonts/Supplemental/Arial.ttf",
      "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
    ]) {
      if (existsSync(c)) {
        copyFileSync(c, dst);
        break;
      }
    }
  }
  return dst;
}
function gridFilters(w, h, n, axis, fz) {
  const F = font(),
    fs = [];
  if (axis === "v" || axis === "both") {
    for (let i = 1; i < n; i++)
      fs.push(`drawbox=x=${Math.round((i * w) / n)}:y=0:w=2:h=${h}:color=${GREEN}@0.8:t=fill`);
    for (let i = 0; i < n; i++)
      fs.push(
        `drawtext=fontfile=${F}:text='${i + 1}':x=${Math.round(((i + 0.5) * w) / n - fz * 0.3)}:y=6:fontsize=${fz}:fontcolor=black:box=1:[email protected]:boxborderw=5`,
      );
  }
  if (axis === "h" || axis === "both") {
    for (let j = 1; j < n; j++)
      fs.push(`drawbox=x=0:y=${Math.round((j * h) / n)}:w=${w}:h=2:color=${BLUE}@0.8:t=fill`);
    for (let j = 0; j < n; j++)
      fs.push(
        `drawtext=fontfile=${F}:text='${j + 1}':x=6:y=${Math.round(((j + 0.5) * h) / n - fz * 0.5)}:fontsize=${fz}:fontcolor=black:box=1:[email protected]:boxborderw=5`,
      );
  }
  return fs;
}
const ids = (s) => String(s).split(",").filter(Boolean).map(Number);
const regionOf = (v, h, n, pad) => [
  Math.max(Math.min(...v) - 1 - pad, 0) / n,
  Math.max(Math.min(...h) - 1 - pad, 0) / n,
  Math.min(Math.max(...v) + pad, n) / n,
  Math.min(Math.max(...h) + pad, n) / n,
];
const r4 = (x) => Math.round(x * 1e4) / 1e4;
const out = (o) => console.log(JSON.stringify(o));

// --- tiny arg parsing: locate.mjs <cmd> <img?> --k v ---
const [cmd, ...rest] = process.argv.slice(2);
const pos = rest.filter((a) => !a.startsWith("--"));
const opt = {};
for (let i = 0; i < rest.length; i++)
  if (rest[i].startsWith("--")) opt[rest[i].slice(2)] = rest[i + 1];
const img = pos[0];

if (cmd === "overlay") {
  const dir = opt.out || ".";
  mkdirSync(dir, { recursive: true });
  const n = Number(opt.n || N1);
  const { w, h } = probe(img);
  const fz = Math.max(16, Math.round(Math.min(w, h) / 22));
  ff(["-i", img, "-vf", gridFilters(w, h, n, "v", fz).join(","), `${dir}/gv.png`]);
  ff(["-i", img, "-vf", gridFilters(w, h, n, "h", fz).join(","), `${dir}/gh.png`]);
  out({
    w,
    h,
    n,
    vertical: `${dir}/gv.png`,
    horizontal: `${dir}/gh.png`,
    next: "READ gv.png + gh.png, then: locate.mjs region <img> --vids .. --hids .. --out " + dir,
  });
} else if (cmd === "region") {
  const dir = opt.out || ".";
  mkdirSync(dir, { recursive: true });
  const n = Number(opt.n || N1),
    nf = Number(opt.nf || N2);
  const reg = regionOf(ids(opt.vids), ids(opt.hids), n, PAD1);
  const { w, h } = probe(img);
  const cx = Math.floor(reg[0] * w),
    cy = Math.floor(reg[1] * h);
  const cw = Math.max(2, Math.floor((reg[2] - reg[0]) * w)),
    ch = Math.max(2, Math.floor((reg[3] - reg[1]) * h));
  const uw = Math.max(cw, 640),
    uh = Math.round((ch * uw) / cw);
  const fz = Math.max(15, Math.round(Math.min(uw, uh) / 16));
  ff([
    "-i",
    img,
    "-vf",
    [
      `crop=${cw}:${ch}:${cx}:${cy}`,
      `scale=${uw}:${uh}`,
      ...gridFilters(uw, uh, nf, "both", fz),
    ].join(","),
    `${dir}/gc.png`,
  ]);
  out({
    region: reg.map(r4),
    crop_grid: `${dir}/gc.png`,
    nf,
    next: `READ gc.png, then: locate.mjs final <img> --region ${reg.map(r4).join(",")} --vids .. --hids ..`,
  });
} else if (cmd === "final") {
  const nf = Number(opt.nf || N2);
  const reg = opt.region.split(",").map(Number);
  const rl = regionOf(ids(opt.vids), ids(opt.hids), nf, PAD2);
  const [x0, y0, x1, y1] = reg;
  const box = [
    x0 + rl[0] * (x1 - x0),
    y0 + rl[1] * (y1 - y0),
    x0 + rl[2] * (x1 - x0),
    y0 + rl[3] * (y1 - y0),
  ].map(r4);
  out({
    box,
    center: [r4((box[0] + box[2]) / 2), r4((box[1] + box[3]) / 2)],
    next:
      "VERIFY: locate.mjs mark <img> --box " +
      box.join(",") +
      " --out <dir>/check.png — then READ it",
  });
} else if (cmd === "mark") {
  const box = opt.box.split(",").map(Number);
  const { w, h } = probe(img);
  const bx = Math.round(box[0] * w),
    by = Math.round(box[1] * h);
  const bw = Math.max(2, Math.round((box[2] - box[0]) * w)),
    bh = Math.max(2, Math.round((box[3] - box[1]) * h));
  const dst = opt.out || "./check.png";
  ff(["-i", img, "-vf", `drawbox=x=${bx}:y=${by}:w=${bw}:h=${bh}:color=${RED}@1:t=6`, dst]);
  out({
    marked: dst,
    next: "READ it — red box on the target? If off, redo region/final with corrected strips.",
  });
} else if (cmd === "auto") {
  // OPTIONAL fast path — only if a strong detector key happens to exist. Never assume it.
  const key = process.env.GEMINI_API_KEY;
  if (!key) {
    console.error(
      "auto needs GEMINI_API_KEY; use the grid loop instead (overlay→region→final→mark)",
    );
    process.exit(1);
  }
  const target = pos[1];
  const b64 = execFileSync("base64", ["-i", img]).toString().replace(/\n/g, "");
  const mime = img.match(/\.png$/i) ? "image/png" : "image/jpeg";
  const body = {
    contents: [
      {
        parts: [
          { inline_data: { mime_type: mime, data: b64 } },
          {
            text: `Detect "${target}". Return ONLY {"box_2d":[ymin,xmin,ymax,xmax]} integers 0-1000.`,
          },
        ],
      },
    ],
    generationConfig: { temperature: 0, response_mime_type: "application/json" },
  };
  const res = await fetch(
    `https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:generateContent?key=${key}`,
    { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) },
  );
  const t = (await res.json()).candidates[0].content.parts[0].text;
  let bb = JSON.parse(t.match(/\{[\s\S]*\}/)[0]).box_2d;
  if (Array.isArray(bb[0])) bb = bb[0];
  const s = Math.max(...bb) > 1.5 ? 1000 : 1;
  const box = [bb[1] / s, bb[0] / s, bb[3] / s, bb[2] / s].map(r4);
  out({ box, center: [r4((box[0] + box[2]) / 2), r4((box[1] + box[3]) / 2)] });
} else {
  console.error("usage: locate.mjs overlay|region|final|mark|auto … (see header comment)");
  process.exit(1);
}
scripts/plugin-cli.mjs›
#!/usr/bin/env node
// Run from the user's project; locate the release from this installed file.
import { spawnSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join, win32 } from "node:path";
import { fileURLToPath } from "node:url";
import { isMainModule } from "./lib/main-module.mjs";

const root = fileURLToPath(new URL("../../../", import.meta.url));
const manifests = [
  "plugin.json",
  ".claude-plugin/plugin.json",
  ".codex-plugin/plugin.json",
  ".cursor-plugin/plugin.json",
  "gemini-extension.json",
];

export function pluginVersion(pluginRoot = root) {
  for (const path of manifests) {
    const file = join(pluginRoot, path);
    if (!existsSync(file)) continue;
    const manifest = JSON.parse(readFileSync(file, "utf8"));
    if (
      manifest.name !== "hyperframes" ||
      !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.version ?? "")
    ) {
      throw new Error(`Invalid HyperFrames plugin release: ${file}`);
    }
    return manifest.version;
  }
  throw new Error(
    "No HyperFrames plugin manifest found. Use the standalone skills installation instructions.",
  );
}

export function invocation(
  args,
  {
    version = pluginVersion(),
    env = process.env,
    platform = process.platform,
    node = process.execPath,
    pathExists = existsSync,
  } = {},
) {
  if (args[0] === "skills")
    throw new Error(
      "Update HyperFrames through your agent's plugin manager; bundled skills are release-managed.",
    );
  const childEnv = {
    ...env,
    HYPERFRAMES_SKIP_SKILLS: "1",
    HYPERFRAMES_SKILL_PKG_VERSION: version,
    HYPERFRAMES_PLUGIN_VERSION: version,
    HYPERFRAMES_NO_UPDATE_CHECK: "1",
  };
  if (args[0] === "--script") {
    if (!args[1]) throw new Error("--script requires a Node script path.");
    return { command: node, args: args.slice(1), env: childEnv };
  }
  const cliArgs = ["--yes", `hyperframes@${version}`, ...args];
  if (platform !== "win32") return { command: "npx", args: cliArgs, env: childEnv };
  const candidates = [
    env.npm_execpath && win32.join(win32.dirname(env.npm_execpath), "npx-cli.js"),
    win32.join(win32.dirname(node), "node_modules", "npm", "bin", "npx-cli.js"),
  ].filter(Boolean);
  const npx = candidates.find(pathExists);
  if (!npx)
    throw new Error(
      "Cannot find npx-cli.js. Install Node.js with npm or run from an npm environment.",
    );
  return { command: node, args: [npx, ...cliArgs], env: childEnv };
}

if (isMainModule(import.meta.url)) {
  try {
    const command = invocation(process.argv.slice(2));
    const result = spawnSync(command.command, command.args, {
      env: command.env,
      stdio: "inherit",
      windowsHide: true,
    });
    if (result.error) throw result.error;
    process.exitCode = result.status ?? 1;
  } catch (error) {
    console.error(error.message);
    process.exitCode = 1;
  }
}
scripts/plugin-cli.test.mjs›
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import {
  copyFileSync,
  mkdirSync,
  mkdtempSync,
  readFileSync,
  realpathSync,
  rmSync,
  writeFileSync,
} from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { test } from "node:test";
import { invocation, pluginVersion } from "./plugin-cli.mjs";

function fixture(t, manifest = "plugin.json", version = "1.2.3") {
  const root = mkdtempSync(join(tmpdir(), "hf-plugin-"));
  t.after(() => rmSync(root, { recursive: true, force: true }));
  mkdirSync(join(root, "skills/hyperframes/scripts/lib"), { recursive: true });
  mkdirSync(join(root, manifest, ".."), { recursive: true });
  writeFileSync(join(root, manifest), JSON.stringify({ name: "hyperframes", version }));
  const launcher = join(root, "skills/hyperframes/scripts/plugin-cli.mjs");
  copyFileSync(new URL("./plugin-cli.mjs", import.meta.url), launcher);
  copyFileSync(
    new URL("./lib/main-module.mjs", import.meta.url),
    join(root, "skills/hyperframes/scripts/lib/main-module.mjs"),
  );
  return { root, launcher };
}

for (const manifest of [
  "plugin.json",
  ".claude-plugin/plugin.json",
  ".codex-plugin/plugin.json",
  ".cursor-plugin/plugin.json",
  "gemini-extension.json",
]) {
  test(`locates ${manifest} without repository packages`, (t) => {
    const { root } = fixture(t, manifest);
    assert.equal(pluginVersion(root), "1.2.3");
  });
}

test("rejects missing and floating plugin versions", (t) => {
  const { root } = fixture(t, "plugin.json", "latest");
  assert.throws(() => pluginVersion(root), /Invalid HyperFrames plugin release/);
  rmSync(join(root, "plugin.json"));
  assert.throws(() => pluginVersion(root), /No HyperFrames plugin manifest/);
});

test("pins CLI, suppresses standalone refresh, preserves unrelated environment", () => {
  const result = invocation(["init", "project with spaces", "--non-interactive"], {
    version: "1.2.3",
    platform: "darwin",
    env: { PATH: "/bin", HYPERFRAMES_SKIP_SKILLS: "0" },
  });
  assert.equal(result.command, "npx");
  assert.deepEqual(result.args, [
    "--yes",
    "[email protected]",
    "init",
    "project with spaces",
    "--non-interactive",
  ]);
  assert.deepEqual(result.env, {
    PATH: "/bin",
    HYPERFRAMES_SKIP_SKILLS: "1",
    HYPERFRAMES_SKILL_PKG_VERSION: "1.2.3",
    HYPERFRAMES_PLUGIN_VERSION: "1.2.3",
    HYPERFRAMES_NO_UPDATE_CHECK: "1",
  });
  assert.throws(() => invocation(["skills", "update"], { version: "1.2.3" }), /plugin manager/);
});

test("Windows launches npx through Node without shell argument interpretation", () => {
  const result = invocation(["render", "a & b"], {
    version: "1.2.3",
    platform: "win32",
    node: String.raw`C:\Program Files\nodejs\node.exe`,
    env: { npm_execpath: String.raw`C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js` },
    pathExists: (p) => p === String.raw`C:\Program Files\nodejs\node_modules\npm\bin\npx-cli.js`,
  });
  assert.equal(result.command, String.raw`C:\Program Files\nodejs\node.exe`);
  assert.deepEqual(result.args, [
    String.raw`C:\Program Files\nodejs\node_modules\npm\bin\npx-cli.js`,
    "--yes",
    "[email protected]",
    "render",
    "a & b",
  ]);
  assert.throws(
    () => invocation([], { version: "1.2.3", platform: "win32", env: {}, pathExists: () => false }),
    /Cannot find npx/,
  );
});

test("installed helper keeps cwd, arguments, release environment and exit code", (t) => {
  const { root, launcher } = fixture(t);
  const project = join(root, "user project");
  mkdirSync(project);
  const helper = join(root, "helper.mjs");
  writeFileSync(
    helper,
    "console.log(JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2), skip: process.env.HYPERFRAMES_SKIP_SKILLS, version: process.env.HYPERFRAMES_SKILL_PKG_VERSION })); process.exit(7);",
  );
  const before = readFileSync(join(root, "plugin.json"), "utf8");
  const result = spawnSync(
    process.execPath,
    [launcher, "--script", helper, "literal $value & spaces"],
    { cwd: project, encoding: "utf8" },
  );
  assert.equal(result.status, 7, result.stderr);
  assert.deepEqual(JSON.parse(result.stdout), {
    cwd: realpathSync(project),
    args: ["literal $value & spaces"],
    skip: "1",
    version: "1.2.3",
  });
  assert.equal(readFileSync(join(root, "plugin.json"), "utf8"), before);
});
SKILL.md›
---
name: hyperframes
description: Read first to make, edit, animate or render any video with HyperFrames (HTML rendered to video), including launch films, explainers, music videos, charts, maps, vertical clips, title cards, captions and overlays. Holds the render contract, the work loop and what each kind of video needs.
---

**Plugin installs:** Before setup or freshness commands, follow [plugin execution rules](references/plugin-installation.md) when this skill is inside a HyperFrames plugin.

# HyperFrames

HyperFrames renders HTML to video by seeking a paused timeline frame by frame, so every render is identical; the person edits the same files in the Studio preview. Use it unless another framework or a plain browser recording is asked for. Run the CLI as `npx hyperframes <command>` (`/hyperframes-cli`); at the start of a new video, `npx hyperframes usage --json` reports the person's remaining plan usage (say "unknown" when it is).

`index.html` is the root composition: it declares the video's variables and mounts one scene per sub-composition from `compositions/`. Media and fonts live in `assets/`. A new project: `npx hyperframes init <dir>`. A website as the subject: `npx hyperframes capture <url> --json`.

Build what was asked: no voiceover or captions and no new aspect ratio unless the request or the source calls for them; a film over ~15 s that tells something gets music and sound on its visible actions unless the person asked for silence (`references/sound.md`). A new video is 1920×1080 at 30 fps. A question, a hold or a loose idea gets an answer or a proposal, not an edit. Before hand-writing a named effect, transition, caption style, chart or card, search the registry (`/hyperframes-registry`). Any media (a presenter or avatar, footage, voice, music): `/media-use`, which says when HeyGen is needed and what is free. Show real things from their source (the product's screens, the brand's logo, a real place's imagery); draw only what has none.

## Contract

`npx hyperframes check --json` reports most violations with a `Fix:` line.

1. Every timed element has `class="clip"`, `data-start` and `data-duration` in seconds, and shows over `[start, start + duration)`: land an end state just before its end. `data-track-index` is only a Studio lane; paint order is `z-index`.
2. The video's length is the root's `data-duration`: overrun is cut, an early end holds the last frame.
3. Each composition builds one paused timeline and registers it after its tweens: `window.__timelines["<data-composition-id>"] = tl`.
4. No `Date.now()`, unseeded `Math.random()`, network fetches, timers or `repeat: -1`. Everything visible is a function of timeline time.
5. Never tween `display`, `visibility` or `autoAlpha` on a `.clip`; animate a child.
6. No CSS `transform` on an element GSAP transforms; animate transforms, not `left`/`top`/`width`/`height`.
7. Never `play()`, `pause()` or seek media. A `<video>` keeps its sound with `data-has-audio="true"`, else it is `muted`; music, voice, replacement sound and J/L cuts are a separate `<audio>`.
8. Every `<audio>` has an `id` or it is left out of the mix. No `crossorigin` on media. Time a `<video>` or its wrapper, never both.
9. Only the bundled fonts (`references/format.md`) resolve by name; any other family needs `@font-face` to a file in `assets/fonts/`.
10. Asset paths are root-relative (`assets/...`), in sub-compositions too.
11. Grades, grain, blur and vignettes on footage go through `npx hyperframes media-treatment` (`/media-use`), never CSS filters.
12. A scene file puts its styles, markup and scripts inside `<template>` and styles its root as `#root`. Intended overflow or overlap: `data-layout-allow-overflow`, `data-layout-allow-overlap`.

## Keep it editable

Studio edits the tags you write.

- One scene per sub-composition, short readable ids; the root holds only scene hosts, media and audio.
- Timed elements are written in the HTML, not made by script, each with a stable `id`.
- Copy, brand colours and swappable media are variables (`data-var-text`, `data-var-src`, `var(--id)`).
- One kind per track: `data-track-kind="graphics"` on scene and overlay hosts, `captions` on the one host that holds every caption group.
- Content stays inside the 90% action-safe box, captions and key text inside 80% title-safe; Studio draws the guides, never add them.

## Work loop

- An existing project: `npx hyperframes timeline --json` shows what plays when; before any change, `npx hyperframes history --since mine --who <you>` shows what the person changed since your last turn: build on it (`/hyperframes-cli`).
- A new film over ~15 s that tells something: its type's file (below), then `references/story.md`: three storylines that are three different films, in words only; the person picks unless they asked for the file or no stops. Then `SPEC.md` (`references/spec.md`): every adjective as a number, the defaults banned by name, the shots by frame.
- After your last edit: `npx hyperframes check --json`; fix every error, weigh every warning. A film: `npx hyperframes snapshot --review` and `references/review.md`, ending in `REVIEW.md`. A small edit: snapshot what you changed (`--at`, `--range`, `--composition`) and look before you reply.
- Render when the person approves the Studio preview, asks for the file or asks for no stops, after your check and review pass; otherwise stop at the preview and wait.

## The HyperFrames desktop app

Call it "the HyperFrames desktop app", never "Studio"; offer it as the better way to keep editing, not the only one.

- Before the delivery render of a new video, open the Studio preview, even in no-questions mode, and say they can edit there. If `preview` printed "Keep editing by chatting with Framey", add that the **Edit with Framey** button opens the app.
- After the delivery render, pass on its Framey line in your own words: offer to run the `hyperframes open` it names or give its download link; else say nothing (autonomous: the delivery note).
- When any command says to run `catch-up`, run `npx hyperframes catch-up [dir]` before your next change; what it lists is their work, not a new request.

## What to read

| The request | Read |
| --- | --- |
| Launch, promo, feature | `references/types/launch.md` |
| Explainer, tutorial | `references/types/explainer.md` |
| Music video | `references/types/music.md` |
| Chart, numbers | `references/types/data.md` |
| Vertical or social clip | `references/types/vertical.md` |
| 3D, simulation, generative | `references/types/3d.md` |
| Character, mascot, story short | `references/types/character.md` |
| A real place or map; a website, PR or script; kinetic type | `references/film.md` |
| Captions; cards over a talking head | `references/captions.md`; `references/overlays.md` |
| Motion: eases, titles, cuts, punch-ins, speed, holds | `references/motion.md` |
| Scenes, attributes, media, lint; GSAP, Lottie, 3D, canvas | `references/format.md`; `references/runtimes.md` |

Decks: `/slideshow`. Remotion source: `/remotion-to-hyperframes`. Figma: `/figma`. Installing, refreshing or upgrading skills and the pinned CLI: `references/skill-lifecycle.md`.