wolves-teaser · git:20260822.03b23c1 · 2026-08-22 · sha256 a89357c99e527d25
wolves-teaser git:20260822.03b23c1A
Immutable. This exact content is served forever at /api/v1/blob/a89357c99e527d25.
---
name: wolves-teaser
description: Use when changing the `/wolves/` teaser hero, Trailer 1 player, the delivered-render embed, poster, ended URL 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 that
plays the owner's delivered render of the cut — every plate, the bridge, and
the end card are burned into the video — plus three browser jobs around it:
the page heading's yield, an opening black mask, and one browser-drawn URL
card that covers YouTube's endscreen after the cut ends. Below the stage, the
page 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, and the playable picture is the owner's own YouTube
upload. Read the sources before changing what the records describe.
## 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, the ended URL 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 sources listed below.
2. The embed is the owner's delivered render. Never redraw its plates in the
browser while it plays — a browser plate over the burned-in plate is a
double-draw and a defect.
3. Keep `wolves-trailer-plates.ts` as the authored record; never reword or
nudge the owner's copy locally. The records drive the heading yield, the
opening black mask, and the ended URL card.
4. Pin timing, treatments, segments, and lossless tokenisation in
`wolvesTrailerPlates.test.ts`.
5. Drive the dev-only `window.__wolvesTeaser` harness to the reference beats.
6. Compare desktop and mobile against the render at the same timestamps.
7. Run lint, typecheck, focused tests, `test:gate`, and build.
## Sources of Truth
The playable picture is the owner's own upload: YouTube `iHXBTH_fwB0`,
"Wolves Trailer Final" — the delivered 4K60 render of Trailer 1.1. Verify any
swap beat by beat against the authored schedule before trusting it.
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 the Delivered Render
The render is full-frame 16:9 throughout, 114.998 s against an authored
115.02 s, with music and every plate burned in. Its three segments:
| Window | Picture |
|---|---|
| 0 → 88.2 | the picture; black through 12.2 |
| 88.2 → 102.2 | day falling into night |
| 102.2 → 115.02 | the night scene as the end-card poster |
Because the render carries the cut, the browser draws nothing over the playing
video. The three remaining browser jobs:
- **Opening black mask.** Keep an opaque black gate over the player through
`BURST = 12.200`, cleared across exactly two 59.94 fps frames
(`2 * 1001 / 60000`). The render opens on the same black, so the mask hides
YouTube's startup chrome and is pixel-identical to the cut beneath it.
- **Heading yield.** The page `h1` still steps aside before the render's own
title card (see "The Title Lands Once").
- **Ended URL card.** When the cut ends, YouTube paints an endscreen over the
faded-to-black last frame. Cover it with the teaser's held URL card — the
March night wallpaper (`public/img/wallpapers/bluefin-03-night.webp`) under
the browser-drawn event rows and the `wolves.projectbluefin.io` CTA — in the
same treatment the cut's own end card uses. The end-card records' opacities
all resolve to 1 at `TRAILER_ENDCARD_HOLD_SECONDS` (113.82), so the ended
overlay is a fully arrived frame, never a mid-fade one.
End detection is redundant on purpose: the `onStateChange` ENDED event **and**
a clock tolerance (`getCurrentTime()` within 0.05 s of the 115.02 s duration),
because the encode stops at 114.998 s and the event may never fire.
## Coordinate Mapping and Sizing
Only the ended URL card is browser-drawn. Its type sizes key off **player
width**: every card clamp resolves to its maximum at 1920px, so the player
carries `container-type: inline-size` and card sizes are expressed in `cqw`,
where 1cqw is 1% of frame width.
| Element | Card value | px at 1920 | cqw |
|---|---|---:|---:|
| 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
These rules govern the ended URL card and any future browser-drawn plate; the
render's own plates already carry them.
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
These windows are burned into the render; the records remain the authored
description and drive the heading yield (`maintitle`) and the ended URL card
(`endcard-*`). The book and day-card records no longer have browser elements.
| 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 |
## Iframe Geometry
The player is the delivered 16:9 frame and the render fills it edge to edge:
`.wt-player-frame` is `inset: 0` and the iframe is 100% × 100%. There is no
aperture — an earlier recreation embedded a letterboxed 2.39:1 music video and
clipped YouTube's chrome into a 1920:804 aperture, and that geometry went away
with that source. Verify a new source's actual frame before reviving it: a 200
from the dev server is not proof (the SPA fallback answers 200 for missing
assets), and a container's 16:9 says nothing about baked bars — extract frames
and look.
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. This rule applies to the poster, not the authored black gate. The black
gate still covers the embed until the 12.2-second reveal.
`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.
The transport clock is YouTube's own for the full 115.02-second runtime: the
render carries the music, the howl, and the five-second URL hold itself, so
there is no synthetic silent hold. The widget reaches `1:55 / 1:55` as the
video ends, and the ended phase freezes the URL card at
`TRAILER_ENDCARD_HOLD_SECONDS` over YouTube's endscreen. 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 browser should keep drawing its plates over the new video." | The render has every plate burned in; a browser copy is a double-draw. |
| "The new video is 16:9, so the old aperture math still applies." | Container aspect says nothing about baked bars. Extract frames and measure. |
| "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
- YouTube startup chrome is visible before the 12.2-second reveal.
- A browser-drawn plate appears over the playing render (double-draw).
- The video is swapped for a different upload without beat-by-beat frame
verification against the authored schedule.
- 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 or its endscreen overlays the picture; the ended phase must
cover it with the opaque URL card.
- 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.
- The transport parks at 1:50 or ends early instead of reaching 1:55 / 1:55.
- 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 render's 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.
- [ ] The browser adds nothing over the render at 13, 30, 95, 104, and 108
seconds, driven there with `window.__wolvesTeaser.seekTo()` — no lockup,
no backdrop, no day card.
- [ ] Exactly one surface spells the film title at 0, 12, and 18 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.
- [ ] Player is fully above the fold on desktop and mobile.
- [ ] No horizontal scroll exists at 1920×1080 or 390×844.
- [ ] The iframe fills the 16:9 frame edge to edge 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.
- [ ] The transport reaches `1:55 / 1:55`; the ended phase shows a fully opaque
`wolves.projectbluefin.io` card over the night wallpaper, with no YouTube
endscreen and no poster.
- [ ] `src/tests/wolvesTeaserApp.test.ts`, `src/tests/wolvesTrailerPlates.test.ts`,
lint, typecheck, `test:gate`, and build pass.