# Styleguide

This page is both the **reference and the proof** — every element below is real
Markdown rendered by Ovellum's default template, so what you see is exactly what
your own pages get. It documents the type scale, the vertical rhythm, the color
system, and each content element you can write.

Try the **appearance control** in the top bar while you read: switch mode
(auto / light / dark), pick a theme, set a color, bump the **text size**, or
change the **font** (Default / Serif / Inter / Geist). Everything on this page
re-tints and re-flows live, because it's all driven by the same design tokens.

## Typography

Text and headings use the **system sans-serif** stack by default — instant first
paint, no webfont hop. Readers can switch to **Serif**, **Inter**, or **Geist**
from the appearance control's **Font** picker (Inter and Geist ship with the
template and load only when chosen), and step the **Text size** up or down. The
typeface and size are the *reader's* choice; the scale, rhythm, and color below
are Ovellum's shipped defaults and apply to any template.

Body text runs at a **`1.55` line-height** inside a **`76ch` measure** — the
reading column is capped at roughly 76 characters per line, the band where long
prose stays comfortable. Headings tighten to a **`1.2` line-height** with a
small negative letter-spacing so they read as one unit rather than spaced
words.

### Type scale

Sizes come from a **modular scale**: one base size, multiplied by a fixed ratio
at each step. Ovellum uses a **major third (1.25)** on a fluid base that grows
from ~15px on small screens to 16px on wide ones (a single `clamp()` — no
breakpoints). Each heading maps to a step, so the hierarchy is mathematical, not
hand-picked:

| Step            | Ratio from base | Used by        |
| --------------- | --------------- | -------------- |
| `--font-size-4` | 1.25⁴ (≈ 2.44×) | `h1`           |
| `--font-size-3` | 1.25³ (≈ 1.95×) | `h2`           |
| `--font-size-2` | 1.25² (≈ 1.56×) | `h3`           |
| `--font-size-1` | 1.25¹ (1.25×)   | `h4`, lead     |
| `--font-size-0` | 1× (base)       | body, `p`, `li`|

Because every step is `calc(previous × var(--ratio))`, changing the one
`--ratio` token reflows the entire hierarchy in proportion.

### Headings

The six levels, rendered at their real sizes:

# Heading level 1
## Heading level 2
### Heading level 3
#### Heading level 4
##### Heading level 5
###### Heading level 6

Each heading gets a stable `id` and a hover anchor (`#`) so any section is
directly linkable, and `h2`/`h3` are collected into the "On this page" list to
the right.

## Vertical rhythm

Spacing isn't ad hoc — it's a **fluid space scale** (`--space-3xs` through
`--space-3xl`), each step a `clamp()` that grows gently with the viewport. Block
elements share one rule: **no top margin, a consistent gap below**, so rhythm
accumulates predictably down the page. Headings add a larger lead-in *above*
(an `h2` opens a new section with generous space before it, tight space after),
which is what makes a heading feel attached to the text it introduces.

## Paragraphs and inline text

A paragraph sets the baseline. This sentence shows **bold for emphasis**,
*italic for stress*, and `inline code` for identifiers — code gets a faint tint
and a monospace face so a symbol like `OvellumUserConfig` never gets mistaken
for prose. Links such as [the configuration reference](/docs/reference/config/)
carry an accent color and a thin underline offset from the baseline; they
[change tint on hover](/docs/reference/config/). You can also strike text with
~~strikethrough~~ for retractions.

