styleguide

This commit is contained in:
2026-08-25 22:33:31 -05:00
parent 6a34a90d11
commit 41e3031843
4 changed files with 662 additions and 148 deletions
+59 -25
View File
@@ -29,82 +29,105 @@
--radius: 4px; --radius: 4px;
} }
html.font-plantin { html.font-plantin,
.font-plantin {
--font: 'Plantin MT Pro', Georgia, serif; --font: 'Plantin MT Pro', Georgia, serif;
--body-weight: 300; --body-weight: 300;
} }
html.font-sohne { html.font-sohne,
.font-sohne {
--font: 'SohneBuch', -apple-system, BlinkMacSystemFont, sans-serif; --font: 'SohneBuch', -apple-system, BlinkMacSystemFont, sans-serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-space { html.font-space,
.font-space {
--font: 'Space Mono', ui-monospace, Menlo, monospace; --font: 'Space Mono', ui-monospace, Menlo, monospace;
--body-weight: 400; --body-weight: 400;
} }
html.font-georgia { html.font-georgia,
.font-georgia {
--font: Georgia, 'Times New Roman', Times, serif; --font: Georgia, 'Times New Roman', Times, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-palatino { html.font-palatino,
.font-palatino {
--font: Palatino, 'Palatino Linotype', 'Book Antiqua', serif; --font: Palatino, 'Palatino Linotype', 'Book Antiqua', serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-redaction { html.font-redaction,
.font-redaction {
--font: 'Redaction', Georgia, serif; --font: 'Redaction', Georgia, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-redaction-10 { html.font-redaction-10,
.font-redaction-10 {
--font: 'Redaction 10', Georgia, serif; --font: 'Redaction 10', Georgia, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-redaction-20 { html.font-redaction-20,
.font-redaction-20 {
--font: 'Redaction 20', Georgia, serif; --font: 'Redaction 20', Georgia, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-redaction-35 { html.font-redaction-35,
.font-redaction-35 {
--font: 'Redaction 35', Georgia, serif; --font: 'Redaction 35', Georgia, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-redaction-50 { html.font-redaction-50,
.font-redaction-50 {
--font: 'Redaction 50', Georgia, serif; --font: 'Redaction 50', Georgia, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-redaction-70 { html.font-redaction-70,
.font-redaction-70 {
--font: 'Redaction 70', Georgia, serif; --font: 'Redaction 70', Georgia, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-redaction-100 { html.font-redaction-100,
.font-redaction-100 {
--font: 'Redaction 100', Georgia, serif; --font: 'Redaction 100', Georgia, serif;
--body-weight: 400; --body-weight: 400;
} }
html.font-system { html.font-system,
.font-system {
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--body-weight: 400; --body-weight: 400;
} }
.dark-mode, .dark-mode,
html.theme-navy, html.theme-navy,
.theme-navy,
html.theme-dusk, html.theme-dusk,
.theme-dusk,
html.theme-midnight, html.theme-midnight,
.theme-midnight,
html.theme-indigo, html.theme-indigo,
.theme-indigo,
html.theme-storm, html.theme-storm,
.theme-storm,
html.theme-slate, html.theme-slate,
.theme-slate,
html.theme-ink, html.theme-ink,
.theme-ink,
html.theme-pine, html.theme-pine,
.theme-pine,
html.theme-wine, html.theme-wine,
html.theme-cocoa { .theme-wine,
html.theme-cocoa,
.theme-cocoa {
--text: #fbfbf7; --text: #fbfbf7;
--muted: #9aa4b8; --muted: #9aa4b8;
--accent: #e78c88; --accent: #e78c88;
@@ -112,7 +135,8 @@ html.theme-cocoa {
--navy: #3c48a8; --navy: #3c48a8;
} }
html.theme-paper { html.theme-paper,
.theme-paper {
--bg: #f6f7f9; --bg: #f6f7f9;
--bg-secondary: #e9ebef; --bg-secondary: #e9ebef;
--text: #2e3140; --text: #2e3140;
@@ -125,7 +149,8 @@ html.theme-paper {
--img-matte: #e9ebef; --img-matte: #e9ebef;
} }
html.theme-navy { html.theme-navy,
.theme-navy {
--bg: #12192c; --bg: #12192c;
--bg-secondary: #1a2238; --bg-secondary: #1a2238;
--hairline: #2a334c; --hairline: #2a334c;
@@ -133,7 +158,8 @@ html.theme-navy {
--img-matte: #1a2238; --img-matte: #1a2238;
} }
html.theme-dusk { html.theme-dusk,
.theme-dusk {
--bg: #1a2236; --bg: #1a2236;
--bg-secondary: #252d44; --bg-secondary: #252d44;
--hairline: #343e58; --hairline: #343e58;
@@ -141,7 +167,8 @@ html.theme-dusk {
--img-matte: #252d44; --img-matte: #252d44;
} }
html.theme-midnight { html.theme-midnight,
.theme-midnight {
--bg: #0e1424; --bg: #0e1424;
--bg-secondary: #161d30; --bg-secondary: #161d30;
--hairline: #242c42; --hairline: #242c42;
@@ -149,7 +176,8 @@ html.theme-midnight {
--img-matte: #161d30; --img-matte: #161d30;
} }
html.theme-indigo { html.theme-indigo,
.theme-indigo {
--bg: #16142a; --bg: #16142a;
--bg-secondary: #1f1c38; --bg-secondary: #1f1c38;
--hairline: #2e2a4a; --hairline: #2e2a4a;
@@ -157,7 +185,8 @@ html.theme-indigo {
--img-matte: #1f1c38; --img-matte: #1f1c38;
} }
html.theme-storm { html.theme-storm,
.theme-storm {
--bg: #1c2438; --bg: #1c2438;
--bg-secondary: #283044; --bg-secondary: #283044;
--hairline: #3a4458; --hairline: #3a4458;
@@ -165,7 +194,8 @@ html.theme-storm {
--img-matte: #283044; --img-matte: #283044;
} }
html.theme-slate { html.theme-slate,
.theme-slate {
--bg: #1e212b; --bg: #1e212b;
--bg-secondary: #2a2d3a; --bg-secondary: #2a2d3a;
--hairline: #3a3e4c; --hairline: #3a3e4c;
@@ -173,7 +203,8 @@ html.theme-slate {
--img-matte: #2a2d3a; --img-matte: #2a2d3a;
} }
html.theme-ink { html.theme-ink,
.theme-ink {
--bg: #0c0c0c; --bg: #0c0c0c;
--bg-secondary: #1c1c1c; --bg-secondary: #1c1c1c;
--hairline: #2a2a2a; --hairline: #2a2a2a;
@@ -181,7 +212,8 @@ html.theme-ink {
--img-matte: #1c1c1c; --img-matte: #1c1c1c;
} }
html.theme-pine { html.theme-pine,
.theme-pine {
--bg: #121c22; --bg: #121c22;
--bg-secondary: #1a262c; --bg-secondary: #1a262c;
--hairline: #2a3840; --hairline: #2a3840;
@@ -189,7 +221,8 @@ html.theme-pine {
--img-matte: #1a262c; --img-matte: #1a262c;
} }
html.theme-wine { html.theme-wine,
.theme-wine {
--bg: #1a1520; --bg: #1a1520;
--bg-secondary: #261c28; --bg-secondary: #261c28;
--hairline: #3a2c38; --hairline: #3a2c38;
@@ -197,7 +230,8 @@ html.theme-wine {
--img-matte: #261c28; --img-matte: #261c28;
} }
html.theme-cocoa { html.theme-cocoa,
.theme-cocoa {
--bg: #1c1814; --bg: #1c1814;
--bg-secondary: #2a241c; --bg-secondary: #2a241c;
--hairline: #3c3428; --hairline: #3c3428;
+602
View File
@@ -0,0 +1,602 @@
<script setup lang="ts">
import {
BODY_FONTS,
REDACTION_STRENGTHS,
redactionFamily,
} from '~/utils/fonts';
import { BG_THEMES } from '~/utils/themes';
import { SITE_URL } from '~/utils/site';
const TOKEN_NAMES = [
'--bg',
'--bg-secondary',
'--text',
'--muted',
'--accent',
'--accent-soft',
'--navy',
'--hairline',
'--code-bg',
'--img-matte',
] as const;
const tokenValues = ref<Record<string, string>>({});
const themeTokens = ref<Record<string, Record<string, string>>>({});
const { id: themeId, setTheme } = useBgTheme();
const { id: fontId, setFont } = useBodyFont();
const pageUrl = `${SITE_URL}/styleguide`;
useSeoMeta({
title: 'Styleguide',
ogTitle: 'Styleguide',
description: 'Colors, type, and components used on this site.',
ogDescription: 'Colors, type, and components used on this site.',
ogUrl: pageUrl,
robots: 'noindex',
});
useHead({
link: [{ rel: 'canonical', href: pageUrl }],
});
const { data: writingPages } = await useAsyncData(
'styleguide-writing',
async () => {
const all = await queryCollection('writing').order('date', 'DESC').all();
return all.filter((page) => !page.hidden).slice(0, 4);
},
);
const { data: projectPages } = await useAsyncData(
'styleguide-projects',
async () => {
const all = await queryCollection('projects').order('title', 'ASC').all();
return all.filter((page) => !page.hidden).slice(0, 4);
},
);
const carouselImages = [
{
src: '/work/edlink/billing-overview.png',
alt: 'Billing overview',
},
{
src: '/work/edlink/invoice-sheet.png',
alt: 'Invoice view',
},
{
src: '/work/edlink/authentication-widget.gif',
alt: 'Auth widget',
},
];
function cssColorToHex(value: string) {
const raw = value.trim().toLowerCase();
if (!raw) return '';
if (raw.startsWith('#')) {
if (raw.length === 4) {
const r = raw[1];
const g = raw[2];
const b = raw[3];
return `#${r}${r}${g}${g}${b}${b}`;
}
return raw.slice(0, 7);
}
const parts = raw.match(/rgba?\(\s*(\d+)[\s,/]+(\d+)[\s,/]+(\d+)/);
if (!parts) return raw;
return (
'#' +
[parts[1], parts[2], parts[3]]
.map((n) => Number(n).toString(16).padStart(2, '0'))
.join('')
);
}
function readCssVars(element: Element) {
const styles = getComputedStyle(element);
const next: Record<string, string> = {};
for (const name of TOKEN_NAMES) {
next[name] = cssColorToHex(styles.getPropertyValue(name));
}
return next;
}
function readTokens() {
if (!import.meta.client) return;
tokenValues.value = readCssVars(document.documentElement);
}
function readThemeTokens() {
if (!import.meta.client) return;
const next: Record<string, Record<string, string>> = {};
for (const theme of BG_THEMES) {
const probe = document.createElement('div');
probe.className = `theme-${theme.id}`;
probe.setAttribute('aria-hidden', 'true');
document.body.append(probe);
next[theme.id] = readCssVars(probe);
probe.remove();
}
themeTokens.value = next;
}
function onFontSelect(event: Event) {
const value = (event.target as HTMLSelectElement).value;
const next = BODY_FONTS.find((font) => font.id === value);
if (next) setFont(next.id);
}
function redactionClass(grade: (typeof REDACTION_STRENGTHS)[number]) {
return grade === 0 ? 'font-redaction' : `font-redaction-${grade}`;
}
onMounted(() => {
readTokens();
readThemeTokens();
const observer = new MutationObserver(readTokens);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});
onUnmounted(() => observer.disconnect());
});
</script>
<template>
<NuxtLayout
name="main"
title="Styleguide"
description="Everything this site knows how to look like."
>
<article>
<p>
This is the kitchen sink. Change the theme up top and the
colors below update with it. The fonts on this page keep their
own face so you can actually compare them.
</p>
<ul class="guide-toc">
<li><a href="#color">Color</a></li>
<li><a href="#type">Type</a></li>
<li><a href="#prose">Prose</a></li>
<li><a href="#components">Components</a></li>
</ul>
<h2 id="color">Color</h2>
<p>
Flip the background in the header and these chips follow.
</p>
<div class="swatches">
<div
v-for="name in TOKEN_NAMES"
:key="name"
class="swatch"
>
<div
class="swatch__chip"
:style="{ background: `var(${name})` }"
/>
<code>{{ name }}</code>
<span class="swatch__value">{{
tokenValues[name] || '—'
}}</span>
</div>
</div>
<p>
Highlight this sentence if you want to see selection. Accent
fill, white type.
</p>
<h3>Themes</h3>
<p>
Click a card to switch the whole site. Same thing as the
dropdown. Current one has the outline.
</p>
<div class="theme-grid">
<button
v-for="theme in BG_THEMES"
:key="theme.id"
type="button"
class="theme-card"
:class="[
`theme-${theme.id}`,
{ 'theme-card--current': themeId === theme.id },
]"
@click="setTheme(theme.id)"
>
<span class="theme-card__name">{{ theme.label }}</span>
<code class="theme-card__value">{{
themeTokens[theme.id]?.['--bg'] || '—'
}}</code>
<span class="theme-card__chips" aria-hidden="true">
<span class="theme-card__chip theme-card__chip--secondary" />
<span class="theme-card__chip theme-card__chip--accent" />
<span class="theme-card__chip theme-card__chip--muted" />
<span class="theme-card__chip theme-card__chip--hairline" />
</span>
</button>
</div>
<h2 id="type">Type</h2>
<p>
Body font is whatever you pick here.
</p>
<label class="guide-label">
Body font
<select
class="guide-select"
:value="fontId"
@change="onFontSelect"
>
<option
v-for="font in BODY_FONTS"
:key="font.id"
:value="font.id"
>
{{ font.label }}
</option>
</select>
</label>
<p>
Three jobs: the body face you pick, Sohne for UI, Space Mono
for code.
</p>
<p class="role-plain">
Sohne for nav, dates, captions, and this line.
</p>
<p class="role-mono">Space Mono for the wordmark, footer, and code.</p>
<h3>Fonts</h3>
<div class="font-list">
<p
v-for="font in BODY_FONTS"
:key="font.id"
class="font-sample"
:class="`font-${font.id}`"
>
<span class="font-sample__name">{{ font.label }}</span>
The best sticking point is the way using the thing
<em>feels</em>.
</p>
</div>
<h3>Redaction</h3>
<p>
You can set a span in Redaction. Strength is 0, 10, 20, 35, 50,
70, or 100. Anything else snaps to the closest. Default is 100.
</p>
<ul>
<li v-for="grade in REDACTION_STRENGTHS" :key="grade">
{{ grade }}:
<span
class="redaction-sample"
:class="redactionClass(grade)"
>you werent supposed to see this</span>
<span class="grade-face">{{ redactionFamily(grade) }}</span>
</li>
</ul>
<h2 id="prose">Prose</h2>
<p>
Regular writing. You can <em>italicize</em>,
<strong>bold</strong>, or
<a href="https://jaron.sh">link out</a>. Second and third level
headings look the same on purpose. Italic, muted, not trying to
be a poster.
</p>
<h3>Still just a heading</h3>
<p>Same size. I dont really do heading hierarchy.</p>
<h3>Lists</h3>
<p>Bullets, if you need them:</p>
<ul>
<li>Plantin for reading</li>
<li>Space Mono for the wordmark, footer, and code</li>
<li>Redaction only if you ask for it</li>
</ul>
<p>Numbers, if order matters:</p>
<ol>
<li>Write the thing</li>
<li>Stick a note in the margin if you have to</li>
<li>Redact whatever should stay almost-said</li>
</ol>
<h3>Blockquote</h3>
<p>Gets a little pointing hand in the gutter.</p>
<blockquote>
<p>
The best sticking point is the way using the thing
<em>feels</em>. Everything else is infrastructure.
</p>
</blockquote>
<h3>Code</h3>
<p>Inline looks like <code>ping</code>. Blocks look like this:</p>
<pre><code>client.chatCommands.register('ping', (context) => {
context.reply('Pong!', true);
});</code></pre>
<p>
Code in a post also gets syntax colors. They flip with the
theme.
</p>
<h3>Divider</h3>
<hr />
<h3>Images</h3>
<p>
An image never gets wider than the actual file. A caption sits
underneath if you add one.
</p>
<ProseImg
src="/work/edlink/billing-overview.png"
alt="Billing overview"
title="The billing screen"
/>
<p>
Small files stay small. This mark is 460px so it just sits
there, not stretched.
</p>
<ProseImg src="/favicon.png" alt="Site mark" />
<h2 id="components">Components</h2>
<p>The extra bits that can show up in a post.</p>
<h3>Margin note</h3>
<p>
For a thought that does not belong in the sentence.
<MarginNote
>(Wide screens put it in the gutter. Narrow screens drop it
under the paragraph.)</MarginNote
>
Skip it if you want. That's kind of the point.
</p>
<p>
Two in a row so you can see them stack. First one.
<MarginNote>(Hey, I'm the first note.)</MarginNote>
Then another.
<MarginNote>(And I'm the second. Should not sit on top of the other one.)</MarginNote>
The rest of the sentence just keeps going.
</p>
<h3>Notice</h3>
<p>When you actually need to interrupt. Stays in the column.</p>
<Notice>
<p>
<strong>In-column.</strong> Navy, hard to miss. Use it when
the aside <em>is</em> the point for a minute.
</p>
</Notice>
<p>And if you want it edge to edge:</p>
<Notice bleed>
<p>
<strong>Bleed.</strong> Same idea, full width. Save it for
the one time you actually want to shout.
</p>
</Notice>
<h3>Redacted</h3>
<p>Same phrase, every grade:</p>
<ul>
<li v-for="grade in REDACTION_STRENGTHS" :key="`r-${grade}`">
{{ grade }}:
<Redacted :strength="grade"
>you werent supposed to see this</Redacted
>
</li>
</ul>
<p>
Mix it into a sentence: I probably shouldnt mention
<Redacted strength="20">the actual implementation</Redacted>,
and later
<Redacted strength="100">almost nothing remains</Redacted>.
<strong
>Bold around
<Redacted strength="50">a redacted span</Redacted></strong
>
should pick Redaction Bold.
</p>
<h3>Carousel</h3>
<p>
The selected work scroller from the homepage. Flick it
sideways.
</p>
<Carousel :images="carouselImages" />
<h3>Writing list</h3>
<p>How posts show up.</p>
<WritingList :pages="writingPages ?? []" />
<h3>Project list</h3>
<p>
How projects show up. Hover one that has an image.
</p>
<ProjectList :pages="projectPages ?? []" />
</article>
</NuxtLayout>
</template>
<style scoped>
.guide-toc {
margin: 0 0 2rem;
}
.swatches {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
gap: 0.85rem;
margin: 1.25rem 0 1.5rem;
}
.swatch__chip {
height: 3.1rem;
border: 1px solid var(--hairline);
border-radius: var(--radius);
margin-bottom: 0.4rem;
}
.swatch code {
display: block;
font-size: 0.72rem;
padding: 0;
background: none;
}
.swatch__value {
display: block;
font-family: var(--font-mono);
font-size: 0.72rem;
color: var(--muted);
margin-top: 0.15rem;
}
.theme-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
gap: 0.65rem;
margin: 1.25rem 0 1.5rem;
}
.theme-card {
appearance: none;
display: flex;
flex-direction: column;
gap: 0.65rem;
padding: 0.75rem 0.8rem 0.85rem;
border: 1px solid var(--hairline);
border-radius: var(--radius);
background: var(--bg);
color: var(--text);
cursor: pointer;
text-align: left;
font: inherit;
}
.theme-card--current {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.theme-card__name {
font-family: var(--font-plain);
font-size: 0.8rem;
}
.theme-card__value {
display: block;
font-size: 0.72rem;
padding: 0;
background: none;
color: var(--muted);
}
.theme-card__chips {
display: flex;
gap: 0.3rem;
}
.theme-card__chip {
flex: 1;
height: 0.7rem;
border-radius: 2px;
}
.theme-card__chip--secondary {
background: var(--bg-secondary);
}
.theme-card__chip--accent {
background: var(--accent);
}
.theme-card__chip--muted {
background: var(--muted);
}
.theme-card__chip--hairline {
background: var(--hairline);
}
.guide-label {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.65rem;
margin: 0 0 1em;
font-family: var(--font-plain);
font-size: 0.8rem;
color: var(--muted);
}
.guide-select {
appearance: none;
background: var(--bg-secondary);
color: var(--text);
border: 1px solid var(--hairline);
border-radius: var(--radius);
font-family: var(--font-plain);
font-size: 0.8rem;
line-height: 1;
padding: 0.4em 1.7em 0.4em 0.65em;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath fill='%239aa4b8' d='M0 0l5 6 5-6z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.55em center;
background-size: 0.5em;
cursor: pointer;
}
.role-plain {
font-family: var(--font-plain);
font-size: 0.9rem;
}
.role-mono {
font-family: var(--font-mono);
font-size: 0.85rem;
}
.font-list {
margin: 1rem 0 1.5rem;
}
.font-sample {
margin: 0 0 0.85rem;
line-height: 1.45;
font-family: var(--font);
font-weight: var(--body-weight);
}
.font-sample__name {
display: block;
font-family: var(--font-plain);
font-size: 0.72rem;
letter-spacing: 0.02em;
color: var(--muted);
margin-bottom: 0.15rem;
}
.redaction-sample {
font-family: var(--font);
font-weight: var(--body-weight);
}
.grade-face {
display: inline-block;
margin-left: 0.45rem;
font-family: var(--font-mono);
font-size: 0.72rem;
color: var(--muted);
}
.guide-toc + h2,
article :deep(.article-figure) {
scroll-margin-top: 1rem;
}
</style>
-123
View File
@@ -1,123 +0,0 @@
---
title: Notes in the margin
date: 2026-08-22
description: Every mark this site knows how to make — body type, asides, notices, redaction, quotes, lists, code, and images.
hidden: true
---
This page is a working sample, not an essay. Walk it when you change type, color, or a content component. The **description** above is the article lead — bold, then the body.
Ordinary copy sits in the column. You can *emphasize*, **stress**, or [link out](https://jaronpate.com). A thought that does not belong in the sentence can sit beside it. :margin-note[(A margin note. Wide screens put it in the right gutter. Narrow screens drop it under the paragraph.)] Parentheticals you can actually skip.
## Headings
`h2` and `h3` share one italic, muted style. Use them as section marks, not display type.
### A third-level mark
Still the same size and color. Hierarchy is the word, not the weight.
## Margin notes
Two notes in one stretch of text, so you can see them stack.
The first is a mutter. :margin-note[(First note — should sit next to this sentence.)] A little later, a second one arrives. :margin-note[(Second note. On a wide screen these should not collide with the column.)] Between them the column keeps moving.
```md
:margin-note[(like this)]
```
## Notices
A notice interrupts on purpose. It stays in the column.
::notice
**In-column.** Use this when the aside *is* the point for a minute. Navy, hard to miss, still inside the measure.
::
```md
::notice
**In-column.**
::
```
And when it needs to break the column:
::notice{bleed}
**Bleed.** Same idea, edge to edge. Save it for the one time in a piece you actually want to shout.
::
```md
::notice{bleed}
**Bleed.**
::
```
## Redaction
A span can be set in Redaction. `strength` is a grade: `0` is the clean face, `100` is almost gone. Other numbers snap to `0`, `10`, `20`, `35`, `50`, `70`, or `100`. Default is `100`.
```md
:redacted[these words]{strength="50"}
```
Same phrase, every grade:
- 0 — :redacted[the record is still intact]{strength="0"}
- 10 — :redacted[the record is still intact]{strength="10"}
- 20 — :redacted[the record is still intact]{strength="20"}
- 35 — :redacted[the record is still intact]{strength="35"}
- 50 — :redacted[the record is still intact]{strength="50"}
- 70 — :redacted[the record is still intact]{strength="70"}
- 100 — :redacted[the record is still intact]{strength="100"}
Inline, mixed into a sentence: the filing names :redacted[a person we will not repeat]{strength="20"}, then later :redacted[almost nothing remains]{strength="100"}.
**Bold around** :redacted[a redacted span]{strength="50"} should pick Redaction Bold.
## Blockquote
The pointing hand sits in the gutter. Use it for a quote, or a line you want pointed at.
> The best sticking point is the way using the thing *feels*. Everything else is infrastructure.
## Lists
Unordered, for a pile of equals:
- Plantin for reading
- Space Mono for the wordmark, footer, and code
- Redaction only where you ask for it
Ordered, when sequence matters:
1. Write the sentence
2. Park the mutter in a margin note
3. Redact what should stay almost-said
## Code
Inline: the helper is `resolveRedactionStrength`, and the shortcode is `:redacted`.
A block:
```ts
const grade = resolveRedactionStrength(strength);
const family = redactionFamily(grade);
```
## Image
A figure in the column. This is an old Edlink shot, just to see the matte, radius, and caption line.
![Billing overview](/work/edlink/billing-overview.png "The billing screen")
The quoted title becomes the caption. Skip it and the image still sits in a figure, never wider than itself.
```md
![Billing overview](/work/edlink/billing-overview.png "The billing screen")
```
---
That’s the set. Margin for a mutter. Notice for a point. Redaction when the words should still be there, barely. Everything else is ordinary prose.
+1
View File
@@ -6,6 +6,7 @@ export default defineNuxtConfig({
nitro: { nitro: {
prerender: { prerender: {
autoSubfolderIndex: false, autoSubfolderIndex: false,
routes: ['/styleguide'],
}, },
preset: 'cloudflare_module', preset: 'cloudflare_module',
cloudflare: { cloudflare: {