git:20260819.00db6f4 to git:20260819.752f856

10 added, 2 removed. Audit A to A.

---
name: wolves-teaser
description: Use when changing the `/wolves/` teaser hero, Trailer 1 player, browser-rendered trailer plates, poster, bridge, end card, or trailer fidelity tests.
metadata:
context7-sources:
- /websites/tailwindcss
---
# Wolves teaser
## Overview
Keep `/wolves/` faithful to the owner-authored Trailer 1 cut while preserving
its role as a teaser website. The page owns a chromeless YouTube iframe, the
browser-rendered plate treatment, the day-to-night wallpaper bridge, the end
card, and links into `/wolves/experience/`.
The trailer design is not derivable from one manifest. Its copy and timing,
visual treatment, composition, and rendered result live in separate
`destiny-vids` sources. Read all of them before changing the recreation.
## When to Use
Use this skill when changing:
- `src/WolvesTeaserApp.vue`
- `src/components/wolves/WolvesTrailerLine.vue`
- `src/data/wolves-trailer-plates.ts`
- `src/tests/wolvesTrailerPlates.test.ts`
- the teaser poster, hero placement, player geometry, plate styling, bridge,
end card, or trailer-specific assets
## When NOT to Use
- General Wolves show or `/wolves/experience/` runtime work: use
[`../wolves-runtime-engineering/SKILL.md`](../wolves-runtime-engineering/SKILL.md).
- Back-catalogue records and approved Wolves assets: use
[`../wolves-content/SKILL.md`](../wolves-content/SKILL.md).
- Guardian cards or share pages: use
[`../guardian-character-cards/SKILL.md`](../guardian-character-cards/SKILL.md).
- Any design change without direct owner approval: load
[`../design-gate/SKILL.md`](../design-gate/SKILL.md) and stop at its gate.
## Core Process
1. Read the authoritative `destiny-vids` sources listed below.
2. Re-port copy and timing; never reword or nudge the owner's record locally.
3. Reproduce card design from the card templates, not from the timing manifest.
4. Preserve the three-picture composition and iframe/frame geometry.
5. Pin timing, treatments, segments, and lossless tokenisation in
`wolvesTrailerPlates.test.ts`.
6. Drive the dev-only `window.__wolvesTeaser` harness to the reference beats.
7. Compare desktop and mobile bounds against the rendered cut.
8. Run lint, typecheck, focused tests, `test:gate`, and build.
## Sources of Truth
All paths in this table are relative to `~/src/destiny-vids`.
| Thing | File |
|---|---|
| Plate schedule and owner copy | `stories/trailer-1-plates.json` |
| Title and end-card design | `cards/maintitle.html` |
| Book-box design | `cards/bookline.html` |
| Day-card design | `cards/daycard.html` |
| Composition and timeline | `scripts/build_trailer1.py` |
| Rendered plates, RGBA 1920×1080 | `renders/plates-trailer-1/*.png` |
| Final comparison picture | `renders/trailer-1.mp4` |
The manifest carries windows and copy. It does **not** carry the design. A
manifest-only implementation can be numerically correct and still look nothing
like the film.
## The Cut Is Three Pictures
The real trailer is three concatenated segments, not one video with overlays:
| Window | Picture |
|---|---|
- | 0 → 88.2 | Nightwish video, letterboxed 2.39:1 into 16:9 |
+ | 0 → 88.2 | Nightwish video, letterboxed 2.39:1 into 16:9; black through 12.2 |
| 88.2 → 102.2 | March Bluefin wallpaper, day fading to night |
| 102.2 → 115.02 | March Bluefin night wallpaper, as the end-card poster |
`BRIDGE_MONTH = 3`; the local assets are:
- `public/img/wallpapers/bluefin-03-day.webp`
- `public/img/wallpapers/bluefin-03-night.webp`
+ The raw Nightwish upload is not the authored opening. Keep an opaque black gate
+ over the complete player through `BURST = 12.200`, then clear it across exactly
+ two 59.94 fps frames (`2 * 1001 / 60000`). The explosion blooms out of black.
+
The last 26.82 seconds must cover the embed completely. Both day cards and the
whole end card sit on the wallpaper at full-frame 16:9, with no letterbox. The
music ends at 110.02 seconds, then the URL card holds for five silent seconds.
The bridge's black backing is opaque from the 88.2-second cut onward; fade the
wallpaper images over that backing, never the whole backdrop. Fading the
backdrop itself exposes post-cut YouTube frames during the black-to-day rise.
The bridge's five authored legs are:
| Leg | Seconds |
|---|---:|
| black → day | 1.4 |
| day hold | 1.0 |
| day → night | 4.4 |
| night hold | 1.4 |
| night → black | 5.8 |
They total 14.0 seconds and open and close on black.
## Coordinate Mapping and Sizing
Cards are authored at 1920×1080. The 1920×804 picture occupies y=138..942 in
the letterboxed frame.
- Wallpaper segments are full-frame; percentages map directly.
- Main title is centred at y=540, so it remains 50%.
- Day cards use `top: 58%` directly because they are on the full-frame
wallpaper, not the letterboxed picture.
- Book anchor `[1030,443]` maps to `left: 53.6%; top: 41%` in the 16:9 frame.
- The second book record keeps source anchor `[1000,470]` but its rendered PNG
has no alpha pixels; it is timing metadata, not a visible box.
All plate type sizes key off **player width**. Every card clamp resolves to its
maximum at 1920px. Put `container-type: inline-size` on the player and express
plate sizes in `cqw`, where 1cqw is 1% of frame width.
| Element | Card value | px at 1920 | cqw |
|---|---|---:|---:|
| title | `clamp(3.2rem,5.8vw,4.9rem)` | 78.4 | 4.0833 |
| eyebrow | `clamp(1.8rem,3vw,2.6rem)` | 41.6 | 2.1667 |
| subtitle | `clamp(1.5rem,2.4vw,2.1rem)` | 33.6 | 1.75 |
| credits | `1.5rem` | 24 | 1.25 |
| day-card line | `clamp(2.8rem,5vw,5.2rem)` | 83.2 | 4.3333 |
| book line | `3.8rem` | 60.8 | 3.1667 |
| poster CTA | `clamp(2.8rem,5vw,5.2rem)` | 83.2 | 4.3333 |
| poster title | `clamp(1.7rem,2.5vw,2.45rem)` | 39.2 | 2.0417 |
| poster subtitle | `clamp(1.1rem,1.7vw,1.5rem)` | 24 | 1.25 |
| poster tag | `1.1rem` | 17.6 | 0.9167 |
The card root is 16px. `wolves-cinematic.scss` sets the site's root near 10px,
so never copy `rem` values across directly; convert through the px column.
## Authored Treatments
1. **Every B/b and F/f is blue** at `#4285f4`, the Bluefin wordmark blue.
It is not `--wc-gold` (`#60a5fa`), which is a UI token. Do not apply this
rule to the Linux Foundation event title or the CTA's b/f.
2. **The O in WOLVES is the Kubernetes helm**, as is the O in *Extinction*. The
mark is `public/brands/kubernetes-icon-white.svg` — CNCF's published white
symbolic icon, reproduced unmodified, so nothing here recolours anybody's
trademark.
3. **A spaced ` | ` is drawn as a sear**, not set as a font glyph. The sear is
blue heat: flare `rgb(196 226 255)`, mid `rgb(147 197 253)`, halo
`rgb(37 99 235)`.
4. **Glyph halo, never a scrim.** The owner removed the black box. Contrast
belongs to the letterforms; a translucent full-card background is a
regression.
5. **Hairline:** 34% wide on the title, 42% on the poster, 1px,
`rgb(96 165 250 / 28%)`.
6. **Typeface:** use `--wc-font-display`. Do not use `--wc-font-weyland` or
`--wc-font-weyland-mono` for the authored plates.
The render host lacked Inter, so the rendered PNGs use DejaVu Sans. The website
has Inter and the card explicitly names the site's display token. Use the
authored token unless the owner asks to reproduce the render host's fallback.
## Plate Schedule
| ID | Window | Notes |
|---|---|---|
| `maintitle-a` | 11.0–15.4 | title only; credits hidden |
| `maintitle-b` | 15.4–22.6 | credits and sear appear; title must not move |
| `book-a` | 26.9–33.64 | four-line box at `[1030,443]` |
| `book-b` | 31.0–34.9 | transparent timing continuation at `[1000,470]`; never render |
| `daycard-extinction` | 88.8–93.8 | fade in 0.4; fade out 0.5 |
| `daycard-survival` | 94.4–100.6 | fade in 0.5; fade out 0.6 |
| `endcard-event` | 102.2–115.02 | title, subtitle, hairline |
| `endcard-cta` | 105.2–115.02 | CTA and tags over the event card |
The book box is opaque `rgb(4 10 20)`, with a 4px `#60a5fa` left border,
3px radius, `1.35rem 2rem` card-root padding, line-height 1.7, and max-width
82%. It is not rotated and hard-cuts; it must hide the printed page beneath it.
## Iframe Geometry
The player is the delivered 16:9 frame. Inside it, `.wt-player-frame` is the
clipped 1920:804 picture aperture. Centre a **16:9 iframe** inside that aperture:
its height is 134.328% of the aperture, so YouTube's own top and bottom
letterbox bars fall outside the clipped area. That hides the title/share/logo
chrome without masking or cropping the film itself.
Set `pointer-events: none` on the iframe. Playback belongs to the site's media
widget; allowing pointer hover over the cross-origin iframe asks YouTube to
paint chrome again, and the page cannot style it away. The opaque poster is an
entry surface only: remove it as soon as playback starts and never restore it
for pause or seek. Covering the player while its clock advances makes Play and
Seek look broken, and replacing a paused frame with poster art hides the video.
A transient YouTube centre glyph is less destructive than hiding the requested
- frame.
+ frame. This rule applies to the poster, not the authored black gate. The black
+ gate still covers the raw upload until the 12.2-second explosion bloom.
`new YT.Player(div, …)` replaces the host div with the iframe. Target the
resulting iframe through the wrapper's `:deep(iframe)` rule.
YouTube rejects numeric loopback origins. Test at
`http://projectbluefin.io.localhost:5173/wolves/`, not `localhost`.
## The Title Lands Once
The page and the cut both know the film's name, and for a while both said it.
`.wt-heading` (page `h1`), `.wt-poster-title` (poster overlay) and the authored
`maintitle` plate each spelled *Seven Days to the Wolves*, so two of them were
legible together at every measured beat — the `h1` sat above the frame for the
eleven seconds before the title card, and the card then "revealed" words the
audience had already read. The page read as glitchy and the reveal did nothing.
A theater darkens before the title hits. The rules that keep it that way:
- **Exactly one surface may carry the film title at any instant.** The poster
is a kicker (`TRAILER 1`) and controls only; it does not name the film.
- **The heading is fully down before the card opens**, never cross-faded with
it. A dissolve still puts two copies on screen at once, which is the defect.
`TRAILER_HEADING_YIELD_SECONDS` is the lead, and it ends *at* the card's start.
- **The yield is opacity, never `display` or `v-if`.** The heading keeps its box
so the measured viewport budget that holds the whole frame above the fold does
not move when the words leave.
- **The heading does not return mid-picture.** Restoring it when the card closes
pops a full-width title back in over a running film. It returns only when the
trailer is not playing, where it is page furniture again.
`trailerHeadingOpacity()` owns this and derives its beat from the `maintitle`
plate's own window, so re-porting the cut moves the heading with the card. The
invariant is pinned in `wolvesTrailerPlates.test.ts`, which sweeps the whole
110 s at 0.1 s and asserts the heading and the card are never both legible.
## Reuse the Wolves Media Widget
`MediaWidget.vue` is store-backed by default and is also the teaser transport.
Its optional `artwork`, `elapsed`, `duration`, and `playing` props activate
external single-track mode without mutating the cinematic store. Keep
`showSkipControls` true by default for the show and set it false for the teaser.
The teaser owns play, pause, replay, and ratio seek through the IFrame API.
Do not copy the widget markup or stylesheet into the teaser. A visual copy
immediately drifts from the show and creates two transport accessibility
surfaces to maintain.
The widget is the ongoing transport, while the idle poster carries centered
**Play** and **Fullscreen** convenience buttons. Once entered, pause, resume,
and seek stay in the widget so the video frame remains visible. Fullscreen the
entire `.wt-stage`, not the iframe, so title, authored overlays, and widget
remain in the fullscreen tree. Hide the standfirst there and fit the 16:9
player to the remaining viewport.
Treat Play and Seek as user intent even when the IFrame API or player is still
loading. The button appears before `onReady`, so queue intent in teaser state;
on readiness, restore the requested time and start only if the phase is still
playing. Calling `playVideo()` on a missing or not-ready player loses the click.
The progress control must seek continuously during pointer drag. A click-only
`div` with `role="slider"` is not a working slider. Use pointer capture from
`pointerdown` through `pointerup` or `pointercancel`, seek on the final
`pointerup` coordinate rather than trusting the last move event, and set
`touch-action: none` on the track so a touch drag seeks instead of scrolling
the page.
At natural end, keep transport time at `1:50 / 1:50` while the visual clock
holds at `TRAILER_ENDCARD_HOLD_SECONDS`, immediately before the URL card's fade.
Do not show the poster over the ended phase; it would replace the requested
last screen.
## Inline Mark Trap
Tailwind Preflight sets replaced elements, including `img`, to `display: block`.
Current documentation confirms the reset and recommends adding an `inline`
class when inline behaviour is required (`source: /websites/tailwindcss`).
A helm standing in for a letter must therefore explicitly use
`display: inline`. Otherwise it takes its own line, ignores text centring, and
renders the title as "SEVEN DAYS TO THE W", helm, "LVES".
Also state `width: 100%` on lockup rows. A flex item containing a replaced
element can collapse below the available width and wrap unexpectedly.
## Common Rationalizations
| Rationalization | Reality |
|---|---|
| "The timing manifest is authoritative, so it is enough." | It has no plate design and no three-picture composition. Read the cards and builder. |
| "The iframe should match the 1920:804 aperture." | Chrome then paints over the picture. Use a clipped aperture around a centred 16:9 iframe. |
| "A dark scrim makes the words safer." | The owner explicitly removed it. Use the authored glyph halo. |
| "The helm is just an image; default image CSS is fine." | Preflight makes it block-level and breaks the word. |
| "The title looks centred." | Measure line count and bounds; visual inspection missed the three-line helm break. |
| "The page needs its `h1`, so the duplicate title is unavoidable." | Keep the `h1` for page identity and yield it on a timer. Identity and the reveal are not in conflict. |
## Red Flags
+ - The raw Nightwish upload is visible before the 12.2-second explosion bloom.
- The embed remains visible after 88.2 seconds.
- Day cards appear over the music video instead of the March wallpaper.
- Plate type uses Michroma.
- A plate has a full-card translucent background.
- An empty `book-b` record renders as a collapsed box.
- The title or *Extinction* wraps only when the helm is present.
- YouTube chrome occupies the frame's black bars or overlays the picture.
- A second teaser-only transport copies `MediaWidget` markup or styles.
- Play is available only in the fixed widget and is not obvious on initial load.
- Fullscreen targets the iframe, causing the title, overlays, or widget to disappear.
- Ended state shows the poster or a faded/empty end card instead of the URL.
- Play or Seek advances behind an opaque poster, or Pause replaces the current frame.
- The progress control jumps on click but does not track mouse or touch drag.
- The iframe accepts pointer events.
- The page heading and the authored title card are legible at the same time, or
the poster names the film.
- The heading is hidden with `display`/`v-if`, moving the frame up the page.
- The event title's B/F is recoloured, or the CTA's b/f is blue.
- Plate timings are adjusted locally rather than re-ported from destiny-vids.
## Verification
+ - [ ] After Play, the complete player is pixel-black at 0 and 12.19 seconds;
+ black opacity is 0.5 halfway through the two-frame reveal and 0 afterward.
- [ ] Compare the browser against `renders/trailer-1.mp4` at 16, 29, 91, and
107 seconds through `window.__wolvesTeaser.seekTo()`.
- [ ] Exactly one surface spells the film title at 0, 12, 18, 24, and 107
seconds, measured from folded opacity and bounds, not by eye.
- [ ] The heading's box is unchanged across the yield at 1920×1080 and 390×844.
- [ ] Main title occupies exactly one computed line-height.
- [ ] Book box centre is 53.6% / 41% for `book-a`; transparent `book-b`
produces no `.wt-book` element.
- [ ] Both day cards report the same vertical centre; only one has a helm, so a
mismatch means the inline mark is wrapping.
- [ ] Player is fully above the fold on desktop and mobile.
- [ ] No horizontal scroll exists at 1920×1080 or 390×844.
- [ ] A 16:9 iframe is centred at 134.328% height inside the clipped 1920:804
aperture and has `pointer-events: none`.
- [ ] Playback, pause, replay, click seek, keyboard seek, and pointer-drag seek
work through the external media-widget mode without restoring the poster.
- [ ] The idle Play and Fullscreen buttons are centered and keyboard accessible;
fullscreen owns `.wt-stage` and exposes an Exit Fullscreen label.
- [ ] Natural end shows the fully opaque `wolves.projectbluefin.io` card while
the widget reads `1:50 / 1:50`; no poster is present.
- [ ] `src/tests/wolvesTrailerPlates.test.ts`, lint, typecheck, `test:gate`, and
build pass.