diff --git a/AGENTS.md b/AGENTS.md index bd352dbec..6d9db2fb9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,11 @@ # j3w1/theme agent instructions This repository **defines** the j3w1 theme: one versioned specification with -exact tokens, component rules, a generated single-page visual reference at -`https://j3w1.github.io/theme/`, and machine exports for agents. If you are -applying the theme somewhere else, read `agents/consume.md` instead of this -file. +exact tokens, component rules, machine exports for agents, and a portal at +`https://j3w1.github.io/theme/` with a compact `/reference/` hub, component +detail on `/components//`, and `/reference/contrast/` and +`/reference/decisions/`. If you are applying the theme somewhere else, read +`agents/consume.md` instead of this file. ## Required reading, in order @@ -23,57 +24,46 @@ Images never override tokens. ## Source of truth -Literal values live in `tokens/`. Meaning and permitted uses live in `spec/`. -Native keys live in port mappings. Test facts live in evidence records. +Literal values live in `tokens/`, meaning and permitted uses in `spec/`, native +keys in port mappings, test facts in evidence records (D-000). A contradiction +between spec and tokens is a defect to resolve, not a choice to make. -These files are generated: `exports/`, `schemas/json/`, -`site/src/styles/tokens.generated.css`, the port files under `ports/*/dist/`, -`ports/orca/install/specimen.json`, `packages/ui/README.md` (a copy of -`docs/ui-consumption.md`), and the marker blocks in `README.md` and the port -guides. Edit their sources and run `npm run generate`; never edit them by -hand. A contradiction between spec and tokens is a defect to resolve, not a -choice to make. +Generated, never edited by hand: `exports/`, `schemas/json/`, +`site/src/styles/tokens.generated.css`, `packages/ui/dist/`, +`packages/ui/README.md` (a copy of `docs/ui-consumption.md`), the port files +under `ports/*/dist/`, `ports/orca/install/specimen.json`, and the marker +blocks in `README.md`, `agents/consume.md` and the port guides. Edit their +sources and run `npm run generate`. ## The check loop -CI picks the checks a change needs (D-031). You can see its choice before you +CI picks the checks a change needs (D-031); `release-gate` is the only required +check and passes when every selected check passed. See the plan before you push: ``` node scripts/ci/select.mjs --event pull_request --base origin/main --head HEAD ``` -`release-gate` is the only required check: it passes when every selected check -passed. +Run what it selects, usually `npm run generate` (after any source change; `npm +run check` only reports drift), `npm run validate`, `npm run check`, `npm +test`, `npm run build && npm run test:dist`, and `npm run test:smoke`. Build +like CI with `GITHUB_SHA` set to the full commit; a pinned build refuses +uncommitted generated files, so commit first. Browser runs use port 4173; set +`PW_PORT` to a free port when another checkout is testing. -**Before you push.** Run what the plan selects. The usual set: +`npm run test:all` is the whole suite, with the browser part in parallel. Run +it when you changed rendering, tokens, components or the package, and say so +in the pull request. On `main`, CI runs the whole matrix for every site change +before it deploys; the workflow dispatch runs it on any branch. -``` -npm run generate # rewrite every generated file (run it after any source change) -npm run validate # sources: manifest, decisions, tokens, docs, spec, contrast, ports, references, private material -npm run check # generated files are current, no orphans -npm test # node --test over sources and exports -npm run build && npm run test:dist # the site under /theme/ -npm run test:smoke # the built site loads, renders from the tokens, takes a keyboard -``` - -`npm run check` only reports drift; `npm run generate` fixes it. - -**The whole suite.** `npm run test:all` runs everything; the browser part runs -in parallel. Run it when you changed rendering, tokens, components or the package, -and say in the pull request that you did. On `main`, CI runs the whole matrix -for every change to the site before it deploys. From the Actions tab, the -workflow dispatch runs it on any branch. +Only the whole suite writes execution evidence. A subset (`test:smoke`, the +specs a pull request selects, one spec, shard or project) writes none and +never claims coverage. Report checks that were not run separately from checks +that passed. -**Evidence.** Only the whole suite writes execution evidence. A subset, such as -`test:smoke`, the specs a pull request selects, or a local run of one spec, -shard or project, writes none and never claims coverage. - -Report checks that were not run separately from checks that passed. - -**Design mode is a working tool, not a gate.** `npm run design` compares the -site at an anchored commit with the live dev server (`docs/design-mode.md`). -Nothing it shows is evidence, and it approves nothing. +Design mode (`npm run design`, `docs/design-mode.md`) is a working tool, not a +gate: nothing it shows is evidence, and it approves nothing. ## What you may do without a decision @@ -115,34 +105,27 @@ exists) and the install commands generated from it into the port guides. Commits are conventional (`feat:`, `fix:`, `docs:`, `spec:`, `tokens:`, `site:`, `chore:`) and explain why. -## Specification UI maintenance - -Every CSS hex literal a person can see on the j3w1 UI Theme Spec must get its -generated inline color swatch (D-014). +## Hex swatches (D-014) -- New sections and components get swatches from the whole-page build - transform. Never add or maintain swatches by hand. -- Explicit runtime renderers, such as the token inspector, use the same - literal parser and presentation helpers. Never scan the runtime DOM for - colors. -- Keep source text, copied text and machine exports unchanged. -- Run the three hex gates: source (`tests/hex-literals.test.js`), dist - (`tests/dist/hex-swatches.test.js`) and browser - (`tests/browser/hex-swatches.spec.js`). +Every visible CSS hex literal on the site gets its generated inline swatch from +the whole-page build transform; never add or maintain one by hand. Runtime +renderers, such as the token inspector, use the same literal parser and +presentation helpers and never scan the DOM for colours. Source text, copied +text and machine exports stay unchanged. The three gates are +`tests/hex-literals.test.js`, `tests/dist/hex-swatches.test.js` and +`tests/browser/hex-swatches.spec.js`. ## Execution evidence -- `tested` stays the compatibility alias for `testImplemented`. It is never a - pass. -- Every browser test needs an explicit `verification` annotation that names its - component, category, states, variants and limits. -- Use the shared evidence fixture for the real environment metadata. -- A test in the matrix proves rendering coverage only. +- `tested` stays the compatibility alias for `testImplemented`; it is never a + pass. A test in the matrix proves rendering coverage only. +- Every browser test needs an explicit `verification` annotation naming its + component, category, states, variants and limits, and uses the shared + evidence fixture for the real environment metadata. - After the browser suite, run `npm run verification:report`, then `npm run verification:check`, then `npm run test:verification`. -- Keep the exact specimen bytes. -- Run records go in the ignored `test-results/` and the published - `dist/verification/`, never in the deterministic committed exports. +- Keep the exact specimen bytes. Run records go in the ignored `test-results/` + and the published `dist/verification/`, never in the committed exports. - Do not claim a manual keyboard or screen-reader pass without a recorded protocol and environment. diff --git a/CHANGELOG.md b/CHANGELOG.md index d85ceaa26..255630f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ ## [Unreleased] +- **Display and title type sizes** (D-037): `font.size.display` 32/40 and + `font.size.title` 24/32 with their line heights, for page heroes, metric + figures and page titles. +- **Compact is the default density** (D-038). `:root` and bare recipe + wrappers carry the compact density values; `data-density="comfortable"` + opts in. A consumer that relied on the old comfortable default sets it. +- **Components follow the spec.** Ordinary controls and rows draw their + focus ring at `focus.offset` (−2px), as foundations rule 2 says (D-006, + proposed); containers and invalid fields keep their own offsets. A + selected item takes the ordinary 1px dashed ring on the selection fill, + and links hover on `interaction.hover.bg` (D-027). Composite actions reuse + the button tone slots; field parts share one partial. +- **Site.** One shell built from the documented components, one primary + action per view, and calls to action as standalone links. `/reference/` + is a static hub (about 0.5 MB, from 4.6 MB); state matrices, token tables + and computed contrast pairs live on `/components//`, and the contrast + report, decision log and fixtures on their own `/reference/` pages. Every + published anchor still resolves. The per-row copy buttons of the reference + token tables are gone: each token's page, `/tokens//`, copies its + path and CSS variable, and the surface and text grids still copy values. +- **Documentation.** The decision log is grouped by topic. Entries keep the + choice, reason and status, and most rule detail moved to the spec section + it governs. README, guides and component specs drop most repeated text. +- **Fixed.** The install guide pins the released package version. Control + enhancement no longer restyles component buttons. Contents search and the + family filter keep each other's results, and search retries a failed index + load. Copy labels cannot stick. The surface and text grids' hex values get + their swatches. The token inspector adds no tab stops and 404 ships no + inspector data. Generated form workflows have one primary action. + Foundation and component spec prose no longer states measured contrast + ratios; the computed report carries them, and the decision log keeps its + historical measurements. - **`color.surface.accent`** (D-035). A new surface for the user's own content, such as outgoing chat messages, on the new primitive `color.primitive.red.40` (`#3d0c0a`), chosen by the owner from a side-by-side comparison; links keep diff --git a/README.md b/README.md index 5f07c84be..d7a115c8b 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,16 @@ **True-black canvas. Near-black surfaces. Rose text. Red focus. Square components.** -This repository is the versioned specification of the j3w1 look. People and AI -agents use it to build the same theme in websites, apps, editors, terminals and -native theme formats. It publishes: +`j3w1/theme` **defines** the j3w1 look: exact tokens, semantic roles, +interaction states, accessibility limits, component specifications and +portability rules. People and AI agents use it to build the same theme in +websites, apps, editors, terminals and native theme formats. It publishes: - a design-system portal: **https://j3w1.github.io/theme/**; - the complete static specification: **https://j3w1.github.io/theme/reference/**; - an independently written [Vue demo](https://j3w1.github.io/theme/demo/); -- machine-readable exports for agents. +- machine-readable exports for agents, the official `@j3w1/ui` web package and + ready-made application themes. Specification version **4.0.0** (release; default: approved, heritage-ansi: heritage, extended: proposed). @@ -20,12 +22,15 @@ Use the pinned approved default profile. Pending roles in that profile use-and-r Approval and consumption eligibility are separate; see the policy above and `spec/decisions.md` D-013. -## Application theme downloads +## Quick start -Theme files for apps, ready to import. Each file link is pinned to this -version's release tag, and the site serves the same file from its **download** -link. Each port's README under `ports/` gives scope, limits and rollback. -Declared status and import evidence are explained under +Pin a release tag or a full commit, never `main`, and read every file at that +same revision. Releases are tags. + +**Theme an application.** Download a file from the table below. Each link is +pinned to this version's release tag, and the site serves the same file from +its **download** link. Each port's README under `ports/` gives scope, limits +and rollback; declared status and import evidence are under [Application ports](#application-ports). @@ -92,57 +97,29 @@ Declared status and import evidence are explained under | Windows 11 native and Windhawk theme | [j3w1-terminal-chrome.json](https://raw.githubusercontent.com/j3w1/theme/v4.0.0/ports/windows/dist/j3w1-terminal-chrome.json) | [download](https://j3w1.github.io/theme/ports/windows/j3w1-terminal-chrome.json) | Use the single pinned ports/windows/install.ps1 lifecycle; chrome changes share its saved rollback. | experimental; import evidence not verified | -To change how the theme looks, use [design mode](docs/design-mode.md) -(`npm run design`). It shows the real site, built at an anchored commit, next -to the live dev server, so you judge a change against what it replaces. It is -a working tool, not a gate. - -History in brief: - -- The owner chose **True Black / Rose** after the - [local visual-foundation review](docs/phase6-visual-review.md). D-023 records - the seven canonical surface changes; D-024 records the strong-red links the - owner asked for next. See [the migration notes](docs/true-black-rose-migration.md) - and [the delivery record](docs/phase6-delivery.md) for verification status. -- D-025 records the owner's request for themed controls across the portal and - the Vue demo. Since version 1.1.0, single and multiple choices use j3w1 - surfaces and selection colors, keep their native form data and fallback, and - come with a supported enhancement for existing app markup. The shared command - palette puts a full-width search field under its label. - -Forms and design tools: - -- The [validation and recovery composition](https://j3w1.github.io/theme/patterns/validation-recovery/) - and the [draft form builder](https://j3w1.github.io/theme/builder/) show local - form behavior with canonical fields. Definitions are bounded, content-pinned - JSON. Entered values never go into downloads or storage. -- The optional [Figma Variables bridge](https://j3w1.github.io/theme/figma/) - is a reviewed one-way import with dry-run, explicit apply and rollback - receipts. Generate its sample with - `node scripts/figma-bridge.mjs --ref FULL_COMMIT_OR_RELEASE_TAG`. -- Limits and evidence: [form patterns](spec/form-patterns.md), - [builder contract](spec/components/form-builder.md) and - [bridge instructions](spec/figma-bridge.md). - -## What this is, and what it is not - -`j3w1/theme` **defines** the theme: exact tokens, semantic roles, interaction -states, accessibility limits, component specifications and portability rules. -The official `@j3w1/ui` package implements all 67 inventory entries with native -HTML, typed Custom Elements, per-component imports and complete copy -distributions. The [consumption instructions](docs/ui-consumption.md) cover the -package, copy and native mapping routes. `j3w1/j3w1.github.io` stays a separate -downstream app. No purchased template source or assets are included. - -## Where it came from - -The palette began on a Manjaro i3 workstation, preserved in -[`j3w1/1w3j`](https://github.com/j3w1/1w3j), and was named as web roles by -[`j3w1/j3w1.github.io`](https://github.com/j3w1/j3w1.github.io). The full -story is in `spec/identity.md`. Both sources are pinned by revision in -`references/sources.json`. Historical ports (a gedit scheme, an IntelliJ -scheme, tmux, browser extensions) are listed in `references/catalogue.json` -as evidence, not downloads. +**Build a web app.** Use the official `@j3w1/ui` package: all 67 inventory +entries as native HTML, typed Custom Elements, per-component imports and +complete copy distributions. [`docs/ui-consumption.md`](docs/ui-consumption.md) +covers the package, copy and native mapping routes. For the official +implementations, read `packages/ui/dist/index.json`, then the +`contracts/.json` and `examples/.json` you need; the installed +`j3w1-ui kit` command prepares bounded framework, package, copy or mapping +kits. npm publication is still an owner action; until then the portal offers an +installable tarball with integrity metadata. No purchased template source or +assets are included, and `j3w1/j3w1.github.io` stays a separate downstream app. + +**Use the tokens.** `exports/tokens.css` exposes every role as a CSS custom +property and `exports/tokens.resolved.json` holds the resolved values of every +profile. Consume roles, never primitives. + +**Point an agent at it.** Follow [`agents/consume.md`](agents/consume.md): + +1. Read `theme.json`. +2. Read `exports/theme.compact.md`. +3. Read the `exports/components/.json` files you need. +4. Record `theme.lock.json` and report deviations. + +`exports/llms.txt` is a discovery index for tools that look for one. ## The identity in seven values @@ -171,242 +148,61 @@ as evidence, not downloads. Three bounded hues (amber, green, blue) exist for status, diagnostics and diffs (decision D-001), and for opt-in syntax themes through `code.hued.*` (D-030). They are forbidden in surfaces, chrome, buttons, links and selections. The -reference syntax highlighting stays monochrome in the default profile. - -## Profiles - -| Profile | Status | Purpose | -| --- | --- | --- | -| `default` | approved | The everyday composition every implementation targets. | -| `heritage-ansi` | heritage | The sixteen historical terminal slots and assignments, exact, including the ones that fail the contrast floor. | -| `extended` | proposed | Semantic hues for syntax, a full sixteen-slot terminal palette that reaches 4.5:1, coloured chart series. | - -## Using the theme - -**People:** browse the portal's foundations, components, patterns and tools. -The full static reference keeps every state matrix, stress fixture, -non-example, contrast report and coverage ledger. Old homepage hashes redirect -to its sections. - -**Agents:** use the deterministic artifacts. Pin a tag or commit, then follow -[`agents/consume.md`](agents/consume.md): - -1. Read `theme.json`. -2. Read `exports/theme.compact.md`. -3. Read the `exports/components/.json` files you need. -4. Record `theme.lock.json` and report deviations. - -`exports/llms.txt` is a discovery index for tools that look for one. For the -official implementations, read `packages/ui/dist/index.json`, then the -`contracts/.json` and `examples/.json` you need. The installed -`j3w1-ui kit` command prepares bounded framework, package, copy or mapping -kits. npm publication is still an owner action; until then the portal offers -an installable tarball with integrity metadata. - -**Contributors:** read [`AGENTS.md`](AGENTS.md). `CLAUDE.md` imports it. - -### Tools on the portal - -- **Shared specimen markup.** State matrices use - `scripts/lib/specimen-markup.mjs` for native state attributes and hidden-cell - focus handling. Its parsed ID-reference rewriting is shared with recipe - instances through `scripts/lib/markup.mjs`. Recipes reject unresolved local - references; matrices keep their documented fixture links. Keep these - transformations shared when you add specimen tools. -- **[Component workbench](https://j3w1.github.io/theme/workbench/).** Live - controls for button, text field, checkbox, tabs and dialog, plus table and - sidebar navigation previews. Pick a maintained variant and starting state, - then check real iframe widths, density, language and RTL. Comparison frames - can use different environments. The anatomy inspector keeps declared roles, - resolved values and browser measurements apart; it reports mismatches - without correcting the specimen. Forced visual states are labelled and do - not replace keyboard interaction or form validation. -- **Contrast comparisons** use the existing unrounded contrast engine. Pick - the declared pair, or an explicit text/UI context and an opaque underlay. - Eligibility and waivers stay separate from the number. -- **Motion controls** replay only maintained transitions. Static endpoints and - reduced motion take precedence. These diagnostic tools do not certify - accessibility. -- **Share links.** Copy a configuration or preview link to reproduce its - starting state. Links name the actual build and source digest; a revision - that is not available is reported with a pinned source link. Entered - specimen text is left out unless you include it in the shown share payload. - Nothing is saved to browser storage. -- **Issue drafts.** Component and token pages link to a reviewed public issue - draft. A draft includes the actual build, profile and public anchor, and - never includes entered specimen text. If a draft is longer than the - 8,000-character portable URL budget, copy it and paste it into GitHub's - composer. Opening the composer never submits an issue. - -Canonical specimens, normative content and direct reporting links all work -without JavaScript. - -`schemas/playground.mjs` defines the closed configuration choices, and -`site/workbench.json` binds controls to maintained specimen parts. The build -checks each binding in every variant. Preview routes and state matrices share -the parsed renderer: add new bindings and coverage instead of writing another -specimen implementation. - -### Choosing a semantic role - -Use the [token usage explorer](https://j3w1.github.io/theme/tokens/), or follow -a token-name link from the specification. - -- Filter by role group, component, part, state, variant, profile, status and - surface. All selected usage filters must match the same declaration; an - unknown relationship matches nothing. -- Exact path, CSS-variable and hex searches still work, and equal values keep - their separate roles. -- Filter URLs can be shared. The explorer stores no preferences and sends no - searches to a service. - -Each token has its own page with every profile value, eligibility, pending -decisions, alias dependencies, provenance, component and port references, and -contrast declarations. Profile tables and details work without JavaScript. -Copy controls copy the role path or the CSS variable. In hosted builds, source -links use the build's full commit and the parsed source line; local builds show -checkout-relative locations and claim no published revision. - -Agents can also fetch `exports/token-usage.json` at the same pinned revision as -the other contracts. This schema-version-1 reverse index comes from source -mappings, not from colour similarity. - -- Variant references are included only when a mapped part's prefix equals a - declared variant id. -- Missing port consumers stay unknown. -- The index is digest-covered and uses `schemas/json/token-usage.schema.json`. - After you edit source mappings, regenerate it with `npm run generate`. -- The usage and port-capability indexes keep complete JSON schemas and data, - with one usage or capability record per line to reduce transfer size and - diff noise. Consume them as JSON; source locations still refer to maintained - source files, not the index's presentation. Other exports retain their format. - -### Standalone component recipes - -The [recipe source viewer](https://j3w1.github.io/theme/recipes/) packages the -default button, text-field and dialog straight from the maintained demo and -CSS sources. Each component section links to its source and standalone HTML. - -1. Download `example.html` to run it without Astro, or include `tokens.css`, - `foundation.css` and `component.css`, in that order, with `markup.html`. -2. Pick a unique instance prefix in the viewer for each copy. Labels and ARIA - references are rewritten together. `two-instances.html` shows two copies - side by side. - -Styles stay inside `.j3w1-recipe`. The dialog is an open visual reference: -modal lifecycle, close actions, focus trapping and focus return are the host's -job. No behavior module or font binary is bundled. - -`site/recipes.json` declares the inputs; `npm run generate` writes and -drift-checks `exports/recipes/`. Source and output digests name the exact -bytes. Use these paths at one pinned commit; the hosted viewer shows the -revision it came from. Recipe markup keeps the specimen's CC BY 4.0 licence and -attribution; CSS keeps its MIT notices. Each recipe's manifest, README and -LICENSE list its dependencies, pending decisions and limits. - -Browser checks use port 4173 by default. If another checkout is testing, set -`PW_PORT` to a free port before `npm run test:browser` and -`npm run test:verification`; both use the same server configuration. For -example, PowerShell: `$env:PW_PORT = '4174'`; POSIX shells: -`PW_PORT=4174 npm run test:browser`. Finish the build before you start tests. - -### Task-scoped implementation kits - -With the [task-kit builder](https://j3w1.github.io/theme/kit/): - -1. Select components. -2. Describe the authorized task and give an integration ID, version and kind. -3. Download a ZIP, or copy the complete Markdown package. - -Inputs stay in the browser; no model service, backend or storage is used. -Without JavaScript, use the CLI from a checkout at a committed revision: - -```sh -npm run task:kit -- --components text-field,checkbox,button,dialog --task "Implement a settings form" --integration-id settings --integration-version 1 --out ../settings-kit -``` - -- The CLI uses the current full commit by default. `--ref` takes a full commit - or a release tag, never a branch. Every input is read from that Git revision. -- The browser embeds its source revision and the expected file digests, and - rejects mixed deployment bytes. A pinned build needs committed generated - inputs and `GITHUB_SHA` set to that full commit. An ordinary unpinned local - build shows the workflow but turns packaging off. -- Standard mode includes the available standalone recipes; `--mode minimal` - leaves out only the optional recipes. Both keep the selected JSON and - specification contracts, all shared global rules and foundations, the - required token/alias closure, the consumer contract, the lock schema and a - bounded prompt. Briefs of unselected components are removed from the derived - compact file. -- `KIT.json` keeps upstream hashes apart from the derived compact/subset - hashes. The lock's `resolvedAt` is the source commit's timestamp, so the same - inputs give the same output. An empty starting deviation list does not - certify a downstream implementation. - -Output must be a new local directory under an existing parent that is not -redirected. Existing destinations, symbolic links, Windows reparse points and -unsafe file names fail closed. The writer never erases a destination -recursively; a failed write leaves partial output for inspection. Keep the -parent directory stable while writing: this is not an OS sandbox against -concurrent changes. The legacy `consumption:kit` command uses the same writer -and refuses existing directories. Its default path and its `--strict` -omission of `tokens.resolved.json` are unchanged; pick a new `--out` for each -rerun. - -The original [single-component protocol](tests/consumption/PROTOCOL.md) and -the [composed task-kit protocol](tests/consumption/TASK-KITS.md) are separate -acceptance exercises. A generated kit does not run an agent or prove that the -consumer's implementation conforms. - -## Pinned release comparison - -The [release comparison](https://j3w1.github.io/theme/releases/) compares two -published revisions from `site/releases.json`. The catalogue pins every tag to -its full commit and tells a release apart from a published commit checkpoint. -Builds need those Git objects locally (`fetch-depth: 0` in CI). Building and -viewing the comparison never fetch a moving branch. - -```sh -npm run release:compare -- --from v0.1.0 --to b601998b3711dcd62949d497ed828af2089f9544 --out ../release-comparison -``` - -- Both arguments take only release tags or full commits. `--profile` picks one - common historical profile. -- The output is a new directory under an existing parent that is not - redirected, written by the same guarded writer as task kits. It holds - JSON/Markdown migration reports and reconstructed before/after specimens. -- It never builds historical code, upgrades consumers or certifies a port. -- An optional `--migrations` JSON file follows the generated - `release-migration` schema and names both commits plus explicit role renames. - Equal values alone never imply a rename. - -How the report reads history: - -- Each historical export is checked against its own digest ledger. Missing or - unsupported historical contracts stay unsupported. -- Missing usage indexes are disclosed; direct component/port mappings, where - present, still give bounded dependency evidence. Missing historical - eligibility fields stay unknown. -- Reports separate value, alias, status/decision, component/state, - portability and appearance-source changes. "Known affected" means a declared - dependency or contract changed, not an observed regression in an app. - Unregistered consumers stay unknown, and an empty port catalogue stays empty. - -The visual reference reuses the parsed specimen renderer with historical -markup/CSS for the workbench's seven components and their variants and states. -Both sides use 640 × 480 frames, comfortable density, LTR and static motion -endpoints. No historical JavaScript and no remote font or asset requests are -allowed. Fixture changes are disclosed; fonts depend on the shared browser and -machine. The frames are reconstructed references, not historical runtime -evidence. Browser tests capture matching specimens and attach their real -environment, while the report itself says truthfully that visual execution is -not run. Other engines, physical devices, manual accessibility and unlisted -component captures stay unverified. Report and specimen outputs live in -`dist/releases/`, outside the committed canonical exports, and are covered by -the ordinary built-artifact evidence. - -To add a published revision, add a reviewed catalogue pin and pass the usual -generate/check/build/dist/browser/evidence gates. Adding a comparison changes -no palette and approves nothing. D-016 records the report schema boundary. +reference syntax highlighting stays monochrome in the default profile. Where +the palette came from, and the three profiles (`default`, `heritage-ansi`, +`extended`), are in [`spec/identity.md`](spec/identity.md). + +## Read further + +- **Specification:** [identity](spec/identity.md), + [foundations](spec/foundations.md), [accessibility](spec/accessibility.md), + [portability](spec/portability.md), the [decision log](spec/decisions.md), + [form patterns](spec/form-patterns.md), the + [Figma bridge](spec/figma-bridge.md) and one file per component under + `spec/components/`. +- **Portal tools:** the + [component workbench](https://j3w1.github.io/theme/workbench/), the + [token usage explorer](https://j3w1.github.io/theme/tokens/), + [standalone recipes](https://j3w1.github.io/theme/recipes/), the + [task-kit builder](https://j3w1.github.io/theme/kit/), the + [release comparison](https://j3w1.github.io/theme/releases/), the + [port capability explorer](https://j3w1.github.io/theme/ports/), the + [validation and recovery pattern](https://j3w1.github.io/theme/patterns/validation-recovery/), + the [form builder](https://j3w1.github.io/theme/builder/) and the + [Figma Variables bridge](https://j3w1.github.io/theme/figma/). Each page + states its own limits; canonical specimens, normative content and reporting + links work without JavaScript. +- **Implementation:** [`docs/ui-consumption.md`](docs/ui-consumption.md) and + [`docs/ui-architecture.md`](docs/ui-architecture.md). +- **Ports:** [`ports/README.md`](ports/README.md) says which guide you need. +- **History:** [`CHANGELOG.md`](CHANGELOG.md), the decision log and the + migration notes for [True Black / Rose](docs/true-black-rose-migration.md) + and [rose typography](docs/rose-typography-migration.md). +- **Contributing:** [`AGENTS.md`](AGENTS.md); `CLAUDE.md` imports it. To change + how the theme looks, use [design mode](docs/design-mode.md) + (`npm run design`), a working tool, not a gate. + +## Local commands + +Run these from a checkout with full Git history (CI uses `fetch-depth: 0`); +each tool page above states its limits. + +- `npm run task:kit -- --components --task "" --integration-id --integration-version --out ` + writes a task kit; `--ref` takes a full commit or release tag, `--mode minimal` + omits only the optional recipes. `--out` must be a new directory under an + existing, unredirected parent; keep that parent stable while writing (the + writer is not an OS sandbox against concurrent changes). The legacy + `consumption:kit` refuses an existing directory, so pick a new `--out` for + each rerun. +- `npm run release:compare -- --from --to --out ` + compares two pinned revisions; `--profile` picks one common profile and + `--migrations ` names explicit role renames. Comparable revisions are + the reviewed pins in `site/releases.json`. +- `npm run parity:private -- --config ` runs the + private parity harness ([protocol](templates/private-parity/README.md)); with + no target it prints a not-run record. +- Browser tests use port 4173; set `PW_PORT` to a free port (for example + `PW_PORT=4174 npm run test:browser`) when another checkout is testing. ## Repository map @@ -419,6 +215,7 @@ no palette and approves nothing. D-016 records the report schema boundary. | `schemas/` | zod schema factories shared by the scripts and the site; `schemas/json/` is generated | | `scripts/` | Validators and generators (`npm run validate`, `npm run generate`, `npm run check`); `scripts/lib/` is hashed into the package identities, `scripts/tooling/` holds local-tooling helpers that are not | | `exports/` | Generated, committed: resolved tokens, semantic usage index, CSS custom properties, contrast report, coverage ledger, per-component JSON and briefs, compact and full Markdown, `llms.txt`, digests | +| `packages/ui/` | The `@j3w1/ui` package; `packages/ui/dist/` is generated | | `site/` | The Astro source of the design-system portal, complete reference and token tools, built to `dist/` and deployed to `/theme/` by CI | | `references/` | Pinned provenance, catalogued historical implementations, excerpts, and any reference screenshots with their provenance | | `ports/` | One folder per app, each with a manifest, mapping, generated theme file and evidence; Orca, Claude Code and Codex also hold their installer and one guide (`ports/README.md` says which one you need) | @@ -427,40 +224,12 @@ no palette and approves nothing. D-016 records the report schema boundary. ## Application ports -The [capability explorer](https://j3w1.github.io/theme/ports/) builds its -summary, role-to-native-key rows, artifact links and evidence state from the -same committed port inputs. `exports/port-capabilities.json` is digest-covered -and included in task kits. It keeps mapped, inherited, unsupported, -out-of-scope and not-implemented roles apart from import verification. Legacy -unmapped roles keep their reason, with no guessed classification. An empty -catalogue publishes no app support. - -`ports/README.md` describes the optional capability metadata and the exact -evidence fingerprint. A stale artifact token digest hides its claimed resolved -values. Changing targets, mappings, capabilities or artifact bytes makes -import evidence stale. Static tables and JSON work without JavaScript. - -### Private framework parity - -With no licensed target supplied, `npm run parity:private` prints a useful -**not run** prerequisite record. Public validation, builds and browser tests -never need a private target. To run the bounded seven-component experiment: - -1. Follow [the private harness protocol](templates/private-parity/README.md). -2. Pass an operator-owned JSON file from outside this repository: - - ```sh - npm run parity:private -- --config /private/location/parity.json - ``` - -The protocol checks the installed versions against the target package and -lock. It copies only the selected script/style inputs into a new private -directory. It captures native references and real framework controls with the -recorded environment, input hashes, property differences and screenshots. It -never starts the target app's backend or runs its package scripts. Finished -diagnostics may report differences; they do not certify an app port. Private -sources, configuration and results stay out of Git and the public site, also -when verification fails. +The [capability explorer](https://j3w1.github.io/theme/ports/) and +`exports/port-capabilities.json` keep mapped, inherited, unsupported, +out-of-scope and not-implemented roles apart from import verification (D-018). +Changing targets, mappings, capabilities or artifact bytes makes import +evidence stale. `ports/README.md` describes the capability metadata and the +evidence fingerprint. | Port | Format | Declared status | Import evidence | Theme | Tested on | Supported / inherited / unsupported | @@ -481,21 +250,14 @@ when verification fails. *Specified* means the component's specification validates. *Demonstrated* means its demo renders every declared variant on the site. *Test implemented* means a browser test exists for it. Real outcomes are published separately in -the linked verification report, with exact scope, configuration and digests. -The ledger is generated from the sources. - -The shared browser evidence fixture records the real browser/OS, the final -viewport, and the page root's declared profile and density when the test ends. -A missing declaration is recorded as `not-declared`; a page context that is -not available is reported explicitly. These fields do not infer a mode for -every nested specimen. Protocols that exercise several local modes describe -them in their verification annotation instead of labelling every run as -comfortable. +the linked verification report, with exact scope, configuration and digests +(`spec/accessibility.md`, Verification evidence). The ledger is generated from +the sources. | Component | Family | Priority | Specified | Demonstrated | Test implemented | | --- | --- | --- | --- | --- | --- | -| `admin-form` | composed | R1 | yes | yes | — | +| `admin-form` | composed | R1 | yes | yes | yes | | `alert` | feedback | R1 | yes | yes | — | | `avatar` | display | R2 | yes | yes | — | | `badge` | display | R1 | yes | yes | — | @@ -506,7 +268,7 @@ comfortable. | `chart` | display | R2 | yes | yes | — | | `checkbox` | forms-basic | R1 | yes | yes | yes | | `chip` | display | R1 | yes | yes | — | -| `code-editor` | developer | R1 | yes | yes | — | +| `code-editor` | developer | R1 | yes | yes | yes | | `combobox` | forms-advanced | R1 | yes | yes | — | | `command-palette` | navigation | R2 | yes | yes | — | | `compact-dashboard` | composed | R2 | yes | yes | — | @@ -581,6 +343,6 @@ note. Releases are tags; consumers pin them. ## Licence MIT for code, tokens, schemas, scripts and generated JSON/CSS; CC BY 4.0 for -specification prose and specimens; reference screenshots all rights reserved. -See [`LICENSE.md`](LICENSE.md). No fonts are distributed; the recommended -family is named in `theme.json`. +specification prose and specimens; reference screenshots all rights reserved +(D-004). See [`LICENSE.md`](LICENSE.md). No fonts are distributed; the +recommended family is named in `theme.json`. diff --git a/agents/consume.md b/agents/consume.md index 8126df171..2d317fdf9 100644 --- a/agents/consume.md +++ b/agents/consume.md @@ -1,12 +1,10 @@ # Consuming j3w1/theme (for agents and integrators) -This document is for people and agents who apply the theme to another project. -The contributor workflow for this repository is in `AGENTS.md`. - -You are consuming **design data and optional official implementations**. The -consumer project's own instructions (its `AGENTS.md`, `CLAUDE.md`, security, -architecture and operational rules) always win over anything in this -repository. +For people and agents who apply the theme to another project; contributors +to this repository read `AGENTS.md`. You are consuming **design data and +optional official implementations**. The consumer project's own instructions +(its `AGENTS.md`, `CLAUDE.md`, security, architecture and operational rules) +always win over anything in this repository. ## Official web implementations @@ -22,20 +20,19 @@ bundles are also supported. ``` Choose `copy` to own the full source, or `mapping` for a host the package does -not fit. Native hosts need mapping mode. Kits include the canonical contracts, -their dependency closure and the shared accessibility/identity rules. They do +not fit; native hosts need mapping mode. Kits include the canonical contracts, +their dependency closure and the shared accessibility/identity rules; they do not declare your app verified. `docs/ui-consumption.md` has the exact -installation, framework binding and runtime checks. npm publication is an -owner release action; until then, use the release tarball and its published -integrity metadata. For canonical mappings, keep the lock and deviation -workflow below. +installation, framework binding and runtime checks. Until npm publication (an +owner release action), use the release tarball and its published integrity +metadata. Canonical mappings keep the lock and deviation workflow below. ## 0. Pin a revision first -`` is a release tag (`v0.1.0`) or a full 40-character commit SHA. Never -`main`, never a branch name, never "latest". Resolve a tag to its commit before -you read anything, and use the same `` in every URL below. Mixing -revisions breaks the contract. +`` is a release tag (`v0.1.0`) or a full 40-character commit SHA, never +`main`, a branch name or "latest". Resolve a tag to its commit before you read +anything, and use the same `` in every URL below; mixing revisions breaks +the contract. ``` GET https://api.github.com/repos/j3w1/theme/git/ref/tags/ → object.sha @@ -65,10 +62,10 @@ Use the pinned approved default profile. Pending roles in that profile use-and-r The policy block above is generated. Each resolved token lists its pending -decision IDs in `eligibility.decisionIds`, including alias dependencies. -Report those IDs together with any real deviations. Using an authorized -pending value is a disclosure, not an invented substitution. A blocked action -implies no replacement. A profile preview does not authorize delivered work. +decision IDs, alias dependencies included, in `eligibility.decisionIds`; report +them with any real deviations. Using an authorized pending value is a +disclosure, not an invented substitution. A blocked action implies no +replacement, and a profile preview does not authorize delivered work. ## 4. Read only what you need, in this order @@ -81,18 +78,17 @@ https://raw.githubusercontent.com/j3w1/theme//exports/digests.json Optional: `exports/theme.full.md` for the whole specification; `exports/tokens.css` for a ready-made custom-property sheet. The website -`https://j3w1.github.io/theme/` is for visual confirmation only. It is not a -source of values, and screenshots never override tokens. +`https://j3w1.github.io/theme/` is for visual confirmation only, never a source +of values; screenshots never override tokens. ## 5. Inspect the target before changing anything 1. Name the integration kind: `css-vars`, `vuetify`, `tailwind`, `jetbrains-icls`, `gtk-css`, `terminal-16` or `other`. -2. List which specified surfaces the target can express natively, and which - it cannot (for example, a framework with no read-only field style, or a - sixteen-colour terminal that cannot show hover). -3. Anything the target cannot express becomes a documented deviation in step - 7, never an invented approximation. +2. List which specified surfaces the target can and cannot express natively + (for example, no read-only field style, or a sixteen-colour terminal that + cannot show hover). Each one it cannot express becomes a documented + deviation in step 7, never an invented approximation. ## 6. Implement the mapping @@ -104,8 +100,8 @@ source of values, and screenshots never override tokens. - Keep host accessibility at least as strong as before: never remove a focus indicator, never lower a contrast ratio the host already met, never set `outline: none` without drawing the specified ring. -- Radii are 0, borders 1px, the family is monospace. A host that forbids one - of these gets a deviation, not a redesign. +- Radii are 0, borders 1px, the family is monospace; a host that forbids one + gets a deviation, not a redesign. - Selection is a fill; focus is a ring. On fills, the ring takes the colour the component JSON declares for that state (D-015). Status roles keep their glyphs. @@ -118,10 +114,10 @@ source of values, and screenshots never override tokens. ## What you may not do -- Invent, blend, lighten, darken or "harmonise" colours. Use resolved values +- Invent, blend, lighten, darken or "harmonise" colours; use resolved values only. - Use a blocked value, deliver a proposed profile, or leave out the required - pending-decision disclosures. Historical-only values are not approved UI + pending-decision disclosures; historical-only values are not approved UI roles. - Take values from the site's HTML, from screenshots or from memory. - Touch unrelated components, layouts, copy, dependencies or build @@ -158,7 +154,7 @@ lock: ## theme.lock.json -A small file, committed next to the integration code and validated by +Committed next to the integration code and validated by `schemas/json/theme.lock.schema.json`. It is not a package manager: no graph, no install, no auto-update. To update the theme, change `ref`, `revision`, `version` and `exports`, rerun the consumer's own checks, and write the diff --git a/apps/demo/index.html b/apps/demo/index.html index f4e150cd8..3b4b2c42c 100644 --- a/apps/demo/index.html +++ b/apps/demo/index.html @@ -1 +1 @@ -j3w1 workspace · Vue demo
+j3w1 workspace · Vue demo
diff --git a/apps/demo/src/App.vue b/apps/demo/src/App.vue index 14fc0adec..f0fc47f51 100644 --- a/apps/demo/src/App.vue +++ b/apps/demo/src/App.vue @@ -1,26 +1,79 @@ diff --git a/apps/demo/src/style.css b/apps/demo/src/style.css index e1bbd76bd..622041bd1 100644 --- a/apps/demo/src/style.css +++ b/apps/demo/src/style.css @@ -1,110 +1,655 @@ -* { box-sizing: border-box; } -html { color-scheme: dark; font: var(--font-size-ui-md)/1.6 var(--font-family-mono); color: var(--color-text-default); background: var(--color-surface-canvas); } -body { margin: 0; min-width: 320px; } -button, input, select, textarea { font: inherit; color: inherit; } -button, select { cursor: pointer; } -button, input, select, textarea { border: 1px solid var(--color-border-control); background: var(--color-surface-input); border-radius: var(--radius-none); padding: 8px 12px; max-width: 100%; } -button:hover { background: var(--color-interaction-hover-bg); } -button:disabled { cursor: default; color: var(--color-text-muted); } -:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); } -a { color: var(--color-text-link); text-decoration: underline; text-decoration-color: var(--color-text-link-underline); text-decoration-thickness: var(--border-width-default); text-underline-offset: var(--space-2); } -a:hover { color: var(--color-text-link-hover); background: var(--color-interaction-hover-bg-strong); text-decoration-color: currentColor; text-decoration-thickness: var(--border-width-emphasis); } -h1,h2,h3 { color: var(--color-text-heading); line-height: 1.3; margin: 0; } -h1 { font-size: clamp(26px, 3vw, 38px); letter-spacing: -.04em; } -h2 { font-size: 18px; } -h3 { font-size: 15px; } -p { margin-block: 10px; } -.eyebrow,.nav-caption { color: var(--color-text-muted); font-size: 10px; letter-spacing: .12em; } -.demo-workspace { min-width: 0; } -.demo-shell { display: grid; grid-template-columns: 216px minmax(0,1fr); min-height: 100vh; } -.demo-sidebar { background: var(--color-surface-chrome); padding: 28px 18px; border-inline-end: 1px solid var(--color-border-default); } -.wordmark { text-decoration: none; font-size: 23px; font-weight: bold; color: var(--color-text-bright); } -.wordmark span { font-size: 11px; color: var(--color-text-muted); } -.demo-sidebar nav section { margin-top: 26px; } -.nav-caption { margin: 0 8px 8px; } -.demo-sidebar nav a { display: block; text-decoration: none; padding: 7px 10px; text-transform: capitalize; border-inline-start: 2px solid transparent; } -.demo-sidebar nav a.router-link-active { color: var(--color-interaction-selection-text); background: var(--color-interaction-selection-bg); border-color: var(--color-interaction-focus-ring); } -.back-spec { display: block; margin-top: 32px; } -.demo-topline { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 16px 32px; border-bottom: 1px solid var(--color-border-default); } -.identity { display: grid; place-items: center; width: 32px; height: 32px; background: var(--color-surface-default); border: 1px solid var(--color-border-control); } -.demo-context { display: flex; justify-content: space-between; gap: 16px; align-items: center; padding: 12px 32px; color: var(--color-text-muted); font-size: 11px; } -main { padding: 24px 32px; max-width: 1600px; margin-inline: auto; } -.view-heading { display: flex; align-items: start; justify-content: space-between; gap: 20px; margin-bottom: 28px; } -.view-heading p { color: var(--color-text-muted); } -.panel { background: var(--color-surface-default); border: 1px solid var(--color-border-default); padding: 24px; min-width: 0; } -.panel-heading { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 20px; } -.metrics { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 16px; margin-block: 24px; } -.metric { font-size: 30px; line-height: 1.4; color: var(--color-text-bright); display: block; margin-block: 12px; } -.metric-label { color: var(--color-text-muted); } -.dashboard-middle { display: grid; grid-template-columns: minmax(0,1.7fr) minmax(260px,1fr); gap: 20px; margin-block: 20px; } -.chart-bars { height: 190px; display: flex; align-items: end; gap: 12px; padding-top: 20px; } -.chart-bar { flex: 1; background: var(--color-chart-series-2); min-width: 2px; } -.chart-caption { display: flex; justify-content: space-between; color: var(--color-text-muted); font-size: 10px; margin-top: 20px; } -.actions,.filters { display: flex; flex-wrap: wrap; gap: 12px; align-items: end; } -.filters { margin-block: 20px; } -label { display: grid; gap: 6px; } -.filters label:first-child { flex: 1; min-width: 160px; } -.table-scroll { overflow: auto; } -.records-table { width: 100%; border-collapse: collapse; white-space: nowrap; } -.records-table thead th { color: var(--color-text-bright); font-weight: var(--font-weight-bold); font-size: 11px; text-align: start; } -.records-table td,.records-table th { border-bottom: 1px solid var(--color-border-default); padding: 14px 12px; } -.records-table td:first-child { padding-inline-start: 0; } -.status { color: var(--color-status-info-text); } -.status.ready { color: var(--color-status-success-text); } -.status.review { color: var(--color-status-warning-text); } -.muted { color: var(--color-text-muted); } -.success { color: var(--color-status-success-text); } -.block { display: block; } -.form-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 20px; } -.form-grid .wide { grid-column: 1/-1; } -.form-grid input,.form-grid select,.form-grid textarea { width: 100%; } -.form-grid fieldset { margin: 0; border: 1px solid var(--color-border-default); } -.form-grid .actions { margin-top: 8px; } -.split { display: grid; grid-template-columns: minmax(220px, .8fr) minmax(0,2fr); gap: 20px; } -.stack { display: grid; gap: 16px; } -.list-button { display: block; width: 100%; text-align: start; padding: 16px; background: var(--color-surface-default); border: 1px solid var(--color-border-default); } -.list-button[aria-pressed="true"] { background: var(--color-interaction-selection-bg); color: var(--color-interaction-selection-text); } -.message { max-width: 85%; padding: 14px; background: var(--color-surface-input); border: 1px solid var(--color-border-default); } -.message.self { margin-inline-start: auto; background: var(--color-surface-raised); } -.kanban { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px; } -.kanban article { padding: 16px; margin-top: 16px; background: var(--color-surface-input); border: 1px solid var(--color-border-default); } -.calendar { display: grid; grid-template-columns: repeat(7,minmax(0,1fr)); gap: 1px; border: 1px solid var(--color-border-default); background: var(--color-border-default); } -.calendar-day { background: var(--color-surface-default); min-height: 105px; padding: 10px; } -.calendar-day button { width: 100%; text-align: start; padding: 4px; margin-top: 8px; font-size: 11px; overflow-wrap: anywhere; } -.gallery-grid { display: grid; grid-template-columns: repeat(auto-fit,minmax(min(100%,240px),1fr)); gap: 16px; } -.gallery-link { text-decoration: none; } -.demo-specimen { min-width: 0; overflow: auto; } -.demo-notice { margin: 0 32px; padding: 12px 0; min-height: 48px; color: var(--color-status-success-text); } -.demo-footer { padding: 24px 32px 100px; color: var(--color-text-muted); font-size: 11px; line-height: 1.9; } -.demo-command [data-open] { position: fixed; bottom: 20px; right: 24px; z-index: 20; } -.demo-command .command-palette-dialog:modal { width: min(540px,calc(100vw - 32px)); max-height: 70vh; } -.demo-command [role="option"] { display: grid; grid-template-columns: 100px 1fr; gap: 12px; } -.demo-command [role="option"] .command-label { color: inherit; } -.mobile-nav { display: none; } -.skip-link { position: fixed; top: -100px; z-index: 100; padding: 12px; background: var(--color-surface-raised); } -.skip-link:focus { top: 8px; } -[hidden] { display: none !important; } -.empty { padding: 48px 24px; text-align: center; } -.price { font-size: 36px; color: var(--color-text-bright); } -.error-message { color: var(--color-status-danger-text); } -pre { overflow: auto; } -@media(max-width:1100px) { .metrics { grid-template-columns: repeat(2,minmax(0,1fr)); } .dashboard-middle { grid-template-columns: 1fr; } } -@media(max-width:780px) { .demo-shell { grid-template-columns: minmax(0,1fr); } .demo-sidebar { display: none; } .demo-sidebar.is-open { display: block; } .mobile-nav { display: block; } main { padding: 20px 16px; } .demo-topline,.demo-context { padding-inline: 16px; } .demo-context { align-items: start; } .form-grid,.split,.kanban { grid-template-columns: 1fr; } .view-heading { flex-wrap: wrap; } .calendar { grid-template-columns: 1fr; } .calendar-day { min-height: 65px; } .panel { padding: 18px; } .metrics { gap: 10px; } .metric { font-size: 24px; } } -@media(prefers-reduced-motion:reduce) { *,*::before,*::after { scroll-behavior: auto !important; } } - -.records-table tbody { color:var(--color-text-default); } -.records-table tbody th { color:inherit; font-weight:var(--font-weight-regular); } - -/* Application hierarchy: page title, section, subheading and supporting copy. */ -.view-heading h1 { color: var(--color-text-heading); font-weight: var(--font-weight-bold); } -.view-heading > div > p:last-child { color: var(--color-text-muted); font-size: var(--font-size-ui-lg); line-height: 1.8; margin-top: var(--space-12); } -main h2 { color: var(--color-text-heading); font-weight: var(--font-weight-bold); } -main h3 { color: var(--color-text-heading); font-weight: var(--font-weight-bold); font-size: var(--font-size-ui-lg); } -.demo-sidebar nav section { margin-top: var(--space-32); } -.demo-sidebar .nav-caption { color: var(--color-text-bright); font-size: var(--font-size-ui-sm); font-weight: var(--font-weight-bold); letter-spacing: .12em; margin: 0 var(--space-8) var(--space-12); padding-bottom: var(--space-8); border-bottom: var(--border-width-default) solid var(--color-border-default); } -.demo-sidebar nav a { font-weight: var(--font-weight-regular); padding-inline-start: var(--space-12); } -.demo-sidebar nav a.router-link-active:focus-visible { outline: var(--focus-ring-container); outline-offset: var(--focus-offset-container); } -.gallery-link h2 { color: var(--color-text-link); text-decoration: underline; text-decoration-color: var(--color-text-link-underline); text-underline-offset: var(--space-4); } -.gallery-link:hover h2 { color: var(--color-text-link-hover); text-decoration-color: currentColor; } - -.demo-command > .command-palette { display: contents; } +/* Vue demo layout. Buttons, links, fields, tables, cards and the rail are + canonical component classes inside their packaged elements; their look + comes only from the packaged component CSS, bare controls from + controls.css. This file gives layout and type from token variables only: + no literal colours, and no rule that restyles every button or input. */ + +:root { + --demo-rail: calc(5 * var(--space-48)); /* 240px, sidebar-nav.md "responsive" */ +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + color-scheme: dark; + background: var(--color-surface-canvas); + color: var(--color-text-default); + font-family: var(--font-family-mono); + font-size: var(--font-size-ui-md); + line-height: var(--font-line-height-ui-md); +} + +body { + margin: 0; + min-width: var(--breakpoint-xs); +} + +[hidden] { + display: none !important; +} + +:focus-visible { + outline: var(--focus-ring); + outline-offset: var(--focus-offset); +} + +/* Base elements at zero specificity, so packaged component CSS always wins. + Application links are canonical .link elements; this only covers a bare + anchor that a component leaves unstyled. */ +:where(a) { + color: var(--color-text-link); + text-decoration: underline; + text-decoration-color: var(--color-text-link-underline); + text-decoration-thickness: var(--border-width-default); + text-underline-offset: var(--space-4); +} + +@media (hover: hover) { + :where(a:hover) { + background: var(--color-interaction-hover-bg-strong); + color: var(--color-text-link-hover); + text-decoration-color: currentColor; + text-decoration-thickness: var(--border-width-emphasis); + } +} + +:where(h1, h2, h3) { + margin: 0; + color: var(--color-text-heading); + font-weight: var(--font-weight-bold); + overflow-wrap: anywhere; +} + +/* D-037: the page title uses the title size; section headings use h1. */ +:where(h1) { + font-size: var(--font-size-title); + line-height: var(--font-line-height-title); +} + +:where(h2) { + font-size: var(--font-size-h1); + line-height: var(--font-line-height-h1); +} + +:where(h3) { + font-size: var(--font-size-h2); + line-height: var(--font-line-height-h2); +} + +:where(p) { + margin-block: var(--space-12); +} + +:where(small) { + font-size: var(--font-size-ui-sm); + line-height: var(--font-line-height-ui-sm); +} + +:where(pre, code) { + font-family: var(--font-family-mono); + font-size: var(--font-size-code); + line-height: var(--font-line-height-code); +} + +pre { + overflow: auto; +} + +/* The packaged elements carry the recipe foundation's canvas fill; inside the + application they sit on panels, so they take the surface they are on. + Specimens keep their foundation. */ +.demo-shell :is(j3w1-button, j3w1-link, j3w1-sidebar-nav, j3w1-table, j3w1-text-field, j3w1-select, j3w1-textarea, j3w1-checkbox, j3w1-card, j3w1-description-list, j3w1-list):not(.demo-specimen *) { + background: transparent; +} + +/* ---- shell ------------------------------------------------------------ */ + +.demo-shell { + display: grid; + grid-template-columns: var(--demo-rail) minmax(0, 1fr); + min-height: 100vh; +} + +.demo-workspace { + min-width: 0; +} + +/* The aside is the rail: it paints the sidebar-nav surface and edge, so the + nav inside spans the rail without a second edge. */ +.demo-sidebar { + min-width: 0; + padding-block: var(--space-24); + background: var(--color-surface-default); + border-inline-end: var(--border-width-default) solid var(--color-border-divider); +} + +.demo-sidebar .sidebar-nav { + max-width: none; + padding: 0; + background: transparent; + border-inline-end: 0; +} + +.demo-sidebar .sidebar-nav-label { + text-transform: capitalize; +} + +.demo-sidebar-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-8); + padding-inline: var(--density-control-padding-x); +} + +.wordmark { + color: var(--color-text-bright); + font-size: var(--font-size-h1); + line-height: var(--font-line-height-h1); + font-weight: var(--font-weight-bold); + text-decoration: none; + white-space: nowrap; +} + +.wordmark span { + color: var(--color-text-muted); + font-size: var(--font-size-ui-sm); + line-height: var(--font-line-height-ui-sm); + font-weight: var(--font-weight-regular); +} + +.eyebrow, +.nav-caption { + color: var(--color-text-muted); + font-size: var(--font-size-caption); + line-height: var(--font-line-height-caption); + letter-spacing: .12em; +} + +.eyebrow { + margin-block: 0 var(--space-8); +} + +.demo-sidebar > .eyebrow { + margin: var(--space-4) var(--density-control-padding-x) 0; +} + +.nav-caption { + margin: var(--space-24) var(--density-control-padding-x) var(--space-4); + font-weight: var(--font-weight-bold); +} + +.back-spec { + margin: var(--space-24) var(--space-8) 0; +} + +.demo-topline { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-12); + min-height: var(--space-48); + padding: var(--space-8) var(--space-32); + background: var(--color-surface-chrome); + border-bottom: var(--border-width-default) solid var(--color-border-default); +} + +.demo-path { + min-width: 0; + overflow-wrap: anywhere; +} + +.identity { + display: grid; + flex: none; + place-items: center; + width: var(--density-control-height); + height: var(--density-control-height); + background: var(--color-surface-default); + border: var(--border-width-default) solid var(--color-border-default); + color: var(--color-text-bright); +} + +/* The command palette's trigger sits in the top bar, never floating. */ +.demo-topline .demo-command { + margin-inline-start: auto; +} + +.demo-context { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: var(--space-8) var(--space-16); + padding: var(--space-12) var(--space-32); + color: var(--color-text-muted); + font-size: var(--font-size-ui-sm); + line-height: var(--font-line-height-ui-sm); +} + +main { + width: 100%; + max-width: var(--layout-app-max); + margin-inline: auto; + padding: var(--space-24) var(--space-32); +} + +.demo-notice { + min-height: var(--space-48); + margin: 0 var(--space-32); + padding-block: var(--space-12); + color: var(--color-status-success-text); +} + +.demo-footer { + padding: var(--space-24) var(--space-32) calc(2 * var(--space-48)); + border-top: var(--border-width-default) solid var(--color-border-default); + color: var(--color-text-muted); + font-size: var(--font-size-ui-sm); + line-height: var(--font-line-height-ui-sm); +} + +.demo-footer p { + margin-block: 0 var(--space-8); +} + +/* The palette's framed root would draw an empty bar: only its trigger and + its modal dialog take part in the layout. */ +.demo-command .command-palette { + display: contents; +} + +.demo-command .command-palette-dialog:modal { + width: min(var(--layout-content-max), calc(100vw - var(--space-32))); + max-height: 70vh; +} + +.demo-command [role="option"] { + display: grid; + grid-template-columns: 13ch minmax(0, 1fr); + gap: var(--space-12); +} + +.demo-command [role="option"] .command-label { + color: inherit; +} + +.demo-specimen { + min-width: 0; + overflow: auto; +} + +/* ---- views ------------------------------------------------------------ */ + +.view-heading { + display: flex; + flex-wrap: wrap; + align-items: start; + justify-content: space-between; + gap: var(--space-16) var(--space-24); + margin-bottom: var(--space-24); +} + +.view-heading h1 + p { + max-width: 72ch; + margin-block: var(--space-12) 0; + color: var(--color-text-prose); + font-size: var(--font-size-reading); + line-height: var(--font-line-height-reading); +} + +.panel { + min-width: 0; + padding: var(--space-24); + background: var(--color-surface-default); + border: var(--border-width-default) solid var(--color-border-default); +} + +.panel > h3 { + margin-block: var(--space-24) var(--space-8); +} + +.panel-heading, +.pager { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: var(--space-12) var(--space-16); +} + +.panel-heading { + margin-bottom: var(--space-16); +} + +.pager { + margin-top: var(--space-16); +} + +.pager p { + margin: 0; +} + +.metrics { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--space-16); + margin-block: var(--space-24); +} + +.metric-label, +.muted { + color: var(--color-text-muted); +} + +.metric, +.price { + display: block; + color: var(--color-text-bright); + font-size: var(--font-size-display); + line-height: var(--font-line-height-display); + font-weight: var(--font-weight-bold); + overflow-wrap: anywhere; +} + +.metric { + margin-block: var(--space-8); +} + +.dashboard-middle { + display: grid; + grid-template-columns: minmax(0, 1.7fr) minmax(16rem, 1fr); + gap: var(--space-16); + margin-block: var(--space-16); +} + +.actions, +.filters { + display: flex; + flex-wrap: wrap; + gap: var(--space-12); +} + +.actions { + align-items: center; +} + +.filters { + align-items: end; + margin-block: var(--space-16); +} + +.panel > .filters:first-child { + margin-top: 0; +} + +/* A field in a filter row takes its full canonical width, then shrinks. */ +.filters > j3w1-text-field { + flex: 0 1 calc(var(--font-size-ui-md) * 28); +} + +.status { + color: var(--color-status-info-text); +} + +.status.ready { + color: var(--color-status-success-text); +} + +.status.review { + color: var(--color-status-warning-text); +} + +.block { + display: block; +} + +.form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: end; + gap: var(--space-16) var(--space-24); +} + +.form-grid .wide { + grid-column: 1 / -1; +} + +.form-grid > p { + margin: 0; +} + +.split { + display: grid; + grid-template-columns: minmax(min(100%, 14rem), .8fr) minmax(0, 2fr); + align-items: start; + gap: var(--space-16); +} + +.stack { + display: grid; + align-content: start; + gap: var(--space-16); +} + +/* Application tables: the canonical table in a scroll region that keeps its + columns readable and scrolls instead of the page. */ +.table-scroll { + min-width: 0; + overflow: auto; +} + +.table-scroll:focus-visible { + outline: var(--focus-ring-container); + outline-offset: var(--focus-offset-container); +} + +.table-scroll > .table { + width: max-content; + min-width: 100%; +} + +.table-scroll .table-body th { + text-align: start; +} + +.message { + max-width: 85%; + padding: var(--space-12); + background: var(--color-surface-input); + border: var(--border-width-default) solid var(--color-border-default); +} + +.message p { + margin-block: var(--space-4) 0; +} + +/* The user's own content set apart from its neighbours (foundations, D-035). */ +.message.self { + margin-inline-start: auto; + background: var(--color-surface-accent); +} + +.kanban { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); + gap: var(--space-16); +} + +.kanban article { + display: grid; + gap: var(--space-8); + margin-top: var(--space-16); + padding: var(--space-16); + background: var(--color-surface-input); + border: var(--border-width-default) solid var(--color-border-default); +} + +/* Each day draws its own end and bottom edge, so a short last week leaves the + panel surface, not a block of grid-line colour. */ +.calendar { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + margin-block: var(--space-16); + border-block-start: var(--border-width-default) solid var(--color-border-default); + border-inline-start: var(--border-width-default) solid var(--color-border-default); +} + +.calendar-day { + display: grid; + align-content: start; + gap: var(--space-4); + min-width: 0; + min-height: calc(2 * var(--space-48)); + padding: var(--space-8); + border-inline-end: var(--border-width-default) solid var(--color-border-default); + border-block-end: var(--border-width-default) solid var(--color-border-default); +} + +.calendar-event { + display: grid; + justify-items: start; + gap: var(--space-4); + margin: 0; + padding-top: var(--space-4); + border-top: var(--border-width-default) solid var(--color-border-divider); + overflow-wrap: anywhere; +} + +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); + gap: var(--space-16); +} + +.gallery-grid .card { + max-width: none; + height: 100%; + align-content: start; +} + +.empty { + padding: var(--space-48) var(--space-24); + text-align: center; +} + +.empty .actions { + justify-content: center; +} + +/* ---- responsive (foundations "Layout": 600, 900, 1280) ----------------- */ + +@media (max-width: 1279.98px) { + .metrics { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .dashboard-middle { + grid-template-columns: minmax(0, 1fr); + } + + .calendar { + grid-template-columns: repeat(auto-fill, minmax(min(100%, 8rem), 1fr)); + } +} + +@media (min-width: 900px) { + .mobile-nav, + .demo-nav-close { + display: none; + } +} + +/* Below 900px the rail becomes a drawer opened from the top bar. */ +@media (max-width: 899.98px) { + .demo-shell { + grid-template-columns: minmax(0, 1fr); + } + + .demo-sidebar { + display: none; + position: fixed; + inset-block: 0; + inset-inline-start: 0; + z-index: var(--z-drawer); + width: min(var(--demo-rail), calc(100vw - var(--space-48))); + overflow-y: auto; + background: var(--color-surface-overlay); + border-inline-end-color: var(--color-border-overlay); + } + + .demo-sidebar.is-open { + display: block; + } + + .demo-topline, + .demo-context { + padding-inline: var(--space-16); + } + + /* The menu and search share the first line; the path takes the second. */ + .demo-path { + order: 1; + flex-basis: 100%; + } + + main { + padding: var(--space-24) var(--space-16); + } + + .demo-notice { + margin-inline: var(--space-16); + } + + .demo-footer { + padding-inline: var(--space-16); + } +} + +/* Below 600px tables keep their first column in view while they scroll. */ +@media (max-width: 599.98px) { + .form-grid, + .split { + grid-template-columns: minmax(0, 1fr); + } + + .panel { + padding: var(--space-16); + } + + .metrics { + gap: var(--space-8); + } + + .metric, + .price { + font-size: var(--font-size-title); + line-height: var(--font-line-height-title); + } + + .table-scroll :is(.table-header, .table-cell):first-child { + position: sticky; + inset-inline-start: 0; + z-index: var(--z-raised); + background: var(--color-surface-default); + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + } +} + +.calendar-notifications { + position: fixed; + inset-inline-end: var(--space-8); + inset-block-end: var(--space-8); + max-inline-size: calc(100% - 2 * var(--space-8)); +} diff --git a/apps/demo/src/views/Dashboard.vue b/apps/demo/src/views/Dashboard.vue index 4b77ab485..fb5eb572e 100644 --- a/apps/demo/src/views/Dashboard.vue +++ b/apps/demo/src/views/Dashboard.vue @@ -3,6 +3,9 @@ import { computed, ref, watch } from 'vue'; import { useRoute } from 'vue-router'; import { state } from '../store.js'; import UiSpecimen from '../UiSpecimen.vue'; +import '@j3w1/ui/register/table'; +import '@j3w1/ui/styles/link.css'; +import '@j3w1/ui/styles/table.css'; const route = useRoute(), period = ref('12'), chart = ref(); const dashboards = { analytics: { title: 'A clear view of your work.', eyebrow: 'ANALYTICS', metrics: [['Active projects','24','↑ 4 this month'],['Components used','148','Across 12 applications'],['Review queue','07','3 awaiting feedback'],['Delivery health','96.8%','↑ 2.1% this week']], activity: 'Steady progress.', unit: 'Completed tasks', readiness: 'Small steps. Solid foundations.', items: ['Canonical tokens ready','Component reviews in progress','Keyboard flows under review','Release rehearsal next'] }, @@ -15,11 +18,91 @@ const config = computed(() => dashboards[route.params.kind] ?? dashboards.analyt const values = [32,44,38,56,48,70,58,82,65,74,88,78]; function draw(root = chart.value) { if (!root) return; chart.value = root; root.data = values.slice(period.value === '6' ? 6 : 0).map((value,index) => ({ label: `Week ${index + (period.value === '6' ? 7 : 1)}`, value })); const caption = root.querySelector('figcaption'); if(caption) caption.textContent = `${config.value.unit} · synthetic fixture`; } watch([period, config], () => draw()); +const glyph = status => status === 'Ready' ? '✓' : status === 'Review' ? '△' : '◌'; - + diff --git a/apps/demo/src/views/Forms.vue b/apps/demo/src/views/Forms.vue index 265a3bbe9..028ddcc7a 100644 --- a/apps/demo/src/views/Forms.vue +++ b/apps/demo/src/views/Forms.vue @@ -2,6 +2,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import UiSpecimen from '../UiSpecimen.vue'; +import '@j3w1/ui/styles/link.css'; const route = useRoute(); const pages = { basic: {title:'Forms that work with the browser.',text:'Native labels, real validation, review and correction.',ids:['admin-form']}, @@ -14,4 +15,22 @@ const pages = { }; const config = computed(() => pages[route.params.kind] ?? pages.basic); - + diff --git a/apps/demo/src/views/Gallery.vue b/apps/demo/src/views/Gallery.vue index 3ff9c529c..6636b19c2 100644 --- a/apps/demo/src/views/Gallery.vue +++ b/apps/demo/src/views/Gallery.vue @@ -4,6 +4,9 @@ import { useRoute } from 'vue-router'; import index from '@j3w1/ui/index.json'; import { loaders } from '../generated-loaders.js'; import UiSpecimen from '../UiSpecimen.vue'; +import '@j3w1/ui/register/card'; +import '@j3w1/ui/styles/card.css'; +import '@j3w1/ui/styles/link.css'; const route = useRoute(), query = ref(''), variants = ref([]), variant = ref('default'), markup = ref(''), message = ref(''); const item = computed(() => index.components.find(item=>item.id===route.params.id)); const filtered = computed(() => index.components.filter(item=>`${item.id} ${item.name}`.toLowerCase().includes(query.value.toLowerCase()))); @@ -12,7 +15,58 @@ watch(() => route.params.id, async id => { const current=++request;variants.valu watch(variant, async value => { const id=route.params.id;if(loaders[id])markup.value=(await loaders[id]())[value] ?? ''; }); async function copy() { try { await navigator.clipboard.writeText(markup.value); message.value='Maintained markup copied. Include its package registration and styles.'; } catch { message.value='Clipboard unavailable. Select the source below to copy it.'; } } -