Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
4854837
refactor(skins): consolidate style tokens and shared skin rules
mihar-22 Sep 2, 2026
549c9f6
refactor(skins): share volume mask and nudge recipes as utilities
mihar-22 Sep 2, 2026
c1229ce
feat(vjsc): export custom variants and functional utilities to the re…
mihar-22 Sep 2, 2026
42d8466
refactor(vjsc): build registry css entries without widening
mihar-22 Sep 2, 2026
42dce60
refactor(skins): share poster layer and object-fit recipes as utilities
mihar-22 Sep 2, 2026
b0da25a
refactor(skins): share the popup transition recipe as a utility
mihar-22 Sep 2, 2026
d9460c7
refactor(skins): share indicator motion properties as a utility
mihar-22 Sep 2, 2026
e3178d3
refactor(skins): share the anchored menu highlight as a utility
mihar-22 Sep 2, 2026
933ebd8
refactor(skins): use truncate for clipped labels
mihar-22 Sep 2, 2026
2692739
refactor(skins): share the flat popup surface reset as a utility
mihar-22 Sep 2, 2026
a67202e
refactor(skins): share slider clip paths as utilities
mihar-22 Sep 2, 2026
d8a51b6
fix(skins): apply the minimal caption offset from the video element
mihar-22 Sep 2, 2026
193b5e6
refactor(skins): tokenize the live color and remaining shadows
mihar-22 Sep 2, 2026
1abc213
refactor(skins): bundle preset theme tokens per skin
mihar-22 Sep 2, 2026
a2e9ec7
refactor(skins): move fullscreen scale steps into the theme
mihar-22 Sep 2, 2026
d6835a3
fix(vjsc): bundle style assets in module dependency order
mihar-22 Sep 2, 2026
65115ac
fix(vjsc): emit a module's style assets in composition order
mihar-22 Sep 2, 2026
fabc5b0
refactor(skins): drop importance flags that ordering already satisfies
mihar-22 Sep 2, 2026
48359c0
feat(vjsc): write a Tailwind candidate manifest for style modules
mihar-22 Sep 2, 2026
51f6211
build(skins): scan style candidates from the generated manifest
mihar-22 Sep 2, 2026
9119a57
test(skins): compile shipped Tailwind classes against the registry theme
mihar-22 Sep 2, 2026
e6f09e6
docs(vjsc): document shared skin utilities
mihar-22 Sep 2, 2026
80a9239
feat(vjsc): alias a default candidate manifest in the Vite cache
mihar-22 Sep 2, 2026
2a1e3a6
refactor(skins): share the highlighted control state as a variant
mihar-22 Sep 2, 2026
b226d11
refactor(skins): name layout and preference variants
mihar-22 Sep 2, 2026
72998be
refactor(skins): drive reduced motion through tokens
mihar-22 Sep 2, 2026
a6dc52c
feat(skins): share the theme stylesheet across html registry skins
mihar-22 Sep 2, 2026
92227cb
feat(skins): document theme tokens and utilities in the registry
mihar-22 Sep 2, 2026
f836598
fix(skins): include the style catalogs in the package project
mihar-22 Sep 2, 2026
6023aad
refactor(skins): alias motion and shadow tokens as Tailwind theme keys
mihar-22 Sep 2, 2026
c8164e5
refactor(skins): name theme tokens after their Tailwind aliases
mihar-22 Sep 2, 2026
686d436
fix(skins): scale the popup entry distance with the player
mihar-22 Sep 2, 2026
fa64418
fix(skins): route scrim backdrops and remaining motion literals throu…
mihar-22 Sep 2, 2026
a3bda94
refactor(skins): add transitioning and anchored variants
mihar-22 Sep 2, 2026
729b16b
refactor(skins): highlight menu items through the media-highlighted v…
mihar-22 Sep 2, 2026
1723dd0
refactor(skins): tokenize dialog and controls bar geometry
mihar-22 Sep 2, 2026
b7e7262
refactor(skins): name the menu resize transition and cover theme aliases
mihar-22 Sep 2, 2026
856bcab
docs(skins): note the variant order the anchored highlight relies on
mihar-22 Sep 2, 2026
de73aff
chore(skins): remove dead Tailwind entry, alias, and dependencies
mihar-22 Sep 2, 2026
4789b21
docs(skins): describe package structure and Tailwind entries
mihar-22 Sep 2, 2026
e97a683
refactor(skins): rename the shared Tailwind source to tailwind.css
mihar-22 Sep 2, 2026
b9d8bec
refactor(skins): align color theme keys with their tokens
mihar-22 Sep 2, 2026
7b4ab50
fix(vjsc): recompile Tailwind when the candidate manifest changes
mihar-22 Sep 2, 2026
3efa003
fix(skins): show a tooltip on the captions menu trigger
mihar-22 Sep 2, 2026
30c3b7a
fix(sandbox): scan the generated Tailwind skin components
mihar-22 Sep 2, 2026
9670f72
fix(skins): reload the playground when updates reach its entry
mihar-22 Sep 2, 2026
d693c88
feat(skins): copy a preview report with environment and preference de…
mihar-22 Sep 2, 2026
62cd30c
test(skins): type the theme key tuples for the package project
mihar-22 Sep 2, 2026
ff6e3df
feat(skins): compare CSS and Tailwind skins together in the playground
mihar-22 Sep 2, 2026
159089a
feat(skins): add a text direction toggle to the playground
mihar-22 Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .agents/skills/create-vjsc-component/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ Avoid structural selectors such as `:has()`, `has-*`, `group-has-*`, descendants
- Organize style modules and output assets by role (`buttons`, `sliders`, `popups`, `feedback`, `layout`). Keep skin-only layout in the skin rather than a generic primitive module.
- Put common utilities in `utilities` and selected skin or target differences in `variants` instead of duplicating a rule.