> A blockquote sits slightly inset with a single rule on the leading edge and
> muted text — quiet, not boxed. Use it for an aside or a pulled quote, not for
> emphasis (that's what bold is for).

### Callouts

GitHub-style alert blockquotes become tinted callout panels:

> [!NOTE]
> Useful context that complements the surrounding prose.

> [!TIP]
> A shortcut or a better way to do the thing.

> [!IMPORTANT]
> Something the reader genuinely shouldn't miss.

> [!WARNING]
> A sharp edge — proceed deliberately.

> [!CAUTION]
> A risk of data loss or a hard-to-reverse action.

### Component directives

The `:::` directives (see the [Components guide](/docs/guides/components/))
rendered live. Callout with a custom label:

:::tip{title="Shortcut"}
The same panels are reachable via `:::note` / `:::warning` directives.
:::

Steps:

::::steps
:::step{title="Install"}
`npm install -D ovellum`
:::
:::step{title="Initialize"}
Run `npx ovellum init`.
:::
::::

Cards:

::::cards
:::card{title="Manual mode" href="/docs/guides/manual-mode/"}
Build a static site from Markdown.
:::
:::card{title="Components" href="/docs/guides/components/"}
The full directive reference.
:::
::::

Tabs:

::::tabs
:::tab{label="npm"}
`npm install -D ovellum`
:::
:::tab{label="pnpm"}
`pnpm add -D ovellum`
:::
::::

Code groups:

:::code-group
```bash
npm install -D ovellum
```
```bash title="pnpm"
pnpm add -D ovellum
```
:::

Diagram (Mermaid — loaded on demand):

```mermaid
graph LR
  Source --> Parser --> Generator --> Merger --> Docs
```

## Lists

Unordered lists use a quiet marker in a subdued color, and nest with reduced
spacing so depth reads as hierarchy:

- A first item, kept to a line or two.
- A second item, with nested detail:
  - A nested point.
  - Another, to show the indent step.
    - One level deeper still.
- A final item.

Ordered lists carry the same rhythm:

1. Author the page in Markdown.
2. Run `ovellum build`.
3. Deploy the `dist/` output anywhere static.

Task lists render their checkboxes:

- [x] Typography scale
- [x] Color system
- [ ] Your next page

## Footnotes

A footnote reference sits as a small superscript[^sg-note] that links down to
the note, and the note links back. References number by order of appearance,
and the notes gather into a tinted panel — one type-step smaller than body
text — at the foot of the page.[^sg-second]

[^sg-note]: The note itself takes inline Markdown — `code`, *emphasis*, even a
[link](/docs/reference/config/).
[^sg-second]: A second note, to show the list numbering and the panel rhythm.

## Code

Inline code reads as `const x = 1`. Fenced blocks are highlighted by Shiki using
a dual light/dark theme emitted through CSS variables — so the colors flip with
the appearance mode at zero runtime cost — with a language eyebrow and a copy
button:

```typescript
import { defineConfig } from 'ovellum';

export default defineConfig({
  mode: 'manual',
  input: 'content',
  output: 'dist',
  site: {
    title: 'My Docs',
    palette: 'default',
  },
});
```

```bash
ovellum build
```

## Tables

Tables are editorial-calm — horizontal rules only, no grid lines, no header
fill. The header reads through weight and a thicker bottom rule; a table wider
than the column scrolls horizontally rather than blowing out the layout:

| Element     | Token            | Note                                  |
| ----------- | ---------------- | ------------------------------------- |
| Body text   | `--font-size-0`  | Fluid 15→16px base                    |
| Measure     | `--content-max`  | `76ch` reading column                 |
| Body rhythm | `--leading-normal` | `1.55` line-height                  |
| Headings    | `--leading-tight`  | `1.2` line-height, negative tracking |

## Images

Images scale to the column width, keep their aspect ratio, and pick up the same
corner radius as other media:

![A sample photograph](/docs/reference/styleguide-sample.jpg)

This one sits next to the page's Markdown file (`styleguide-sample.jpg`) and is
referenced with a root-absolute path — the pattern the
[Assets & downloads](/docs/guides/assets/) guide recommends for page-specific
images.

## Video

Paste the embed code straight from YouTube or Vimeo — "Share → Embed", copied
verbatim — and it just works. Ovellum allows `<iframe>` **only** from those known
video hosts, hardens it, and wraps it in a responsive 16:9 frame:

<iframe width="560" height="315" src="https://www.youtube.com/embed/1Jpjw2w_0l8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

For the YouTube video embed above, here is the code — the exact snippet YouTube
hands you under **Share → Embed**, dropped straight into the Markdown:

```html
<iframe
  width="560"
  height="315"
  src="https://www.youtube.com/embed/1Jpjw2w_0l8"
  title="YouTube video player"
  frameborder="0"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
  referrerpolicy="strict-origin-when-cross-origin"
  allowfullscreen
></iframe>
```

Nothing else to do — the fixed `width`/`height` and `frameborder` come along
harmlessly. Ovellum's responsive wrapper overrides the pixel dimensions, and
`loading="lazy"` plus a strict referrer policy are applied automatically. (Swap
in `https://www.youtube-nocookie.com/embed/…` for YouTube's privacy-preserving
host.) For self-hosted clips, the native `<video>` / `<audio>` players work too.
Both are covered in [Assets & downloads](/docs/guides/assets/).

## Horizontal rule

A thematic break sets a quiet divider between passages:

---

That rule, like everything above, is the default template doing its job — no
custom CSS on this page.

## Color system

Five palettes ship, each authored in **OKLCH** (perceptually uniform, so a ramp
steps in even visual increments rather than the uneven jumps sRGB hex gives):

- **Ovellum** — neutral grays, the default.
- **Nord** — cool, frosty blue-grays.
- **Flexoki** — warm, inky paper.
- **Solarized** — Ethan Schoonover's measured base tones.
- **E-ink** — high-contrast warm paper and ink, monochrome.

Each palette is one gray ramp plus an accent; **dark mode reuses the same ramp
with reversed indices**, so there's no second set of values to maintain. The
appearance control's **Color** picker overrides just the accent, live. All of it
flows through semantic tokens (surface, border, foreground, link), which is why
a single switch re-tints the entire page — this one included.
