# Crusher UI Kit

**A token-driven, framework-agnostic Web Components design system.**
42 public custom elements, 7 visual dialects, light/dark, density, runtime + static delivery. Built with Lit. Zero framework lock-in.

```bash
npm i crusher-ui-kit
```

Live demo: [ui.muhammadhassaanjaved.com](https://ui.muhammadhassaanjaved.com/)
Current release: `0.3.1`

---

## Why

You're shipping multiple surfaces — a marketing site, a portfolio, an admin tool, an AI workspace. They each want a slightly different *feel* but the same *system* underneath. Crusher gives you one token pipeline, one component library, and seven visual dialects (Glass, Brutal, NeoBrutal, Neumorph, Minimal, Futuristic, Bento) that you switch with a single root-attribute.

The components are Web Components, so they work in **React, Vue, Astro, vanilla HTML, or no framework at all**.

---

## Install paths

### 1. Bundler app (Vite / Next / Astro / etc.)

```js
// Register all custom elements + import the core token CSS.
import 'crusher-ui-kit';
import 'crusher-ui-kit/styles/tokens.css';
import 'crusher-ui-kit/styles/modes.css';
import 'crusher-ui-kit/styles/semantic.css';
import 'crusher-ui-kit/styles/bridge.css';
import 'crusher-ui-kit/themes/glass.css'; // pick one or more dialect overlays

import {
  setTheme, setMode, setDensity, setBrand,
  showToast, openPalette, createHashPanelController,
} from 'crusher-ui-kit/runtime';
```

Theme imports are extension-qualified: `import 'crusher-ui-kit/themes/<name>.css'`. Pick only the dialects you ship.

### 2. Static HTML (no bundler)

```html
<html data-theme="minimal" data-mode="dark">
<head>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/crusher-ui-kit@0.3.1/dist/crusher-ui.min.css" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/crusher-ui-kit@0.3.1/dist/themes/minimal.css" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/crusher-ui-kit@0.3.1/dist/static/tool-shell.css" />
  <script defer src="https://cdn.jsdelivr.net/npm/crusher-ui-kit@0.3.1/dist/static/tool-shell.js"></script>
  <script type="module" src="https://cdn.jsdelivr.net/npm/crusher-ui-kit@0.3.1/dist/crusher-ui.standalone.esm.js"></script>
</head>
<body>
  <crusher-button>Hello</crusher-button>
</body>
</html>
```

For utility-tool sites, start from [`templates/static-tool-shell.html`](./templates/static-tool-shell.html). The `tool-shell.js` IIFE hydrates `<html data-default-*>` attributes from saved prefs before paint, so themed sites don't flash.

### 3. Astro / SSR

Same as a bundler app — register on the client. The recommended pattern for Astro is in [`hassaan-site`](https://github.com/itxcrusher/hassaan-site) and [`crusher-portfolio`](https://github.com/itxcrusher/crusher-portfolio): a small `scripts/resolve-crusher-ui.mjs` runs on `predev`/`prebuild` that links a sibling framework checkout for local work and installs the pinned published version for hosted builds.

---

## Concepts

**Theme (dialect)** — the *aesthetic*. Set on the root: `<html data-theme="glass">`. Seven dialects ship today. Each overrides design tokens (radii, shadows, transitions, type) on top of the base palette.

**Mode** — light vs dark. Set on the root: `<html data-mode="dark">`. Independent of theme; every dialect supports both modes.

**Density** — `comfortable` (default) or `compact`. Set on the root: `<html data-density="compact">`. Tightens spacing tokens for data-heavy surfaces.

**Brand color** — a single tokenized accent (`--crusher-color-brand-primary`). Override via CSS or via `setBrand('#7c3aed')`. New in 0.3.0: `--crusher-brand-on-surface` is auto-derived to soften brand against the dark canvas via `color-mix`.

**Theme lock** — pin a fixed dialect for product sites: `<html data-theme="minimal" data-theme-lock="minimal">`. Lock wins in memory only and does not write back to `localStorage`, so the user's saved brand color is preserved.

---

## Component catalog (42 public elements)

### Atoms (10)

| Tag | Notes |
| --- | --- |
| `<crusher-badge>` | Inline label / count |
| `<crusher-button>` | Variants: `primary` (default), `outline`, `ghost`, `link`, `destructive` / `danger` alias. Sizes: `sm` / `md` / `lg`. `loading` boolean. Respects `prefers-reduced-motion`. |
| `<crusher-checkbox>` | Accessible checkbox |
| `<crusher-chip>` | Token-styled chip / tag. Supports `color` attribute. |
| `<crusher-code-block>` | Code snippet with token-aware syntax highlighting |
| `<crusher-icon-link>` | Anchor styled as an icon button |
| `<crusher-input>` | Text input, validates, ARIA-wired |
| `<crusher-switch>` | Toggle switch |
| `<crusher-textarea>` | Multi-line input |
| `<crusher-tooltip>` | Hover/focus tooltip |

### Molecules (20)

| Tag | Notes |
| --- | --- |
| `<crusher-alert>` | Variants: `info` / `success` / `warning` / `danger` / `neutral`. Dismissible with persistence by `id`. |
| `<crusher-audio-player>` | Ambient audio control. `preload="none"` by default (set `preload="metadata"`/`"auto"` to override). Album art deferred until first play. |
| `<crusher-banner>` | Site-wide announcement strip. Variants: `brand` / `info` / `success` / `warning` / `danger`. Dismissible with separate-map persistence. |
| `<crusher-card>` | Surface container. Variants: `default` / `data` / `highlight` / `muted`. Density-aware. `hover` opts into hover-lift. |
| `<crusher-card-link>` | Whole-card link wrapper for `<crusher-card>` |
| `<crusher-command-palette>` | ⌘K-style palette. Async loaders supported. |
| `<crusher-disclosure>` | Lit wrapper over native `<details>` with two-way `open` reflection. Also available as the `.crusher-disclosure` CSS-only utility class for no-build consumers. |
| `<crusher-dropdown>` | Menu with keyboard navigation |
| `<crusher-empty-state>` | No-results / empty surface. Slots for icon and actions. |
| `<crusher-filter-bar>` | Wires `data-value` chips to filter a target selector by attribute |
| `<crusher-grid>` | Responsive auto-fit grid primitive. `min="13rem"` controls column width. |
| `<crusher-menubar>` | Horizontal menu / toolbar |
| `<crusher-modal>` | Focus-trap modal. `close-on-overlay` opt-in. Emits cancelable `request-close`. |
| `<crusher-select>` | Async + type-ahead select, ARIA-announced |
| `<crusher-skill-bar>` | Animated progress bar |
| `<crusher-stack>` | Flex layout primitive. `gap`, `align`, `justify`, `orientation` (`vertical` default / `horizontal`), `wrap`. |
| `<crusher-stat>` | Data metric tile. `tone`, `size` (`sm`/`md`/`lg`/`xl`), `orientation`. Slots for icon + trend. |
| `<crusher-style-switcher>` | Theme/mode/brand control. Hide-* boolean attrs scope each section. `inline` boolean renders in-flow (new in 0.3.1). Use `swatches` attribute for consumer-defined palettes. |
| `<crusher-tabs>` | ARIA tablist with roving focus |
| `<crusher-toast-center>` | Global toast region. Drive via `showToast(...)` from `crusher-ui-kit/runtime`. |
| `<crusher-toolbar>` | Section toolbar — leading / center / actions slots |

### Organisms (7)

| Tag | Notes |
| --- | --- |
| `<crusher-app-shell>` | Page-level shell. Slots: `nav`, `aside`, `footer`, default. Composes with `<crusher-page-header>` + `<crusher-grid>` + `<crusher-stack>`. |
| `<crusher-nav-list>` | Sidebar / drawer nav list |
| `<crusher-page-header>` | Page-level header with kicker, title, lede, meta, pills |
| `<crusher-section-title>` | Section heading with under-bar treatment |
| `<crusher-table>` | Data table with token-driven density |
| `<crusher-timeline>` | Vertical / branching timeline container |
| `<crusher-timeline-item>` | Individual timeline event |

### Forms (4)

| Tag | Notes |
| --- | --- |
| `<crusher-field>` | Wrapper that auto-wires `aria-labelledby`/`aria-describedby`/`aria-invalid` on the `[slot="control"]` element. `label` / `helper` / `error` attrs or slots. `orientation` `vertical` / `horizontal`. |
| `<crusher-label>` | Standalone label primitive |
| `<crusher-hint>` | Helper text primitive |
| `<crusher-error>` | Error text primitive |

A new public element must appear in `index.html` (`check:demo`) and `types/index.d.ts` (`check:types`) — both gates run in CI.

---

## Runtime helpers

```js
import {
  setTheme, lockTheme, setMode, setDensity, setBrand,
  showToast, openPalette, createHashPanelController,
} from 'crusher-ui-kit/runtime';

setTheme('glass');
lockTheme('minimal');        // pin a fixed dialect (in-memory only — no localStorage write)
setMode('dark');
setDensity('compact');
setBrand('#7c3aed');

showToast({ title: 'Saved', variant: 'success' });
openPalette();

createHashPanelController({
  defaultId: 'home',
  panelSelector: '[data-crusher-panel]',
  linkSelector: '[data-crusher-panel-link]',
  inactiveClass: 'is-hidden',
});
```

All preferences persist to `localStorage` under `crusher_prefs` (schema version `v: 1`). Future versions up-migrate on read; unknown future versions become read-only with a one-time `console.warn`.

---

## Accessibility

- **Modal** traps focus, restores on close, locks body scroll, dismisses on Esc + overlay click, emits cancelable `request-close`.
- **Tabs** follow the WAI-ARIA tablist pattern: `role="tablist" / tab / tabpanel`, roving focus, live announcements.
- **Select** announces selections via `aria-live`, async data + type-ahead.
- **Field** auto-generates stable IDs and wires `aria-labelledby` / `aria-describedby` / `aria-invalid` on the slotted control.
- **Style switcher** has `:focus-visible` outlines, `aria-pressed` / `aria-expanded`, respects `prefers-reduced-motion`.
- **Contrast** validated via `npm run check:contrast` (WCAG AA contrast on every theme/mode pair).
- **Reduced motion** honored in spinners, transitions, hover lifts, and modal animations.

---

## Browser support

- Modern evergreen browsers (Chrome / Edge / Safari / Firefox last two versions).
- `color-mix(in srgb, ...)` is used widely — requires Chrome 111+, Safari iOS 16.2+, Firefox 113+.
- `interpolate-size: allow-keywords` for `<crusher-disclosure>` animation requires Chrome 129+ / Safari 18+; gracefully degrades to instant open/close elsewhere.
- Node 18+ for `dev` / `build` toolchain.

---

## Migration notes

### From `^0.2.x` to `^0.3.x`

No breaking changes. Additive only:

- New: `<crusher-field>`, `<crusher-disclosure>`, `<crusher-stack orientation="horizontal" wrap>`, `<crusher-button variant="danger">` alias, `--crusher-brand-on-surface` per-mode token.
- New (0.3.1): `<crusher-style-switcher inline>` for in-flow rendering, `<crusher-audio-player preload="...">` attribute (default flipped to `"none"`).
- Element count: 41 → 42.

If you used `<crusher-audio-player>` and relied on the browser default preloading the source on page load, set `preload="metadata"` or `"auto"` explicitly to restore that behavior.

### From `^0.1.x` to `^0.2.x`

- `data-theme-lock` now wins in memory only — it does not write the locked theme back to `localStorage`. Smoke checks in `hassaan-site` / `crusher-portfolio` were updated to assert this contract.
- `crusher_prefs` schema versioned (`v: 1`). Pre-`0.1.6` prefs up-migrate automatically.
- Element count: 39 → 41 (M3 added `crusher-alert` and `crusher-banner`).

---

## Scripts

| Command | Purpose |
| --- | --- |
| `npm run dev` | Vite dev server on the framework demo site |
| `npm run build:tokens` | Generate token + theme CSS from JSON sources |
| `npm run build` | Build distributable bundles (ESM + standalone ESM + CSS + static assets) |
| `npm run check:contrast` | WCAG color-contrast validation across every theme/mode pair |
| `npm run check:package` | Verify runtime exports + package contract |
| `npm run check:browser` | Playwright-driven browser contract assertions |
| `npm run check:demo` | Every public custom element renders in `index.html` |
| `npm run check:types` | Every public custom element typed in `types/index.d.ts` |

Release gate (run before tagging):

```bash
npm run build:tokens && npm run check:contrast && npm run build && \
  npm run check:package && npm run check:browser && \
  npm run check:demo && npm run check:types && npm pack --dry-run
```

Releases are Changesets-driven via GitHub Actions. The publish command uses `npm publish --no-provenance --access public` (the GitHub repo is private; the npm package is public). `NPM_TOKEN` is configured as a GitHub Actions secret.

---

## Project structure

```text
crusher-ui-kit/
├── design/tokens/          ← base token JSON (source of truth)
├── design/themes/          ← per-dialect token overlays (JSON)
├── src/
│   ├── components/         ← Lit Web Components (atoms / molecules / organisms / forms)
│   ├── css/                ← generated tokens.css, modes.css, themes/*.css (do not hand-edit)
│   ├── runtime/            ← setTheme, setMode, setDensity, setBrand, prefs, panels, toast
│   ├── static/             ← tool-shell.css + tool-shell.js (no-build CDN consumers)
│   ├── scripts/            ← framework demo site scripts
│   └── js/main.js          ← bundler entry; registers every public element
├── templates/              ← canonical static-tool-shell.html
├── scripts/                ← build / contract checks / token regeneration
├── types/index.d.ts        ← TS / VS Code IntelliSense for `<crusher-*>` tags
└── docs/                   ← internal architecture + roadmap (not published)
```

Public package contract is defined by the `exports` field in `package.json`. Anything outside it is internal and may change.

---

## TypeScript

`types/index.d.ts` declares every public custom element in the global `HTMLElementTagNameMap`. With `moduleResolution: "NodeNext"` or `"Bundler"`, `document.createElement('crusher-button')` returns the correctly typed instance. Bring-your-own framework typings (Vue / Svelte / Solid) can extend the same map.

---

## Changelog

See [`CHANGELOG.md`](./CHANGELOG.md) for per-release notes generated by Changesets.

---

## License

MIT. Built and maintained by Hassaan ([itxcrusher](https://github.com/itxcrusher)).
[muhammadhassaanjaved.com](https://muhammadhassaanjaved.com)
