diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7aac2bf..fbe4b9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,3 +85,14 @@ jobs: name: smoke-screenshots path: .tmp/smoke/ if-no-files-found: ignore + + + design-foundations: + name: shared design token export + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python3 design-system/build.py --output design-system/foundations.css --check diff --git a/CHANGELOG.md b/CHANGELOG.md index cefab17..c5523a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ still pre-1.0, minor bumps carry new features and patch bumps carry fixes. ### Improved +- Keyboard navigation now has a clear focus outline across controls. UI control transitions respect reduced motion without changing authored caption animation or video exports. - The setup wizard no longer shows the unavailable "Claude subscription" card. Claude models can be used through OpenRouter instead. ## [0.13.0] - 2026-09-25 diff --git a/design-system/README.md b/design-system/README.md new file mode 100644 index 0000000..fbc5202 --- /dev/null +++ b/design-system/README.md @@ -0,0 +1,111 @@ +# Metachlorian and Cutawan design system + +Version 1.0.0 · 4 October 2026 + +The family helps filmmakers find a useful moment, understand it, and turn it into an edit. Its visual thesis is **precise frames, expressive footage, quiet tools**. Metachlorian discovers and explains shots. Cutawan creates and exports edits. The website introduces that workflow. + +This is an implemented foundation and an adoption specification, not a claim that every component in both applications has been migrated. The website consumes the generated foundation CSS today. The application proposals introduce the same foundation source and adapters while preserving their existing working surfaces. + +## What makes this a system + +A useful system reduces decisions without hiding the work. It needs semantic tokens, predictable component behaviour, content rules, real workflow examples, accessible states, and a process for changing them. A colour palette or a component catalogue alone is insufficient. + +Three layers prevent the website's presentation needs from leaking into an editing workspace: + +1. **Foundations:** approved identity, neutral and brand palettes, spacing, shape, motion, minimum targets, and status meanings. `tokens.json` uses DTCG-style `$type` / `$value` definitions. `build.py` produces framework-independent CSS without dependencies. +2. **Profiles:** marketing, library, and editor map those foundations to semantic roles. Fonts, density, native window chrome, and a functional selection colour can differ for a reason. +3. **Components and patterns:** accessible controls and footage workflows built in each repository's existing stack. Shared behaviour comes before shared implementation. + +The canonical foundation source belongs in `JeremySNR/Metachlorian/design-system`. Each independent repo vendors the same version of `tokens.json` and `build.py`; generated CSS is committed so static hosting and existing application builds need no new installation step. Do not fetch tokens or fonts at runtime. Record the source version in changes and compare the token files when updating the family. A shared package is appropriate later if maintaining these copies becomes a recurring problem. + +## Identity + +The approved **Found frame** mark shows two footage frames, with selection corners identifying the useful shot. The full lockup is the approved image, including its wordmark. The header and footer use it instead of typesetting a replacement name beside a similar icon. + +- `Metachlorian-site/dist/assets/metachlorian-logo.png`: unchanged approved master, 2172 × 724. +- `metachlorian-logo.svg`: self-contained SVG embedding that exact PNG with its original viewport. This is an exact SVG container, **not editable vector paths**. Enlarging it retains the PNG's resolution limit. +- `metachlorian-mark.svg`: the same embedded source with a viewport around the original mark. Use with an accessible product name in compact app chrome. +- `favicon.svg`: the same original mark centred in a square viewport. No redrawing or alternate corner geometry. + +The PNG is embedded rather than linked, so SVG consumers need no second network request. `verify.py` compares its decoded bytes with the master. The source SHA-256 is `3e96712252f308a84198b706fb5d1ec0c7a0be90663c701a277ecbc872a1f61e`. + +Keep the original aspect ratio, colours, wordmark, and relative symbol size. Allow at least half a symbol-height of clearspace around the visible lockup. Do not stretch, tint, outline, animate, or run filters over the approved artwork. Marketing uses a 250px visible lockup at desktop and 192–220px on small screens. The mark in app chrome is 28 × 18px; keep the product's accessible name even when its visible wordmark is hidden. In forced-colour environments, readable system-colour text can replace the image. + +Cutawan retains its established name and icon. The family relationship comes from shared footage frames, neutral working surfaces, control states and handoff language; replacing Cutawan's logo is a separate identity decision. The Star Wars origin informs the relationship between the products, while the mark communicates their actual use. + +## Colour and profiles + +| Role | Marketing site | Metachlorian workspace | Cutawan workspace | +| --- | --- | --- | --- | +| Brand | Violet `#5140c6`, ink `#25253d` | Approved mark in chrome; neutral surfaces | Existing Cutawan identity; neutral surfaces | +| Main background | White; pale violet in narrative sections | Edge Code neutral light/dark ramp | Existing near-monochrome dark ramp | +| Around footage | Neutral paper or black | Neutral grey or black | Opaque black playback well, including macOS glass | +| Primary action | Violet with white text | Existing Key selection/action tokens | Existing warm-white accent tokens | +| Selection | Explicit selected state; no image tint | Existing external Key ring + semantic state | Explicit range/clip state; no image tint | +| Keyboard focus | Neutral 2px outline, 4px offset | Existing neutral high-contrast focus | Neutral high-contrast outline | +| Rights/success | Icon + words + green | Recorded rights state + evidence | Export/result state + explanation | + +Brand identity and interaction semantics are different roles. This release does not silently replace Metachlorian's deliberately chosen magenta Key or Cutawan's warm-white controls. A future change to those functional accents must update their contrast tables, screenshots and selection behaviour together. Shared roles and familiar workflows are more valuable than forcing identical colours onto different tasks. + +Violet is for brand identity and the website's main actions. Green means a recorded cleared/success state, amber requires attention, red signals a blocked/error state, and blue is informational. Always pair status with text and a shape or icon. A selected clip is not a success state. Unknown rights are never shown as cleared. Keep brand colour, status colour, and user-configured caption colours separate. + +## Typography, density and shape + +| Surface | Typeface and hierarchy | Density | +| --- | --- | --- | +| Marketing | Existing self-hosted Bricolage Grotesque for headings; DM Sans for prose | 16px+ body, 14px controls, generous section spacing | +| Metachlorian | Existing Instrument Sans Variable for UI; JetBrains Mono for timecodes and evidence | Existing compact / standard / comfortable modes | +| Cutawan | Existing Inter/system UI stack; explicit mono for time and technical data | Dense editor with readable setup, transcript and settings views | + +Do not use caption fonts as UI fonts. Caption styling is user content and must continue to match exported video. Use tabular numerals and disabled number ligatures for durations, timecodes and numeric comparisons. Leave browser text sizing enabled; reflow at 200% zoom without clipped controls. Dense metadata may be 12px; essential labels, error messages and frequently used controls should be 14px where space permits. Verify the actual fonts instead of relying on font-size alone. + +Use the 4px spacing scale: 4, 8, 12, 16, 20, 24, 32, 40, 48 and 64. Controls have 4px corners, popovers 6px, dialogs 8px. Reserve rounded-full shapes for things that require them, such as switch tracks. Use lines and spacing to group information; avoid nesting decorative cards inside other cards. Website illustrations may be expressive, while its controls follow the family shape rules. + +## Component contracts + +| Component | Required states and behaviour | Ownership | +| --- | --- | --- | +| Button | Default, hover, pressed, focus, disabled, busy; visible action verb; stable width; one primary action per task | Site HTML; React Aria in library; existing React/Tailwind in editor | +| Field/search | Persistent label; hint; invalid with recovery; keyboard submit; preserve input on failure | Each repo, shared semantics | +| Choice/tab | Explicit selected/checked state; correct roles; arrow-key behaviour; no colour-only state | Reuse each repo's primitives | +| Dialog/menu | Accessible name; keyboard operation; Escape; focus containment/restoration; scrollable content | Native dialog on site; React Aria in library; editor follows same contract | +| Status | Icon or shape + concise words; explanation and remedy where useful; live announcement only when state changes | Semantic status adapters | +| Shot/clip frame | Correct aspect ratio; in/out or duration nearby; independent preview, selection and focus; recorded rights visible | Product-specific rendering | +| Progress | Real stage/progress; cancellable when supported; failure recovery; preserve completed work | Product-specific processing | + +Pointer targets are at least 24px under the WCAG 2.2 minimum; aim for 44px on coarse pointers and 48px for marketing actions. Target spacing exceptions require deliberate verification. Text contrast is at least 4.5:1 for normal text; meaningful control boundaries and focus indicators at least 3:1. Logos are a separate identity asset, not a way to justify low-contrast controls. + +Feedback uses 80ms, controls 120ms, panels 180ms, dialogs 240ms. Motion should explain a change or preserve spatial context. Respect reduced motion for UI transitions and autoplay. Do not disable the user's authored caption animation or alter video exports because the OS asks for reduced UI motion. Preview/content controls need their own explicit setting if reduced playback motion is supported. + +## Patterns specific to these products + +**Discover → inspect → select → hand off.** Metachlorian shows why each shot matched, signal provenance and confidence, recorded rights, and exact ranges. Human corrections remain distinguishable from model output. Cutawan receives the selected ranges and presents an editable result, with preview, trim, transcript and export. Avoid implying that a search score proves usability or that a proposed edit is guaranteed to perform well. + +**The handoff summary is shared.** Use the same order: collection/project name; shot count and total duration; destination; availability of original media/proxies; recorded rights and unresolved restrictions; explicit action; result and recovery. Keep source in/out, frame rate, media path and rights verdict with every shot. An unavailable file needs a relink action, not an empty preview. A destination app that is absent needs a useful export alternative. Design-system consistency does not by itself implement the package/API contract. + +**Language carries trust.** Use “shot” for a source range in Metachlorian, “clip” for a selected/edited item in Cutawan, “file” for source media and “export” for an output. Say “recorded as cleared”, “model estimate” or “measured” where relevant. Use sentence case in control labels. Error messages say what happened and what the user can do. Keep the demo labelled as illustrative and preserve accurate provider/rights limitations. + +**Protect footage judgement.** No violet or magenta tint over thumbnails, no coloured surround in a playback well, and no decorative glow over the viewer. Display selection outside the media. Metachlorian's fixed/resizable rails and Cutawan's timeline are domain tools; do not redesign them into marketing layouts. + +## Adoption and maintenance + +| Repository | Implemented foundation | Next component work | +| --- | --- | --- | +| `Metachlorian-site` | Approved SVG lockup; original-source favicon; generated family CSS; semantic marketing adapter; common controls, focus, statuses and neutral demo; guides generated from shared header/footer | Replace remaining legacy literal styles incrementally; keep generator and static checks in sync | +| `Metachlorian` | Proposed family source/export; existing Edge Code token names mapped to shared spacing, shape and timings; approved mark in app chrome | Review real light/dark screenshots, then expand the handoff summary and component documentation | +| `cutawan` | Proposed shared foundation/export; visible keyboard focus and UI reduced-motion rules; existing palette, caption fonts and macOS chrome preserved | Consolidate repeated buttons/fields as components; validate settings, timeline and handoff against these contracts | + +The library's existing `docs/design/system.md` remains the detailed component and workflow specification. This document defines the family layer; it does not replace the existing API, keyboard, virtualisation or media-performance requirements. Cutawan's adoption note describes its Electron and Tailwind adapter. + +Changes should include the user task being improved, token/component changes, affected profiles, accessibility evidence and screenshots when behaviour/layout changes. Adding a shared primitive requires at least two actual consumers; avoid a speculative component library. Version shared foundations using semantic versioning: fixes are patch, additive tokens/patterns minor, removals or role changes major. Deprecate names before removing them. + +Run the token generator in each repo and `--check` in CI or review. Check search → inspect → select → handoff on the library; import → trim → captions → export on the editor; demo → guide → install on the site. Test keyboard focus, empty/error/processing states, reduced motion, small viewports, text enlargement and each supported theme. Include missing footage, unknown rights and unavailable destination cases. Evaluate a design change by task completion and clarity; do not invent user-study scores. + +## Research and decisions + +- [Primer Primitives](https://github.com/primer/primitives): inspected its published source documentation for separation of base/functional tokens, generated exports, accessible colour modes and overrides. +- [Adobe Spectrum Tokens](https://github.com/adobe/spectrum-tokens): inspected its published token documentation for a reusable token layer alongside components. +- [Metachlorian's existing design research](https://github.com/JeremySNR/Metachlorian/blob/HEAD/docs/design/research.md) and [Edge Code specification](https://github.com/JeremySNR/Metachlorian/blob/HEAD/docs/design/system.md): existing project evidence for neutral footage surfaces, familiar editing geometry and inspectable model output. +- Cutawan's renderer CSS, top bar and README: inspected the current palette, native window constraints, caption-font handling and clip-editing workflows. + +These sources informed architecture and product fit. This session did not run a new usability study or independently reverify the claims in earlier research. Browser verification may be limited by the execution environment; report that separately from passing source and token checks. diff --git a/design-system/build.py b/design-system/build.py new file mode 100644 index 0000000..7a9ab55 --- /dev/null +++ b/design-system/build.py @@ -0,0 +1,40 @@ +"""Compile the family DTCG-style foundation tokens; no third-party dependencies.""" +from pathlib import Path +import argparse +import json + +ROOT = Path(__file__).resolve().parent + +def compile_tokens(): + source = json.loads((ROOT / 'tokens.json').read_text()) + version = source['$extensions']['org.metachlorian.family']['version'] + lines = [f'/* Family foundations {version}. Generated by design-system/build.py; edit tokens.json. */', ':root {'] + def visit(group, path=()): + for name, value in group.items(): + if name.startswith('$'): + continue + key = (*path, name) + if '$value' not in value: + visit(value, key) + continue + raw = value['$value'] + if value['$type'] in ('dimension', 'duration'): + raw = f"{raw['value']:g}{raw['unit']}" + lines.append(f" --family-{'-'.join(key)}: {raw};") + visit(source) + return '\n'.join([*lines, '}', '']) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', type=Path, default=ROOT / 'foundations.css') + parser.add_argument('--check', action='store_true') + args = parser.parse_args() + css = compile_tokens() + if args.check: + if not args.output.exists() or args.output.read_text() != css: + raise SystemExit(f'Stale token export: {args.output}. Run the generator.') + print(f'PASS: {args.output} matches the versioned token source.') + else: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(css) + print(f'Generated {args.output}') diff --git a/design-system/foundations.css b/design-system/foundations.css new file mode 100644 index 0000000..a69a8ef --- /dev/null +++ b/design-system/foundations.css @@ -0,0 +1,55 @@ +/* Family foundations 1.0.0. Generated by design-system/build.py; edit tokens.json. */ +:root { + --family-color-brand-violet: #5140c6; + --family-color-brand-violet-hover: #4031a6; + --family-color-brand-violet-pressed: #332580; + --family-color-brand-violet-on-dark: #b7aaff; + --family-color-brand-ink: #25253d; + --family-color-brand-tint: #efedf7; + --family-color-neutral-white: #ffffff; + --family-color-neutral-paper: #f4f4f4; + --family-color-neutral-canvas: #e8e8e8; + --family-color-neutral-line: #d4d4d4; + --family-color-neutral-muted: #545454; + --family-color-neutral-secondary: #434343; + --family-color-neutral-ink: #141414; + --family-color-neutral-dark-panel: #1a1a1a; + --family-color-neutral-dark-raised: #212121; + --family-color-neutral-dark-border: #828282; + --family-color-neutral-dark-muted: #a0a0a0; + --family-color-neutral-dark-text: #ececec; + --family-color-neutral-black: #000000; + --family-color-status-cleared: #11663e; + --family-color-status-cleared-bg: #dcf1e5; + --family-color-status-caution: #754c00; + --family-color-status-caution-bg: #f8ebcf; + --family-color-status-blocked: #ad1f28; + --family-color-status-blocked-bg: #f9dfe0; + --family-color-status-info: #1a52a6; + --family-color-status-info-bg: #dde8f8; + --family-space-1: 4px; + --family-space-2: 8px; + --family-space-3: 12px; + --family-space-4: 16px; + --family-space-5: 20px; + --family-space-6: 24px; + --family-space-8: 32px; + --family-space-10: 40px; + --family-space-12: 48px; + --family-space-16: 64px; + --family-radius-control: 4px; + --family-radius-popover: 6px; + --family-radius-dialog: 8px; + --family-size-target-min: 24px; + --family-size-target-touch: 44px; + --family-size-marketing-control: 48px; + --family-size-brand-mark-width: 28px; + --family-size-brand-mark-height: 18px; + --family-motion-feedback: 80ms; + --family-motion-control: 120ms; + --family-motion-panel: 180ms; + --family-motion-dialog: 240ms; + --family-type-body: 1rem; + --family-type-control: 0.875rem; + --family-type-metadata: 0.75rem; +} diff --git a/design-system/tokens.json b/design-system/tokens.json new file mode 100644 index 0000000..6087eca --- /dev/null +++ b/design-system/tokens.json @@ -0,0 +1,309 @@ +{ + "$extensions": { + "org.metachlorian.family": { + "version": "1.0.0", + "canonical": "https://github.com/JeremySNR/Metachlorian/blob/HEAD/design-system/tokens.json" + } + }, + "color": { + "brand": { + "violet": { + "$type": "color", + "$value": "#5140c6" + }, + "violet-hover": { + "$type": "color", + "$value": "#4031a6" + }, + "violet-pressed": { + "$type": "color", + "$value": "#332580" + }, + "violet-on-dark": { + "$type": "color", + "$value": "#b7aaff" + }, + "ink": { + "$type": "color", + "$value": "#25253d" + }, + "tint": { + "$type": "color", + "$value": "#efedf7" + } + }, + "neutral": { + "white": { + "$type": "color", + "$value": "#ffffff" + }, + "paper": { + "$type": "color", + "$value": "#f4f4f4" + }, + "canvas": { + "$type": "color", + "$value": "#e8e8e8" + }, + "line": { + "$type": "color", + "$value": "#d4d4d4" + }, + "muted": { + "$type": "color", + "$value": "#545454" + }, + "secondary": { + "$type": "color", + "$value": "#434343" + }, + "ink": { + "$type": "color", + "$value": "#141414" + }, + "dark-panel": { + "$type": "color", + "$value": "#1a1a1a" + }, + "dark-raised": { + "$type": "color", + "$value": "#212121" + }, + "dark-border": { + "$type": "color", + "$value": "#828282" + }, + "dark-muted": { + "$type": "color", + "$value": "#a0a0a0" + }, + "dark-text": { + "$type": "color", + "$value": "#ececec" + }, + "black": { + "$type": "color", + "$value": "#000000" + } + }, + "status": { + "cleared": { + "$type": "color", + "$value": "#11663e" + }, + "cleared-bg": { + "$type": "color", + "$value": "#dcf1e5" + }, + "caution": { + "$type": "color", + "$value": "#754c00" + }, + "caution-bg": { + "$type": "color", + "$value": "#f8ebcf" + }, + "blocked": { + "$type": "color", + "$value": "#ad1f28" + }, + "blocked-bg": { + "$type": "color", + "$value": "#f9dfe0" + }, + "info": { + "$type": "color", + "$value": "#1a52a6" + }, + "info-bg": { + "$type": "color", + "$value": "#dde8f8" + } + } + }, + "space": { + "1": { + "$type": "dimension", + "$value": { + "value": 4, + "unit": "px" + } + }, + "2": { + "$type": "dimension", + "$value": { + "value": 8, + "unit": "px" + } + }, + "3": { + "$type": "dimension", + "$value": { + "value": 12, + "unit": "px" + } + }, + "4": { + "$type": "dimension", + "$value": { + "value": 16, + "unit": "px" + } + }, + "5": { + "$type": "dimension", + "$value": { + "value": 20, + "unit": "px" + } + }, + "6": { + "$type": "dimension", + "$value": { + "value": 24, + "unit": "px" + } + }, + "8": { + "$type": "dimension", + "$value": { + "value": 32, + "unit": "px" + } + }, + "10": { + "$type": "dimension", + "$value": { + "value": 40, + "unit": "px" + } + }, + "12": { + "$type": "dimension", + "$value": { + "value": 48, + "unit": "px" + } + }, + "16": { + "$type": "dimension", + "$value": { + "value": 64, + "unit": "px" + } + } + }, + "radius": { + "control": { + "$type": "dimension", + "$value": { + "value": 4, + "unit": "px" + } + }, + "popover": { + "$type": "dimension", + "$value": { + "value": 6, + "unit": "px" + } + }, + "dialog": { + "$type": "dimension", + "$value": { + "value": 8, + "unit": "px" + } + } + }, + "size": { + "target-min": { + "$type": "dimension", + "$value": { + "value": 24, + "unit": "px" + } + }, + "target-touch": { + "$type": "dimension", + "$value": { + "value": 44, + "unit": "px" + } + }, + "marketing-control": { + "$type": "dimension", + "$value": { + "value": 48, + "unit": "px" + } + }, + "brand-mark-width": { + "$type": "dimension", + "$value": { + "value": 28, + "unit": "px" + } + }, + "brand-mark-height": { + "$type": "dimension", + "$value": { + "value": 18, + "unit": "px" + } + } + }, + "motion": { + "feedback": { + "$type": "duration", + "$value": { + "value": 80, + "unit": "ms" + } + }, + "control": { + "$type": "duration", + "$value": { + "value": 120, + "unit": "ms" + } + }, + "panel": { + "$type": "duration", + "$value": { + "value": 180, + "unit": "ms" + } + }, + "dialog": { + "$type": "duration", + "$value": { + "value": 240, + "unit": "ms" + } + } + }, + "type": { + "body": { + "$type": "dimension", + "$value": { + "value": 1, + "unit": "rem" + } + }, + "control": { + "$type": "dimension", + "$value": { + "value": 0.875, + "unit": "rem" + } + }, + "metadata": { + "$type": "dimension", + "$value": { + "value": 0.75, + "unit": "rem" + } + } + } +} diff --git a/docs/design-system.md b/docs/design-system.md new file mode 100644 index 0000000..3992fff --- /dev/null +++ b/docs/design-system.md @@ -0,0 +1,29 @@ +# Cutawan's family design-system adapter + +The [family specification](../design-system/README.md) defines shared foundations with Metachlorian and its website. Cutawan's role is creating and exporting edits; its timeline, transcript, framing and caption controls remain domain-specific. + +## Implemented adoption + +`src/renderer/src/index.css` imports the committed `design-system/foundations.css` after Tailwind. The family CSS uses only `--family-*` names, so it does not override existing `@theme` surface or accent values. Keyboard focus uses a visible neutral outline. Reduced motion suppresses UI control transitions while leaving authored caption/video animation untouched. + +Existing renderer colours, the Cutawan icon, Inter/system UI fonts, bundled export/caption fonts, range controls and macOS vibrancy remain intact. The player stays opaque black. A family identity does not require a different Cutawan logo or a tinted video surround. + +Generate and verify the export: + +```sh +python3 design-system/build.py +python3 design-system/build.py --check +``` + +Run `npm run typecheck`, `npm run lint`, `npm test`, and the existing Xvfb smoke suite for application changes. CI also checks the committed token export. Review visible focus in import/setup, clips, editor, settings, dialogs, timeline and transcript; do not rely only on stylesheet inspection. + +## Next implementation slices + +1. Consolidate repeated button/field/segmented-choice markup into local components with focus, disabled, busy and invalid states. Keep existing actions and keyboard behaviour. +2. Map the existing Tailwind semantic roles to family foundations as controls are migrated. A token migration must preserve contrast on macOS translucent surfaces, as well as Windows/Linux opaque ones. +3. Introduce consistent timecode/duration treatment in the clip list, transcript, trim and timeline, without changing source or export timing. +4. Use the shared handoff summary when integrating Metachlorian packages: ranges, source availability, recorded rights, destination, explicit action and recovery. This document does not implement that API or claim the current editor supports every library package. + +Metachlorian uses “shot” for source ranges; Cutawan uses “clip” for edited selections. Model scores are provisional editorial judgements, not guaranteed performance. Unknown rights and missing sources need explicit text and recovery. + +The canonical token JSON lives in the Metachlorian repo. This repo vendors version 1.0.0 and its compiler. Update the source/export together; never fetch tokens at runtime or change caption typography as part of a UI-font migration. diff --git a/src/renderer/src/index.css b/src/renderer/src/index.css index 01f96a5..6ea76f4 100644 --- a/src/renderer/src/index.css +++ b/src/renderer/src/index.css @@ -1,4 +1,5 @@ @import 'tailwindcss'; +@import '../../../design-system/foundations.css'; /* Bundled caption fonts — same files libass burns into exports. */ @font-face { @@ -131,3 +132,17 @@ input[type='range']::-webkit-slider-thumb { .hook-in { animation: hookIn 220ms ease-out; } + + +/* Family accessibility foundation. Caption/video motion is authored content. */ +:where(button, a, input, select, textarea, [tabindex]):focus-visible { + outline: 2px solid var(--family-color-neutral-dark-text); + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + :where(button, a, input, select, textarea, [role='tab']) { + transition-duration: 0ms !important; + scroll-behavior: auto; + } +}