<div align="center">

<img src="brand/keystone-wordmark.svg" alt="Keystone" height="46">

### One framework. Three pillars. Pick any.

**Styles, icons, and behavior as three independent layers** — each a single
include that works from a CDN or a local file, on its own or together.
No dependencies. No build step. Drop the files in and go.

<br>

![Version](https://img.shields.io/badge/version-1.0.0-466A99?style=flat-square)
![License](https://img.shields.io/badge/license-Proprietary-595D61?style=flat-square)
![Dependencies](https://img.shields.io/badge/dependencies-0-466A99?style=flat-square)
![Build step](https://img.shields.io/badge/build_step-none-466A99?style=flat-square)
![Icons](https://img.shields.io/badge/icons-601-595D61?style=flat-square)
![Core size](https://img.shields.io/badge/core-~24KB_gzip-595D61?style=flat-square)

**[Live demo](demo/index.html)** · **[Icon browser](icons/index.html)** · **[Design principles](DESIGN-PRINCIPLES.md)** · **[Roadmap](ROADMAP.md)** · **[Changelog](CHANGELOG.md)**

</div>

---

Each pillar is **independent** and reduces to a **single include**. Use any one,
any two, or all three — they're designed to be used together but never depend on
each other.

| Pillar | Include | What it gives you |
|--------|---------|-------------------|
| **Style** | `<link rel="stylesheet" href="keystone.css">` | Design tokens (`--ks-*`) + component classes (`.ks-*`) |
| **Icons** | `<link rel="stylesheet" href="keystone-icons.css">` | 601 icons via `.ks-i-<category>-<name>` — font embedded, **one self-contained file** |
| **Behavior** | `<script type="module" src="keystone.js"></script>` | From-scratch vanilla JS components + a runtime plugin system |

There is **no required build step** to *use* Keystone and **no bundler** — the
three files are shipped ready to serve. `keystone-icons.css` is generated
(WOFF2 embedded); the other two are authored directly.

## Quick start

```html
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
  <link rel="stylesheet" href="keystone.css">
  <link rel="stylesheet" href="keystone-icons.css">
  <script type="module" src="keystone.js"></script>
</head>
<body class="ks-body ks-scope">
  <button class="ks-btn ks-btn--primary" data-ks-modal-open="hello">
    <span class="ks-i ks-i-controls-search"></span> Open
  </button>

  <div class="ks-modal" data-ks-modal id="hello">
    <div class="ks-modal-dialog">
      <div class="ks-modal-header">
        <h3 class="ks-modal-title">Hello</h3>
        <button class="ks-modal-close" data-ks-modal-close></button>
      </div>
      <div class="ks-modal-body">It works.</div>
    </div>
  </div>
</body>
</html>
```

See [`demo/index.html`](demo/index.html) for a full showcase of every component.

## Loading: remote (CDN) or local

The includes are identical either way:

```html
<!-- Local download -->
<link rel="stylesheet" href="/keystone/keystone.css">

<!-- Remote / CDN -->
<link rel="stylesheet" href="https://ui.aheliotech.app/keystone.css">
<link rel="stylesheet" href="https://ui.aheliotech.app/keystone-icons.css">
<script type="module" src="https://ui.aheliotech.app/keystone.js"></script>
```

- **Icons** embed their WOFF2 as a `data:` URI, so `keystone-icons.css` is genuinely a single
  file — no sibling assets folder, and the same file works from a CDN or local disk unchanged.
- **Behavior** resolves its plugins config relative to its own URL (`import.meta.url`), so the
  CDN and local cases both find `plugins/config.json` automatically.

## Pillar 1 — Style

All custom properties are namespaced `--ks-*`; all classes are `.ks-*`. Add `class="ks-body"`
to `<body>` for base typography, and `ks-scope` to opt into `box-sizing` on a subtree.

- **Theming:** set `data-theme="dark"` on `<html>` for the dark token set. Tokens are plain CSS
  custom properties — override any `--ks-*` value to retheme.
- **Components:** `.ks-btn`, `.ks-card`, `.ks-input`/`.ks-field`, `.ks-badge`, `.ks-chip`,
  `.ks-alert`, `.ks-table`, `.ks-navbar`, `.ks-tabs`, `.ks-modal`, `.ks-dropdown`, `.ks-tooltip`,
  `.ks-toast`, `.ks-accordion`, `.ks-progress`, `.ks-spinner`, `.ks-avatar`, `.ks-list`, and more.

CSS provides the **look**; interactive components respond to state classes the behavior layer
toggles (`.is-open`, `.is-active`). Without the JS, they sit in their default state.

## Pillar 2 — Icons

```html
<span class="ks-i ks-i-controls-search"></span>            <!-- inherits font-size -->
<span class="ks-i ks-i-status-check-circle ks-i-32"></span> <!-- 32px -->
```

Icons use `currentColor`, so set `color` to recolor them. Size with `font-size` or the helpers
`.ks-i-12 / -16 / -20 / -24 / -32 / -48`. This file is standalone — no JS or `keystone.css` needed.
Browse and copy any of the 601 glyphs in [`icons/index.html`](icons/index.html).

## Pillar 3 — Behavior

One `<script type="module">` exposes `window.Keystone` (alias `window.KS`) and auto-initializes
components from `data-ks-*` attributes on load.

**Components & their hooks:** modal (`data-ks-modal` / `data-ks-modal-open` / `data-ks-modal-close`),
drawer (`data-ks-drawer` / `data-ks-drawer-open`), dropdown (`data-ks-dropdown` +
`data-ks-dropdown-trigger`), tabs (`data-ks-tabs` + `data-ks-tab`/`data-ks-panel`), accordion
(`data-ks-accordion` + `-item`/`-trigger`/`-content`), collapsible (`data-ks-toggle="id"`),
tooltip (`data-ks-tooltip="text"`), popover (`data-ks-popover="id"`), alert dismiss
(`data-ks-alert-dismiss`), theme toggle (`data-ks-theme-toggle`), form validation
(`<form data-ks-validate>`).

**Imperative API:**

```js
Keystone.toast({ title: 'Saved', message: 'All good', variant: 'success' });
Keystone.openModal('hello');
Keystone.theme.toggle();
const el = Keystone.icon('chevron-down', 20); // inline SVG, no emoji
```

Affordances the JS injects (close ×, chevrons, status marks) are **inline SVG drawn from the
Keystone icon set** — never emoji — so they render even without `keystone-icons.css`.

### Stateful form fields

Add the `stateful` attribute to a form field and Keystone persists its value and restores it
on the next visit — a port of [pio](https://github.com/VisionMise/pio):

```html
<input type="text" stateful>
<select stateful multiple>…</select>
<div contenteditable stateful></div>
```

Values are written to the **Origin Private File System** via the File System Access API
(`navigator.storage.getDirectory()`), one file per field, keyed by element id or DOM path,
with 300ms debounced saves. Covers text inputs, checkboxes, radio groups, single/multi
`<select>`, `<textarea>`, and contenteditable. Browsers without the File System Access API
(e.g. Firefox) no-op silently. `Keystone.stateful.supported()` reports availability.

### Plugins (no build, runtime)

Extend Keystone by dropping a module under `plugins/` and listing it in `plugins/config.json`
(loaded in order):

```json
[
  { "path": "copy/main.js", "enabled": true },
  { "path": "haptics/main.js", "enabled": false },
  { "path": "sounds/main.js", "enabled": false },
  { "path": "my-plugin/main.js" }
]
```

Set `"enabled": false` to ship a plugin but keep it off until a developer opts in.

#### Official plugins (ship in `plugins/`)

| Plugin | Default | What it does |
|--------|---------|--------------|
| **copy** | on | `data-ks-copy` copies text (its value, or the element's text) to the clipboard. |
| **haptics** | off | Vibration feedback on `.ks-btn` / `[data-ks-haptic]` presses and scroll-detent ticks on `[data-ks-haptic-scroll]`. Mobile only; no-op elsewhere. API: `Keystone.haptics`. |
| **sounds** | off | Soft synthesized UI sounds (Web Audio, no files) on presses, toggles, and `[data-ks-sound-scroll]`. Two themes — `bleeps` (tonal) and `clicks` (percussive) via `data-ks-sound-theme`. API: `Keystone.sound`. |

Haptics and sounds are **opt-in**: they ship disabled — flip `enabled` to `true` (or load them yourself) to turn them on.

Each plugin is an ES module exporting a default (or `register`) function that receives the
Keystone API:

```js
// plugins/my-plugin/main.js
export default function register(ks) {
  ks.register('greeter', {
    selector: '[data-greet]',
    mount(el) {
      ks.utils.on(el, 'click', () => ks.toast({ message: 'Hi!' }));
    },
  });
}
```

The core fetches `config.json` relative to `keystone.js`, then imports each listed module in
order. Override the location with `<script type="module" src="keystone.js" data-ks-plugins="/path/config.json">`
or `window.KeystoneConfig = { plugins: '/path/config.json' }`. A missing config is harmless —
Keystone works with zero plugins. See [`plugins/copy/main.js`](plugins/copy/main.js) for a minimal reference.

## The icons CSS

`keystone-icons.css` is a **pre-generated, committed artifact** — the WOFF2 font is base64-embedded,
so the single file is everything you need and there is nothing to build to use it. The font/CSS
generation pipeline (Deno + FontForge) lives in the design-system source repo, not here; this
repository ships the finished icon set.

## Brand

Brand assets live in [`brand/`](brand/): `keystone-mark-color.svg` (the three-band keystone
wedge — one band per pillar), `keystone-mark.svg` (monochrome, `currentColor`), and
`keystone-wordmark.svg` (the lockup). The mono mark uses the icon house style, so it recolors
with `color`. For dark backgrounds, inline the wordmark and set its text `fill="currentColor"`.

## Independence matrix

| Loaded | Result |
|--------|--------|
| Icons only | `.ks-i-*` glyphs render. |
| Style only | Components fully styled; interactive ones static. |
| Behavior only | Behaviors work; injected marks are inline SVG; appearance unstyled. |
| Any combination | Progressive enhancement, no errors when a pillar is absent. |

## Design language

Keystone looks like infrastructure, not a toy: flat solid surfaces, depth through
tone rather than borders, a fine film-grain texture, low-radius squared edges, and
a muted **steel-blue + warm-grey** palette. The full rationale behind every token,
component, and icon is in [`DESIGN-PRINCIPLES.md`](DESIGN-PRINCIPLES.md).

## License

Keystone is **proprietary** — © 2026 AhelioTech, all rights reserved. See [`LICENSE`](LICENSE).