## Shared utilities and tokens

- Put a recipe that several rules repeat into `packages/skins/src/styles/tailwind.css` as an `@utility`, and prefix it at the use site instead of building class strings dynamically. Name property families `<property>-media-<token>` (`mask-media-volume`, `clip-media-x-*`) and multi-property recipes `<recipe>-media` (`surface-media`, `focus-ring-media`).
- Keep `@utility` bodies flat declaration lists. The Shadcn registry exporter cannot represent nested rules, so state, pseudo-element, and media handling belongs on the use site through variants, and preference modes belong in `themes/preferences.css` through tokens.
- Functional utilities read their argument with `--value([*])`; custom variants use the block form because the shorthand splits comma-separated media query lists. Name variants `media-<state>` and use the layout variants `media-compact`, `media-wide`, `media-max-compact`, and `media-max-wide` instead of raw container prefixes. Reach for `media-transitioning` when starting and ending styles share a value, `media-highlighted` for hover, focus, expanded, and highlighted states, and `media-anchored` for anchor-positioning support checks.
- Prefer a `--media-*` token with an `@theme inline` alias over literal values, and use the named utility the alias produces, such as `duration-media-fast`, `scale-media-hidden`, or `shadow-media-thumb`. Reserve the `(--var)` shorthand for runtime values such as `--media-slider-pointer`. Use `rounded-media-pill` rather than `rounded-full`, whose `calc(infinity * 1px)` the style pipeline cannot serialize.
- The vjsc plugin writes a candidate manifest into the Vite cache directory and aliases it as `vjsc:candidates`; the dev Tailwind entry imports it so scanning sees the utilities the transform resolves. The plugin also re-includes the manifest in the Vite watcher, which skips the cache directory by default, so Tailwind recompiles as modules record. Raw style modules are not scanned.
- Composed rules override the rules they extend by order in CSS output, but Tailwind output has no runtime class merging, so a same-property override across composed rules still needs `!` unless Tailwind emits the shorthand first.

## Example

Input: “Add a tooltip to the volume-popover button.”
Expand Down
1 change: 1 addition & 0 deletions apps/sandbox/app/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

@source "../app";
@source "../templates";
@source "./_generated/components";
@source "./_generated/html";

