From 43fe76158c2cad6ae429a9cee43d0eae165be4d5 Mon Sep 17 00:00:00 2001
From: Tony James
Date: Tue, 13 Oct 2026 22:16:21 +0100
Subject: [PATCH] Extract independent MIT Mortar kit and contribution workflow
---
.gitea/workflows/check.yml | 22 +
.gitignore | 7 +
.prettierignore | 6 +
AGENTS.md | 9 +
CONTRIBUTING.md | 11 +
DESIGN.md | 15 +
LICENSE | 21 +
README.md | 63 +
baseline-policy.json | 11 +
catalog.json | 1520 ++++++++++++
design-principles.json | 30 +
manifests/mortar-action-link.json | 40 +
manifests/mortar-activity-feed.json | 39 +
manifests/mortar-app-shell.json | 45 +
manifests/mortar-appearance-control.json | 36 +
manifests/mortar-badge.json | 39 +
manifests/mortar-button.json | 43 +
manifests/mortar-call-to-action.json | 40 +
manifests/mortar-checkbox.json | 40 +
manifests/mortar-code-block.json | 39 +
manifests/mortar-data-table.json | 49 +
manifests/mortar-dropdown.json | 39 +
manifests/mortar-empty-state.json | 40 +
manifests/mortar-faq.json | 40 +
manifests/mortar-feature-grid.json | 41 +
manifests/mortar-feature-section.json | 41 +
manifests/mortar-footer.json | 42 +
manifests/mortar-hero.json | 44 +
manifests/mortar-input.json | 44 +
manifests/mortar-logo-cloud.json | 39 +
manifests/mortar-marketing-header.json | 42 +
manifests/mortar-metric-group.json | 39 +
manifests/mortar-modal.json | 42 +
manifests/mortar-notice.json | 41 +
manifests/mortar-page-heading.json | 43 +
manifests/mortar-pagination.json | 40 +
manifests/mortar-preview-frame.json | 39 +
manifests/mortar-pricing.json | 40 +
manifests/mortar-radio-group.json | 42 +
manifests/mortar-segmented-control.json | 41 +
manifests/mortar-select.json | 43 +
manifests/mortar-settings-section.json | 40 +
manifests/mortar-switch.json | 40 +
manifests/mortar-tabs.json | 41 +
manifests/mortar-testimonial.json | 40 +
manifests/mortar-text-area.json | 42 +
manifests/mortar-theme-provider.json | 41 +
package-lock.json | 2862 ++++++++++++++++++++++
package.json | 72 +
playground/index.html | 12 +
playground/main.tsx | 55 +
playground/styles.css | 4 +
playwright.config.mjs | 17 +
previews/ActionLinkPreview.tsx | 4 +
previews/ActivityFeedPreview.tsx | 4 +
previews/AppShellPreview.tsx | 4 +
previews/AppearanceControlPreview.tsx | 4 +
previews/BadgePreview.tsx | 4 +
previews/ButtonPreview.tsx | 4 +
previews/CallToActionPreview.tsx | 4 +
previews/CheckboxPreview.tsx | 4 +
previews/CodeBlockPreview.tsx | 4 +
previews/DataTablePreview.tsx | 4 +
previews/DropdownPreview.tsx | 4 +
previews/EmptyStatePreview.tsx | 4 +
previews/FAQPreview.tsx | 4 +
previews/FeatureGridPreview.tsx | 4 +
previews/FeatureSectionPreview.tsx | 4 +
previews/Fixture.tsx | 476 ++++
previews/FooterPreview.tsx | 4 +
previews/HeroPreview.tsx | 4 +
previews/InputPreview.tsx | 4 +
previews/LogoCloudPreview.tsx | 4 +
previews/MarketingHeaderPreview.tsx | 4 +
previews/MetricGroupPreview.tsx | 4 +
previews/ModalPreview.tsx | 4 +
previews/NoticePreview.tsx | 4 +
previews/PageHeadingPreview.tsx | 4 +
previews/PaginationPreview.tsx | 4 +
previews/PreviewFramePreview.tsx | 4 +
previews/PricingPreview.tsx | 4 +
previews/RadioGroupPreview.tsx | 4 +
previews/SegmentedControlPreview.tsx | 4 +
previews/SelectPreview.tsx | 4 +
previews/SettingsSectionPreview.tsx | 4 +
previews/SwitchPreview.tsx | 4 +
previews/TabsPreview.tsx | 4 +
previews/TestimonialPreview.tsx | 4 +
previews/TextAreaPreview.tsx | 4 +
previews/ThemeProviderPreview.tsx | 4 +
scripts/check.mjs | 172 ++
scripts/release.mjs | 46 +
src/application.tsx | 418 ++++
src/components.css | 1289 ++++++++++
src/foundation.tsx | 99 +
src/index.ts | 57 +
src/inspection.tsx | 59 +
src/marketing.tsx | 342 +++
src/primitives.tsx | 429 ++++
src/tokens.css | 90 +
styles.css | 15 +
tests/baseline.test.mjs | 44 +
tests/browser/components.spec.mjs | 33 +
tests/browser/interactions.spec.mjs | 54 +
tests/controls.test.mjs | 63 +
tsconfig.json | 14 +
tsconfig.playground.json | 15 +
vite.config.ts | 9 +
108 files changed, 10081 insertions(+)
create mode 100644 .gitea/workflows/check.yml
create mode 100644 .gitignore
create mode 100644 .prettierignore
create mode 100644 AGENTS.md
create mode 100644 CONTRIBUTING.md
create mode 100644 DESIGN.md
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 baseline-policy.json
create mode 100644 catalog.json
create mode 100644 design-principles.json
create mode 100644 manifests/mortar-action-link.json
create mode 100644 manifests/mortar-activity-feed.json
create mode 100644 manifests/mortar-app-shell.json
create mode 100644 manifests/mortar-appearance-control.json
create mode 100644 manifests/mortar-badge.json
create mode 100644 manifests/mortar-button.json
create mode 100644 manifests/mortar-call-to-action.json
create mode 100644 manifests/mortar-checkbox.json
create mode 100644 manifests/mortar-code-block.json
create mode 100644 manifests/mortar-data-table.json
create mode 100644 manifests/mortar-dropdown.json
create mode 100644 manifests/mortar-empty-state.json
create mode 100644 manifests/mortar-faq.json
create mode 100644 manifests/mortar-feature-grid.json
create mode 100644 manifests/mortar-feature-section.json
create mode 100644 manifests/mortar-footer.json
create mode 100644 manifests/mortar-hero.json
create mode 100644 manifests/mortar-input.json
create mode 100644 manifests/mortar-logo-cloud.json
create mode 100644 manifests/mortar-marketing-header.json
create mode 100644 manifests/mortar-metric-group.json
create mode 100644 manifests/mortar-modal.json
create mode 100644 manifests/mortar-notice.json
create mode 100644 manifests/mortar-page-heading.json
create mode 100644 manifests/mortar-pagination.json
create mode 100644 manifests/mortar-preview-frame.json
create mode 100644 manifests/mortar-pricing.json
create mode 100644 manifests/mortar-radio-group.json
create mode 100644 manifests/mortar-segmented-control.json
create mode 100644 manifests/mortar-select.json
create mode 100644 manifests/mortar-settings-section.json
create mode 100644 manifests/mortar-switch.json
create mode 100644 manifests/mortar-tabs.json
create mode 100644 manifests/mortar-testimonial.json
create mode 100644 manifests/mortar-text-area.json
create mode 100644 manifests/mortar-theme-provider.json
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 playground/index.html
create mode 100644 playground/main.tsx
create mode 100644 playground/styles.css
create mode 100644 playwright.config.mjs
create mode 100644 previews/ActionLinkPreview.tsx
create mode 100644 previews/ActivityFeedPreview.tsx
create mode 100644 previews/AppShellPreview.tsx
create mode 100644 previews/AppearanceControlPreview.tsx
create mode 100644 previews/BadgePreview.tsx
create mode 100644 previews/ButtonPreview.tsx
create mode 100644 previews/CallToActionPreview.tsx
create mode 100644 previews/CheckboxPreview.tsx
create mode 100644 previews/CodeBlockPreview.tsx
create mode 100644 previews/DataTablePreview.tsx
create mode 100644 previews/DropdownPreview.tsx
create mode 100644 previews/EmptyStatePreview.tsx
create mode 100644 previews/FAQPreview.tsx
create mode 100644 previews/FeatureGridPreview.tsx
create mode 100644 previews/FeatureSectionPreview.tsx
create mode 100644 previews/Fixture.tsx
create mode 100644 previews/FooterPreview.tsx
create mode 100644 previews/HeroPreview.tsx
create mode 100644 previews/InputPreview.tsx
create mode 100644 previews/LogoCloudPreview.tsx
create mode 100644 previews/MarketingHeaderPreview.tsx
create mode 100644 previews/MetricGroupPreview.tsx
create mode 100644 previews/ModalPreview.tsx
create mode 100644 previews/NoticePreview.tsx
create mode 100644 previews/PageHeadingPreview.tsx
create mode 100644 previews/PaginationPreview.tsx
create mode 100644 previews/PreviewFramePreview.tsx
create mode 100644 previews/PricingPreview.tsx
create mode 100644 previews/RadioGroupPreview.tsx
create mode 100644 previews/SegmentedControlPreview.tsx
create mode 100644 previews/SelectPreview.tsx
create mode 100644 previews/SettingsSectionPreview.tsx
create mode 100644 previews/SwitchPreview.tsx
create mode 100644 previews/TabsPreview.tsx
create mode 100644 previews/TestimonialPreview.tsx
create mode 100644 previews/TextAreaPreview.tsx
create mode 100644 previews/ThemeProviderPreview.tsx
create mode 100644 scripts/check.mjs
create mode 100644 scripts/release.mjs
create mode 100644 src/application.tsx
create mode 100644 src/components.css
create mode 100644 src/foundation.tsx
create mode 100644 src/index.ts
create mode 100644 src/inspection.tsx
create mode 100644 src/marketing.tsx
create mode 100644 src/primitives.tsx
create mode 100644 src/tokens.css
create mode 100644 styles.css
create mode 100644 tests/baseline.test.mjs
create mode 100644 tests/browser/components.spec.mjs
create mode 100644 tests/browser/interactions.spec.mjs
create mode 100644 tests/controls.test.mjs
create mode 100644 tsconfig.json
create mode 100644 tsconfig.playground.json
create mode 100644 vite.config.ts
diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml
new file mode 100644
index 0000000..b0a122f
--- /dev/null
+++ b/.gitea/workflows/check.yml
@@ -0,0 +1,22 @@
+name: Mortar contribution checks
+on:
+ push:
+ branches: [main, "codex/**"]
+ pull_request:
+ branches: [main]
+jobs:
+ quality:
+ name: Mortar baseline and browser checks
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+ - run: npm ci --ignore-scripts
+ - run: npm run check
+ - run: npx playwright install --with-deps chromium
+ - run: npm run test:browser
+# No package-publishing or cloud-governance credentials belong in this workflow.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a23c384
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+dist/
+playground-dist/
+release/
+test-results/
+playwright-report/
+*.tgz
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..892d767
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+playground-dist
+release
+test-results
+playwright-report
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..13953d6
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,9 @@
+# Contributing to Mortar
+
+Mortar is a public MIT kit. You can develop, preview and check it without any governance-service account.
+
+Read DESIGN.md before composing UI. Implement reusable components in src, previews in previews, and typed component contracts in manifests. Shells and chrome are shared components, not copies in consumer apps. Product data and actions belong in props and slots.
+
+Run npm ci, npm run catalog, npm run check and npm run test:browser. npm run check:json returns baseline rule IDs, files, lines and corrective messages. npm run dev opens the local playground. Update manifests when changing exports. Include a regression test for a bug fix. Never mark yourself approved, publish a release, or change protected CI rules to make a contribution pass.
+
+Open a PR with rationale, screenshots and actual check results. Maintainer review and protected final-commit CI precede releases. Baseline checks are not proof of visual quality; inspect desktop/mobile, light/dark, keyboard and focus states. Shared design changes require central fixes, not local overrides.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..8aed47c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,11 @@
+# Contribute
+
+Clone, run npm ci, npm run catalog, npm run check, npm run dev. Use npm run test:browser for both-theme accessibility/rendering checks. The local playground defaults to port 4340. No service registration or npm token is required.
+
+Use src for central implementations, previews for fixtures and manifests for contracts. Read DESIGN.md and AGENTS.md. Include a reproducible issue and a regression test for fixes. Open a PR with actual check results and visual evidence. Shared shells are versioned alongside controls; applications supply content through props.
+
+Baseline rules are versioned in baseline-policy.json. CI must use the same rules but contributors cannot authorize weakening protected checks; policy, workflow and dependency changes require maintainer review. Branch protection must require successful checks and dismiss approvals when reviewed content changes. Public PR runners must be isolated from release credentials and production networks.
+
+Releases are operator-owned. Verify the final merged commit, run all checks, create an immutable version tag, and prepare the exact package using npm run release:prepare. Upload archives and integrity/provenance records before atomically advancing registry metadata. Never overwrite an existing version. The publisher must verify maintainer approval and successful final-commit CI independently. Publication does not call the paid service; ARC indexes published release manifests afterward.
+
+The cloud service may impose additional organisation adoption policies. Those policies cannot prevent development or installation of the public MIT kit.
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..042067d
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,15 @@
+# Mortar design principles
+
+1. **shell-first** — Choose the shared purpose shell before composing a page. Use AppShell for application screens and MarketingHeader for public pages. Document deliberate standalone exceptions.
+
+2. **page-pattern-reuse** — Reuse complete page patterns: navigation, headings, filters, list/detail workspaces, states and action placement. Reusing controls alone is insufficient.
+
+3. **project-agnostic-chrome** — Keep product names, routes, user data, permissions and actions in props and slots. Shared chrome must remain project agnostic.
+
+4. **central-fixes** — Contribute missing patterns centrally. Do not duplicate source, create repeated local wrappers or apply consumer CSS overrides.
+
+5. **semantic-themes** — Use semantic tokens; keep brand separate from appearance and feedback colours. Verify every supported theme.
+
+6. **interaction-quality** — Verify responsive layout, accessible names, keyboard operation, focus and loading/empty/error states. Automated checks alone cannot prove visual quality.
+
+The machine-readable contract is design-principles.json. Both the local contribution workflow and hosted MCP should expose these principles. Detection is split between structural checks and visual maintainer review.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..03bc1df
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Tony James and Mortar contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e4e8b72
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+# Mortar
+
+A shared design foundation and purpose-built Application UI and Marketing UI kits for React 19 and Tailwind CSS 4. MIT licensed. No account, token, registry connection or subscription is required to use the kit, including use by coding agents.
+
+The package is `@tony-ui/mortar`, distributed from owner-hosted infrastructure. Releases are immutable and publicly downloadable without a service account. First publication is pending protected release verification.
+
+## Install
+
+After public release:
+
+```sh
+npm config set @tony-ui:registry https://git.tony.codes/api/packages/tony/npm/ --location=project
+npm install @tony-ui/mortar react react-dom tailwindcss
+```
+
+No UI service credential is needed.
+
+## Local install
+
+Build and pack from this workspace:
+
+```sh
+npm run build
+npm pack
+```
+
+Install the resulting `tony-ui-mortar-0.1.0.tgz` in the consuming app, with React 19, React DOM 19 and Tailwind 4. Public releases use https://git.tony.codes/api/packages/tony/npm/.
+
+In the app's Tailwind CSS entry:
+
+```css
+@import "tailwindcss";
+@import "@tony-ui/mortar/styles.css";
+```
+
+The package ships its component styles. Standalone use requires no source scanning or hosted account. The separately governed hosted workflow currently also requires `@source '../node_modules/@tony-ui/mortar/src';` in the CSS entry; generated agent starters include that policy declaration.
+
+```tsx
+import { ThemeProvider, AppShell, Button } from "@tony-ui/mortar";
+
+
+
+
+
+;
+```
+
+`brand` (`neutral | green`) and `density` (`compact | comfortable | spacious`) are controlled provider props. Appearance (`light | dark | system`) is changed using `AppearanceControl`; system changes are observed. Use one root provider. It restores document attributes on unmount. The provider uses effects: SSR applications should set matching initial document attributes to prevent a first-paint theme flash.
+
+## Purpose kits
+
+The catalogue assigns each export to one purpose. Foundation primitives serve both purpose kits. Application UI provides shells, page headings, metrics, selectable tables, settings and activity. Marketing UI provides headers, heroes, features, social proof, pricing, FAQs, calls to action and footers. Product content and callbacks remain in the consumer. Sample names and prices belong only in the catalogue demonstrations.
+
+Controls use native HTML and Headless UI. Tabs support arrow/Home/End navigation; dialogs and menus use Headless UI focus management. Density has a 44px control floor on coarse pointers. Feedback colours remain separate from the brand.
+
+## Hosted services
+
+Mortar Cloud's registry, agent discovery, approvals, audit and managed release workflow are separate proprietary hosted services. Payment buys service access, not permission to use the MIT components. The kit performs no network requests and contains no licence check or telemetry.
+
+This licence covers this package only. The central service, repository history, design reference documents, candidate fixtures and catalogue application are excluded from the public package.
diff --git a/baseline-policy.json b/baseline-policy.json
new file mode 100644
index 0000000..e56f24e
--- /dev/null
+++ b/baseline-policy.json
@@ -0,0 +1,11 @@
+{
+ "schemaVersion": 1,
+ "version": "1.0.0",
+ "rules": {
+ "M001": "Exports require unique manifests and preview fixtures",
+ "M002": "Source and preview paths stay inside the repository",
+ "M003": "Dependencies reference known component IDs",
+ "M004": "Shared components cannot depend on hosted services",
+ "M005": "Component implementations use semantic colour tokens"
+ }
+}
diff --git a/catalog.json b/catalog.json
new file mode 100644
index 0000000..b21c51f
--- /dev/null
+++ b/catalog.json
@@ -0,0 +1,1520 @@
+{
+ "schemaVersion": 1,
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "policyVersion": "1.0.0",
+ "components": [
+ {
+ "id": "mortar-action-link",
+ "logicalId": "action-link",
+ "name": "ActionLink",
+ "description": "Mortar foundation action link, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "ActionLink",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "href": "string",
+ "variant": "primary | secondary | ghost | danger"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { ActionLink } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ActionLinkPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-activity-feed",
+ "logicalId": "activity-feed",
+ "name": "ActivityFeed",
+ "description": "Mortar application activity feed, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "ActivityFeed",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application", "status"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ id: string; title: string; description?: string; time: string; tone?: Tone }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { ActivityFeed } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ActivityFeedPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-app-shell",
+ "logicalId": "app-shell",
+ "name": "AppShell",
+ "description": "Mortar application app shell, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "AppShell",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "brand": "string",
+ "workspace": "string",
+ "navigation": "NavigationItem[]",
+ "secondaryNavigation": "{ label: string; items: NavigationItem[] }[]",
+ "user": "ReactNode",
+ "header": "ReactNode",
+ "actions": "ReactNode",
+ "children": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { AppShell } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button"]
+ },
+ "preview": {
+ "source": "previews/AppShellPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-appearance-control",
+ "logicalId": "appearance-control",
+ "name": "AppearanceControl",
+ "description": "Mortar foundation appearance control, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "AppearanceControl",
+ "source": "src/foundation.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {},
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { AppearanceControl } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-theme-provider"]
+ },
+ "preview": {
+ "source": "previews/AppearanceControlPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-badge",
+ "logicalId": "badge",
+ "name": "Badge",
+ "description": "Mortar foundation badge, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Badge",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "status"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "children": "ReactNode",
+ "tone": "neutral | success | warning | danger"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Badge } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/BadgePreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-button",
+ "logicalId": "button",
+ "name": "Button",
+ "description": "Mortar foundation button, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Button",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "children": "ReactNode",
+ "variant": "primary | secondary | ghost | danger",
+ "loading": "boolean",
+ "disabled": "boolean",
+ "type": "button | submit | reset",
+ "onClick": "MouseEventHandler"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Button } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ButtonPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-call-to-action",
+ "logicalId": "call-to-action",
+ "name": "CallToAction",
+ "description": "Mortar marketing call to action, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "CallToAction",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "actions": "LinkAction[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { CallToAction } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/CallToActionPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-checkbox",
+ "logicalId": "checkbox",
+ "name": "Checkbox",
+ "description": "Mortar foundation checkbox, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Checkbox",
+ "source": "src/primitives.tsx",
+ "capabilities": [
+ "theme",
+ "foundation",
+ "input",
+ "validation",
+ "keyboard"
+ ],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "checked": "boolean",
+ "onChange": "ChangeEventHandler"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Checkbox } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/CheckboxPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-code-block",
+ "logicalId": "code-block",
+ "name": "CodeBlock",
+ "description": "Mortar application code block, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "CodeBlock",
+ "source": "src/inspection.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "code": "string",
+ "label": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { CodeBlock } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/CodeBlockPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-data-table",
+ "logicalId": "data-table",
+ "name": "DataTable",
+ "description": "Mortar application data table, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "DataTable",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application", "table"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "caption": "string",
+ "columns": "{ key: string; label: string; render: (row: T) => ReactNode; align?: left | right }[]",
+ "rows": "T[] where T extends { id: string }",
+ "rowLabel": "(row: T) => string",
+ "selectedIds": "string[]",
+ "onSelectionChange": "(ids: string[]) => void",
+ "toolbar": "ReactNode",
+ "bulkActions": "ReactNode",
+ "loading": "boolean",
+ "error": "string",
+ "emptyTitle": "string",
+ "emptyDescription": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { DataTable } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button", "mortar-empty-state", "mortar-notice"]
+ },
+ "preview": {
+ "source": "previews/DataTablePreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-dropdown",
+ "logicalId": "dropdown",
+ "name": "Dropdown",
+ "description": "Mortar foundation dropdown, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Dropdown",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ label: string; onSelect: () => void; danger?: boolean; disabled?: boolean }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Dropdown } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/DropdownPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-empty-state",
+ "logicalId": "empty-state",
+ "name": "EmptyState",
+ "description": "Mortar foundation empty state, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "EmptyState",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "action": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { EmptyState } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/EmptyStatePreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-faq",
+ "logicalId": "faq",
+ "name": "FAQ",
+ "description": "Mortar marketing faq, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "FAQ",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "ReactNode",
+ "items": "{ question: string; answer: ReactNode }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { FAQ } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FAQPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-feature-grid",
+ "logicalId": "feature-grid",
+ "name": "FeatureGrid",
+ "description": "Mortar marketing feature grid, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "FeatureGrid",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "eyebrow": "string",
+ "description": "string",
+ "items": "{ title: string; description: string; icon?: ReactNode }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { FeatureGrid } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FeatureGridPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-feature-section",
+ "logicalId": "feature-section",
+ "name": "FeatureSection",
+ "description": "Mortar marketing feature section, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "FeatureSection",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "children": "ReactNode",
+ "reverse": "boolean"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { FeatureSection } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FeatureSectionPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-footer",
+ "logicalId": "footer",
+ "name": "Footer",
+ "description": "Mortar marketing footer, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Footer",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "brand": "string",
+ "description": "string",
+ "columns": "{ title: string; links: LinkAction[] }[]",
+ "copyright": "string",
+ "legal": "LinkAction[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Footer } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FooterPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-hero",
+ "logicalId": "hero",
+ "name": "Hero",
+ "description": "Mortar marketing hero, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Hero",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "eyebrow": "string",
+ "title": "string",
+ "description": "string",
+ "actions": "LinkAction[]",
+ "children": "ReactNode",
+ "align": "center | split",
+ "headingLevel": "1 | 2"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Hero } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/HeroPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-input",
+ "logicalId": "input",
+ "name": "Input",
+ "description": "Mortar foundation input, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Input",
+ "source": "src/primitives.tsx",
+ "capabilities": [
+ "theme",
+ "foundation",
+ "input",
+ "validation",
+ "keyboard"
+ ],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string | number",
+ "onChange": "ChangeEventHandler",
+ "hint": "string",
+ "error": "string",
+ "prefix": "string",
+ "suffix": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Input } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/InputPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-logo-cloud",
+ "logicalId": "logo-cloud",
+ "name": "LogoCloud",
+ "description": "Mortar marketing logo cloud, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "LogoCloud",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ name: string; image?: string }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { LogoCloud } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/LogoCloudPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-marketing-header",
+ "logicalId": "marketing-header",
+ "name": "MarketingHeader",
+ "description": "Mortar marketing marketing header, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "MarketingHeader",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "brand": "string",
+ "homeHref": "string",
+ "links": "LinkAction[]",
+ "action": "LinkAction",
+ "controls": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { MarketingHeader } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button", "mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/MarketingHeaderPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-metric-group",
+ "logicalId": "metric-group",
+ "name": "MetricGroup",
+ "description": "Mortar application metric group, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "MetricGroup",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application", "summary"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ label: string; value: string; change?: string; tone?: Tone }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { MetricGroup } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/MetricGroupPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-modal",
+ "logicalId": "modal",
+ "name": "Modal",
+ "description": "Mortar foundation modal, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Modal",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "open": "boolean",
+ "onClose": "() => void",
+ "children": "ReactNode",
+ "actions": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Modal } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button"]
+ },
+ "preview": {
+ "source": "previews/ModalPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-notice",
+ "logicalId": "notice",
+ "name": "Notice",
+ "description": "Mortar foundation notice, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Notice",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "status"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "children": "ReactNode",
+ "tone": "neutral | success | warning | danger",
+ "announce": "boolean"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Notice } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/NoticePreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-page-heading",
+ "logicalId": "page-heading",
+ "name": "PageHeading",
+ "description": "Mortar application page heading, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "PageHeading",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "eyebrow": "string",
+ "actions": "ReactNode",
+ "breadcrumbs": "{ label: string; href?: string }[]",
+ "headingLevel": "1 | 2"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { PageHeading } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/PageHeadingPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-pagination",
+ "logicalId": "pagination",
+ "name": "Pagination",
+ "description": "Mortar application pagination, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Pagination",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "page": "number",
+ "pageCount": "number",
+ "onChange": "(page: number) => void"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Pagination } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button"]
+ },
+ "preview": {
+ "source": "previews/PaginationPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-preview-frame",
+ "logicalId": "preview-frame",
+ "name": "PreviewFrame",
+ "description": "Mortar application preview frame, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "PreviewFrame",
+ "source": "src/inspection.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "src": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { PreviewFrame } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/PreviewFramePreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-pricing",
+ "logicalId": "pricing",
+ "name": "Pricing",
+ "description": "Mortar marketing pricing, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Pricing",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "plans": "PricingPlan[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Pricing } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/PricingPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-radio-group",
+ "logicalId": "radio-group",
+ "name": "RadioGroup",
+ "description": "Mortar foundation radio group, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "RadioGroup",
+ "source": "src/primitives.tsx",
+ "capabilities": [
+ "theme",
+ "foundation",
+ "input",
+ "validation",
+ "keyboard"
+ ],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "name": "string",
+ "value": "string",
+ "onChange": "(value: string) => void",
+ "options": "{ value: string; label: string; description?: string }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { RadioGroup } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/RadioGroupPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-segmented-control",
+ "logicalId": "segmented-control",
+ "name": "SegmentedControl",
+ "description": "Mortar foundation segmented control, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "SegmentedControl",
+ "source": "src/inspection.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string",
+ "options": "{ value: string; label: string }[]",
+ "onChange": "(value: string) => void"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { SegmentedControl } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SegmentedControlPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-select",
+ "logicalId": "select",
+ "name": "Select",
+ "description": "Mortar foundation select, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Select",
+ "source": "src/primitives.tsx",
+ "capabilities": [
+ "theme",
+ "foundation",
+ "input",
+ "validation",
+ "keyboard"
+ ],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "options": "{ value: string; label: string }[]",
+ "value": "string",
+ "onChange": "ChangeEventHandler",
+ "hint": "string",
+ "error": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Select } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SelectPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-settings-section",
+ "logicalId": "settings-section",
+ "name": "SettingsSection",
+ "description": "Mortar application settings section, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "SettingsSection",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "children": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { SettingsSection } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SettingsSectionPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-switch",
+ "logicalId": "switch",
+ "name": "Switch",
+ "description": "Mortar foundation switch, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Switch",
+ "source": "src/primitives.tsx",
+ "capabilities": [
+ "theme",
+ "foundation",
+ "input",
+ "validation",
+ "keyboard"
+ ],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "checked": "boolean",
+ "onChange": "ChangeEventHandler"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Switch } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SwitchPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-tabs",
+ "logicalId": "tabs",
+ "name": "Tabs",
+ "description": "Mortar foundation tabs, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Tabs",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string",
+ "onChange": "(value: string) => void",
+ "items": "{ id: string; label: string; content: ReactNode }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Tabs } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/TabsPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-testimonial",
+ "logicalId": "testimonial",
+ "name": "Testimonial",
+ "description": "Mortar marketing testimonial, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Testimonial",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "quote": "string",
+ "name": "string",
+ "role": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Testimonial } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/TestimonialPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-text-area",
+ "logicalId": "text-area",
+ "name": "TextArea",
+ "description": "Mortar foundation text area, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "TextArea",
+ "source": "src/primitives.tsx",
+ "capabilities": [
+ "theme",
+ "foundation",
+ "input",
+ "validation",
+ "keyboard"
+ ],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string",
+ "onChange": "ChangeEventHandler",
+ "hint": "string",
+ "error": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { TextArea } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/TextAreaPreview.tsx"
+ }
+ },
+ {
+ "id": "mortar-theme-provider",
+ "logicalId": "theme-provider",
+ "name": "ThemeProvider",
+ "description": "Mortar foundation theme provider, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "ThemeProvider",
+ "source": "src/foundation.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "children": "ReactNode",
+ "initialAppearance": "light | dark | system",
+ "brand": "neutral | green",
+ "density": "compact | comfortable | spacious"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { ThemeProvider } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ThemeProviderPreview.tsx"
+ }
+ }
+ ]
+}
diff --git a/design-principles.json b/design-principles.json
new file mode 100644
index 0000000..7f04100
--- /dev/null
+++ b/design-principles.json
@@ -0,0 +1,30 @@
+{
+ "schemaVersion": 1,
+ "version": "1.0.0",
+ "principles": [
+ {
+ "id": "shell-first",
+ "instruction": "Choose the shared purpose shell before composing a page. Use AppShell for application screens and MarketingHeader for public pages. Document deliberate standalone exceptions."
+ },
+ {
+ "id": "page-pattern-reuse",
+ "instruction": "Reuse complete page patterns: navigation, headings, filters, list/detail workspaces, states and action placement. Reusing controls alone is insufficient."
+ },
+ {
+ "id": "project-agnostic-chrome",
+ "instruction": "Keep product names, routes, user data, permissions and actions in props and slots. Shared chrome must remain project agnostic."
+ },
+ {
+ "id": "central-fixes",
+ "instruction": "Contribute missing patterns centrally. Do not duplicate source, create repeated local wrappers or apply consumer CSS overrides."
+ },
+ {
+ "id": "semantic-themes",
+ "instruction": "Use semantic tokens; keep brand separate from appearance and feedback colours. Verify every supported theme."
+ },
+ {
+ "id": "interaction-quality",
+ "instruction": "Verify responsive layout, accessible names, keyboard operation, focus and loading/empty/error states. Automated checks alone cannot prove visual quality."
+ }
+ ]
+}
diff --git a/manifests/mortar-action-link.json b/manifests/mortar-action-link.json
new file mode 100644
index 0000000..4264a28
--- /dev/null
+++ b/manifests/mortar-action-link.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-action-link",
+ "logicalId": "action-link",
+ "name": "ActionLink",
+ "description": "Mortar foundation action link, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "ActionLink",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "href": "string",
+ "variant": "primary | secondary | ghost | danger"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { ActionLink } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ActionLinkPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-activity-feed.json b/manifests/mortar-activity-feed.json
new file mode 100644
index 0000000..fadd31e
--- /dev/null
+++ b/manifests/mortar-activity-feed.json
@@ -0,0 +1,39 @@
+{
+ "id": "mortar-activity-feed",
+ "logicalId": "activity-feed",
+ "name": "ActivityFeed",
+ "description": "Mortar application activity feed, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "ActivityFeed",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application", "status"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ id: string; title: string; description?: string; time: string; tone?: Tone }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { ActivityFeed } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ActivityFeedPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-app-shell.json b/manifests/mortar-app-shell.json
new file mode 100644
index 0000000..480d3f7
--- /dev/null
+++ b/manifests/mortar-app-shell.json
@@ -0,0 +1,45 @@
+{
+ "id": "mortar-app-shell",
+ "logicalId": "app-shell",
+ "name": "AppShell",
+ "description": "Mortar application app shell, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "AppShell",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "brand": "string",
+ "workspace": "string",
+ "navigation": "NavigationItem[]",
+ "secondaryNavigation": "{ label: string; items: NavigationItem[] }[]",
+ "user": "ReactNode",
+ "header": "ReactNode",
+ "actions": "ReactNode",
+ "children": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { AppShell } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button"]
+ },
+ "preview": {
+ "source": "previews/AppShellPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-appearance-control.json b/manifests/mortar-appearance-control.json
new file mode 100644
index 0000000..128df1d
--- /dev/null
+++ b/manifests/mortar-appearance-control.json
@@ -0,0 +1,36 @@
+{
+ "id": "mortar-appearance-control",
+ "logicalId": "appearance-control",
+ "name": "AppearanceControl",
+ "description": "Mortar foundation appearance control, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "AppearanceControl",
+ "source": "src/foundation.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {},
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { AppearanceControl } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-theme-provider"]
+ },
+ "preview": {
+ "source": "previews/AppearanceControlPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-badge.json b/manifests/mortar-badge.json
new file mode 100644
index 0000000..fbf2a8e
--- /dev/null
+++ b/manifests/mortar-badge.json
@@ -0,0 +1,39 @@
+{
+ "id": "mortar-badge",
+ "logicalId": "badge",
+ "name": "Badge",
+ "description": "Mortar foundation badge, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Badge",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "status"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "children": "ReactNode",
+ "tone": "neutral | success | warning | danger"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Badge } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/BadgePreview.tsx"
+ }
+}
diff --git a/manifests/mortar-button.json b/manifests/mortar-button.json
new file mode 100644
index 0000000..a672c12
--- /dev/null
+++ b/manifests/mortar-button.json
@@ -0,0 +1,43 @@
+{
+ "id": "mortar-button",
+ "logicalId": "button",
+ "name": "Button",
+ "description": "Mortar foundation button, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Button",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "children": "ReactNode",
+ "variant": "primary | secondary | ghost | danger",
+ "loading": "boolean",
+ "disabled": "boolean",
+ "type": "button | submit | reset",
+ "onClick": "MouseEventHandler"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Button } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ButtonPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-call-to-action.json b/manifests/mortar-call-to-action.json
new file mode 100644
index 0000000..d232d95
--- /dev/null
+++ b/manifests/mortar-call-to-action.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-call-to-action",
+ "logicalId": "call-to-action",
+ "name": "CallToAction",
+ "description": "Mortar marketing call to action, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "CallToAction",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "actions": "LinkAction[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { CallToAction } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/CallToActionPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-checkbox.json b/manifests/mortar-checkbox.json
new file mode 100644
index 0000000..cb5a447
--- /dev/null
+++ b/manifests/mortar-checkbox.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-checkbox",
+ "logicalId": "checkbox",
+ "name": "Checkbox",
+ "description": "Mortar foundation checkbox, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Checkbox",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "input", "validation", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "checked": "boolean",
+ "onChange": "ChangeEventHandler"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Checkbox } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/CheckboxPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-code-block.json b/manifests/mortar-code-block.json
new file mode 100644
index 0000000..7d91764
--- /dev/null
+++ b/manifests/mortar-code-block.json
@@ -0,0 +1,39 @@
+{
+ "id": "mortar-code-block",
+ "logicalId": "code-block",
+ "name": "CodeBlock",
+ "description": "Mortar application code block, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "CodeBlock",
+ "source": "src/inspection.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "code": "string",
+ "label": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { CodeBlock } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/CodeBlockPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-data-table.json b/manifests/mortar-data-table.json
new file mode 100644
index 0000000..8f7bb65
--- /dev/null
+++ b/manifests/mortar-data-table.json
@@ -0,0 +1,49 @@
+{
+ "id": "mortar-data-table",
+ "logicalId": "data-table",
+ "name": "DataTable",
+ "description": "Mortar application data table, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "DataTable",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application", "table"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "caption": "string",
+ "columns": "{ key: string; label: string; render: (row: T) => ReactNode; align?: left | right }[]",
+ "rows": "T[] where T extends { id: string }",
+ "rowLabel": "(row: T) => string",
+ "selectedIds": "string[]",
+ "onSelectionChange": "(ids: string[]) => void",
+ "toolbar": "ReactNode",
+ "bulkActions": "ReactNode",
+ "loading": "boolean",
+ "error": "string",
+ "emptyTitle": "string",
+ "emptyDescription": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { DataTable } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button", "mortar-empty-state", "mortar-notice"]
+ },
+ "preview": {
+ "source": "previews/DataTablePreview.tsx"
+ }
+}
diff --git a/manifests/mortar-dropdown.json b/manifests/mortar-dropdown.json
new file mode 100644
index 0000000..8b27c3f
--- /dev/null
+++ b/manifests/mortar-dropdown.json
@@ -0,0 +1,39 @@
+{
+ "id": "mortar-dropdown",
+ "logicalId": "dropdown",
+ "name": "Dropdown",
+ "description": "Mortar foundation dropdown, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Dropdown",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ label: string; onSelect: () => void; danger?: boolean; disabled?: boolean }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Dropdown } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/DropdownPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-empty-state.json b/manifests/mortar-empty-state.json
new file mode 100644
index 0000000..6daa4fe
--- /dev/null
+++ b/manifests/mortar-empty-state.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-empty-state",
+ "logicalId": "empty-state",
+ "name": "EmptyState",
+ "description": "Mortar foundation empty state, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "EmptyState",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "action": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { EmptyState } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/EmptyStatePreview.tsx"
+ }
+}
diff --git a/manifests/mortar-faq.json b/manifests/mortar-faq.json
new file mode 100644
index 0000000..f929632
--- /dev/null
+++ b/manifests/mortar-faq.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-faq",
+ "logicalId": "faq",
+ "name": "FAQ",
+ "description": "Mortar marketing faq, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "FAQ",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "ReactNode",
+ "items": "{ question: string; answer: ReactNode }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { FAQ } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FAQPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-feature-grid.json b/manifests/mortar-feature-grid.json
new file mode 100644
index 0000000..8aa6f1d
--- /dev/null
+++ b/manifests/mortar-feature-grid.json
@@ -0,0 +1,41 @@
+{
+ "id": "mortar-feature-grid",
+ "logicalId": "feature-grid",
+ "name": "FeatureGrid",
+ "description": "Mortar marketing feature grid, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "FeatureGrid",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "eyebrow": "string",
+ "description": "string",
+ "items": "{ title: string; description: string; icon?: ReactNode }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { FeatureGrid } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FeatureGridPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-feature-section.json b/manifests/mortar-feature-section.json
new file mode 100644
index 0000000..7a54858
--- /dev/null
+++ b/manifests/mortar-feature-section.json
@@ -0,0 +1,41 @@
+{
+ "id": "mortar-feature-section",
+ "logicalId": "feature-section",
+ "name": "FeatureSection",
+ "description": "Mortar marketing feature section, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "FeatureSection",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "children": "ReactNode",
+ "reverse": "boolean"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { FeatureSection } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FeatureSectionPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-footer.json b/manifests/mortar-footer.json
new file mode 100644
index 0000000..011e495
--- /dev/null
+++ b/manifests/mortar-footer.json
@@ -0,0 +1,42 @@
+{
+ "id": "mortar-footer",
+ "logicalId": "footer",
+ "name": "Footer",
+ "description": "Mortar marketing footer, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Footer",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "brand": "string",
+ "description": "string",
+ "columns": "{ title: string; links: LinkAction[] }[]",
+ "copyright": "string",
+ "legal": "LinkAction[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Footer } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/FooterPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-hero.json b/manifests/mortar-hero.json
new file mode 100644
index 0000000..da2994d
--- /dev/null
+++ b/manifests/mortar-hero.json
@@ -0,0 +1,44 @@
+{
+ "id": "mortar-hero",
+ "logicalId": "hero",
+ "name": "Hero",
+ "description": "Mortar marketing hero, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Hero",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "eyebrow": "string",
+ "title": "string",
+ "description": "string",
+ "actions": "LinkAction[]",
+ "children": "ReactNode",
+ "align": "center | split",
+ "headingLevel": "1 | 2"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Hero } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/HeroPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-input.json b/manifests/mortar-input.json
new file mode 100644
index 0000000..12c0ec9
--- /dev/null
+++ b/manifests/mortar-input.json
@@ -0,0 +1,44 @@
+{
+ "id": "mortar-input",
+ "logicalId": "input",
+ "name": "Input",
+ "description": "Mortar foundation input, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Input",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "input", "validation", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string | number",
+ "onChange": "ChangeEventHandler",
+ "hint": "string",
+ "error": "string",
+ "prefix": "string",
+ "suffix": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Input } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/InputPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-logo-cloud.json b/manifests/mortar-logo-cloud.json
new file mode 100644
index 0000000..f666626
--- /dev/null
+++ b/manifests/mortar-logo-cloud.json
@@ -0,0 +1,39 @@
+{
+ "id": "mortar-logo-cloud",
+ "logicalId": "logo-cloud",
+ "name": "LogoCloud",
+ "description": "Mortar marketing logo cloud, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "LogoCloud",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ name: string; image?: string }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { LogoCloud } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/LogoCloudPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-marketing-header.json b/manifests/mortar-marketing-header.json
new file mode 100644
index 0000000..4bf1250
--- /dev/null
+++ b/manifests/mortar-marketing-header.json
@@ -0,0 +1,42 @@
+{
+ "id": "mortar-marketing-header",
+ "logicalId": "marketing-header",
+ "name": "MarketingHeader",
+ "description": "Mortar marketing marketing header, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "MarketingHeader",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "brand": "string",
+ "homeHref": "string",
+ "links": "LinkAction[]",
+ "action": "LinkAction",
+ "controls": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { MarketingHeader } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button", "mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/MarketingHeaderPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-metric-group.json b/manifests/mortar-metric-group.json
new file mode 100644
index 0000000..a2fa40c
--- /dev/null
+++ b/manifests/mortar-metric-group.json
@@ -0,0 +1,39 @@
+{
+ "id": "mortar-metric-group",
+ "logicalId": "metric-group",
+ "name": "MetricGroup",
+ "description": "Mortar application metric group, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "MetricGroup",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application", "summary"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "items": "{ label: string; value: string; change?: string; tone?: Tone }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { MetricGroup } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/MetricGroupPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-modal.json b/manifests/mortar-modal.json
new file mode 100644
index 0000000..793ee7f
--- /dev/null
+++ b/manifests/mortar-modal.json
@@ -0,0 +1,42 @@
+{
+ "id": "mortar-modal",
+ "logicalId": "modal",
+ "name": "Modal",
+ "description": "Mortar foundation modal, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Modal",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "open": "boolean",
+ "onClose": "() => void",
+ "children": "ReactNode",
+ "actions": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Modal } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button"]
+ },
+ "preview": {
+ "source": "previews/ModalPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-notice.json b/manifests/mortar-notice.json
new file mode 100644
index 0000000..046b579
--- /dev/null
+++ b/manifests/mortar-notice.json
@@ -0,0 +1,41 @@
+{
+ "id": "mortar-notice",
+ "logicalId": "notice",
+ "name": "Notice",
+ "description": "Mortar foundation notice, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Notice",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "status"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "children": "ReactNode",
+ "tone": "neutral | success | warning | danger",
+ "announce": "boolean"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Notice } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/NoticePreview.tsx"
+ }
+}
diff --git a/manifests/mortar-page-heading.json b/manifests/mortar-page-heading.json
new file mode 100644
index 0000000..de16885
--- /dev/null
+++ b/manifests/mortar-page-heading.json
@@ -0,0 +1,43 @@
+{
+ "id": "mortar-page-heading",
+ "logicalId": "page-heading",
+ "name": "PageHeading",
+ "description": "Mortar application page heading, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "PageHeading",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "eyebrow": "string",
+ "actions": "ReactNode",
+ "breadcrumbs": "{ label: string; href?: string }[]",
+ "headingLevel": "1 | 2"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { PageHeading } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/PageHeadingPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-pagination.json b/manifests/mortar-pagination.json
new file mode 100644
index 0000000..40e3a4a
--- /dev/null
+++ b/manifests/mortar-pagination.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-pagination",
+ "logicalId": "pagination",
+ "name": "Pagination",
+ "description": "Mortar application pagination, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Pagination",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "page": "number",
+ "pageCount": "number",
+ "onChange": "(page: number) => void"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Pagination } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-button"]
+ },
+ "preview": {
+ "source": "previews/PaginationPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-preview-frame.json b/manifests/mortar-preview-frame.json
new file mode 100644
index 0000000..dc75f1e
--- /dev/null
+++ b/manifests/mortar-preview-frame.json
@@ -0,0 +1,39 @@
+{
+ "id": "mortar-preview-frame",
+ "logicalId": "preview-frame",
+ "name": "PreviewFrame",
+ "description": "Mortar application preview frame, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "PreviewFrame",
+ "source": "src/inspection.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "src": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { PreviewFrame } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/PreviewFramePreview.tsx"
+ }
+}
diff --git a/manifests/mortar-pricing.json b/manifests/mortar-pricing.json
new file mode 100644
index 0000000..c334cc8
--- /dev/null
+++ b/manifests/mortar-pricing.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-pricing",
+ "logicalId": "pricing",
+ "name": "Pricing",
+ "description": "Mortar marketing pricing, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Pricing",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "plans": "PricingPlan[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Pricing } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": ["mortar-action-link"]
+ },
+ "preview": {
+ "source": "previews/PricingPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-radio-group.json b/manifests/mortar-radio-group.json
new file mode 100644
index 0000000..f905f83
--- /dev/null
+++ b/manifests/mortar-radio-group.json
@@ -0,0 +1,42 @@
+{
+ "id": "mortar-radio-group",
+ "logicalId": "radio-group",
+ "name": "RadioGroup",
+ "description": "Mortar foundation radio group, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "RadioGroup",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "input", "validation", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "name": "string",
+ "value": "string",
+ "onChange": "(value: string) => void",
+ "options": "{ value: string; label: string; description?: string }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { RadioGroup } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/RadioGroupPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-segmented-control.json b/manifests/mortar-segmented-control.json
new file mode 100644
index 0000000..774f4c7
--- /dev/null
+++ b/manifests/mortar-segmented-control.json
@@ -0,0 +1,41 @@
+{
+ "id": "mortar-segmented-control",
+ "logicalId": "segmented-control",
+ "name": "SegmentedControl",
+ "description": "Mortar foundation segmented control, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "SegmentedControl",
+ "source": "src/inspection.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string",
+ "options": "{ value: string; label: string }[]",
+ "onChange": "(value: string) => void"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { SegmentedControl } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SegmentedControlPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-select.json b/manifests/mortar-select.json
new file mode 100644
index 0000000..3315b30
--- /dev/null
+++ b/manifests/mortar-select.json
@@ -0,0 +1,43 @@
+{
+ "id": "mortar-select",
+ "logicalId": "select",
+ "name": "Select",
+ "description": "Mortar foundation select, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Select",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "input", "validation", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "options": "{ value: string; label: string }[]",
+ "value": "string",
+ "onChange": "ChangeEventHandler",
+ "hint": "string",
+ "error": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Select } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SelectPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-settings-section.json b/manifests/mortar-settings-section.json
new file mode 100644
index 0000000..3d89bb7
--- /dev/null
+++ b/manifests/mortar-settings-section.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-settings-section",
+ "logicalId": "settings-section",
+ "name": "SettingsSection",
+ "description": "Mortar application settings section, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "SettingsSection",
+ "source": "src/application.tsx",
+ "capabilities": ["theme", "application"],
+ "owner": "Mortar maintainers",
+ "kitId": "application",
+ "provenance": {
+ "kitId": "application",
+ "exampleId": "mortar-application-reference",
+ "sourceDigest": "9c3a1c251ac5dcf28a566a4a6ee4ae6724296996e47b7eb3ccacad1517668ef3"
+ },
+ "contract": {
+ "props": {
+ "title": "string",
+ "description": "string",
+ "children": "ReactNode"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { SettingsSection } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SettingsSectionPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-switch.json b/manifests/mortar-switch.json
new file mode 100644
index 0000000..5a25ba9
--- /dev/null
+++ b/manifests/mortar-switch.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-switch",
+ "logicalId": "switch",
+ "name": "Switch",
+ "description": "Mortar foundation switch, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Switch",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "input", "validation", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "checked": "boolean",
+ "onChange": "ChangeEventHandler"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Switch } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/SwitchPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-tabs.json b/manifests/mortar-tabs.json
new file mode 100644
index 0000000..29ec41b
--- /dev/null
+++ b/manifests/mortar-tabs.json
@@ -0,0 +1,41 @@
+{
+ "id": "mortar-tabs",
+ "logicalId": "tabs",
+ "name": "Tabs",
+ "description": "Mortar foundation tabs, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Tabs",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "action", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string",
+ "onChange": "(value: string) => void",
+ "items": "{ id: string; label: string; content: ReactNode }[]"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Tabs } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/TabsPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-testimonial.json b/manifests/mortar-testimonial.json
new file mode 100644
index 0000000..7af280f
--- /dev/null
+++ b/manifests/mortar-testimonial.json
@@ -0,0 +1,40 @@
+{
+ "id": "mortar-testimonial",
+ "logicalId": "testimonial",
+ "name": "Testimonial",
+ "description": "Mortar marketing testimonial, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "Testimonial",
+ "source": "src/marketing.tsx",
+ "capabilities": ["theme", "marketing"],
+ "owner": "Mortar maintainers",
+ "kitId": "marketing",
+ "provenance": {
+ "kitId": "marketing",
+ "exampleId": "mortar-marketing-reference",
+ "sourceDigest": "4ec7866f2bbb06475a555bfde970c052111c84efde4ce0f38134e7fb400733d2"
+ },
+ "contract": {
+ "props": {
+ "quote": "string",
+ "name": "string",
+ "role": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { Testimonial } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/TestimonialPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-text-area.json b/manifests/mortar-text-area.json
new file mode 100644
index 0000000..68ebcb2
--- /dev/null
+++ b/manifests/mortar-text-area.json
@@ -0,0 +1,42 @@
+{
+ "id": "mortar-text-area",
+ "logicalId": "text-area",
+ "name": "TextArea",
+ "description": "Mortar foundation text area, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "TextArea",
+ "source": "src/primitives.tsx",
+ "capabilities": ["theme", "foundation", "input", "validation", "keyboard"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "label": "string",
+ "value": "string",
+ "onChange": "ChangeEventHandler",
+ "hint": "string",
+ "error": "string"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { TextArea } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/TextAreaPreview.tsx"
+ }
+}
diff --git a/manifests/mortar-theme-provider.json b/manifests/mortar-theme-provider.json
new file mode 100644
index 0000000..347a6ee
--- /dev/null
+++ b/manifests/mortar-theme-provider.json
@@ -0,0 +1,41 @@
+{
+ "id": "mortar-theme-provider",
+ "logicalId": "theme-provider",
+ "name": "ThemeProvider",
+ "description": "Mortar foundation theme provider, using central semantic tokens.",
+ "stack": "react-tailwind",
+ "package": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "export": "ThemeProvider",
+ "source": "src/foundation.tsx",
+ "capabilities": ["theme", "foundation"],
+ "owner": "Mortar maintainers",
+ "kitId": "foundation",
+ "provenance": {
+ "kitId": "foundation",
+ "exampleId": "mortar-foundation-reference",
+ "sourceDigest": "f132052b4ac52f3909ac437073c076b9f615849effea14dabf7d30759d714c2d"
+ },
+ "contract": {
+ "props": {
+ "children": "ReactNode",
+ "initialAppearance": "light | dark | system",
+ "brand": "neutral | green",
+ "density": "compact | comfortable | spacious"
+ },
+ "states": [
+ "Light",
+ "Dark",
+ "Neutral brand",
+ "Green brand",
+ "Keyboard and focus"
+ ],
+ "usage": "Import { ThemeProvider } from @tony-ui/mortar. Compose with the public typed props.",
+ "avoid": "Do not copy implementations, override central colours or grant component approval.",
+ "accessibility": "Verify accessible names, keyboard interaction, visible focus, responsive layout and supported states.",
+ "dependencies": []
+ },
+ "preview": {
+ "source": "previews/ThemeProviderPreview.tsx"
+ }
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..40cb463
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2862 @@
+{
+ "name": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "@headlessui/react": "^2.2.0"
+ },
+ "devDependencies": {
+ "@axe-core/playwright": "^4.13.0",
+ "@playwright/test": "^1.63.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^5.0.0",
+ "prettier": "^3.9.9",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwindcss": "^4.0.0",
+ "typescript": "^5.9.3",
+ "vite": "^7.1.0"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwindcss": "^4.0.0"
+ }
+ },
+ "node_modules/@axe-core/playwright": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.13.0.tgz",
+ "integrity": "sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "axe-core": "~4.13.0"
+ },
+ "peerDependencies": {
+ "playwright-core": ">= 1.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz",
+ "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz",
+ "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.9",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
+ "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.12"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
+ "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.8.0",
+ "@floating-ui/utils": "^0.2.12"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.26.28",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
+ "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.1.2",
+ "@floating-ui/utils": "^0.2.8",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz",
+ "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.8.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
+ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
+ "license": "MIT"
+ },
+ "node_modules/@headlessui/react": {
+ "version": "2.2.10",
+ "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.10.tgz",
+ "integrity": "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react": "^0.26.16",
+ "@react-aria/focus": "^3.20.2",
+ "@react-aria/interactions": "^3.25.0",
+ "@tanstack/react-virtual": "^3.13.9",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@internationalized/date": {
+ "version": "3.12.4",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz",
+ "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@internationalized/number": {
+ "version": "3.6.9",
+ "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.9.tgz",
+ "integrity": "sha512-GgWy2caO/+hV9sT/oCTWGHvbjmJMPT9kkXzoKRLnkEq7adghhR7UprhvY9sXrKfrp3NcDfhF6QQHXR1KfQbB8g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@internationalized/string": {
+ "version": "3.2.10",
+ "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz",
+ "integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.64.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.0.tgz",
+ "integrity": "sha512-oTN9FNYuN6bqQMwiho6jJKLCG2/QOYCixGXuCjRmzOq23eODeukuY2XiAVoVpAECbuezMt1MHQjAiQxK8F+Y+g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.64.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@react-aria/focus": {
+ "version": "3.22.1",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.1.tgz",
+ "integrity": "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0",
+ "react-aria": "^3.48.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/interactions": {
+ "version": "3.28.1",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.1.tgz",
+ "integrity": "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-types/shared": "^3.34.0",
+ "@swc/helpers": "^0.5.0",
+ "react-aria": "^3.48.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/shared": {
+ "version": "3.37.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.37.0.tgz",
+ "integrity": "sha512-VNlZuifu4tHdxWPfHvoMfKsHA9kVTCGkduv5vKVTdinm8wHFJNhyVB5RzIMiRFj0q3RKoCbP68u2zaoBSiFueg==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.6.tgz",
+ "integrity": "sha512-FkO19Fowv/OQeJZPVfdetJaI3/VpbZmBsnmAlR/rQ+b3QwfiGQiOpVegpSRH331kelvHTVw9hXYG3DwjAaRzyQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.6.tgz",
+ "integrity": "sha512-si5/Etm6MphPc2xYHnk455Shls/hoMjTy3mhXge4OPmvCw7TwC4KP4fKokAlKLV4XaQvLvgSHfafPcblLMYYdQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.6.tgz",
+ "integrity": "sha512-PQCl3q7b0BgvVt72i2xrWM6I5KliwwVe4GpWW5QDGyUQawDS9aPZss/aGyFqBmxdRxlE8wDc9CaxiQ5DHRSp/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.6.tgz",
+ "integrity": "sha512-l9DFl1Itb1YYdSrP1K3U8ih44AOHa/p+Q4WMMDfGvH5qVWp+ajfgS1DlYo4yCRh8wJ2ZJv0Fqccf+ULAmMFjmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.6.tgz",
+ "integrity": "sha512-bR4GR3205N9+od0+l5Dd9/iZTDpJkIe6d6Ydp2qbcQZJLmKBdYQQRp8gbWpbMt92twKJY66o1vK4NkoKQODIrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.6.tgz",
+ "integrity": "sha512-XTVNaDzcFl2TeBglvxdGgsCv9JGtAGRAoJENmGBR/XvDH2mA2K0IXc2aMfP0dR8mexSXSKifJ85FixoXX1wfTw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.6.tgz",
+ "integrity": "sha512-qWc6qbc+25ugG9vqPR9P422KJBbVfZ05mjcMggoq8tsfWP+eRfC+/tiDdIlRF5VL2pDVZWN9zAhWsmu7R1+7Dw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.6.tgz",
+ "integrity": "sha512-V4Nwkp8844X/Xnww3PI1LYS0YPei8XQEVG70R+5MPZGO7AhYoun4hCN7Svs4U3wbQLZbwiz+9xRFhUZZdRl8Ww==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.6.tgz",
+ "integrity": "sha512-xb72A1aPaaIsp+a88VtRae/ovIEXislbUMyp7b9AQQU7ZkP2m/+ik6DW9TQcJyw5R8/RIailF9zExYwkjR++UA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.6.tgz",
+ "integrity": "sha512-pbcs1BVBwC43hZkjhjlF8Rx8m5G0KnhLZs9Azy/gqccP6UsNSzJr0hKGIAVPqzAH1O/NdPxxC9GVGb4msQg/aw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.6.tgz",
+ "integrity": "sha512-EKOC8os6Vsg487ZLKUqzLF99Jsmnq6tu7O7mAELakNpcA+8KadktFf/JYdRuHe/DzCUXmN38pUKu4XBcKlqGbQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.6.tgz",
+ "integrity": "sha512-v/SQ68GnSlr/HyjB79sNXesGDwkOPu9JjbHfmWaymCiM+nZsQ9TstG1KfA4MuEvTr2d3PoY8PhFxj9bt0e32JA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.6.tgz",
+ "integrity": "sha512-G3pmliMQ7r15zFFNwBlaT/xTdSLi4lfgI7CUrQueyKYheNVhrd8JrWCE6Ll8xf4AA1RZFqq+mNJYntiElsVOyA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.6.tgz",
+ "integrity": "sha512-XQTB/IPoB/wG6x/wu9uomAt8YH61IAr5iZTubMiad/lu88sotq2ocz9hJd556+Jz2kIkF7ZQZIsUuWE3OWqtcA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.6.tgz",
+ "integrity": "sha512-O5mpokth6+ydMuwiGtY8fIjvOBfH7u6SzmDNmZ3k6GK5C6iEAC2q2ZN/T5L9jYILx0IcCT0KnfMDihQ5yQFSBw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.6.tgz",
+ "integrity": "sha512-aIFxbAD9v3TvIaEDsKmukjcDg1JS4/tFXw8SHyQQgzOwzWCQ4cyDK7Ju1pQKqUXDFkEu/BZCFDCDATsKhQcB/Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.6.tgz",
+ "integrity": "sha512-fuNo1UwBc6BxnHDOxxguQL/InhheMN3RJcJMb0b4xP8r5Q3CNCrJbVg0eCIFuJ4e/zAfjR2gYbOfbzO/G7CIyw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.6.tgz",
+ "integrity": "sha512-05v6wFKDKJPZs9xlytVrrRu6IbRuoSvlH9xNwEOafXsKyjBFZPvAcZz2VIAB/M+lblYeKa9iOYC4RDVdfcAAaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.6.tgz",
+ "integrity": "sha512-wJXKcdMjvkc/FaavZXNP1yW4i0pfOmhZAIWRSLG2yJ0PB0TWi7dkLErhX0u0vptDcXKKO/m1Y/YTeuyvb2MXwg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.6.tgz",
+ "integrity": "sha512-cwg7/h0ThvJwy8CBkmACdiHAxRWpbsCmIsjOt+Zzx0hTnIcSUmKYEV03bGg+hD827OnwUsbKh1ZZDW754xWyBA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.6.tgz",
+ "integrity": "sha512-T0RkYIJbdYXJEQFw+tMBO09KDFdgmNxkHsof3wWAABrvnx15Yh8RytmrgYy9+q9cBeBNZF66MMQZC+5nzEz6vg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.6.tgz",
+ "integrity": "sha512-KlfpmF1AAIdAWxt8QQNClbpLFlAUfYOzqIuJ7IXEStMHpFRuH78RG6FjqPb3Rqhh0K89F5npvG+/i3Fjl6IveQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.6.tgz",
+ "integrity": "sha512-MrTLjd7Jlt9/ZavJZYG61KQv9Iy9bzbY+yXy73Xvwoa2uP66wvDShkyBr5Chz/yrE6lbD/zHMuTmKQVNQqBApg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.6.tgz",
+ "integrity": "sha512-8fWDH1CkPI8GdBKAxzHDza7UmgficsKo2+MVbvUgOGhB0629w5sHMg3Qu5y7TZsrYKHHWpz8fla4KvoT7WsO3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.6.tgz",
+ "integrity": "sha512-9/c9PWdr5GCIHkkxiHM1HjNBQMVdftP4O8QTcoUAaRza2FSqJ4axbf8QAnBuE6BIGQDl71SaRPeDdDYid5ecTw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.23",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
+ "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
+ "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.24.1",
+ "jiti": "^2.7.0",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
+ "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-x64": "4.3.3",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.3",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
+ "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
+ "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
+ "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
+ "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
+ "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
+ "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
+ "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
+ "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
+ "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
+ "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.11.1",
+ "@emnapi/runtime": "^1.11.1",
+ "@emnapi/wasi-threads": "^1.2.2",
+ "@napi-rs/wasm-runtime": "^1.1.4",
+ "@tybys/wasm-util": "^0.10.2",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
+ "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
+ "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
+ "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.3.3",
+ "@tailwindcss/oxide": "4.3.3",
+ "tailwindcss": "4.3.3"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.14.19",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.19.tgz",
+ "integrity": "sha512-zmVdJO8DDqHphZ2H6SnTmgpVXcUy5DprB+clZF0ik679/d0MQ1UPEb2jtaX6F5VTAX4o3ozSLQaRCnybS2KYLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.19.4"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.19.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.19.4.tgz",
+ "integrity": "sha512-ygZ3O99Il18dl0gG0q7sH78iSkb96F07G+xK9V83eCgV8NQbcNd7R+cgcJJMFO7llBoebd0A82sCCstQq2Vp6Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.3.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
+ "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz",
+ "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.28",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz",
+ "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.29.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
+ "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.26",
+ "caniuse-lite": "^1.0.30001813",
+ "electron-to-chromium": "^1.5.439",
+ "node-releases": "^2.0.57",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001816",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001816.tgz",
+ "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.454",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.454.tgz",
+ "integrity": "sha512-NqD5GhkXnxJpDQVCmmpc546qjH6kei2VI9m45CTF+sQU7bcS7YeVt7ba0cnjueMH1AXGRu/driZUP9iYVSv7vg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.26.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz",
+ "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.58",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz",
+ "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.64.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.0.tgz",
+ "integrity": "sha512-kVzTnYFYEyQ9RGzBjNCd2+yEqWmVbxl3AHPynv3qQ6JTjv4a+kwRcnqQwIpuP3NnrnIrE/Q6wWAIAvMfIE2/Yg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.64.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.64.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.0.tgz",
+ "integrity": "sha512-T9r+MZkTECl2+oUcZ26YgZzTrYidTsIhYYkJix+6iDOymOb1FB2RLLSOxRGnZGv5DStt+aduIVbt+E8WygXYpg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.30",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz",
+ "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz",
+ "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-aria": {
+ "version": "3.53.1",
+ "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.53.1.tgz",
+ "integrity": "sha512-dikisyOjJd3D2fFrIDWmVWzqiLz5B86GzZv1zxs7xI4dVc0JIz/1Wa52I20gYvVL2WXeFXlygYsle/QcOMhtcg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@internationalized/date": "^3.12.4",
+ "@internationalized/number": "^3.6.9",
+ "@internationalized/string": "^3.2.10",
+ "@react-types/shared": "^3.37.0",
+ "@swc/helpers": "^0.5.0",
+ "aria-hidden": "^1.2.3",
+ "clsx": "^2.0.0",
+ "react-stately": "3.51.0",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": "^19.3.0"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-stately": {
+ "version": "3.51.0",
+ "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.51.0.tgz",
+ "integrity": "sha512-YJcmkAXu2XOQP97hO/qz/cC3qv9kni3oVhwBlxWr358BHePJUpY15XwL3YSRMKxYt4EkslPqAz11JRirMfANGw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@internationalized/date": "^3.12.4",
+ "@internationalized/number": "^3.6.9",
+ "@internationalized/string": "^3.2.10",
+ "@react-types/shared": "^3.37.0",
+ "@swc/helpers": "^0.5.0",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.64.6",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.6.tgz",
+ "integrity": "sha512-fX1Vy+UNKdXn+xKDnu/HBXbHVr1AY8cSdQj9BU4kLv5aFCMqGpvrVbyUMmjN3DdzWhSAuTbcX8PCNEVaumthYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.64.6",
+ "@rollup/rollup-android-arm64": "4.64.6",
+ "@rollup/rollup-darwin-arm64": "4.64.6",
+ "@rollup/rollup-darwin-x64": "4.64.6",
+ "@rollup/rollup-freebsd-arm64": "4.64.6",
+ "@rollup/rollup-freebsd-x64": "4.64.6",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.64.6",
+ "@rollup/rollup-linux-arm-musleabihf": "4.64.6",
+ "@rollup/rollup-linux-arm64-gnu": "4.64.6",
+ "@rollup/rollup-linux-arm64-musl": "4.64.6",
+ "@rollup/rollup-linux-loong64-gnu": "4.64.6",
+ "@rollup/rollup-linux-loong64-musl": "4.64.6",
+ "@rollup/rollup-linux-ppc64-gnu": "4.64.6",
+ "@rollup/rollup-linux-ppc64-musl": "4.64.6",
+ "@rollup/rollup-linux-riscv64-gnu": "4.64.6",
+ "@rollup/rollup-linux-riscv64-musl": "4.64.6",
+ "@rollup/rollup-linux-s390x-gnu": "4.64.6",
+ "@rollup/rollup-linux-x64-gnu": "4.64.6",
+ "@rollup/rollup-linux-x64-musl": "4.64.6",
+ "@rollup/rollup-openbsd-x64": "4.64.6",
+ "@rollup/rollup-openharmony-arm64": "4.64.6",
+ "@rollup/rollup-win32-arm64-msvc": "4.64.6",
+ "@rollup/rollup-win32-ia32-msvc": "4.64.6",
+ "@rollup/rollup-win32-x64-gnu": "4.64.6",
+ "@rollup/rollup-win32-x64-msvc": "4.64.6",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
+ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tabbable": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz",
+ "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==",
+ "license": "MIT"
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
+ "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
+ "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz",
+ "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.7",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz",
+ "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6a32036
--- /dev/null
+++ b/package.json
@@ -0,0 +1,72 @@
+{
+ "name": "@tony-ui/mortar",
+ "version": "0.1.0",
+ "description": "Mortar: a shared design foundation, application and marketing UI for React and Tailwind.",
+ "type": "module",
+ "files": [
+ "dist",
+ "src",
+ "styles.css",
+ "README.md",
+ "LICENSE",
+ "catalog.json",
+ "baseline-policy.json",
+ "design-principles.json",
+ "DESIGN.md"
+ ],
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ },
+ "./styles.css": "./styles.css"
+ },
+ "sideEffects": [
+ "**/*.css"
+ ],
+ "scripts": {
+ "build": "tsc -p tsconfig.json",
+ "dev": "vite --host 127.0.0.1",
+ "catalog": "node scripts/check.mjs --write-catalog",
+ "check": "npm run format:check && npm run build && node scripts/check.mjs && node --test tests/*.test.mjs && npm run build:playground",
+ "check:json": "node scripts/check.mjs --format json",
+ "test:browser": "playwright test",
+ "build:playground": "tsc --noEmit -p tsconfig.playground.json && vite build",
+ "format:check": "prettier --check .",
+ "release:prepare": "node scripts/release.mjs"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwindcss": "^4.0.0"
+ },
+ "dependencies": {
+ "@headlessui/react": "^2.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "typescript": "^5.9.3",
+ "vite": "^7.1.0",
+ "@vitejs/plugin-react": "^5.0.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "tailwindcss": "^4.0.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "prettier": "^3.9.9",
+ "@playwright/test": "^1.63.0",
+ "@axe-core/playwright": "^4.13.0"
+ },
+ "license": "MIT",
+ "publishConfig": {
+ "registry": "https://git.tony.codes/api/packages/tony/npm/",
+ "access": "public"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://git.tony.codes/tony/mortar.git"
+ },
+ "homepage": "https://ui.tony.codes/"
+}
diff --git a/playground/index.html b/playground/index.html
new file mode 100644
index 0000000..76d18ec
--- /dev/null
+++ b/playground/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Mortar component playground
+
+
+
+
+
+
diff --git a/playground/main.tsx b/playground/main.tsx
new file mode 100644
index 0000000..6822465
--- /dev/null
+++ b/playground/main.tsx
@@ -0,0 +1,55 @@
+import { Suspense, lazy, type ComponentType } from "react";
+import { createRoot } from "react-dom/client";
+import {
+ ThemeProvider,
+ Select,
+ AppearanceControl,
+ PageHeading,
+ Notice,
+} from "../src/index.js";
+import catalog from "../catalog.json";
+import "./styles.css";
+const fixtures = import.meta.glob("../previews/*Preview.tsx");
+const params = new URLSearchParams(location.search);
+const component = params.get("component") || "Button";
+const loader = fixtures[`../previews/${component}Preview.tsx`];
+const Preview = loader
+ ? lazy(loader as () => Promise<{ default: ComponentType }>)
+ : null;
+createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+ Loading preview…
}>
+ {Preview ? (
+
+ ) : (
+
+ Choose a component from the catalogue.
+
+ )}
+
+
+ ,
+);
diff --git a/playground/styles.css b/playground/styles.css
new file mode 100644
index 0000000..eda5361
--- /dev/null
+++ b/playground/styles.css
@@ -0,0 +1,4 @@
+@import "tailwindcss";
+@import "../styles.css";
+@source '../src';
+@source '../previews';
diff --git a/playwright.config.mjs b/playwright.config.mjs
new file mode 100644
index 0000000..d3340e9
--- /dev/null
+++ b/playwright.config.mjs
@@ -0,0 +1,17 @@
+import { defineConfig } from "@playwright/test";
+export default defineConfig({
+ testDir: "tests/browser",
+ workers: 4,
+ timeout: 30000,
+ use: {
+ baseURL: "http://127.0.0.1:4340",
+ launchOptions: process.env.UI_TEST_CHROME
+ ? { executablePath: process.env.UI_TEST_CHROME }
+ : {},
+ },
+ webServer: {
+ command: "npm run dev",
+ url: "http://127.0.0.1:4340",
+ reuseExistingServer: false,
+ },
+});
diff --git a/previews/ActionLinkPreview.tsx b/previews/ActionLinkPreview.tsx
new file mode 100644
index 0000000..6b62403
--- /dev/null
+++ b/previews/ActionLinkPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/ActivityFeedPreview.tsx b/previews/ActivityFeedPreview.tsx
new file mode 100644
index 0000000..0823bcf
--- /dev/null
+++ b/previews/ActivityFeedPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/AppShellPreview.tsx b/previews/AppShellPreview.tsx
new file mode 100644
index 0000000..ec97141
--- /dev/null
+++ b/previews/AppShellPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/AppearanceControlPreview.tsx b/previews/AppearanceControlPreview.tsx
new file mode 100644
index 0000000..c221531
--- /dev/null
+++ b/previews/AppearanceControlPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/BadgePreview.tsx b/previews/BadgePreview.tsx
new file mode 100644
index 0000000..f8c5984
--- /dev/null
+++ b/previews/BadgePreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/ButtonPreview.tsx b/previews/ButtonPreview.tsx
new file mode 100644
index 0000000..31cff08
--- /dev/null
+++ b/previews/ButtonPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/CallToActionPreview.tsx b/previews/CallToActionPreview.tsx
new file mode 100644
index 0000000..c54b27e
--- /dev/null
+++ b/previews/CallToActionPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/CheckboxPreview.tsx b/previews/CheckboxPreview.tsx
new file mode 100644
index 0000000..e83a6e7
--- /dev/null
+++ b/previews/CheckboxPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/CodeBlockPreview.tsx b/previews/CodeBlockPreview.tsx
new file mode 100644
index 0000000..b104e82
--- /dev/null
+++ b/previews/CodeBlockPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/DataTablePreview.tsx b/previews/DataTablePreview.tsx
new file mode 100644
index 0000000..47454c4
--- /dev/null
+++ b/previews/DataTablePreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/DropdownPreview.tsx b/previews/DropdownPreview.tsx
new file mode 100644
index 0000000..3199068
--- /dev/null
+++ b/previews/DropdownPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/EmptyStatePreview.tsx b/previews/EmptyStatePreview.tsx
new file mode 100644
index 0000000..e4d0f52
--- /dev/null
+++ b/previews/EmptyStatePreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/FAQPreview.tsx b/previews/FAQPreview.tsx
new file mode 100644
index 0000000..7b2c064
--- /dev/null
+++ b/previews/FAQPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/FeatureGridPreview.tsx b/previews/FeatureGridPreview.tsx
new file mode 100644
index 0000000..5b3e9c5
--- /dev/null
+++ b/previews/FeatureGridPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/FeatureSectionPreview.tsx b/previews/FeatureSectionPreview.tsx
new file mode 100644
index 0000000..3046ea5
--- /dev/null
+++ b/previews/FeatureSectionPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/Fixture.tsx b/previews/Fixture.tsx
new file mode 100644
index 0000000..d024145
--- /dev/null
+++ b/previews/Fixture.tsx
@@ -0,0 +1,476 @@
+import { useState, type ReactNode } from "react";
+import * as UI from "../src/index.js";
+export function PreviewCase({ name }: { name: string }) {
+ const [value, setValue] = useState("first"),
+ [checked, setChecked] = useState(false),
+ [open, setOpen] = useState(false),
+ [page, setPage] = useState(1);
+ const controls = (
+
+ );
+ const action = { label: "Explore", href: "#example" };
+ const items = [
+ {
+ title: "Shared foundation",
+ description: "One theme across every interface.",
+ },
+ {
+ title: "Accessible controls",
+ description: "Clear labels and keyboard operation.",
+ },
+ ];
+ let example: ReactNode;
+ switch (name) {
+ case "ThemeProvider":
+ case "AppearanceControl":
+ example = (
+ <>
+
+ Primary action
+ {controls}
+ >
+ );
+ break;
+ case "Button":
+ example = (
+ <>
+ Save changes
+ Cancel
+ Delete
+ Saving
+ >
+ );
+ break;
+ case "ActionLink":
+ example = ;
+ break;
+ case "Badge":
+ example = (
+ <>
+ {(["neutral", "success", "warning", "danger"] as const).map(
+ (tone) => (
+
+ {tone}
+
+ ),
+ )}
+ >
+ );
+ break;
+ case "Input":
+ example = (
+ <>
+ {controls}
+
+ >
+ );
+ break;
+ case "Select":
+ example = (
+ <>
+ setValue(e.target.value)}
+ options={[
+ { value: "first", label: "Production" },
+ { value: "second", label: "Preview" },
+ ]}
+ />
+
+
+ >
+ );
+ break;
+ case "TextArea":
+ example = (
+
+ );
+ break;
+ case "Checkbox":
+ example = (
+ setChecked(e.target.checked)}
+ />
+ );
+ break;
+ case "Switch":
+ example = (
+ setChecked(e.target.checked)}
+ />
+ );
+ break;
+ case "RadioGroup":
+ example = (
+
+ );
+ break;
+ case "Tabs":
+ example = (
+ Overview content,
+ },
+ {
+ id: "second",
+ label: "Activity",
+ content: Activity content
,
+ },
+ ]}
+ />
+ );
+ break;
+ case "Modal":
+ example = (
+ <>
+ setOpen(true)}>Open dialog
+ setOpen(false)}
+ title="Create project"
+ >
+ {controls}
+ setOpen(false)}>Create project
+
+ >
+ );
+ break;
+ case "Dropdown":
+ example = (
+ setChecked(true) },
+ {
+ label: "Delete",
+ danger: true,
+ onSelect: () => setChecked(false),
+ },
+ ]}
+ />
+ );
+ break;
+ case "Notice":
+ example = (
+
+ Your project can use the latest release.
+
+ );
+ break;
+ case "EmptyState":
+ example = (
+ Create project}
+ />
+ );
+ break;
+ case "AppShell":
+ example = (
+
+
+
+ {controls}
+
+
+ );
+ break;
+ case "PageHeading":
+ example = (
+ New project}
+ />
+ );
+ break;
+ case "MetricGroup":
+ example = (
+
+ );
+ break;
+ case "DataTable":
+ example = (
+ row.name}
+ columns={[
+ { key: "name", label: "Name", render: (row) => row.name },
+ {
+ key: "status",
+ label: "Status",
+ render: (row) => {row.status},
+ },
+ ]}
+ rows={[{ id: "example", name: "Northwind", status: "Active" }]}
+ />
+ );
+ break;
+ case "Pagination":
+ example = ;
+ break;
+ case "SettingsSection":
+ example = (
+
+ {controls}
+ Save changes
+
+ );
+ break;
+ case "ActivityFeed":
+ example = (
+
+ );
+ break;
+ case "MarketingHeader":
+ example = (
+
+ );
+ break;
+ case "Hero":
+ example = (
+
+ );
+ break;
+ case "FeatureGrid":
+ example = ;
+ break;
+ case "FeatureSection":
+ example = (
+
+ {controls}
+
+ );
+ break;
+ case "Testimonial":
+ example = (
+
+ );
+ break;
+ case "LogoCloud":
+ example = (
+
+ );
+ break;
+ case "Pricing":
+ example = (
+
+ );
+ break;
+ case "FAQ":
+ example = (
+
+ );
+ break;
+ case "CallToAction":
+ example = (
+
+ );
+ break;
+ case "Footer":
+ example = (
+
+ );
+ break;
+ case "SegmentedControl":
+ example = (
+
+ );
+ break;
+ case "CodeBlock":
+ example = (
+
+ );
+ break;
+ case "PreviewFrame":
+ example = ;
+ break;
+ default:
+ example = (
+
+ This component is not registered.
+
+ );
+ }
+ return (
+
+ {example}
+
+ );
+}
diff --git a/previews/FooterPreview.tsx b/previews/FooterPreview.tsx
new file mode 100644
index 0000000..895090a
--- /dev/null
+++ b/previews/FooterPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/HeroPreview.tsx b/previews/HeroPreview.tsx
new file mode 100644
index 0000000..c424476
--- /dev/null
+++ b/previews/HeroPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/InputPreview.tsx b/previews/InputPreview.tsx
new file mode 100644
index 0000000..cb98728
--- /dev/null
+++ b/previews/InputPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/LogoCloudPreview.tsx b/previews/LogoCloudPreview.tsx
new file mode 100644
index 0000000..4bdfdb3
--- /dev/null
+++ b/previews/LogoCloudPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/MarketingHeaderPreview.tsx b/previews/MarketingHeaderPreview.tsx
new file mode 100644
index 0000000..8c95c27
--- /dev/null
+++ b/previews/MarketingHeaderPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/MetricGroupPreview.tsx b/previews/MetricGroupPreview.tsx
new file mode 100644
index 0000000..4afe340
--- /dev/null
+++ b/previews/MetricGroupPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/ModalPreview.tsx b/previews/ModalPreview.tsx
new file mode 100644
index 0000000..ecc0d57
--- /dev/null
+++ b/previews/ModalPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/NoticePreview.tsx b/previews/NoticePreview.tsx
new file mode 100644
index 0000000..bcc7673
--- /dev/null
+++ b/previews/NoticePreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/PageHeadingPreview.tsx b/previews/PageHeadingPreview.tsx
new file mode 100644
index 0000000..ceaf5b6
--- /dev/null
+++ b/previews/PageHeadingPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/PaginationPreview.tsx b/previews/PaginationPreview.tsx
new file mode 100644
index 0000000..1d40741
--- /dev/null
+++ b/previews/PaginationPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/PreviewFramePreview.tsx b/previews/PreviewFramePreview.tsx
new file mode 100644
index 0000000..8e8fe1a
--- /dev/null
+++ b/previews/PreviewFramePreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/PricingPreview.tsx b/previews/PricingPreview.tsx
new file mode 100644
index 0000000..2222d4f
--- /dev/null
+++ b/previews/PricingPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/RadioGroupPreview.tsx b/previews/RadioGroupPreview.tsx
new file mode 100644
index 0000000..05ebe3e
--- /dev/null
+++ b/previews/RadioGroupPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/SegmentedControlPreview.tsx b/previews/SegmentedControlPreview.tsx
new file mode 100644
index 0000000..7abb728
--- /dev/null
+++ b/previews/SegmentedControlPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/SelectPreview.tsx b/previews/SelectPreview.tsx
new file mode 100644
index 0000000..471a635
--- /dev/null
+++ b/previews/SelectPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/SettingsSectionPreview.tsx b/previews/SettingsSectionPreview.tsx
new file mode 100644
index 0000000..8bc8cf3
--- /dev/null
+++ b/previews/SettingsSectionPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/SwitchPreview.tsx b/previews/SwitchPreview.tsx
new file mode 100644
index 0000000..db55a08
--- /dev/null
+++ b/previews/SwitchPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/TabsPreview.tsx b/previews/TabsPreview.tsx
new file mode 100644
index 0000000..1cd3d43
--- /dev/null
+++ b/previews/TabsPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/TestimonialPreview.tsx b/previews/TestimonialPreview.tsx
new file mode 100644
index 0000000..b217fc8
--- /dev/null
+++ b/previews/TestimonialPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/TextAreaPreview.tsx b/previews/TextAreaPreview.tsx
new file mode 100644
index 0000000..84675cb
--- /dev/null
+++ b/previews/TextAreaPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/previews/ThemeProviderPreview.tsx b/previews/ThemeProviderPreview.tsx
new file mode 100644
index 0000000..2972231
--- /dev/null
+++ b/previews/ThemeProviderPreview.tsx
@@ -0,0 +1,4 @@
+import { PreviewCase } from "./Fixture";
+export default function Preview() {
+ return ;
+}
diff --git a/scripts/check.mjs b/scripts/check.mjs
new file mode 100644
index 0000000..5b34b78
--- /dev/null
+++ b/scripts/check.mjs
@@ -0,0 +1,172 @@
+import {
+ readFile,
+ readdir,
+ realpath,
+ lstat,
+ writeFile,
+} from "node:fs/promises";
+import { resolve, dirname } from "node:path";
+import { fileURLToPath, pathToFileURL } from "node:url";
+import { createHash } from "node:crypto";
+import ts from "typescript";
+export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+export async function checkKit(root = ROOT) {
+ const findings = [];
+ const add = (rule, file, message, line = 1) =>
+ findings.push({ rule, file, line, severity: "error", message });
+ const policy = JSON.parse(
+ await readFile(resolve(root, "baseline-policy.json"), "utf8"),
+ );
+ const pkg = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
+ const manifests = [];
+ for (const file of (await readdir(resolve(root, "manifests"))).sort()) {
+ if (!file.endsWith(".json")) continue;
+ try {
+ manifests.push(
+ JSON.parse(await readFile(resolve(root, "manifests", file), "utf8")),
+ );
+ } catch {
+ add("M001", `manifests/${file}`, "Manifest must be valid JSON.");
+ }
+ }
+ const actualExports = new Set();
+ const ast = ts.createSourceFile(
+ "index.ts",
+ await readFile(resolve(root, "src/index.ts"), "utf8"),
+ ts.ScriptTarget.Latest,
+ true,
+ );
+ for (const node of ast.statements)
+ if (
+ ts.isExportDeclaration(node) &&
+ !node.isTypeOnly &&
+ node.exportClause &&
+ ts.isNamedExports(node.exportClause)
+ )
+ for (const e of node.exportClause.elements)
+ if (!e.isTypeOnly) actualExports.add(e.name.text);
+ const ids = new Set();
+ const names = new Set();
+ const base = await realpath(root);
+ for (const c of manifests) {
+ if (
+ !c.id ||
+ ids.has(c.id) ||
+ names.has(c.export) ||
+ !actualExports.has(c.export)
+ )
+ add(
+ "M001",
+ `manifests/${c.id}.json`,
+ "Use a unique component ID and an existing named export.",
+ );
+ ids.add(c.id);
+ names.add(c.export);
+ for (const [file, prefix] of [
+ [c.source, "src/"],
+ [c.preview?.source, "previews/"],
+ ]) {
+ try {
+ if (
+ typeof file !== "string" ||
+ !file.startsWith(prefix) ||
+ file.includes("..") ||
+ !(await lstat(resolve(root, file))).isFile() ||
+ !(await realpath(resolve(root, file))).startsWith(base + "/")
+ )
+ throw Error();
+ } catch {
+ add(
+ "M002",
+ String(file),
+ "Source and explicit preview must be regular files within their canonical repository directories.",
+ );
+ }
+ }
+ }
+ for (const name of actualExports)
+ if (!names.has(name))
+ add("M001", "src/index.ts", `Add a manifest and preview for ${name}.`);
+ for (const c of manifests)
+ for (const id of c.contract?.dependencies || [])
+ if (!ids.has(id))
+ add(
+ "M003",
+ `manifests/${c.id}.json`,
+ `Unknown component dependency ${id}.`,
+ );
+ async function inspect(directory) {
+ for (const entry of await readdir(resolve(root, directory), {
+ withFileTypes: true,
+ })) {
+ const file = `${directory}/${entry.name}`;
+ if (entry.isSymbolicLink()) {
+ add("M002", file, "Symlinks cannot hide shared source.");
+ continue;
+ }
+ if (entry.isDirectory()) {
+ await inspect(file);
+ continue;
+ }
+ if (!/\.tsx?$/.test(file)) continue;
+ const source = await readFile(resolve(root, file), "utf8");
+ const lines = source.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ if (
+ /\b(?:fetch|XMLHttpRequest)\b|UI_TOKEN|@tony-ui\/control|ui\.tony\.codes|packages\/control/.test(
+ lines[i],
+ )
+ )
+ add(
+ "M004",
+ file,
+ "Shared UI must not depend on hosted governance, credentials or network requests.",
+ i + 1,
+ );
+ if (
+ /(?:bg|text|border|ring)-(?:indigo|blue|green|red|amber|violet|slate|gray)-\d{2,3}\b/.test(
+ lines[i],
+ )
+ )
+ add(
+ "M005",
+ file,
+ "Use semantic component tokens instead of a fixed Tailwind palette.",
+ i + 1,
+ );
+ }
+ }
+ }
+ await inspect("src");
+ const catalog = {
+ schemaVersion: 1,
+ package: pkg.name,
+ version: pkg.version,
+ policyVersion: policy.version,
+ components: manifests,
+ };
+ return {
+ passed: findings.length === 0,
+ policyVersion: policy.version,
+ findings,
+ catalog,
+ };
+}
+if (
+ process.argv[1] &&
+ import.meta.url === pathToFileURL(resolve(process.argv[1])).href
+) {
+ const report = await checkKit();
+ if (process.argv.includes("--write-catalog") && report.passed)
+ await writeFile(
+ resolve(ROOT, "catalog.json"),
+ JSON.stringify(report.catalog, null, 2) + "\n",
+ );
+ const { catalog, ...result } = report;
+ console.log(
+ process.argv.includes("--format")
+ ? JSON.stringify(result, null, 2)
+ : `${report.passed ? "PASS" : "FAIL"}: ${catalog.components.length} components; baseline ${report.policyVersion}\n${report.findings.map((f) => `${f.file}:${f.line} ${f.rule} ${f.message}`).join("\n")}`,
+ );
+ process.exitCode = report.passed ? 0 : 1;
+}
diff --git a/scripts/release.mjs b/scripts/release.mjs
new file mode 100644
index 0000000..e508814
--- /dev/null
+++ b/scripts/release.mjs
@@ -0,0 +1,46 @@
+// Run by a protected release job after maintainer review and exact-commit CI.
+import { readFile, mkdir, writeFile } from "node:fs/promises";
+import { execFileSync } from "node:child_process";
+import { resolve } from "node:path";
+import { createHash } from "node:crypto";
+import { checkKit, ROOT } from "./check.mjs";
+const report = await checkKit();
+if (!report.passed)
+ throw Error("Baseline checks failed; run npm run check:json.");
+if (
+ execFileSync("git", ["status", "--porcelain"], {
+ cwd: ROOT,
+ encoding: "utf8",
+ }).trim()
+)
+ throw Error("Release requires a clean checkout.");
+const commit = execFileSync("git", ["rev-parse", "HEAD"], {
+ cwd: ROOT,
+ encoding: "utf8",
+}).trim();
+const pkg = JSON.parse(await readFile(resolve(ROOT, "package.json"), "utf8"));
+const out = resolve(ROOT, "release", pkg.version);
+await mkdir(resolve(ROOT, "release"), { recursive: true });
+await mkdir(out, { recursive: false });
+const packed = JSON.parse(
+ execFileSync("npm", ["pack", "--json", "--pack-destination", out], {
+ cwd: ROOT,
+ encoding: "utf8",
+ }),
+)[0];
+const bytes = await readFile(resolve(out, packed.filename));
+const provenance = {
+ schemaVersion: 1,
+ package: pkg.name,
+ version: pkg.version,
+ commit,
+ policyVersion: report.policyVersion,
+ integrity: "sha512-" + createHash("sha512").update(bytes).digest("base64"),
+ filename: packed.filename,
+};
+await writeFile(
+ resolve(out, "provenance.json"),
+ JSON.stringify(provenance, null, 2) + "\n",
+ { flag: "wx" },
+);
+console.log(JSON.stringify(provenance, null, 2));
diff --git a/src/application.tsx b/src/application.tsx
new file mode 100644
index 0000000..913f606
--- /dev/null
+++ b/src/application.tsx
@@ -0,0 +1,418 @@
+import { useId, useRef, useEffect, useState, type ReactNode } from "react";
+import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
+import { Button, EmptyState, Notice, type Tone } from "./primitives.js";
+export interface NavigationItem {
+ label: string;
+ href: string;
+ current?: boolean;
+ count?: string;
+}
+export interface AppShellProps {
+ brand: string;
+ workspace?: string;
+ user?: ReactNode;
+ navigation: NavigationItem[];
+ secondaryNavigation?: { label: string; items: NavigationItem[] }[];
+ header?: ReactNode;
+ actions?: ReactNode;
+ children: ReactNode;
+}
+export function AppShell({
+ brand,
+ workspace,
+ user,
+ navigation,
+ secondaryNavigation = [],
+ header,
+ actions,
+ children,
+}: AppShellProps) {
+ const [open, setOpen] = useState(false);
+ const links = (items: NavigationItem[]) =>
+ items.map((item) => (
+ setOpen(false)}
+ >
+
+ {item.label}
+ {item.count && {item.count}}
+
+ ));
+ const content = (
+ <>
+
+
+
+ {brand}
+ {workspace && {workspace}}
+
+
+
+ {user && {user}
}
+ >
+ );
+ return (
+
+
+ Skip to content
+
+
+
+
+
+ );
+}
+export function PageHeading({
+ title,
+ description,
+ eyebrow,
+ actions,
+ breadcrumbs,
+ headingLevel = 1,
+}: {
+ title: string;
+ description?: string;
+ eyebrow?: string;
+ actions?: ReactNode;
+ breadcrumbs?: { label: string; href?: string }[];
+ headingLevel?: 1 | 2;
+}) {
+ const Heading = headingLevel === 1 ? "h1" : "h2";
+ return (
+
+ {breadcrumbs && (
+
+ )}
+
+
+ {eyebrow &&
{eyebrow}
}
+
{title}
+ {description &&
{description}
}
+
+ {actions &&
{actions}
}
+
+
+ );
+}
+export function MetricGroup({
+ label,
+ items,
+}: {
+ label: string;
+ items: { label: string; value: string; change?: string; tone?: Tone }[];
+}) {
+ return (
+
+ {items.map((item) => (
+
+
- {item.label}
+ -
+ {item.value}
+ {item.change && (
+
+ {item.change}
+
+ )}
+
+
+ ))}
+
+ );
+}
+export interface DataTableProps {
+ caption: string;
+ columns: {
+ key: string;
+ label: string;
+ render: (row: T) => ReactNode;
+ align?: "left" | "right";
+ }[];
+ rows: T[];
+ selectedIds?: string[];
+ onSelectionChange?: (ids: string[]) => void;
+ rowLabel: (row: T) => string;
+ toolbar?: ReactNode;
+ bulkActions?: ReactNode;
+ emptyTitle?: string;
+ emptyDescription?: string;
+ loading?: boolean;
+ error?: string;
+}
+function SelectAll({
+ checked,
+ partial,
+ onChange,
+}: {
+ checked: boolean;
+ partial: boolean;
+ onChange: () => void;
+}) {
+ const ref = useRef(null);
+ useEffect(() => {
+ if (ref.current) ref.current.indeterminate = partial;
+ }, [partial]);
+ return (
+
+ );
+}
+export function DataTable({
+ caption,
+ columns,
+ rows,
+ selectedIds = [],
+ onSelectionChange,
+ rowLabel,
+ toolbar,
+ bulkActions,
+ emptyTitle = "No records",
+ emptyDescription = "Records will appear here when they are available.",
+ loading,
+ error,
+}: DataTableProps) {
+ const selected = rows.filter((row) => selectedIds.includes(row.id));
+ return (
+
+ {(toolbar || selected.length > 0) && (
+
+ {selected.length > 0 ? (
+ <>
+
{selected.length} selected
+
+ {bulkActions}
+
+
+ >
+ ) : (
+ toolbar
+ )}
+
+ )}
+ {loading ? (
+
+ Loading records…
+
+ ) : error ? (
+
+ {error}
+
+ ) : !rows.length ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+export function Pagination({
+ page,
+ pageCount,
+ onChange,
+}: {
+ page: number;
+ pageCount: number;
+ onChange: (page: number) => void;
+}) {
+ return (
+
+ );
+}
+export function SettingsSection({
+ title,
+ description,
+ children,
+}: {
+ title: string;
+ description?: string;
+ children: ReactNode;
+}) {
+ const id = useId();
+ return (
+
+
+
{title}
+ {description &&
{description}
}
+
+ {children}
+
+ );
+}
+export function ActivityFeed({
+ label,
+ items,
+}: {
+ label: string;
+ items: {
+ id: string;
+ title: string;
+ description?: string;
+ time: string;
+ tone?: Tone;
+ }[];
+}) {
+ return (
+
+ {items.map((item) => (
+ -
+
+
+
{item.title}
+ {item.description &&
{item.description}
}
+
+ {item.time}
+
+ ))}
+
+ );
+}
diff --git a/src/components.css b/src/components.css
new file mode 100644
index 0000000..8ebd75c
--- /dev/null
+++ b/src/components.css
@@ -0,0 +1,1289 @@
+@layer components {
+ [data-mortar] body {
+ margin: 0;
+ background: var(--m-paper);
+ color: var(--m-ink);
+ font-family: var(--m-font);
+ font-size: 15px;
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+ }
+ [data-mortar] :where(button, input, select, textarea) {
+ font: inherit;
+ color: inherit;
+ }
+ [data-mortar]
+ :where(
+ button,
+ a,
+ input,
+ select,
+ textarea,
+ summary,
+ [tabindex]
+ ):focus-visible {
+ outline: 2px solid var(--m-focus);
+ outline-offset: 3px;
+ }
+ [data-mortar] a {
+ color: var(--m-link);
+ }
+ [data-mortar] :where(h1, h2, h3, p, figure, dl) {
+ margin: 0;
+ }
+ .m-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-height: var(--m-control);
+ padding: 4px var(--m-pad);
+ border: 1px solid transparent;
+ border-radius: var(--m-radius);
+ font-size: var(--m-font-size);
+ font-weight: 600;
+ line-height: 1.35;
+ text-decoration: none;
+ cursor: pointer;
+ }
+ .m-button-primary {
+ background: var(--m-primary);
+ color: var(--m-on-primary) !important;
+ border-color: var(--m-primary);
+ }
+ .m-button-primary:hover {
+ background: var(--m-primary-hover);
+ }
+ .m-button-primary:active {
+ background: var(--m-primary-active);
+ }
+ .m-button-secondary {
+ background: var(--m-surface);
+ color: var(--m-ink);
+ border-color: var(--m-line-strong);
+ }
+ .m-button-secondary:hover,
+ .m-button-ghost:hover {
+ background: var(--m-sunken);
+ }
+ .m-button-ghost {
+ background: transparent;
+ color: var(--m-secondary);
+ }
+ .m-button-danger {
+ background: transparent;
+ color: var(--m-danger);
+ border-color: var(--m-danger);
+ }
+ .m-button-danger:hover {
+ background: var(--m-sunken);
+ }
+ .m-button:active:not(:disabled) {
+ transform: translateY(1px);
+ }
+ .m-button:disabled {
+ background: var(--m-sunken);
+ color: var(--m-muted) !important;
+ border: 1px dashed var(--m-line-strong);
+ cursor: not-allowed;
+ }
+ .m-spinner {
+ width: 13px;
+ height: 13px;
+ border: 2px solid currentColor;
+ border-right-color: transparent;
+ border-radius: 50%;
+ animation: m-spin 0.8s linear infinite;
+ }
+ .m-field {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-width: 0;
+ }
+ .m-field > label,
+ .m-radio-group > legend {
+ font-size: 13px;
+ font-weight: 600;
+ }
+ .m-field p {
+ font-size: 12px;
+ color: var(--m-muted);
+ }
+ .m-input-group {
+ display: flex;
+ align-items: stretch;
+ border: 1px solid var(--m-line-strong);
+ border-radius: var(--m-radius);
+ background: var(--m-surface);
+ overflow: hidden;
+ }
+ .m-input-group:focus-within {
+ outline: 2px solid var(--m-focus);
+ outline-offset: 2px;
+ }
+ .m-input-group input {
+ min-width: 0;
+ width: 100%;
+ min-height: var(--m-control);
+ padding: 4px var(--m-pad);
+ border: 0;
+ background: transparent;
+ font-size: var(--m-font-size);
+ outline: none;
+ }
+ .m-input-group input:focus-visible {
+ outline: none;
+ }
+ .m-input-group > span {
+ display: flex;
+ align-items: center;
+ background: var(--m-sunken);
+ padding: 0 10px;
+ color: var(--m-muted);
+ font: 12px var(--m-mono);
+ }
+ .m-input-group[data-invalid],
+ .m-field [aria-invalid="true"] {
+ border-color: var(--m-danger);
+ }
+ .m-field select,
+ .m-field textarea {
+ min-height: var(--m-control);
+ padding: 6px var(--m-pad);
+ border: 1px solid var(--m-line-strong);
+ border-radius: var(--m-radius);
+ background: var(--m-surface);
+ font-size: var(--m-font-size);
+ }
+ .m-select-control {
+ position: relative;
+ display: grid;
+ min-width: 0;
+ }
+ .m-select-control select {
+ appearance: none;
+ width: 100%;
+ min-width: 0;
+ line-height: 1.4;
+ }
+ .m-select-control:not([data-listbox]) select {
+ height: var(--m-control);
+ padding-block: 0;
+ padding-inline-end: calc(var(--m-pad) + 24px);
+ cursor: pointer;
+ }
+ .m-select-control select:hover:not(:disabled):not([aria-invalid="true"]) {
+ border-color: var(--m-secondary);
+ }
+ .m-select-control svg {
+ position: absolute;
+ inset-inline-end: var(--m-pad);
+ top: 50%;
+ width: 16px;
+ height: 16px;
+ transform: translateY(-50%);
+ color: var(--m-secondary);
+ pointer-events: none;
+ }
+ .m-select-control:has(select:disabled) svg {
+ color: var(--m-muted);
+ }
+ @media (forced-colors: active) {
+ .m-select-control select {
+ appearance: auto;
+ }
+ .m-select-control svg {
+ display: none;
+ }
+ }
+ .m-field textarea {
+ min-height: 110px;
+ resize: vertical;
+ }
+ .m-field :disabled {
+ color: var(--m-muted);
+ background: var(--m-sunken);
+ cursor: not-allowed;
+ }
+ .m-error,
+ .m-field .m-error,
+ .m-tone-danger {
+ color: var(--m-danger);
+ }
+ .m-tone-neutral {
+ color: var(--m-secondary);
+ }
+ .m-tone-success {
+ color: var(--m-success);
+ }
+ .m-tone-warning {
+ color: var(--m-warning);
+ }
+ .m-choice {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-height: var(--m-control);
+ font-size: var(--m-font-size);
+ cursor: pointer;
+ }
+ [data-mortar] input:is([type="checkbox"], [type="radio"]) {
+ width: 18px;
+ height: 18px;
+ flex-shrink: 0;
+ accent-color: var(--m-primary);
+ cursor: pointer;
+ }
+ [data-mortar] input.m-switch {
+ appearance: none;
+ width: 34px;
+ height: 20px;
+ border: 1px solid var(--m-line-strong);
+ border-radius: 20px;
+ background: var(--m-sunken);
+ position: relative;
+ }
+ .m-switch::after {
+ content: "";
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ top: 2px;
+ left: 2px;
+ border-radius: 50%;
+ background: var(--m-muted);
+ transition: transform 0.15s;
+ }
+ [data-mortar] input.m-switch:checked {
+ background: var(--m-primary);
+ border-color: var(--m-primary);
+ }
+ .m-switch:checked::after {
+ transform: translateX(14px);
+ background: var(--m-on-primary);
+ }
+ .m-radio-group {
+ border: 0;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ gap: 10px;
+ }
+ .m-radio-group legend {
+ margin-bottom: 10px;
+ }
+ .m-radio-card {
+ display: flex;
+ align-items: start;
+ gap: 12px;
+ border: 1px solid var(--m-line);
+ padding: 16px;
+ border-radius: var(--m-radius);
+ cursor: pointer;
+ }
+ .m-radio-card:has(:checked) {
+ background: var(--m-selected);
+ border-color: var(--m-primary);
+ }
+ .m-radio-card strong,
+ .m-radio-card span > span {
+ display: block;
+ font-size: 13px;
+ }
+ .m-radio-card span > span {
+ color: var(--m-muted);
+ margin-top: 4px;
+ }
+ .m-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 8px;
+ gap: 6px;
+ font-size: 12px;
+ font-weight: 600;
+ border: 1px solid var(--m-line);
+ border-radius: 20px;
+ }
+ .m-badge::before {
+ content: "";
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: currentColor;
+ }
+ .m-segmented {
+ display: inline-flex;
+ width: fit-content;
+ background: var(--m-sunken);
+ padding: 3px;
+ gap: 2px;
+ border-radius: var(--m-radius);
+ }
+ .m-segmented button {
+ border: 1px solid transparent;
+ background: transparent;
+ color: var(--m-muted);
+ font-size: 12px;
+ font-weight: 500;
+ border-radius: 6px;
+ padding: 5px 10px;
+ cursor: pointer;
+ min-height: 28px;
+ }
+ .m-segmented [aria-pressed="true"] {
+ background: var(--m-surface);
+ color: var(--m-ink);
+ border-color: var(--m-line);
+ }
+ .m-tabs {
+ display: flex;
+ gap: 24px;
+ border-bottom: 1px solid var(--m-line);
+ overflow-x: auto;
+ }
+ .m-tabs button {
+ border: 0;
+ border-bottom: 2px solid transparent;
+ background: none;
+ color: var(--m-muted);
+ font-size: 14px;
+ font-weight: 600;
+ padding: 12px 0;
+ white-space: nowrap;
+ cursor: pointer;
+ }
+ .m-tabs [aria-selected="true"] {
+ color: var(--m-link);
+ border-bottom-color: var(--m-link);
+ }
+ [role="tabpanel"]:not([hidden]) {
+ padding-top: 24px;
+ }
+ .m-notice {
+ border: 1px solid var(--m-line);
+ border-radius: var(--m-radius);
+ padding: 16px;
+ font-size: 14px;
+ }
+ .m-notice > div {
+ margin-top: 6px;
+ }
+ .m-empty {
+ text-align: center;
+ padding: 64px 24px;
+ }
+ .m-empty h3 {
+ font-size: 18px;
+ font-weight: 600;
+ }
+ .m-empty p {
+ color: var(--m-muted);
+ margin: 10px 0 20px;
+ }
+ .m-dialog {
+ position: fixed;
+ inset: 0;
+ z-index: 100;
+ }
+ .m-backdrop {
+ position: fixed;
+ inset: 0;
+ background: rgb(0 0 0 / 0.5);
+ }
+ .m-dialog-position {
+ position: fixed;
+ inset: 0;
+ overflow-y: auto;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ padding: 24px;
+ }
+ .m-dialog-panel {
+ margin-block: auto;
+ flex-shrink: 0;
+ width: 100%;
+ max-width: 520px;
+ border: 1px solid var(--m-line);
+ border-radius: var(--m-radius-lg);
+ background: var(--m-surface);
+ padding: 24px;
+ box-shadow: var(--m-float);
+ }
+ .m-dialog-heading {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 20px;
+ }
+ .m-dialog-heading h2 {
+ font-size: 20px;
+ font-weight: 600;
+ }
+ .m-dialog-panel > .m-actions {
+ margin-top: 24px;
+ justify-content: flex-end;
+ }
+ .m-menu {
+ z-index: 110;
+ min-width: 200px;
+ border: 1px solid var(--m-line);
+ padding: 6px;
+ border-radius: var(--m-radius);
+ background: var(--m-surface);
+ box-shadow: var(--m-float);
+ --anchor-gap: 6px;
+ }
+ .m-menu button {
+ display: block;
+ width: 100%;
+ text-align: left;
+ border: 0;
+ background: none;
+ padding: 8px 10px;
+ border-radius: 4px;
+ font-size: 14px;
+ cursor: pointer;
+ }
+ .m-menu button[data-focus] {
+ background: var(--m-selected);
+ }
+ .m-menu button[data-disabled] {
+ color: var(--m-muted);
+ cursor: not-allowed;
+ }
+ .m-actions {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+ }
+ .m-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ }
+ .m-sr-only,
+ .m-skip {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ }
+ .m-skip:focus {
+ width: auto;
+ height: auto;
+ padding: 12px;
+ top: 8px;
+ left: 8px;
+ clip-path: none;
+ z-index: 120;
+ background: var(--m-surface);
+ }
+ .m-app-shell {
+ min-height: 100vh;
+ background: var(--m-paper);
+ }
+ .m-sidebar {
+ position: fixed;
+ inset: 0 auto 0 0;
+ width: 240px;
+ padding: 12px;
+ border-right: 1px solid var(--m-line);
+ background: var(--m-sunken);
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ z-index: 30;
+ }
+ .m-workspace {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px;
+ border: 1px solid var(--m-line);
+ border-radius: 10px;
+ background: var(--m-surface);
+ text-decoration: none;
+ }
+ .m-workspace strong {
+ font-size: 14px;
+ display: block;
+ }
+ .m-workspace small {
+ display: block;
+ color: var(--m-muted);
+ font-size: 11px;
+ }
+ .m-mark {
+ display: inline-block;
+ width: 22px;
+ height: 22px;
+ flex-shrink: 0;
+ border-radius: 5px;
+ background: var(--m-primary);
+ }
+ :is(.m-sidebar, .m-mobile-sidebar) nav {
+ margin-top: 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ }
+ :is(.m-sidebar, .m-mobile-sidebar) nav a {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 6px 10px;
+ min-height: var(--m-control);
+ border-radius: var(--m-radius);
+ text-decoration: none;
+ font-size: var(--m-font-size);
+ font-weight: 500;
+ color: var(--m-secondary);
+ }
+ :is(.m-sidebar, .m-mobile-sidebar) nav a:hover {
+ background: var(--m-surface);
+ }
+ :is(.m-sidebar, .m-mobile-sidebar) nav a[aria-current="page"] {
+ background: var(--m-surface);
+ color: var(--m-link);
+ font-weight: 600;
+ }
+ .m-nav-marker {
+ width: 6px;
+ height: 6px;
+ border-radius: 2px;
+ background: var(--m-line-strong);
+ }
+ [aria-current="page"] .m-nav-marker {
+ background: currentColor;
+ }
+ .m-nav-count {
+ margin-left: auto;
+ font: 11px var(--m-mono);
+ color: var(--m-muted);
+ }
+ :is(.m-sidebar, .m-mobile-sidebar) nav h2 {
+ margin: 24px 10px 6px;
+ text-transform: uppercase;
+ font-size: 11px;
+ letter-spacing: 0.04em;
+ color: var(--m-muted);
+ }
+ .m-user {
+ margin-top: auto;
+ padding: 16px 8px 4px;
+ font-size: 13px;
+ }
+ .m-app-main {
+ margin-left: 240px;
+ min-width: 0;
+ }
+ .m-app-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ min-height: 56px;
+ padding: 10px 28px;
+ border-bottom: 1px solid var(--m-line);
+ background: var(--m-surface);
+ }
+ .m-header-context {
+ flex: 1;
+ min-width: 0;
+ color: var(--m-muted);
+ font-size: 13px;
+ }
+ .m-app-main > main {
+ padding: 28px;
+ }
+ .m-menu-trigger {
+ display: none;
+ }
+ .m-mobile-sidebar {
+ position: fixed;
+ inset: 0 auto 0 0;
+ width: min(320px, 90vw);
+ padding: 16px;
+ background: var(--m-sunken);
+ overflow-y: auto;
+ }
+ .m-page-heading :is(h1, h2) {
+ font-size: 28px;
+ letter-spacing: -0.025em;
+ line-height: 1.2;
+ font-weight: 600;
+ }
+ .m-page-heading p {
+ margin-top: 6px;
+ color: var(--m-muted);
+ font-size: 14px;
+ }
+ .m-heading-row {
+ display: flex;
+ align-items: end;
+ gap: 20px;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ }
+ .m-page-heading nav ol {
+ display: flex;
+ gap: 8px;
+ margin: 0 0 12px;
+ padding: 0;
+ list-style: none;
+ font-size: 13px;
+ color: var(--m-muted);
+ }
+ .m-page-heading nav li:not(:first-child)::before {
+ content: "/";
+ margin-right: 8px;
+ }
+ .m-eyebrow {
+ font: 12px var(--m-mono);
+ color: var(--m-muted);
+ margin-bottom: 16px !important;
+ }
+ .m-metrics {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ border: 1px solid var(--m-line);
+ border-radius: var(--m-radius-lg);
+ background: var(--m-surface);
+ overflow: hidden;
+ }
+ .m-metrics > div {
+ padding: 18px;
+ border-right: 1px solid var(--m-line);
+ }
+ .m-metrics > div:last-child {
+ border-right: 0;
+ }
+ .m-metrics dt {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--m-muted);
+ }
+ .m-metrics dd {
+ margin: 10px 0 0;
+ font-size: 26px;
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ font-variant-numeric: tabular-nums;
+ }
+ .m-metrics span {
+ display: block;
+ margin-top: 4px;
+ font: 12px var(--m-mono);
+ }
+ .m-data-table {
+ border: 1px solid var(--m-line);
+ border-radius: var(--m-radius-lg);
+ background: var(--m-surface);
+ overflow: hidden;
+ min-width: 0;
+ }
+ .m-table-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 12px;
+ padding: 12px;
+ border-bottom: 1px solid var(--m-line);
+ font-size: 13px;
+ }
+ .m-table-scroll {
+ overflow-x: auto;
+ }
+ .m-data-table table {
+ border-collapse: collapse;
+ width: 100%;
+ font-size: var(--m-font-size);
+ text-align: left;
+ }
+ .m-data-table :is(th, td) {
+ padding: var(--m-row-pad);
+ border-bottom: 1px solid var(--m-line);
+ white-space: nowrap;
+ }
+ .m-data-table thead th {
+ color: var(--m-muted);
+ font-size: 12px;
+ font-weight: 600;
+ background: var(--m-paper);
+ }
+ .m-data-table tbody th {
+ font-weight: 600;
+ }
+ .m-data-table tr:last-child :is(th, td) {
+ border-bottom: 0;
+ }
+ .m-data-table [data-align="right"] {
+ text-align: right;
+ font-variant-numeric: tabular-nums;
+ }
+ .m-data-table [data-selected] {
+ background: var(--m-selected);
+ }
+ .m-table-state {
+ padding: 32px;
+ color: var(--m-muted);
+ }
+ .m-pagination {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+ font-size: 13px;
+ color: var(--m-muted);
+ flex-wrap: wrap;
+ }
+ .m-settings-section {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
+ gap: 40px;
+ border-bottom: 1px solid var(--m-line);
+ padding: 28px 0;
+ }
+ .m-settings-section h2 {
+ font-size: 16px;
+ font-weight: 600;
+ }
+ .m-settings-section p {
+ margin-top: 6px;
+ font-size: 13px;
+ color: var(--m-muted);
+ }
+ .m-activity {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ }
+ .m-activity li {
+ display: flex;
+ align-items: start;
+ gap: 12px;
+ padding: 16px 0;
+ border-bottom: 1px solid var(--m-line);
+ font-size: 13px;
+ }
+ .m-activity li > div {
+ flex: 1;
+ }
+ .m-activity p,
+ .m-activity li > span:last-child {
+ color: var(--m-muted);
+ }
+ .m-activity-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: currentColor;
+ margin-top: 6px;
+ flex-shrink: 0;
+ }
+ .m-marketing-header {
+ max-width: 1280px;
+ margin: auto;
+ padding: 18px 32px;
+ display: flex;
+ align-items: center;
+ gap: 28px;
+ border-bottom: 1px solid var(--m-line);
+ }
+ .m-brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ font-size: 17px;
+ font-weight: 700;
+ letter-spacing: -0.02em;
+ }
+ .m-marketing-header nav {
+ display: flex;
+ gap: 22px;
+ flex: 1;
+ font-size: 13px;
+ }
+ .m-marketing-header nav a {
+ text-decoration: none;
+ color: var(--m-secondary);
+ }
+ .m-marketing-header > .m-actions {
+ margin-left: auto;
+ }
+ .m-hero {
+ padding: 100px 32px 80px;
+ max-width: 1280px;
+ margin: auto;
+ }
+ .m-hero-center {
+ text-align: center;
+ }
+ .m-hero :is(h1, h2) {
+ font-size: clamp(42px, 6vw, 80px);
+ font-weight: 600;
+ letter-spacing: -0.045em;
+ line-height: 1.02;
+ text-wrap: balance;
+ max-width: 960px;
+ margin: auto;
+ }
+ .m-hero-description {
+ color: var(--m-secondary);
+ font-size: 18px;
+ max-width: 660px;
+ margin: 28px auto !important;
+ text-wrap: pretty;
+ }
+ .m-hero-center .m-actions {
+ justify-content: center;
+ }
+ .m-hero-split {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 56px;
+ align-items: center;
+ }
+ .m-hero-split :is(h1, h2) {
+ font-size: clamp(40px, 5vw, 64px);
+ }
+ .m-hero-preview {
+ margin-top: 48px;
+ text-align: left;
+ min-width: 0;
+ }
+ .m-hero-split .m-hero-preview {
+ margin-top: 0;
+ }
+ .m-marketing-section,
+ .m-feature-section,
+ .m-faq {
+ max-width: 1280px;
+ margin: auto;
+ padding: 80px 32px;
+ border-top: 1px solid var(--m-line);
+ }
+ :is(.m-marketing-section, .m-feature-section, .m-faq, .m-cta) h2 {
+ font-size: clamp(30px, 3.5vw, 48px);
+ font-weight: 600;
+ letter-spacing: -0.035em;
+ line-height: 1.1;
+ text-wrap: balance;
+ max-width: 800px;
+ }
+ .m-section-description {
+ color: var(--m-secondary);
+ font-size: 16px;
+ margin-top: 18px !important;
+ max-width: 640px;
+ }
+ .m-feature-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 48px 40px;
+ margin-top: 48px;
+ }
+ .m-feature-icon {
+ display: grid;
+ place-items: center;
+ width: 36px;
+ height: 36px;
+ border: 1px solid var(--m-line);
+ border-radius: 8px;
+ font: 12px var(--m-mono);
+ margin-bottom: 16px;
+ }
+ .m-feature-grid h3 {
+ font-size: 16px;
+ font-weight: 600;
+ }
+ .m-feature-grid p,
+ .m-feature-section p {
+ color: var(--m-secondary);
+ font-size: 15px;
+ margin-top: 10px;
+ }
+ .m-feature-section {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
+ gap: 56px;
+ align-items: center;
+ }
+ .m-feature-section[data-reverse="true"] > div:first-child {
+ order: 2;
+ }
+ .m-feature-preview {
+ border: 1px solid var(--m-line);
+ border-radius: 12px;
+ padding: 28px;
+ background: var(--m-sunken);
+ min-width: 0;
+ }
+ .m-testimonial {
+ padding: 64px 32px;
+ text-align: center;
+ max-width: 960px;
+ margin: auto;
+ }
+ .m-testimonial blockquote {
+ font-size: clamp(24px, 3vw, 36px);
+ line-height: 1.3;
+ letter-spacing: -0.02em;
+ margin: 0 0 28px;
+ }
+ .m-testimonial figcaption {
+ display: flex;
+ flex-direction: column;
+ font-size: 14px;
+ }
+ .m-testimonial figcaption span {
+ color: var(--m-muted);
+ }
+ .m-logo-cloud {
+ padding: 40px 32px;
+ text-align: center;
+ color: var(--m-muted);
+ }
+ .m-logo-cloud > p {
+ font-size: 13px;
+ }
+ .m-logo-cloud ul {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 48px;
+ list-style: none;
+ padding: 24px 0;
+ margin: 0;
+ font-size: 22px;
+ font-weight: 600;
+ }
+ .m-logo-cloud img {
+ max-height: 32px;
+ max-width: 140px;
+ }
+ .m-pricing > .m-segmented {
+ margin-top: 28px;
+ }
+ .m-pricing-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
+ gap: 18px;
+ margin-top: 36px;
+ }
+ .m-pricing-grid article {
+ border: 1px solid var(--m-line);
+ border-radius: 12px;
+ padding: 28px;
+ display: flex;
+ flex-direction: column;
+ }
+ .m-pricing-grid article[data-featured="true"] {
+ background: var(--m-ink);
+ color: var(--m-paper);
+ }
+ .m-pricing-grid h3 {
+ font-size: 18px;
+ font-weight: 600;
+ }
+ .m-pricing-grid p {
+ font-size: 14px;
+ margin-top: 6px;
+ }
+ .m-price {
+ margin: 28px 0;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+ .m-price strong {
+ font-size: 44px;
+ line-height: 1;
+ letter-spacing: -0.04em;
+ font-weight: 600;
+ }
+ .m-price span {
+ font-size: 13px;
+ }
+ .m-pricing-grid ul {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 28px;
+ flex: 1;
+ }
+ .m-pricing-grid li {
+ font-size: 14px;
+ margin: 10px 0;
+ }
+ .m-pricing-grid li::before {
+ content: "—";
+ opacity: 0.6;
+ margin-right: 10px;
+ }
+ .m-faq {
+ display: grid;
+ grid-template-columns: 1fr 2fr;
+ gap: 56px;
+ }
+ .m-faq > div > div {
+ color: var(--m-secondary);
+ margin-top: 14px;
+ }
+ .m-faq details {
+ border-bottom: 1px solid var(--m-line);
+ }
+ .m-faq details:first-child {
+ border-top: 1px solid var(--m-line);
+ }
+ .m-faq summary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20px;
+ font-size: 17px;
+ font-weight: 600;
+ padding: 20px 0;
+ cursor: pointer;
+ list-style: none;
+ }
+ .m-faq summary::-webkit-details-marker {
+ display: none;
+ }
+ .m-faq summary span {
+ font-size: 22px;
+ color: var(--m-muted);
+ }
+ .m-faq [open] summary span {
+ transform: rotate(45deg);
+ }
+ .m-faq details > div {
+ color: var(--m-secondary);
+ padding: 0 0 22px;
+ font-size: 15px;
+ }
+ .m-cta {
+ text-align: center;
+ background: var(--m-ink);
+ color: var(--m-paper);
+ padding: 96px 32px;
+ }
+ .m-cta h2 {
+ margin: auto;
+ }
+ .m-cta p {
+ margin: 20px auto;
+ max-width: 640px;
+ }
+ .m-cta .m-actions {
+ justify-content: center;
+ margin-top: 32px;
+ }
+ .m-cta .m-button-primary {
+ background: var(--m-paper);
+ color: var(--m-ink) !important;
+ border-color: var(--m-paper);
+ }
+ .m-cta .m-button-secondary {
+ background: transparent;
+ color: var(--m-paper);
+ border-color: var(--m-paper);
+ }
+ .m-footer {
+ max-width: 1280px;
+ padding: 56px 32px 32px;
+ margin: auto;
+ }
+ .m-footer-grid {
+ display: grid;
+ grid-template-columns: 1.5fr repeat(4, 1fr);
+ gap: 32px;
+ }
+ .m-footer-grid p {
+ color: var(--m-muted);
+ margin-top: 10px;
+ font-size: 14px;
+ max-width: 260px;
+ }
+ .m-footer-grid h2 {
+ font-size: 13px;
+ font-weight: 600;
+ margin-bottom: 12px;
+ }
+ .m-footer-grid nav a {
+ display: block;
+ font-size: 14px;
+ color: var(--m-muted);
+ text-decoration: none;
+ margin-top: 8px;
+ }
+ .m-footer-bottom {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 16px;
+ border-top: 1px solid var(--m-line);
+ margin-top: 56px;
+ padding-top: 20px;
+ color: var(--m-muted);
+ font-size: 13px;
+ }
+ .m-footer-bottom > div {
+ display: flex;
+ gap: 20px;
+ }
+ @media (max-width: 900px) {
+ .m-sidebar {
+ display: none;
+ }
+ .m-app-main {
+ margin-left: 0;
+ }
+ .m-menu-trigger {
+ display: inline-flex;
+ }
+ .m-app-header {
+ flex-wrap: wrap;
+ padding: 12px 20px;
+ }
+ .m-app-main > main {
+ padding: 24px 20px;
+ }
+ .m-marketing-header {
+ flex-wrap: wrap;
+ padding: 16px 20px;
+ gap: 16px;
+ }
+ .m-marketing-header .m-menu-trigger {
+ margin-left: auto;
+ }
+ .m-marketing-header > .m-actions {
+ margin-left: 0;
+ }
+ .m-marketing-header nav {
+ display: none;
+ order: 4;
+ flex-basis: 100%;
+ flex-direction: column;
+ }
+ .m-marketing-header nav[data-open="true"] {
+ display: flex;
+ }
+ .m-hero-split,
+ .m-feature-section,
+ .m-faq {
+ grid-template-columns: minmax(0, 1fr);
+ gap: 32px;
+ }
+ .m-feature-section[data-reverse="true"] > div:first-child {
+ order: 0;
+ }
+ .m-feature-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 32px;
+ }
+ .m-footer-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ .m-footer-grid > div {
+ grid-column: 1/-1;
+ }
+ }
+ @media (max-width: 540px) {
+ .m-input-group input,
+ .m-field select,
+ .m-field textarea {
+ font-size: 16px;
+ }
+ .m-hero,
+ .m-marketing-section,
+ .m-feature-section,
+ .m-faq {
+ padding: 48px 20px;
+ }
+ .m-hero-description {
+ font-size: 16px;
+ }
+ .m-feature-grid {
+ grid-template-columns: 1fr;
+ }
+ .m-settings-section {
+ grid-template-columns: 1fr;
+ gap: 20px;
+ }
+ .m-pricing-grid article {
+ padding: 24px;
+ }
+ .m-metrics {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ .m-metrics > div {
+ border-bottom: 1px solid var(--m-line);
+ }
+ .m-metrics dd {
+ font-size: 24px;
+ }
+ }
+ @media (prefers-reduced-motion: reduce) {
+ .m-spinner {
+ animation: none;
+ }
+ .m-switch::after {
+ transition: none;
+ }
+ }
+ @media (pointer: coarse) {
+ .m-segmented button,
+ .m-tabs button {
+ min-height: 44px;
+ }
+ }
+}
+@keyframes m-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+@layer components {
+ .m-code-block {
+ min-width: 0;
+ margin: 0;
+ }
+ .m-code-block figcaption {
+ margin-bottom: 0.5rem;
+ color: var(--m-muted);
+ font-size: 0.875rem;
+ }
+ .m-code-block pre {
+ overflow: auto;
+ max-height: 32rem;
+ padding: 1rem;
+ border: 1px solid var(--m-line);
+ border-radius: 0.5rem;
+ background: var(--m-sunken);
+ font-size: 0.875rem;
+ line-height: 1.6;
+ }
+ .m-preview-frame {
+ display: grid;
+ min-width: 0;
+ gap: 0.75rem;
+ }
+ .m-preview-frame > a {
+ justify-self: end;
+ text-decoration: underline;
+ }
+ .m-preview-frame iframe {
+ width: 100%;
+ height: 36rem;
+ border: 1px solid var(--m-line);
+ border-radius: 0.5rem;
+ background: var(--m-paper);
+ }
+}
diff --git a/src/foundation.tsx b/src/foundation.tsx
new file mode 100644
index 0000000..4c9afbd
--- /dev/null
+++ b/src/foundation.tsx
@@ -0,0 +1,99 @@
+import {
+ createContext,
+ useContext,
+ useEffect,
+ useState,
+ type ReactNode,
+} from "react";
+
+export type Appearance = "light" | "dark" | "system";
+export type Brand = "neutral" | "green";
+export type Density = "compact" | "comfortable" | "spacious";
+type Theme = {
+ appearance: Appearance;
+ setAppearance: (value: Appearance) => void;
+ brand: Brand;
+ density: Density;
+};
+const ThemeContext = createContext(null);
+export interface ThemeProviderProps {
+ children: ReactNode;
+ initialAppearance?: Appearance;
+ brand?: Brand;
+ density?: Density;
+}
+/** One provider per application. Selection never requires a hosted account. */
+export function ThemeProvider({
+ children,
+ initialAppearance = "system",
+ brand = "neutral",
+ density = "comfortable",
+}: ThemeProviderProps) {
+ const [appearance, setAppearance] = useState(initialAppearance);
+ useEffect(() => {
+ const element = document.documentElement;
+ const attributes = {
+ "data-mortar": "",
+ "data-mortar-brand": brand,
+ "data-mortar-density": density,
+ };
+ const previous = Object.keys(attributes).map(
+ (key) => [key, element.getAttribute(key)] as const,
+ );
+ for (const [key, value] of Object.entries(attributes))
+ element.setAttribute(key, value);
+ return () => {
+ for (const [key, value] of previous)
+ value === null
+ ? element.removeAttribute(key)
+ : element.setAttribute(key, value);
+ };
+ }, [brand, density]);
+ useEffect(() => {
+ const element = document.documentElement;
+ const previous = element.getAttribute("data-mortar-appearance");
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
+ const update = () =>
+ element.setAttribute(
+ "data-mortar-appearance",
+ appearance === "system"
+ ? media.matches
+ ? "dark"
+ : "light"
+ : appearance,
+ );
+ update();
+ media.addEventListener("change", update);
+ return () => {
+ media.removeEventListener("change", update);
+ previous === null
+ ? element.removeAttribute("data-mortar-appearance")
+ : element.setAttribute("data-mortar-appearance", previous);
+ };
+ }, [appearance]);
+ return (
+
+ {children}
+
+ );
+}
+export function AppearanceControl() {
+ const context = useContext(ThemeContext);
+ if (!context) throw new Error("AppearanceControl requires ThemeProvider");
+ return (
+
+ {(["light", "dark", "system"] as const).map((value) => (
+
+ ))}
+
+ );
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..19c0e6a
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,57 @@
+export { ThemeProvider, AppearanceControl } from "./foundation.js";
+export type {
+ ThemeProviderProps,
+ Appearance,
+ Brand,
+ Density,
+} from "./foundation.js";
+export {
+ Button,
+ ActionLink,
+ Input,
+ Select,
+ TextArea,
+ Checkbox,
+ RadioGroup,
+ Switch,
+ Badge,
+ Tabs,
+ Modal,
+ Dropdown,
+ Notice,
+ EmptyState,
+} from "./primitives.js";
+export type {
+ ButtonProps,
+ InputProps,
+ LinkAction,
+ Tone,
+} from "./primitives.js";
+export {
+ AppShell,
+ PageHeading,
+ MetricGroup,
+ DataTable,
+ Pagination,
+ SettingsSection,
+ ActivityFeed,
+} from "./application.js";
+export type {
+ AppShellProps,
+ NavigationItem,
+ DataTableProps,
+} from "./application.js";
+export {
+ MarketingHeader,
+ Hero,
+ FeatureGrid,
+ FeatureSection,
+ Testimonial,
+ LogoCloud,
+ Pricing,
+ FAQ,
+ CallToAction,
+ Footer,
+} from "./marketing.js";
+export type { PricingPlan } from "./marketing.js";
+export { SegmentedControl, CodeBlock, PreviewFrame } from "./inspection.js";
diff --git a/src/inspection.tsx b/src/inspection.tsx
new file mode 100644
index 0000000..9fdd390
--- /dev/null
+++ b/src/inspection.tsx
@@ -0,0 +1,59 @@
+import { useId } from "react";
+export function SegmentedControl({
+ label,
+ value,
+ options,
+ onChange,
+}: {
+ label: string;
+ value: string;
+ options: { value: string; label: string }[];
+ onChange: (value: string) => void;
+}) {
+ return (
+
+ {options.map((option) => (
+
+ ))}
+
+ );
+}
+export function CodeBlock({
+ code,
+ label = "Code",
+}: {
+ code: string;
+ label?: string;
+}) {
+ const id = useId();
+ return (
+
+ {label}
+
+ {code}
+
+
+ );
+}
+export function PreviewFrame({ title, src }: { title: string; src: string }) {
+ return (
+
+ );
+}
diff --git a/src/marketing.tsx b/src/marketing.tsx
new file mode 100644
index 0000000..cbaf3b0
--- /dev/null
+++ b/src/marketing.tsx
@@ -0,0 +1,342 @@
+import { useState, type ReactNode } from "react";
+import { ActionLink, Button, type LinkAction } from "./primitives.js";
+export function MarketingHeader({
+ brand,
+ homeHref = "/",
+ links,
+ action,
+ controls,
+}: {
+ brand: string;
+ homeHref?: string;
+ links: LinkAction[];
+ action?: LinkAction;
+ controls?: ReactNode;
+}) {
+ const [open, setOpen] = useState(false);
+ return (
+
+ );
+}
+export function Hero({
+ eyebrow,
+ title,
+ description,
+ actions = [],
+ children,
+ align = "center",
+ headingLevel = 1,
+}: {
+ eyebrow?: string;
+ title: string;
+ description: string;
+ actions?: LinkAction[];
+ children?: ReactNode;
+ align?: "center" | "split";
+ headingLevel?: 1 | 2;
+}) {
+ const Heading = headingLevel === 1 ? "h1" : "h2";
+ return (
+
+
+ {eyebrow &&
{eyebrow}
}
+
{title}
+
{description}
+
+ {actions.map((action, i) => (
+
+ ))}
+
+
+ {children && {children}
}
+
+ );
+}
+export function FeatureGrid({
+ eyebrow,
+ title,
+ description,
+ items,
+}: {
+ eyebrow?: string;
+ title: string;
+ description?: string;
+ items: { title: string; description: string; icon?: ReactNode }[];
+}) {
+ return (
+
+ {eyebrow && {eyebrow}
}
+ {title}
+ {description && {description}
}
+
+ {items.map((item, i) => (
+
+
+ {item.icon || String(i + 1).padStart(2, "0")}
+
+ {item.title}
+ {item.description}
+
+ ))}
+
+
+ );
+}
+export function FeatureSection({
+ title,
+ description,
+ children,
+ reverse = false,
+}: {
+ title: string;
+ description: string;
+ children: ReactNode;
+ reverse?: boolean;
+}) {
+ return (
+
+
+
{title}
+
{description}
+
+ {children}
+
+ );
+}
+export function Testimonial({
+ quote,
+ name,
+ role,
+}: {
+ quote: string;
+ name: string;
+ role?: string;
+}) {
+ return (
+
+ {quote}
+
+ {name}
+ {role && {role}}
+
+
+ );
+}
+export function LogoCloud({
+ label,
+ items,
+}: {
+ label: string;
+ items: { name: string; image?: string }[];
+}) {
+ return (
+
+ {label}
+
+ {items.map((item) => (
+ -
+ {item.image ? (
+
+ ) : (
+ {item.name}
+ )}
+
+ ))}
+
+
+ );
+}
+export interface PricingPlan {
+ id: string;
+ name: string;
+ description: string;
+ monthly: string;
+ annual?: string;
+ unit?: string;
+ features: string[];
+ action: LinkAction;
+ featured?: boolean;
+}
+export function Pricing({
+ title,
+ description,
+ plans,
+}: {
+ title: string;
+ description?: string;
+ plans: PricingPlan[];
+}) {
+ const [annual, setAnnual] = useState(true),
+ toggle = plans.some((p) => p.annual);
+ return (
+
+ {title}
+ {description && {description}
}
+ {toggle && (
+
+
+
+
+ )}
+
+ {plans.map((plan) => (
+
+ {plan.name}
+ {plan.description}
+
+
+ {annual && plan.annual ? plan.annual : plan.monthly}
+
+ {plan.unit && {plan.unit}}
+
+
+ {plan.features.map((feature) => (
+ - {feature}
+ ))}
+
+
+
+ ))}
+
+
+ );
+}
+export function FAQ({
+ title,
+ description,
+ items,
+}: {
+ title: string;
+ description?: ReactNode;
+ items: { question: string; answer: ReactNode }[];
+}) {
+ return (
+
+
+
{title}
+ {description &&
{description}
}
+
+
+ {items.map((item) => (
+
+
+ {item.question}
+ +
+
+ {item.answer}
+
+ ))}
+
+
+ );
+}
+export function CallToAction({
+ title,
+ description,
+ actions,
+}: {
+ title: string;
+ description?: string;
+ actions: LinkAction[];
+}) {
+ return (
+
+ {title}
+ {description && {description}
}
+
+ {actions.map((action, i) => (
+
+ ))}
+
+
+ );
+}
+export function Footer({
+ brand,
+ description,
+ columns,
+ copyright,
+ legal = [],
+}: {
+ brand: string;
+ description: string;
+ columns: { title: string; links: LinkAction[] }[];
+ copyright: string;
+ legal?: LinkAction[];
+}) {
+ return (
+
+ );
+}
diff --git a/src/primitives.tsx b/src/primitives.tsx
new file mode 100644
index 0000000..0a2af37
--- /dev/null
+++ b/src/primitives.tsx
@@ -0,0 +1,429 @@
+import {
+ forwardRef,
+ useId,
+ type ButtonHTMLAttributes,
+ type InputHTMLAttributes,
+ type SelectHTMLAttributes,
+ type TextareaHTMLAttributes,
+ type ReactNode,
+} from "react";
+import {
+ Dialog as HeadlessDialog,
+ DialogPanel,
+ DialogTitle,
+ Menu,
+ MenuButton,
+ MenuItems,
+ MenuItem,
+} from "@headlessui/react";
+export interface ButtonProps extends ButtonHTMLAttributes {
+ variant?: "primary" | "secondary" | "ghost" | "danger";
+ loading?: boolean;
+}
+export const Button = forwardRef(
+ function Button(
+ {
+ variant = "primary",
+ loading = false,
+ disabled,
+ children,
+ className = "",
+ type = "button",
+ ...props
+ },
+ ref,
+ ) {
+ return (
+
+ );
+ },
+);
+export interface LinkAction {
+ label: string;
+ href: string;
+}
+export function ActionLink({
+ label,
+ href,
+ variant = "primary",
+}: LinkAction & { variant?: ButtonProps["variant"] }) {
+ return (
+
+ {label}
+
+ );
+}
+export type Tone = "neutral" | "success" | "warning" | "danger";
+export function Badge({
+ children,
+ tone = "neutral",
+}: {
+ children: ReactNode;
+ tone?: Tone;
+}) {
+ return {children};
+}
+interface FieldDescription {
+ label: string;
+ hint?: string;
+ error?: string;
+}
+export interface InputProps
+ extends InputHTMLAttributes, FieldDescription {
+ prefix?: string;
+ suffix?: string;
+}
+export const Input = forwardRef(function Input(
+ { label, hint, error, id, prefix, suffix, className = "", ...props },
+ ref,
+) {
+ const generated = useId(),
+ fieldId = id || generated,
+ messageId = fieldId + "-message";
+ return (
+
+
+
+ {prefix && {prefix}}
+
+ {suffix && {suffix}}
+
+ {(error || hint) && (
+
+ {error || hint}
+
+ )}
+
+ );
+});
+export function Select({
+ label,
+ options,
+ id,
+ hint,
+ error,
+ ...props
+}: SelectHTMLAttributes &
+ FieldDescription & { options: { value: string; label: string }[] }) {
+ const generated = useId(),
+ fieldId = id || generated,
+ listbox = !!props.multiple || (props.size || 0) > 1;
+ return (
+
+
+
+
+ {!listbox && (
+
+ )}
+
+ {(hint || error) && (
+
+ {error || hint}
+
+ )}
+
+ );
+}
+export function TextArea({
+ label,
+ id,
+ hint,
+ error,
+ ...props
+}: TextareaHTMLAttributes & FieldDescription) {
+ const generated = useId(),
+ fieldId = id || generated;
+ return (
+
+
+
+ {(hint || error) && (
+
+ {error || hint}
+
+ )}
+
+ );
+}
+export function Checkbox({
+ label,
+ ...props
+}: Omit, "type"> & { label: string }) {
+ return (
+
+ );
+}
+export function Switch({
+ label,
+ ...props
+}: Omit, "type" | "role"> & {
+ label: string;
+}) {
+ return (
+
+ );
+}
+export function RadioGroup({
+ label,
+ name,
+ value,
+ onChange,
+ options,
+}: {
+ label: string;
+ name: string;
+ value: string;
+ onChange: (value: string) => void;
+ options: { value: string; label: string; description?: string }[];
+}) {
+ return (
+
+ );
+}
+export function Tabs({
+ label,
+ value,
+ onChange,
+ items,
+}: {
+ label: string;
+ value: string;
+ onChange: (value: string) => void;
+ items: { id: string; label: string; content: ReactNode }[];
+}) {
+ const base = useId();
+ return (
+
+
{
+ const i = items.findIndex((item) => item.id === value);
+ let next = i;
+ if (e.key === "ArrowRight") next = (i + 1) % items.length;
+ else if (e.key === "ArrowLeft")
+ next = (i - 1 + items.length) % items.length;
+ else if (e.key === "Home") next = 0;
+ else if (e.key === "End") next = items.length - 1;
+ else return;
+ e.preventDefault();
+ onChange(items[next].id);
+ document.getElementById(`${base}-${items[next].id}-tab`)?.focus();
+ }}
+ >
+ {items.map((item) => (
+
+ ))}
+
+ {items.map((item) => (
+
+ {item.content}
+
+ ))}
+
+ );
+}
+export function Modal({
+ open,
+ onClose,
+ title,
+ children,
+ actions,
+}: {
+ open: boolean;
+ onClose: () => void;
+ title: string;
+ children: ReactNode;
+ actions?: ReactNode;
+}) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {children}
+ {actions && {actions}
}
+
+
+
+ );
+}
+export function Dropdown({
+ label,
+ items,
+}: {
+ label: string;
+ items: {
+ label: string;
+ onSelect: () => void;
+ danger?: boolean;
+ disabled?: boolean;
+ }[];
+}) {
+ return (
+
+ );
+}
+export function Notice({
+ title,
+ children,
+ tone = "neutral",
+ announce = false,
+}: {
+ title: string;
+ children?: ReactNode;
+ tone?: Tone;
+ announce?: boolean;
+}) {
+ return (
+
+
{title}
+ {children &&
{children}
}
+
+ );
+}
+export function EmptyState({
+ title,
+ description,
+ action,
+}: {
+ title: string;
+ description: string;
+ action?: ReactNode;
+}) {
+ return (
+
+
{title}
+
{description}
+ {action}
+
+ );
+}
diff --git a/src/tokens.css b/src/tokens.css
new file mode 100644
index 0000000..8377919
--- /dev/null
+++ b/src/tokens.css
@@ -0,0 +1,90 @@
+/* Project-agnostic semantic tokens derived from the supplied Mortar v2 designs.
+ Status colours deliberately differ from brand colours. No hosted service dependency. */
+:root {
+ --m-font:
+ -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
+ sans-serif;
+ --m-mono: "SF Mono", ui-monospace, Menlo, monospace;
+ --m-paper: #ffffff;
+ --m-surface: #ffffff;
+ --m-sunken: #f5f5f7;
+ --m-line: #e5e5ea;
+ --m-line-strong: #8e8e93;
+ --m-ink: #000000;
+ --m-secondary: #3a3a3c;
+ --m-muted: #6e6e73;
+ --m-primary: #000000;
+ --m-primary-hover: #29292c;
+ --m-primary-active: #3a3a3c;
+ --m-on-primary: #ffffff;
+ --m-focus: #000000;
+ --m-link: #000000;
+ --m-selected: #f2f2f4;
+ --m-success: #1f7a36;
+ --m-warning: #945700;
+ --m-danger: #c70014;
+ --m-control: 36px;
+ --m-pad: 14px;
+ --m-font-size: 14px;
+ --m-row-pad: 14px;
+ --m-radius-sm: 4px;
+ --m-radius: 8px;
+ --m-radius-lg: 12px;
+ --m-float: 0 12px 32px -12px rgb(0 0 0 / 0.14);
+}
+:root[data-mortar] {
+ color-scheme: light;
+}
+:root[data-mortar-appearance="dark"] {
+ color-scheme: dark;
+ --m-paper: #000000;
+ --m-surface: #0e0e10;
+ --m-sunken: #1c1c1e;
+ --m-line: #29292c;
+ --m-line-strong: #727278;
+ --m-ink: #f5f5f7;
+ --m-secondary: #c7c7cc;
+ --m-muted: #98989d;
+ --m-primary: #ffffff;
+ --m-primary-hover: #e5e5ea;
+ --m-primary-active: #c7c7cc;
+ --m-on-primary: #000000;
+ --m-focus: #ffffff;
+ --m-link: #f5f5f7;
+ --m-selected: #1c1c1e;
+ --m-success: #30d158;
+ --m-warning: #ffd60a;
+ --m-danger: #ff6961;
+ --m-float: none;
+}
+:root[data-mortar-brand="green"] {
+ --m-primary: #047857;
+ --m-primary-hover: #065f46;
+ --m-primary-active: #064e3b;
+ --m-on-primary: #ffffff;
+ --m-focus: #047857;
+ --m-link: #047857;
+ --m-selected: #e6f5ed;
+}
+:root[data-mortar-brand="green"][data-mortar-appearance="dark"] {
+ --m-focus: #6ee7b7;
+ --m-link: #6ee7b7;
+ --m-selected: #062d23;
+}
+:root[data-mortar-density="compact"] {
+ --m-control: 30px;
+ --m-pad: 10px;
+ --m-font-size: 13px;
+ --m-row-pad: 10px;
+}
+:root[data-mortar-density="spacious"] {
+ --m-control: 44px;
+ --m-pad: 18px;
+ --m-font-size: 15px;
+ --m-row-pad: 18px;
+}
+@media (pointer: coarse) {
+ :root[data-mortar] {
+ --m-control: 44px;
+ }
+}
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..8f71e50
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,15 @@
+@import "./src/tokens.css";
+@import "./src/components.css";
+@custom-variant mortar-dark (&:where([data-mortar-appearance='dark'], [data-mortar-appearance='dark'] *));
+@theme inline {
+ --color-paper: var(--m-paper);
+ --color-surface: var(--m-surface);
+ --color-sunken: var(--m-sunken);
+ --color-ink: var(--m-ink);
+ --color-muted: var(--m-muted);
+ --color-line: var(--m-line);
+ --color-primary: var(--m-primary);
+ --color-on-primary: var(--m-on-primary);
+ --color-focus: var(--m-focus);
+ --font-sans: var(--m-font);
+}
diff --git a/tests/baseline.test.mjs b/tests/baseline.test.mjs
new file mode 100644
index 0000000..7b11ecd
--- /dev/null
+++ b/tests/baseline.test.mjs
@@ -0,0 +1,44 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { mkdtemp, cp, readFile, writeFile, rm } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { checkKit, ROOT } from "../scripts/check.mjs";
+test("all public exports have valid contracts and previews", async () =>
+ assert.deepEqual((await checkKit()).findings, []));
+test("baseline rejects path escape, missing dependencies, hidden exports and hosted coupling", async () => {
+ const dir = await mkdtemp(join(tmpdir(), "mortar-baseline-"));
+ try {
+ for (const name of [
+ "src",
+ "previews",
+ "manifests",
+ "package.json",
+ "baseline-policy.json",
+ ])
+ await cp(join(ROOT, name), join(dir, name), { recursive: true });
+ const file = join(dir, "manifests/mortar-button.json");
+ const c = JSON.parse(await readFile(file, "utf8"));
+ c.source = "../outside.tsx";
+ c.contract.dependencies = ["missing"];
+ await writeFile(file, JSON.stringify(c));
+ await writeFile(
+ join(dir, "src/index.ts"),
+ "export { Surprise } from './primitives.js';\n",
+ { flag: "a" },
+ );
+ await writeFile(
+ join(dir, "src/forbidden.ts"),
+ "fetch('https://example.invalid'); const classes='bg-indigo-600';\n",
+ );
+ const result = await checkKit(dir);
+ assert.equal(result.passed, false);
+ for (const rule of ["M001", "M002", "M003", "M004", "M005"])
+ assert.ok(
+ result.findings.some((f) => f.rule === rule),
+ rule,
+ );
+ } finally {
+ await rm(dir, { recursive: true, force: true });
+ }
+});
diff --git a/tests/browser/components.spec.mjs b/tests/browser/components.spec.mjs
new file mode 100644
index 0000000..1510743
--- /dev/null
+++ b/tests/browser/components.spec.mjs
@@ -0,0 +1,33 @@
+import { test, expect } from "@playwright/test";
+import AxeBuilder from "@axe-core/playwright";
+import { readFile } from "node:fs/promises";
+const { components } = JSON.parse(
+ await readFile(new URL("../../catalog.json", import.meta.url), "utf8"),
+);
+for (const component of components)
+ for (const theme of ["light", "dark"])
+ test(`${component.export} ${theme}`, async ({ page }) => {
+ const errors = [];
+ page.on("pageerror", (e) => errors.push(e.message));
+ await page.goto(`/?component=${component.export}&theme=${theme}`);
+ await expect(
+ page.getByRole("heading", { name: "Mortar playground" }),
+ ).toBeVisible();
+ await expect(
+ page.getByRole("status", { name: "Loading preview…" }),
+ ).toHaveCount(0);
+ await expect(page.locator("[data-mortar-appearance]")).toHaveAttribute(
+ "data-mortar-appearance",
+ theme,
+ );
+ const results = await new AxeBuilder({ page })
+ .withTags(["wcag2a", "wcag2aa", "wcag21aa"])
+ .analyze();
+ expect(
+ results.violations.map((v) => ({
+ id: v.id,
+ nodes: v.nodes.map((n) => n.target),
+ })),
+ ).toEqual([]);
+ expect(errors).toEqual([]);
+ });
diff --git a/tests/browser/interactions.spec.mjs b/tests/browser/interactions.spec.mjs
new file mode 100644
index 0000000..abd9890
--- /dev/null
+++ b/tests/browser/interactions.spec.mjs
@@ -0,0 +1,54 @@
+import { test, expect } from "@playwright/test";
+// Rendering and axe coverage for every export lives in mortar-fixtures.spec.mjs.
+for (const theme of ["light", "dark"]) {
+ test(`Mortar menu keyboard and escape: ${theme}`, async ({ page }) => {
+ await page.goto(`/?component=Dropdown&theme=${theme}`);
+ const button = page.getByRole("button", { name: "Actions", exact: true });
+ await button.focus();
+ await page.keyboard.press("ArrowDown");
+ await expect(page.getByRole("menu")).toBeVisible();
+ await page.keyboard.press("Escape");
+ await expect(page.getByRole("menu")).toHaveCount(0);
+ await expect(button).toBeFocused();
+ });
+ test(`Mortar modal traps focus and restores it: ${theme}`, async ({
+ page,
+ }) => {
+ await page.goto(`/?component=Modal&theme=${theme}`);
+ const button = page.getByRole("button", { name: "Open dialog" });
+ await button.click();
+ const dialog = page.getByRole("dialog");
+ await expect(dialog).toHaveAttribute("aria-modal", "true");
+ for (let i = 0; i < 5; i++) {
+ await page.keyboard.press("Tab");
+ expect(
+ await dialog.evaluate((e) => e.contains(document.activeElement)),
+ ).toBe(true);
+ }
+ await page.keyboard.press("Escape");
+ await expect(dialog).toHaveCount(0);
+ await expect(button).toBeFocused();
+ });
+ test(`Mortar controlled choice fields: ${theme}`, async ({ page }) => {
+ await page.goto(`/?component=Checkbox&theme=${theme}`);
+ const checkbox = page.getByRole("checkbox", {
+ name: "Email notifications",
+ });
+ await expect(checkbox).not.toBeChecked();
+ await checkbox.click();
+ await expect(checkbox).toBeChecked();
+ await page.goto(`/?component=Switch&theme=${theme}`);
+ const toggle = page.getByRole("switch", { name: "Automatic updates" });
+ await expect(toggle).not.toBeChecked();
+ await toggle.click();
+ await expect(toggle).toBeChecked();
+ await page.goto(`/?component=RadioGroup&theme=${theme}`);
+ const radio = page.getByRole("radio", { name: /Advanced/ });
+ await radio.click();
+ await expect(radio).toBeChecked();
+ await page.goto(`/?component=Select&theme=${theme}`);
+ const select = page.getByLabel("Environment", { exact: true });
+ await select.selectOption("second");
+ await expect(select).toHaveValue("second");
+ });
+}
diff --git a/tests/controls.test.mjs b/tests/controls.test.mjs
new file mode 100644
index 0000000..3b0b140
--- /dev/null
+++ b/tests/controls.test.mjs
@@ -0,0 +1,63 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import React from "react";
+import { renderToStaticMarkup } from "react-dom/server";
+import { AppShell, Input, DataTable, CodeBlock } from "../dist/index.js";
+test("Mortar shell supplies page landmarks and labelled navigation", () => {
+ const html = renderToStaticMarkup(
+ React.createElement(
+ AppShell,
+ {
+ brand: "Example",
+ navigation: [{ label: "Overview", href: "#overview", current: true }],
+ },
+ "Content",
+ ),
+ );
+ assert.match(html, /aria-current="page"/);
+ assert.match(html, / {
+ const html = renderToStaticMarkup(
+ React.createElement(
+ React.Fragment,
+ {},
+ React.createElement(Input, {
+ label: "Email",
+ error: "Invalid email",
+ "aria-describedby": "external",
+ }),
+ React.createElement(Input, { label: "Name" }),
+ ),
+ );
+ const ids = [...html.matchAll(/]*\sid="([^"]+)"/g)].map((m) => m[1]);
+ assert.equal(ids.length, 2);
+ assert.notEqual(ids[0], ids[1]);
+ assert.ok(html.includes("external " + ids[0] + "-message"));
+ assert.ok(html.includes('for="' + ids[1] + '"'));
+});
+test("Mortar table separates loading, error and empty states and keeps row headers", () => {
+ const props = {
+ caption: "Records",
+ columns: [{ key: "name", label: "Name", render: (row) => row.name }],
+ rowLabel: (row) => row.name,
+ rows: [{ id: "1", name: "Example" }],
+ };
+ const render = (extra) =>
+ renderToStaticMarkup(
+ React.createElement(DataTable, { ...props, ...extra }),
+ );
+ assert.match(render({}), /scope="row"/);
+ assert.match(render({}), /tabindex="0"/);
+ assert.match(render({ rows: [] }), /No records/);
+ assert.match(render({ loading: true }), /role="status"/);
+ assert.match(render({ error: "Unavailable" }), /role="alert"/);
+});
+test("Mortar source inspection never executes submitted markup", () => {
+ const html = renderToStaticMarkup(
+ React.createElement(CodeBlock, { code: "" }),
+ );
+ assert.ok(!html.includes("