Appearance
Architecture
approvedThe target source structure and module boundaries. Designed for this package; deliberately not a copy of any prior implementation.
Folder structure
Proposed, finalized with the first implementation stories:
text
docs/ the binding specification
data/ versioned demo content
demo/ the showcase application
src/
runtime/ bootstrap, host mounting, the page API
config/ parsing, validation, defaults, schema
strings/ resolution machinery and language catalogs
core/ player instance, media engine adapter, state store
layouts/ landscape and portrait shells
capabilities/ chapters, captions, speed, share, context-panel
ui/ shared primitives
styles/ tokens and base styles
lib/ utilitiesModule boundaries
- Player instance: owns the media element (never recreated across configuration updates), the media engine adapter, the state store, and the current resolved configuration.
- Layout shell: renders the arrangement for the instance's fixed layout.
- Capability modules: subscribe to the instance's resolved configuration and re-resolve on every update, so dormancy is re-evaluated per video.
- Context panel: a capability owned by the player instance (see its document).
- Shorts feed: a separate orchestrator above the player that drives one instance through the public page API only, proving that API sufficient by construction.
- Media engine: internal. It never appears in the public contract; strings and configuration feed it through one-directional adapters.
Capability module contract
A capability ships its UI, its options, its string defaults, and its documentation section as one unit. One capability is one internal module, declaring:
| Declaration | Meaning | Example |
|---|---|---|
| Key | Its name in capabilities | chapters |
| Availability | A predicate over resolved content and the capability's resolved options deciding whether required data exists | content.chapters non-empty |
| Options | Its options schema and defaults, where it has options | speed's rate list |
| Strings | The string keys it owns, with English defaults; the framework aggregates all modules into the catalogs | captionsOff |
| Surfaces | The UI it contributes, rendered into one or more layout-owned slots (see the responsibility split) | the chapters menu |
| Lifecycle | How it participates in mount, configuration application, and disposal | below |
Binding rules:
- Fixed internal registry. All capability modules are compiled into the player. There is no public plugin or extension API; the config contract is the only extension surface.
- Always instantiated. Every registered module is built exactly once per player instance, at mount, and disposed only with the instance. Dormancy and removal are states the module re-resolves on every configuration application, never reasons to create or destroy it. A feed swapping between videos with and without chapters flips the chapters module between active and dormant; the module object, and any user memory it holds (such as the chosen caption track), lives on.
- Final disposal is idempotent. Applying another configuration after disposal is an internal lifecycle error, because it means the runtime applied an update to a destroyed player instance.
- Availability normally follows content. The share capability is the exception: its
canonicalUrlis resolved capability data, so its availability predicate reads that option alongside content.
Layout and capability responsibility split
| Owner | Owns |
|---|---|
| Layout shell | Arrangement: the named slots (control bar regions, overlay stack, the portrait trigger rail, the landscape top icons), spacing and safe areas, and the show and hide choreography |
| Capability | Its widgets and their behavior: what the trigger does, what the menu contains. Identical in both layouts |
| Core player | The core controls themselves (play, timeline, volume, time); the layout places them |
The capability framework exposes this fixed internal slot set:
| Slot | Purpose |
|---|---|
timeline | Optional composition around the core timeline |
controlBarLeft | Left control-bar region |
controlBarCenter | Center control-bar region |
controlBarRight | Right control-bar region |
topIcons | Top-icon region in both layouts |
captions | Caption text region |
triggerRail | Portrait context-trigger rail |
overlay | Media overlay stack and top-layer dialog triggers |
menu | Control-anchored menu region |
contextPanel | Context content arranged beside or separately from media |
Binding rules:
- A capability never positions itself and never asks which layout it is in. It declares surfaces for named slots; one surface may target several slots so that both layouts can place the same widget. The layout decides where each slot sits and renders only the slots it owns.
- A layout may omit a slot entirely. Portrait has no fullscreen slot; that fact is encoded in the portrait layout, not in control or capability code.
- Configuration pointing at an omitted slot is silently ignored: not an error and not a warning. The same configuration legitimately serves both layouts, so enabling something the current layout has no place for is the layout's design at work, not a configuration mistake.
- The context panel has its own slot. A desktop layout may place
contextPanelbeside the media frame, while a constrained layout may present the same slot as a dialog. It is not part of the media overlay stack; the layout owns that presentation decision. - The timeline slot is compositional. An empty
timelineslot means the layout renders the core timeline widget. Non-empty content replaces that widget and composes around the core slider, as chapters does; it never adds a second sibling slider. At most one timeline replacement may be registered. - The menu slot is an anchor region only. Its deterministic registration order has no stacking meaning and does not provide one-open-menu exclusivity; modules and the media engine own that behavior.
Styling strategy
- Central tokens, co-located styles, one stylesheet. The theming document's design tokens live in one central file as CSS custom properties, set on the player root. Each capability co-locates its component styles in its module. Everything compiles into the single stylesheet the package ships.
- Isolation without Shadow DOM. Every class is prefixed (
ltp-*), tokens are scoped to the player root rather than the page's:root, and a defensive reset on the player root guards against hosting page CSS bleeding in. Shadow DOM is deliberately not used: it complicates fonts, caption styling, and the media engine for little practical gain. - The font ships in the package. Nunito Sans is bundled with the player and loaded from it; the player never fetches fonts or any styling asset from an external CDN at runtime, per the self-contained principle.