:root {
Expand Down
72 changes: 64 additions & 8 deletions packages/skins/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,73 @@
# @videojs/skins

> **Internal package — do not install directly.**
> **Internal package.** Private and unpublished. The framework packages and the Shadcn registry consume its output.

Canonical VJSC skin sources and the generators that deliver them through [`@videojs/html`](../html), [`@videojs/react`](../react), CDN templates, and the Shadcn registry.
Canonical VJSC skin sources and the generators that deliver them to [`@videojs/html`](../html), [`@videojs/react`](../react), and the Shadcn registry. Write a skin once here; the build lowers it to every framework and styling target.

The package is private (`"private": true` in `package.json`) and is not published to npm.
## How a skin comes together

## Structure
Follow one skin from source to output.

- `src/` — target-neutral skin components, styles, target transforms, and contract tests.
- `build/` — shared Skin and framework-package output helpers.
- `registry/` — Shadcn catalog items, build configuration, and focused policy validation.
- `dev/` — the VJSC React/HTML and CSS/Tailwind development matrix.
1. **A skin is a component tree.** [`src/skins/default-video/skin.tsx`](./src/skins/default-video/skin.tsx) composes preset parts from [`src/skins/video/`](./src/skins/video) with shared components such as `Container` and `Poster`. Each `<theme>-<preset>` folder owns only what differs for that skin.
2. **Components pair markup with styles.** [`src/components/`](./src/components) holds the target-neutral UI. Every `x.tsx` sits beside an `x.styles.ts` that lists Tailwind classes per rule, with `default` and `minimal` variants where the themes differ. Skin-only overrides live beside the skin, for example [`src/skins/default-video/controls.styles.ts`](./src/skins/default-video/controls.styles.ts).
3. **Classes resolve through tokens.** Style modules read `--media-*` tokens through Tailwind theme keys such as `duration-media-fast`, never literal values that vary per theme. Tokens are declared in [`src/styles/themes/`](./src/styles/themes) and classified in [`src/styles/vars.ts`](./src/styles/vars.ts).
4. **The build lowers everything per target.** The [vjsc](../vjsc) compiler, configured in [`build/`](./build), turns each module into React and HTML implementations, compiles class lists into scoped CSS for the CSS targets, and emits Shadcn registry items.
5. **The playground shows the result.** [`dev/`](./dev) renders every skin across framework, styling, width, and color scheme. Add `compare=styles` to the URL to see the CSS and Tailwind variants together, `dir=rtl` to flip the text direction, and use the copy button for a report with environment details.

## Where things live

| Path | Owns |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [`src/components/`](./src/components) | Shared UI grouped as buttons, controls, feedback, layout, menus, and sliders. |
| [`src/skins/`](./src/skins) | One folder per skin (`default-video`, `minimal-audio`, and so on), plus `video/`, `live-video/`, `audio/`, and `shared/` for reused parts. |
| [`src/styles/`](./src/styles) | Base resets, themes, tokens, the shared Tailwind source, and style modules grouped like the components. |
| [`src/presets/`](./src/presets) | The handwritten background preset, copied into both packages as is. |
| [`src/meta.ts`](./src/meta.ts), [`src/render.ts`](./src/render.ts) | Skin and component metadata for the registry, and render targets that pick an element per framework. |
| [`src/gaps.md`](./src/gaps.md) | Deferred parity gaps. Maintain it with the `maintain-vjsc-skin-gaps` skill. |
| [`src/tests/`](./src/tests) | Contract tests for tokens, the utility catalog, metadata, and poster behavior. |
| [`build/`](./build) | Pack config, transform resolvers, framework targets, package writers, and the Shadcn registry. |
| [`dev/`](./dev) | The preview matrix and its Vite config. |

## Styles and tokens

[`base.css`](./src/styles/base.css) fixes the cascade: `base.theme` holds tokens and `base.preferences` overrides them, so a reduced motion, reduced transparency, or forced colors preference wins regardless of selector specificity.

- [`themes/theme.css`](./src/styles/themes/theme.css) declares every default token, grouped by colors, shadows, controls, motion, popups, sliders, and frame.
- [`themes/minimal.css`](./src/styles/themes/minimal.css), [`themes/video.css`](./src/styles/themes/video.css), and [`themes/audio.css`](./src/styles/themes/audio.css) override tokens per theme and preset.
- [`themes/preferences.css`](./src/styles/themes/preferences.css) collapses durations and neutralizes hidden-state values under reduced motion, and switches backdrop filters off under reduced transparency.
- [`base.video.css`](./src/styles/base.video.css) and [`base.audio.css`](./src/styles/base.audio.css) are the preset entries each skin stylesheet starts from.
- [`vars.ts`](./src/styles/vars.ts) classifies every token as public, runtime, or internal and feeds the registry docs. [`utilities.ts`](./src/styles/utilities.ts) describes every shared utility, variant, and computed theme key.

## Tailwind entry files

Three files in [`src/styles/`](./src/styles) chain together. Only the first ships to consumers.

| File | Purpose | Used by |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| [`tailwind.css`](./src/styles/tailwind.css) | The design system: theme keys that alias `--media-*` tokens, shared `@utility` recipes, and `media-*` variants. No Tailwind import and no `@source`, so it works inside a consumer's own setup. | Both entries below, the registry theme item, the docs generator, and the catalog tests. |
| [`tailwind.compiler.css`](./src/styles/tailwind.compiler.css) | The build design system. Imports Tailwind, base, captions, presets, and the shared file, and aliases `--spacing` to the scaled media unit. No `@source`: the compiler applies class lists directly and never scans files. | [`build/transform.ts`](./build/transform.ts) and the registry theme test. |
| [`tailwind.dev.css`](./src/styles/tailwind.dev.css) | The playground entry. Extends the compiler entry, imports the plugin's candidate manifest through `vjsc:candidates`, and scans the dev TSX. The only place scanning happens. | [`dev/main.tsx`](./dev/main.tsx) in Tailwind mode. |

Add a shared recipe to `tailwind.css` as a flat `@utility`, describe it in `utilities.ts`, and prefer a token plus theme key over a literal. The [component skill](../../.agents/skills/create-vjsc-component/SKILL.md) has the full rules.

## Build outputs

`generate` runs [`build/vite.config.ts`](./build/vite.config.ts) and writes three things:

- Skin implementations into the ignored `packages/html/src/internal/skins/` and `packages/react/src/internal/skins/` folders, plus preset registrations and stylesheets under `packages/html/src/define/` and the background preset under `packages/react/src/presets/`, through [`build/packages/`](./build/packages).
- Shadcn source registries for React with Tailwind, React with CSS, and HTML into `dist/registry/source/r/`, from the items in [`build/registry/items/`](./build/registry/items) and the targets in [`build/registry/targets.ts`](./build/registry/targets.ts).
- The hosted registry in `dist/shadcn/` through `build:shadcn`, which [`netlify.toml`](./netlify.toml) publishes.

## Commands

Run these from `packages/skins`.

```bash
pnpm dev # preview matrix
pnpm exec vp run generate # regenerate package inputs and registries
pnpm exec vp run validate:shadcn # schema and policy checks on the hosted registry
pnpm test # type check plus unit, build, and registry tests
```

## License

Expand Down
4 changes: 2 additions & 2 deletions packages/skins/build/packages/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Graph, GraphModule } from 'vjsc/graph';
import { bundleStyles, collectModules, renderHtml } from 'vjsc/graph';

