# swisskit

A drop-in CSS and JS design kit in the Swiss, or International Typographic, style: white, ink, one red, hairline rules, sharp corners, and a strict left grid. Light by default with a dark toggle. Built for explainers, docs, and working notes.

Sibling of [jawnpaper](../). Where jawnpaper is marker on cream paper, swisskit is ink on a grid. Both are namespaced (`np-`/`--np-` vs `sk-`/`--sk-`), so they coexist on the same page without collisions.

Live demo: https://jamditis.com/jawnpaper/swisskit/

It is the system behind the [autonomy explainer](https://skills.amditis.tech/autonomy/), packaged so any page can use it.

## install

Two ways.

**CDN.** Paste two lines into your `<head>`. No download, no build step.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jamditis/stash/docs/jawnpaper/swisskit/swisskit.css">
<script defer src="https://cdn.jsdelivr.net/gh/jamditis/stash/docs/jawnpaper/swisskit/swisskit.js"></script>
<body class="swiss">
```

Pin to a commit for production by inserting `@<sha>` after the repo segment, e.g. `https://cdn.jsdelivr.net/gh/jamditis/stash@abc1234/docs/jawnpaper/swisskit/swisskit.css`.

**Copy + paste.** Drop these files into your project:

```
swisskit.css   - tokens, base, components, utilities, themes
swisskit.js    - opt-in behaviors (theme toggle, hamburger nav, diagram zoom, copy)
favicon.svg    - the swiss mark
og-image.svg   - 1200x630 social image
```

Then in your HTML:

```html
<link rel="stylesheet" href="swisskit.css">
<script src="swisskit.js" defer></script>
<body class="swiss">
```

A complete starter page is at [STARTER.html](./STARTER.html). Save it, open it, you're done.

The `swiss` class on `<body>` opts you in to the base typography, colors, and grid. Component classes (`.sk-card`, `.sk-callout`, and so on) also work on their own.

## light by default, dark on toggle

Set `data-sk-theme="light"` on `<html>` for a light default, and add the no-flash snippet so a returning dark-mode reader doesn't see a flash of light on load:

```html
<html lang="en" data-sk-theme="light">
<head>
  <script>try { var t = localStorage.getItem('sk-theme'); if (t) document.documentElement.setAttribute('data-sk-theme', t); } catch (e) {}</script>
  ...
```

With no `data-sk-theme` attribute set, the page follows the reader's system preference. The toggle button writes the choice to `localStorage` so it sticks.

## fonts

`swisskit.css` loads Archivo and IBM Plex Mono from Google Fonts via `@import` at the top of the file. That is a render-blocking external request. Two faster options:

**Preconnect.** Drop the `@import` and add this to your `<head>` instead:

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500&display=swap">
```

**System fonts.** Drop the `@import` and override the font tokens:

```css
:root {
  --sk-font-body: system-ui, -apple-system, sans-serif;
  --sk-font-mono: 'SF Mono', Consolas, monospace;
}
```

## tokens

All color, type, and spacing live as CSS custom properties on `:root`. Override any of them in your own stylesheet to retheme.

| token | what it is |
| --- | --- |
| `--sk-bg` | page background |
| `--sk-surface` / `--sk-surface-2` | card and inset backgrounds |
| `--sk-ink` | strong rules and borders |
| `--sk-text` / `--sk-text-bright` / `--sk-text-dim` | body, emphasis, muted text |
| `--sk-border` / `--sk-border-bright` | hairline borders |
| `--sk-accent` / `--sk-accent-dim` | the single red, and its tint |
| `--sk-neutral` | the "a step" gray in diagrams |
| `--sk-font-body` / `--sk-font-mono` | Archivo and IBM Plex Mono |
| `--sk-pad` | page side padding (drops on mobile) |
| `--sk-measure` | body text max width |

## components

| class | what it is |
| --- | --- |
| `.sk-wrap` / `.sk-main` | two-column grid: sidebar + 820px content column |
| `.sk-toc` | sticky sidebar table of contents; hamburger menu under 1000px |
| `.sk-theme-toggle` | fixed light/dark toggle, bottom right |
| `.sk-hero` | title block with staggered load and a `.sk-lead` intro |
| `.sk-stats` / `.sk-stat` | bordered stat strip |
| `.sk-sec-head` | numbered section header with optional flush-right `.sk-sec-head__meta` |
| `.sk-prose` | body copy, lists with red markers, inline `code` |
| `.sk-diagram` / `.sk-mermaid-wrap` | Mermaid container with zoom, pan, and open-full-size |
| `.sk-lanes` / `.sk-lane` | two-column CSS diagram, no Mermaid |
| `.sk-pipeline` | horizontal step strip |
| `.sk-gauge` | a zoned bar with a marker |
| `.sk-card` / `.sk-card-grid` | bordered cards in a shared-border grid |
| `.sk-callout` | aside, with `--warn` / `--ok` / `--info` variants |
| `.sk-pullquote` | scaled pull quote |
| `.sk-prompt` | a copyable block with a wired copy button |
| `.sk-table` / `.sk-data` | ruled data table with uppercase mono headers |
| `.sk-tag` | inline label, with `--accent` variant |
| `.sk-sample` | reproduce a chat or notification |
| `.sk-redact` | a chip standing in for a name or project kept out of the source |
| `.sk-collapsible` | styled native `<details>` |
| `.sk-foot` | page footer |

Utilities: `.sk-rule` (hairline divider), `.sk-mono`, `.sk-text-accent`, `.sk-text-dim`, `.sk-code` (inline code outside `.swiss`).

## behaviors

`swisskit.js` is plain JavaScript with no dependencies. It wires each behavior only when the matching markup is on the page.

| markup | behavior |
| --- | --- |
| `.sk-theme-toggle` or `[data-sk-toggle-theme]` | flips light/dark, persists to `localStorage`, fires `sk:themechange` |
| `.sk-toc` with `[data-sk-toc-toggle]` (or `.sk-toc-toggle`) | hamburger open/close, closes on link tap, Escape, or outside click |
| `.sk-toc a` linked to section ids | scroll spy: highlights the current section |
| `.sk-mermaid-wrap` | zoom (`Ctrl/Cmd` + scroll or buttons), drag to pan, open full size |
| `.sk-prompt__copy` or `[data-sk-copy]` | copies the body; selects the text if the clipboard is blocked |

Public API on `window.SwissKit`: `isDark()`, `setTheme('dark'|'light')`, `toggleTheme()`, and `mermaidThemeVars(dark)`.

All motion respects `prefers-reduced-motion`. There is a print stylesheet that drops the toggle, sidebar, and zoom controls.

## diagrams (Mermaid)

The kit does not bundle Mermaid, but it ships the theme variables and the container behaviors. Add a `<pre class="sk-mermaid">` inside a `.sk-mermaid-wrap`, then load Mermaid yourself and call `SwissKit.mermaidThemeVars()`:

```html
<div class="sk-mermaid-wrap">
  <div class="sk-zoom">
    <button type="button" data-sk-zoom="in" aria-label="Zoom in">+</button>
    <button type="button" data-sk-zoom="out" aria-label="Zoom out">&minus;</button>
    <button type="button" data-sk-zoom="reset" aria-label="Reset zoom">&#8634;</button>
    <button type="button" data-sk-zoom="expand" aria-label="Open full size">&#x26F6;</button>
  </div>
  <div class="sk-mermaid-viewport">
    <pre class="sk-mermaid">flowchart TD ...</pre>
  </div>
</div>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  var nodes = Array.prototype.slice.call(document.querySelectorAll('.sk-mermaid'));
  var sources = nodes.map(function (n) { return n.textContent; });
  async function render() {
    var dark = window.SwissKit ? window.SwissKit.isDark() : false;
    mermaid.initialize({
      startOnLoad: false, theme: 'base',
      flowchart: { curve: 'basis', padding: 14, useMaxWidth: true },
      themeVariables: window.SwissKit ? window.SwissKit.mermaidThemeVars(dark) : {}
    });
    nodes.forEach(function (n, i) { n.removeAttribute('data-processed'); n.textContent = sources[i]; });
    try { await mermaid.run({ nodes: nodes }); } catch (e) { console.error('mermaid render failed', e); }
  }
  document.addEventListener('sk:themechange', render);
  render();
</script>
```

`useMaxWidth: true` is the one setting that matters for mobile: it stamps the SVG with a viewBox so it scales to its container instead of overflowing.

## redaction

Use `.sk-redact` chips to stand in for names and projects. Keep the real values out of the HTML source, not just visually hidden — a chip like `<span class="sk-redact">a project</span>` ships nothing private.

## browser support

Anything modern. Uses `clamp()` for fluid type, `color-mix()` for the gauge tints, `:has()` to scope smooth scroll, and `100dvh` for the mobile menu height.

## license

MIT. Take it, fork it, retheme it.
