Appearance
Theming
approvedThe player's visual system: one theme, a fixed token set, shipped typography.
Design-value authority
The checked-in video.js playground is the read-only donor for numeric visual values: sizing formulas, spacing, radii, and typography measurements. Its current declarations supersede stale numeric examples in stories or older design notes. That authority is deliberately narrow: repository scope, behavior, documented product deviations, DOM and component ownership, and the working agreement remain governed by the player specification, LT-1652, and AGENTS.md. A conflict in those non-numeric rules stops for review instead of being resolved from the playground.
One theme
The player has exactly one appearance: the light theme. There is no dark variant, no theme switching, and no theme key in the configuration. Surface tones are fixed per component by the design: the over-video chrome is dark by nature, the context panel is a white card, the share dialog is a frosted dark card. None of that varies at runtime.
Design tokens
The primitive ramps come from the checked-in playground and live on the player root. New semantic player colors derive from these primitives; they are the names components consume. Existing LT semantic aliases without an exact ramp match remain documented bespoke exceptions rather than being silently moved to a nearby stop. The context-panel palette remains the separately bound shorts-context/Figma palette documented below.
Colors
| Ramp | Stop | Value |
|---|---|---|
| Neutral | 50 | hsl(0, 0%, 100%) |
| Neutral | 100 | hsl(0, 0%, 95%) |
| Neutral | 200 | hsl(0, 0%, 90%) |
| Neutral | 300 | hsl(202, 5%, 67%) |
| Neutral | 400 | hsl(0, 0%, 60%) |
| Neutral | 500 | hsl(0, 0%, 50%) |
| Neutral | 600 | hsl(0, 0%, 40%) |
| Neutral | 700 | hsl(197, 5%, 26%) |
| Neutral | 800 | hsl(0, 0%, 17.5%) |
| Neutral | 900 | hsl(220, 4%, 16%) |
| Neutral | 1000 | hsl(0, 0%, 0%) |
| Primary | 100 | hsl(48, 75%, 95%) |
| Primary | 200 | hsl(48, 75%, 82.5%) |
| Primary | 300 | hsl(48, 75%, 70%) |
| Primary | 400 | hsl(48, 75%, 60%) |
| Primary | 500 | hsl(60, 65%, 56%) |
| Primary | 600 | hsl(60, 65%, 46%) |
| Primary | 700 | hsl(60, 47%, 36%) |
| Primary | 800 | hsl(48, 75%, 17.5%) |
| Primary | 900 | hsl(48, 75%, 7.5%) |
| Accent | 100 | hsl(183, 100%, 80%) |
| Accent | 200 | hsl(183, 100%, 75%) |
| Accent | 300 | hsl(183, 100%, 70%) |
| Accent | 400 | hsl(183, 100%, 60%) |
| Accent | 500 | hsl(183, 100%, 50%) |
| Accent | 600 | hsl(201, 87%, 25%) |
| Accent | 700 | hsl(201, 87%, 21%) |
| Accent | 800 | hsl(201, 87%, 15%) |
| Accent | 900 | hsl(201, 87%, 10%) |
Accessibility contrast
Rendered semantic pairs are tested against WCAG 2.2 rather than treating an isolated token as accessible. Normal text must reach 4.5:1; meaningful graphical and focus indicators must reach 3:1 against their adjacent color. Disabled controls and purely decorative marks are excluded.
| Surface or role | Foreground / indicator | Adjacent surface | Minimum | Audited result |
|---|---|---|---|---|
| Over-video controls and floating panels | Neutral 50 | Neutral 900 | 4.5:1 | 14.35:1 |
| Subdued floating-panel text | Neutral 300 | Neutral 900 | 4.5:1 | 6.26:1 |
| Primary controls and active marks | Neutral 900 | Primary 500 | 4.5:1 | 9.45:1 |
| Context-panel headings and links | Context accent #074566 | Neutral 50 | 4.5:1 | 10.23:1 |
| Context-panel timestamps | Context time #3f70bd | Neutral 50 | 4.5:1 | 4.93:1 |
| Context-panel metadata | Context muted #687685 | Neutral 50 | 4.5:1 | 4.65:1 |
| Context-panel unselected tabs | Context subtle #6c717a | Neutral 50 | 4.5:1 | 4.91:1 |
| Caption text over a bright video frame | Neutral 900 | Primary 500 at 0.8 over Neutral 50 | 4.5:1 | 10.26:1 |
| Dark-surface focus indicator | Neutral 300 | Neutral 900 | 3:1 | 6.26:1 |
| Primary focus and copy-field indicators | Primary 500 | Neutral 900 | 3:1 | 9.45:1 |
| Context-panel focus indicator | Context accent #074566 | Neutral 50 | 3:1 | 10.23:1 |
| Context header glyph | Context accent soft #487ed1 | Context surface soft #f2f7fa | 3:1 | 3.76:1 |
| Context close glyph | Context close #7c8798 | Neutral 50 | 3:1 | 3.64:1 |
The shared --ltp-focus-ring-color is the dark-surface indicator. The context panel locally remaps it to --ltp-color-context-accent so keyboard focus remains visible on its light surfaces. Components may suppress the shared outline only when they provide an equivalent token-backed indicator, such as the cover affordance or Share copy-field ring.
Translucent semantic colors use relative HSL, for example hsl(from var(--ltp-neutral-900) h s l / 0.96). An opaque declaration precedes every relative-color declaration as the fallback for browsers that do not support that syntax. Baked eight-digit hexadecimal alpha values are not part of the token contract.
Retained bespoke semantic aliases
The following pre-LT-1653 values have no exact equivalent in the playground ramps. They remain fixed semantic exceptions so adopting the primitive inventory does not silently change a current or future surface.
| Semantic token or group | Retained value(s) |
|---|---|
primary-pale, primary-dark | #e2e4a9, #757535 |
accent-dark, accent-3 | #1a1a1a, #3e4345 |
secondary, secondary-400, secondary-200, secondary-light | #00f4ff, #92cbcb, #bbdbdd, #eaf5f5 |
neutral-50, neutral-100, neutral-200, neutral-300, neutral-500 | #ededed, #f5f5f5, #e3e3e3, #dbdbdb, #73797c |
text-secondary, text-tertiary | #73797c, #b2b3b3 |
surface-grey, surface-grey-dark | #f5f5f5, #e3e3e3 |
| Primary disabled surface/text | #e2e4a9, #b2b3b3 |
| Secondary surface and disabled surface/text | #e3e3e3, #f5f5f5, #b2b3b3 |
These values are inherited LT semantics, not additions to the donor ramps. Their rendered contrast and appearance are unchanged by LT-1653. A later remap requires the same evidence and contrast ledger as any other semantic change.
Floating surfaces
| Surface | Background and effects |
|---|---|
| Tooltip and timeline preview | Neutral 900 at 0.96 alpha; no blur |
| Chapters and captions menus | Neutral 900 at 0.96 alpha; no blur |
| Share dialog | Neutral 900 at 0.96 alpha; 14px backdrop blur; 1px neutral 50 at 0.08 border; 0 30px 50px neutral 1000 at 0.42 shadow |
| Share dialog without blur | Opaque neutral 900 fallback; the border and shadow remain |
| Context side card and dialog | Opaque white; no blur |
| Context-card play affordance | Its existing local 0.1875rem blur |
| Scrims, captions, feedback, and state surfaces | Preserve their existing semantic treatment; they are not frosted panels |
The standalone player has no surface corresponding to the playground shorts header's 18px frost; that remains feed work. Menus deliberately remain translucent but unblurred. The share card adopts the playground overlay-dialog frost while retaining its LT-specific dimensions and lower-left radius. The context panel stays the separately bound opaque white shorts-context surface.
System colors
| Token | Value | Light tint |
|---|---|---|
| Error | #de776c | #faebe9 |
| Warning | #bea61f | #f5f2de |
| Success | #3ba06f | #e7f3ee |
| Progress | #d58854 | #f8ece2 |
| Ready | #5c8dd6 | none defined |
Buttons
| Button | Surface | Text | Click state | Disabled surface | Disabled text |
|---|---|---|---|---|---|
| Primary | Primary 500 | Neutral 900 | Neutral 900 at 0.2 | Bespoke #e2e4a9 | Bespoke #b2b3b3 |
| Secondary | Bespoke #e3e3e3 | Neutral 900 | Neutral 900 at 0.1 | Bespoke #f5f5f5 | Bespoke #b2b3b3 |
| Destructive | System error | Neutral 50 | Neutral 900 at 0.1 | Error light | Neutral 50 |
Spacing
The spacing scale is 0.25rem, 0.5rem, 1rem, 1.25rem, and 1.5rem. Component spacing taken from the playground stays in rem, including intermediate donor values such as 0.35rem, rather than being rounded onto this convenience scale.
Fluid sizing
The player root is the inline-size query container. Fluid tokens use the playground's rendered clamp(min, base + Ncqi, max) declarations unchanged; component styles consume the tokens instead of repeating formulas.
| Role | Binding value | Playground declaration |
|---|---|---|
| Standard control/icon | clamp(1.875rem, 0.4545rem + 6.0606cqi, 2.5rem) | _controls.scss |
| Time/chapter label | clamp(1.125rem, 0.75rem + 0.8333cqi, 1.25rem) | _controls.scss |
| Controls inset | clamp(0.625rem, -0.0543rem + 2.8986cqi, 1.25rem) | _controls.scss |
| Feedback padding | clamp(0.5rem, 2.5cqi, 0.875rem) | _input-feedback.scss |
| Feedback icon | clamp(1.5rem, 7cqi, 2.5rem) | _input-feedback.scss |
| Landscape cover author | clamp(1rem, 0.592rem + 1.739cqi, 3.375rem) | _cover.scss |
| Landscape cover title | clamp(1rem, 0.271rem + 3.112cqi, 5.25rem) | _cover.scss |
| Portrait cover author | clamp(1rem, 0.723rem + 1.585cqi, 2.625rem) | _cover--portrait.scss |
| Portrait cover title | clamp(1rem, 0.402rem + 3.415cqi, 4.5rem) | _cover--portrait.scss |
| Caption text, landscape | clamp(1.05rem, 2.25cqi, 1.6rem) | _captions.scss |
| Caption text, portrait | clamp(0.96rem, 3.8cqi, 1.28rem) | _captions.scss |
cqi and cqb are allowed only for player-owned surfaces whose query container is the player. Portaled and browser top-layer surfaces use rem, percentages, their measured available width, or explicit viewport geometry supplied by the owner; they never resolve fluid sizing against the browser viewport by accident. round(nearest, ..., 1px) snaps percentage-derived timeline fill, buffer-range, thumb, and chapter-segment edges, where fractional pixels can otherwise produce visible seams. Hairline borders and deliberately fixed panel geometry may remain in px. Fluid spacing and typography are never pixel-rounded.
Retained values without a donor
When the playground has no equivalent surface, LT keeps the existing value and records why rather than inventing a donor mapping.
| Surface | Retained LT value | Reason |
|---|---|---|
| Buffering spinner | Control-sized ring with 0.25rem stroke | No playground-owned buffering presentation equivalent |
| Error presentation | 32ch message, 1rem gap/padding, compact primary Retry | No playground-owned error presentation equivalent |
| Captions menu behavior | Off row, localized labels, available-size anchoring | No dedicated playground captions menu; visual shell inherits chapter-popup values |
| Share announcement | Visually hidden live region geometry | LT accessibility-only surface |
| Share top-layer bounds | Owner-supplied center plus percentage/max-height constraints | Native top-layer architecture is a deliberate LT deviation |
| Chapter menu width constraint | min(16rem, available player width) | Preserves the donor cap without its viewport-relative fallback |
Context panel
The responsive context panel uses semantic measurements bound by its capability document:
| Token | Value |
|---|---|
| Side-card minimum width | 420px |
| Side-card maximum width | 650px |
| Side-card minimum gap | 2rem |
| Side-card maximum gap | 12rem |
| Side-card preferred gap | 12% of the presentation stage |
| Side-card outer allowance | 2rem |
| Desktop header padding | 0.9rem 1rem 0.85rem |
| Desktop body inset | 1.15rem 1rem 1rem |
| Dialog viewport inset | 8px plus safe areas |
| Dialog header padding | 1rem |
| Dialog body inset | 1.5rem 1rem 1rem |
| Header glyph / close control | 1.5rem / 2.25rem |
| Desktop / dialog radius | 1rem / 0.5rem |
| Side-card fade | 220ms ease |
| Side-card slide | 340ms cubic-bezier(0.22, 1, 0.36, 1) |
These are internal design tokens, not consumer override points.
Selection and user-select
Selection inside the player uses Primary 500 behind Neutral 900 text. Selection remains available only where copying authored or generated text is a legitimate task:
- context-panel reading content, including transcript and publication metadata;
- the Share dialog's read-only URL and embed-code fields.
Player chrome, the cover identity, rendered captions, menus, labels, status and error text, and interactive elements are not selectable. This prevents accidental drag selection without removing selection from the two reading/copying surfaces.
Motion
| Token | Value | Consumers |
|---|---|---|
| Fade duration | 220ms | Landscape chrome, menus, context-panel opacity |
| Movement duration | 340ms | Context-panel horizontal entrance |
| Entrance easing | cubic-bezier(0.22, 1, 0.36, 1) | Menus and context-panel movement |
| Menu entrance offset | 1rem | Chapters and captions menus |
Chapters and captions menus enter from opacity zero and translateY(1rem) over the fade duration with the entrance easing. The context panel combines the fade duration with the movement duration. Reduced-motion preference removes entrance/movement animation and makes visibility changes immediate. Persistent will-change declarations are forbidden; an animating surface may use it only for the active animation interval.
Intentional visual changes in LT-1653
| Surface or semantic | Previous value | LT-1653 value | Contrast result | Evidence |
|---|---|---|---|---|
| Share dialog | Opaque #27282a | Neutral 900 at 0.96 with the documented frost | Existing light text remains above AA; automated translucent-surface checks pass | Playground _overlays.scss overlay-dialog panel |
| Chapters and captions menu backgrounds | Opaque #27282a | Neutral 900 at 0.96, without blur | Existing light text remains above AA; automated translucent-surface checks pass | Playground _overlays.scss and _slider.scss floating surfaces |
| Landscape control fade | 250ms | Shared 220ms fade | Not applicable to color contrast | Playground shared fade timing |
| Chapters and captions menu entrance | No entrance motion | 220ms, translateY(1rem), shared easing | Not applicable to color contrast | Playground popup entrance motion |
--ltp-color-neutral-400 | #b2b3b3 | Neutral 300, hsl(202, 5%, 67%) (approximately #a7acaf) | 7.02:1 to 6.41:1 against Neutral 900; both exceed WCAG AA | Exact donor Neutral 300 declaration in src/css/theme/_colors.scss |
--ltp-color-text-subdued-on-dark | #a8adb0 | Neutral 300, hsl(202, 5%, 67%) (approximately #a7acaf) | 6.51:1 to 6.41:1 against Neutral 900; both exceed WCAG AA | Exact donor Neutral 300 declaration in src/css/theme/_colors.scss |
| Context time | #487ed1 | #3f70bd | 4.05:1 to 4.93:1 against Neutral 50; now exceeds WCAG AA | Accessibility correction to the bound Figma context semantic |
| Context muted | #6f7e8d | #687685 | 4.16:1 to 4.65:1 against Neutral 50; now exceeds WCAG AA | Accessibility correction to the bound Figma context semantic |
| Context subtle | #7a7f89 | #6c717a | 4.02:1 to 4.91:1 against Neutral 50; now exceeds WCAG AA | Accessibility correction to the bound Figma context semantic |
| Context-panel focus ring | Neutral 300 | Context accent #074566 | 2.29:1 to 10.23:1 against Neutral 50; now exceeds WCAG 1.4.11 | Accessibility remap to the bound Figma context accent |
| Chapter-aware timeline preview drop shadow | Black at 0.32 | Black at 0.32; the proposed 0.25 remap was rejected | Not applicable; this is a non-content elevation shadow | Exact rgba(0, 0, 0, 0.32) preview shadow in playground _slider.scss |
No other semantic color changes are authorized without recording the previous value, new value, contrast result, and donor or Figma evidence here first.
Typography
The player's typeface is Nunito Sans, one family for every player text, shipped inside the package under the SIL Open Font License with a generic system fallback stack. This is a deliberate decision superseding the Inter, Inter Tight, and SF Pro entries found in the design file's variables; implementation must not reintroduce those families.
The type scale carries over from the design tokens with the family swapped:
| Style | Size / line height | Weight | Tracking |
|---|---|---|---|
| Title 1 | 2rem / 2.125rem | 600 | 0 |
| Title 2 | 1.625rem / 2rem | 600 | 0 |
| Title 3 | 1.25rem / 1.75rem | 600 | 0 |
| Headline | 1rem / 1.625rem | 500 | 0 |
| Body | 1rem / 1.5rem | 400 | -0.025rem |
| Body medium | 1rem / 1.5rem | 500 | -0.025rem |
| Footnote | 0.8125rem / 1.25rem | 400 | -0.0125rem |
| Footnote medium | 0.8125rem / 1rem | 500 | -0.0125rem |
| Caption | 0.75rem / 1rem | 400 | -0.0125rem |
| Small button | 0.875rem / 1rem | 600 | -0.0125rem |
The shared weight scale is light 300, normal 400, medium 500, semibold 600, and bold 700. The base line-height scale is tight 1, text 1.2, body 1.5, and relaxed 1.65; role-specific token values may use another exact donor ratio where required.
All weights the player uses (300, 400, 500, 600, 700) ship, subset for the languages the player serves. The package vendors the normal Latin and Latin Extended WOFF2 subsets from @fontsource/nunito-sans 5.3.0, whose metadata identifies the upstream font as Nunito Sans v19. Each weight and subset is a separate file so browsers fetch only the glyph range they need. The SIL Open Font License 1.1 ships beside the font files and is included in the package's generated third-party notices.
Icons
Icons ship as an SVG sprite inside the player. One canonical set serves both layouts; landscape has only the six source-specific exceptions named in the layouts document. Icon colors come from the token set.
Consumer overrides
None. The player is visually locked: configuration controls what is shown, never how it looks. This is deliberate brand protection, consistent with the license. A sanctioned set of CSS custom properties may become a future, documented addition if a real consumer need appears; nothing is exposed today.