import { isSkinName, type SkinMeta, type SkinModuleMeta, type SkinName } from '../../src/meta.ts';
import { skinPreset, skinPresets, type SkinPreset } from '../skin.ts';
import { skinBaseStylesheet, skinPreset, skinPresets, type SkinPreset } from '../skin.ts';
import type { GeneratedPackageFile } from './files.ts';
import { addCopiedFiles, addGenerated, generatedFiles, pascalCase } from './utils.ts';

Expand Down Expand Up @@ -47,7 +47,7 @@ export async function createHtmlPackageSkins(
`${root}/skin.css`,
await bundleStyles(graph, skin.modules, {
label: name,
files: options.baseStyles ?? ['./styles/base.css'],
files: options.baseStyles ?? [`./styles/${skinBaseStylesheet(skin.preset)}`],
})
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/skins/build/packages/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Graph, GraphModule } from 'vjsc/graph';
import { bundleStyles, collectModules, relativeImport, rewriteImports, stripStyleImports } from 'vjsc/graph';

import { isSkinName, type SkinMeta, type SkinModuleMeta, type SkinName } from '../../src/meta.ts';
import { skinPreset, skinPresets, type SkinPreset } from '../skin.ts';
import { skinBaseStylesheet, skinPreset, skinPresets, type SkinPreset } from '../skin.ts';
import type { GeneratedPackageFile } from './files.ts';
import { addCopiedFiles, addGenerated, generatedFiles, pascalCase } from './utils.ts';

