mirror of
https://github.com/jaronpate/folio.git
synced 2026-10-18 17:06:56 +00:00
styleguide
This commit is contained in:
+59
-25
@@ -29,82 +29,105 @@
|
||||
--radius: 4px;
|
||||
}
|
||||
|
||||
html.font-plantin {
|
||||
html.font-plantin,
|
||||
.font-plantin {
|
||||
--font: 'Plantin MT Pro', Georgia, serif;
|
||||
--body-weight: 300;
|
||||
}
|
||||
|
||||
html.font-sohne {
|
||||
html.font-sohne,
|
||||
.font-sohne {
|
||||
--font: 'SohneBuch', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-space {
|
||||
html.font-space,
|
||||
.font-space {
|
||||
--font: 'Space Mono', ui-monospace, Menlo, monospace;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-georgia {
|
||||
html.font-georgia,
|
||||
.font-georgia {
|
||||
--font: Georgia, 'Times New Roman', Times, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-palatino {
|
||||
html.font-palatino,
|
||||
.font-palatino {
|
||||
--font: Palatino, 'Palatino Linotype', 'Book Antiqua', serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction {
|
||||
html.font-redaction,
|
||||
.font-redaction {
|
||||
--font: 'Redaction', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction-10 {
|
||||
html.font-redaction-10,
|
||||
.font-redaction-10 {
|
||||
--font: 'Redaction 10', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction-20 {
|
||||
html.font-redaction-20,
|
||||
.font-redaction-20 {
|
||||
--font: 'Redaction 20', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction-35 {
|
||||
html.font-redaction-35,
|
||||
.font-redaction-35 {
|
||||
--font: 'Redaction 35', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction-50 {
|
||||
html.font-redaction-50,
|
||||
.font-redaction-50 {
|
||||
--font: 'Redaction 50', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction-70 {
|
||||
html.font-redaction-70,
|
||||
.font-redaction-70 {
|
||||
--font: 'Redaction 70', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction-100 {
|
||||
html.font-redaction-100,
|
||||
.font-redaction-100 {
|
||||
--font: 'Redaction 100', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-system {
|
||||
html.font-system,
|
||||
.font-system {
|
||||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
.dark-mode,
|
||||
html.theme-navy,
|
||||
.theme-navy,
|
||||
html.theme-dusk,
|
||||
.theme-dusk,
|
||||
html.theme-midnight,
|
||||
.theme-midnight,
|
||||
html.theme-indigo,
|
||||
.theme-indigo,
|
||||
html.theme-storm,
|
||||
.theme-storm,
|
||||
html.theme-slate,
|
||||
.theme-slate,
|
||||
html.theme-ink,
|
||||
.theme-ink,
|
||||
html.theme-pine,
|
||||
.theme-pine,
|
||||
html.theme-wine,
|
||||
html.theme-cocoa {
|
||||
.theme-wine,
|
||||
html.theme-cocoa,
|
||||
.theme-cocoa {
|
||||
--text: #fbfbf7;
|
||||
--muted: #9aa4b8;
|
||||
--accent: #e78c88;
|
||||
@@ -112,7 +135,8 @@ html.theme-cocoa {
|
||||
--navy: #3c48a8;
|
||||
}
|
||||
|
||||
html.theme-paper {
|
||||
html.theme-paper,
|
||||
.theme-paper {
|
||||
--bg: #f6f7f9;
|
||||
--bg-secondary: #e9ebef;
|
||||
--text: #2e3140;
|
||||
@@ -125,7 +149,8 @@ html.theme-paper {
|
||||
--img-matte: #e9ebef;
|
||||
}
|
||||
|
||||
html.theme-navy {
|
||||
html.theme-navy,
|
||||
.theme-navy {
|
||||
--bg: #12192c;
|
||||
--bg-secondary: #1a2238;
|
||||
--hairline: #2a334c;
|
||||
@@ -133,7 +158,8 @@ html.theme-navy {
|
||||
--img-matte: #1a2238;
|
||||
}
|
||||
|
||||
html.theme-dusk {
|
||||
html.theme-dusk,
|
||||
.theme-dusk {
|
||||
--bg: #1a2236;
|
||||
--bg-secondary: #252d44;
|
||||
--hairline: #343e58;
|
||||
@@ -141,7 +167,8 @@ html.theme-dusk {
|
||||
--img-matte: #252d44;
|
||||
}
|
||||
|
||||
html.theme-midnight {
|
||||
html.theme-midnight,
|
||||
.theme-midnight {
|
||||
--bg: #0e1424;
|
||||
--bg-secondary: #161d30;
|
||||
--hairline: #242c42;
|
||||
@@ -149,7 +176,8 @@ html.theme-midnight {
|
||||
--img-matte: #161d30;
|
||||
}
|
||||
|
||||
html.theme-indigo {
|
||||
html.theme-indigo,
|
||||
.theme-indigo {
|
||||
--bg: #16142a;
|
||||
--bg-secondary: #1f1c38;
|
||||
--hairline: #2e2a4a;
|
||||
@@ -157,7 +185,8 @@ html.theme-indigo {
|
||||
--img-matte: #1f1c38;
|
||||
}
|
||||
|
||||
html.theme-storm {
|
||||
html.theme-storm,
|
||||
.theme-storm {
|
||||
--bg: #1c2438;
|
||||
--bg-secondary: #283044;
|
||||
--hairline: #3a4458;
|
||||
@@ -165,7 +194,8 @@ html.theme-storm {
|
||||
--img-matte: #283044;
|
||||
}
|
||||
|
||||
html.theme-slate {
|
||||
html.theme-slate,
|
||||
.theme-slate {
|
||||
--bg: #1e212b;
|
||||
--bg-secondary: #2a2d3a;
|
||||
--hairline: #3a3e4c;
|
||||
@@ -173,7 +203,8 @@ html.theme-slate {
|
||||
--img-matte: #2a2d3a;
|
||||
}
|
||||
|
||||
html.theme-ink {
|
||||
html.theme-ink,
|
||||
.theme-ink {
|
||||
--bg: #0c0c0c;
|
||||
--bg-secondary: #1c1c1c;
|
||||
--hairline: #2a2a2a;
|
||||
@@ -181,7 +212,8 @@ html.theme-ink {
|
||||
--img-matte: #1c1c1c;
|
||||
}
|
||||
|
||||
html.theme-pine {
|
||||
html.theme-pine,
|
||||
.theme-pine {
|
||||
--bg: #121c22;
|
||||
--bg-secondary: #1a262c;
|
||||
--hairline: #2a3840;
|
||||
@@ -189,7 +221,8 @@ html.theme-pine {
|
||||
--img-matte: #1a262c;
|
||||
}
|
||||
|
||||
html.theme-wine {
|
||||
html.theme-wine,
|
||||
.theme-wine {
|
||||
--bg: #1a1520;
|
||||
--bg-secondary: #261c28;
|
||||
--hairline: #3a2c38;
|
||||
@@ -197,7 +230,8 @@ html.theme-wine {
|
||||
--img-matte: #261c28;
|
||||
}
|
||||
|
||||
html.theme-cocoa {
|
||||
html.theme-cocoa,
|
||||
.theme-cocoa {
|
||||
--bg: #1c1814;
|
||||
--bg-secondary: #2a241c;
|
||||
--hairline: #3c3428;
|
||||
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
The quoted title becomes the caption. Skip it and the image still sits in a figure, never wider than itself.
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -6,6 +6,7 @@ export default defineNuxtConfig({
|
||||
nitro: {
|
||||
prerender: {
|
||||
autoSubfolderIndex: false,
|
||||
routes: ['/styleguide'],
|
||||
},
|
||||
preset: 'cloudflare_module',
|
||||
cloudflare: {
|
||||
|
||||
Reference in New Issue
Block a user