Expand Down Expand Up @@ -91,7 +91,7 @@ export async function createReactPackageSkins(
`${publicRoot}/${publicName}.css`,
await bundleStyles(graph, skin.modules, {
label: `${skin.theme}-${skin.preset}`,
files: options.baseStyles ?? ['./styles/base.css'],
files: options.baseStyles ?? [`./styles/${skinBaseStylesheet(skin.preset)}`],
})
);
}
Expand Down
16 changes: 8 additions & 8 deletions packages/skins/build/registry/items/skins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { skinDirectory, skinPreset } from '../../skin.ts';
import type { VideojsRegistryMeta } from '../meta.ts';
import { packageRequirements, registryPaths, type RegistryTarget } from '../targets.ts';
import { exportedComponentName } from './components.ts';
import { reactHelperDependency } from './support.ts';
import { reactHelperDependency, themeStyleDependency } from './support.ts';

export async function htmlSkinItem(
skin: RenderedHtmlSkin,
Expand All @@ -22,9 +22,11 @@ export async function htmlSkinItem(
const template = createSourceOwnedHtml(skin.template);

const styleTarget = `${directory}/skin.css`;
const themeImport = relativeRegistryImport(`${directory}/skin.ts`, 'styles/theme.css');
const styleImport = relativeRegistryImport(`${directory}/skin.ts`, styleTarget);

const registration = `${`import '${styleImport}';`}\n\n${createHtmlSkinRegistration(
// The shared theme item must load before the skin's own scoped rules.
const registration = `import '${themeImport}';\nimport '${styleImport}';\n\n${createHtmlSkinRegistration(
template,
skin.modules,
'registry'
Expand All @@ -47,10 +49,8 @@ export async function htmlSkinItem(
path: 'skin.css',
target: `${registryPaths.install}/${directory}/skin.css`,
type: 'registry:style',
content: await bundleStyles(graph, skin.modules, {
label: name,
files: ['./styles/base.css'],
}),
// Theme tokens, resets, and presets ship once through the shared theme item.
content: await bundleStyles(graph, skin.modules, { label: name }),
},
];

Expand All @@ -62,7 +62,7 @@ export async function htmlSkinItem(
categories: ['media', 'skins', skin.preset],
docs: skinDocs(skin.root, meta, meta.name, target, directory),
dependencies: ['@videojs/html'],
registryDependencies: [],
registryDependencies: [themeStyleDependency],
files,
meta: {
role: 'skin',
Expand Down Expand Up @@ -158,7 +158,7 @@ function skinDocs(
const mediaEntry = preset.endsWith('audio') ? 'hls-audio' : 'hlsjs-video';

if (target.framework === 'html') {
return `Installs editable ${meta.title} source under \`${registryPaths.install}/${directory}\`. Requires \`${packageRequirements.html}\`; import the matching Player and media registrations before using the installed light-DOM template.`;
return `Installs editable ${meta.title} source under \`${registryPaths.install}/${directory}\` together with the shared theme stylesheet. Requires \`${packageRequirements.html}\`; import the matching Player and media registrations before using the installed light-DOM template.`;
}

return `Requires \`${packageRequirements.react}\`, which is installed with this item.
Expand Down
Loading
Loading