Refactored prose and added style for ToC.astro

This commit is contained in:
2026-09-28 12:03:07 +02:00
parent d06f0293ce
commit 99d2635763
15 changed files with 559 additions and 60 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

View File

@@ -55,3 +55,24 @@ const resolved = resolveImage(image)
</figcaption> </figcaption>
)} )}
</figure> </figure>
<style>
@layer components {
@media (--bp-desktop) {
.prose .figure--inline,
.prose .figure--wide {
@mixin me calc(-1 * (var(--size-12) + var(--layout-margin-width)));
display: grid;
grid-template-columns: minmax(0, 1fr) var(--layout-margin-width);
grid-template-rows: auto 1fr;
column-gap: var(--size-12);
}
.prose .figure--inline > :first-child,
.prose .figure--wide > :first-child {
grid-area: 1 / 1 / 3 / 2;
}
}
}
</style>

View File

@@ -4,7 +4,7 @@ import type { Heading } from '@/lib/markdoc/types'
type Props = { type Props = {
headings: Heading[] headings: Heading[]
min?: number min?: number
level: number level?: number
} }
const { headings, min = 3, level = 3 } = Astro.props const { headings, min = 3, level = 3 } = Astro.props
@@ -19,9 +19,81 @@ const entries = headings.filter((heading) => heading.level <= level)
<ol> <ol>
{entries.map((heading) => ( {entries.map((heading) => (
<li class:list={['entry', `entry--${heading.level}`]}> <li class:list={['entry', `entry--${heading.level}`]}>
<a href={`#${heading.id}`}>{heading.text}</a> <a href={`#${heading.id}`}>
<span class="n" aria-hidden="true" />
<span class="label">{heading.text}</span>
</a>
</li> </li>
))} ))}
</ol> </ol>
</nav> </nav>
)} )}
<style>
.toc {
@mixin border-t var(--border-card), solid, var(--color-border-strong);
counter-reset: toc;
position: sticky;
top: var(--size-10);
}
.title {
@mixin typo_label;
@mixin my var(--size-0);
@mixin py var(--size-4);
@mixin border-b var(--border-hairline), solid, var(--color-border-normal);
font-size: var(--type--2);
}
ol {
list-style: none;
}
a {
@mixin py var(--size-3);
@mixin border-b var(--border-hairline), solid, var(--color-border-subtle);
display: grid;
grid-template-columns: 4ch minmax(0, 1fr);
font-family: var(--font-mono);
font-size: var(--type--1);
line-height: var(--leading-2);
color: var(--color-text-primary);
text-decoration: none;
&:hover,
&:focus-visible {
background-color: var(--color-surface-hover);
}
}
.n {
color: var(--color-text-quiet);
}
.entry--2 {
counter-increment: toc;
& .n {
&::before {
content: counter(toc, upper-roman);
}
}
}
.entry--3 {
& a {
@mixin ps var(--size-8);
}
& .label {
color: var(--color-text-secondary);
}
& .n {
&::before {
content: '⟫';
}
}
}
</style>

View File

@@ -25,7 +25,7 @@ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliqu
## Heading two, long enough to wrap onto a second line ## Heading two, long enough to wrap onto a second line
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur{% footnote note="A footnote with *emphasis* and a [link](https://example.com)." /%}. Excepteur sint occaecat cupidatat non proident{% sidenote marker="†" note="A sidenote in the margin, beside the line it belongs to." /%}. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur{% footnote note="A footnote with *emphasis* and a [link](https://example.com)." /%}. Excepteur sint occaecat cupidatat non proident{% sidenote type="default" marker="†" note="A sidenote in the margin, beside the line it belongs to." /%}.
### Heading three ### Heading three
@@ -45,10 +45,10 @@ At vero eos et accusamus et iusto odio dignissimos ducimus.
- Third level - Third level
1. First step 1. First step
2. Second step 1. Second step
1. Sub-step 1. Sub-step
2. Another sub-step 1. Another sub-step
3. Third step 1. Third step
## Quotes and rules ## Quotes and rules
@@ -56,7 +56,10 @@ At vero eos et accusamus et iusto odio dignissimos ducimus.
> >
> With a second paragraph. > With a second paragraph.
{% quote cite="Paracelsus" source="Die dritte Defension" href="https://example.com" %} {% quote
cite="Paracelsus"
source="Die dritte Defension"
href="https://example.com" %}
All things are poison, and nothing is without poison; the dosage alone makes it so a thing is not a poison. All things are poison, and nothing is without poison; the dosage alone makes it so a thing is not a poison.
{% /quote %} {% /quote %}
@@ -113,8 +116,15 @@ for (const [key, value] of Object.entries(crucible)) {
## Images ## Images
{% figure image="/src/assets/content/articles/type-specimen/example.png" alt="A placeholder image" caption="An inline figure with a caption" credit="Dave Damage" size="inline" ratio="16-9" effect="none" /%} {% figure
image="/src/assets/content/articles/prima-materia/john_martin_-_the_great_day_of_his_wrath_-_google_art_project.jpg"
alt="A placeholder image"
caption="An inline figure with a caption"
credit="Dave Damage"
effect="none"
ratio="16-9"
size="inline" /%}
Aliquam erat volutpat. Fusce sed velit at arcu tincidunt vulputate{% sidenote marker="‡" type="example" note="A second sidenote, typed as an example." /%}. Nam vitae lacus a nisl ultrices dictum. Aliquam erat volutpat. Fusce sed velit at arcu tincidunt vulputate{% sidenote type="example" marker="‡" note="A second sidenote, typed as an example." /%}. Nam vitae lacus a nisl ultrices dictum.
Finally, a link to another article: {% xref reference="the-crucible" %}The Crucible{% /xref %}. Finally, a link to another article: {% xref reference="the-crucible" %}The Crucible{% /xref %}.

View File

@@ -42,6 +42,9 @@ const previewUrl = preview ? new URL(preview.src, Astro.site) : undefined
<Font cssVariable="--font-blaka" preload /> <Font cssVariable="--font-blaka" preload />
<Font cssVariable="--font-julia-mono" preload /> <Font cssVariable="--font-julia-mono" preload />
<Font cssVariable="--font-unigrim" /> <Font cssVariable="--font-unigrim" />
<style is:inline>
@layer reset, tokens, base, components, utilities;
</style>
</head> </head>
<body class="ui"> <body class="ui">
<slot /> <slot />

View File

@@ -1,9 +1,9 @@
--- ---
import DevBanner from '@/components/dev/DevBanner.astro' import DevBanner from '@/components/dev/DevBanner.astro'
import FootnoteList from '@/components/markdoc/blocks/general/FootnoteList.astro' import FootnoteList from '@/components/markdoc/blocks/general/FootnoteList.astro'
import SidenoteList from '@/components/markdoc/blocks/general/SidenoteList.astro'
import Node from '@/components/markdoc/Node.astro' import Node from '@/components/markdoc/Node.astro'
import ArticleHeader from '@/components/post/Header/ArticleHeader.astro' import ArticleHeader from '@/components/post/Header/ArticleHeader.astro'
import ToC from '@/components/post/ToC.astro'
import BaseLayout from '@/layouts/BaseLayout.astro' import BaseLayout from '@/layouts/BaseLayout.astro'
import type { Rendered } from '@/lib/markdoc/types' import type { Rendered } from '@/lib/markdoc/types'
import { metaOf } from '@/lib/site/meta' import { metaOf } from '@/lib/site/meta'
@@ -20,17 +20,21 @@ const meta = metaOf(article)
<BaseLayout {...meta}> <BaseLayout {...meta}>
<article class="article"> <article class="article">
<ArticleHeader article={article} /> <!--<ArticleHeader article={article} />-->
<div class="frame"> <div class="frame">
<div class="rail">
<ToC headings={rendered.headings} />
</div>
<div class="body content prose"> <div class="body content prose">
<Node node={rendered.tree} /> <Node node={rendered.tree} />
</div> </div>
<aside class="margin"> <aside class="margin">
<slot name="margin" /> <slot name="margin" />
<SidenoteList sidenotes={rendered.sidenotes} />
</aside> </aside>
</div> <div class="notes content">
<FootnoteList footnotes={rendered.footnotes} /> <FootnoteList footnotes={rendered.footnotes} />
</div>
</div>
</article> </article>
<DevBanner <DevBanner
collection={article.collection} collection={article.collection}
@@ -43,19 +47,52 @@ const meta = metaOf(article)
<style> <style>
.frame { .frame {
@mixin layout-wrapper; @mixin layout-wrapper;
@mixin my var(--size-12);
position: relative; display: flex;
flex-direction: column;
gap: var(--layout-gutter);
@media (--bp-desktop) { @media (--bp-desktop) {
display: grid; display: grid;
grid-template-columns: var(--layout-content-width) var(--layout-margin-width); grid-template-columns:
[rail-start] minmax(var(--layout-rail-width), 1fr)
[rail-end body-start] var(--layout-content-width)
[body-end margin-start] var(--layout-margin-width) [margin-end];
gap: var(--layout-gutter); gap: var(--layout-gutter);
align-items: start; align-items: start;
} }
} }
.body {
position: relative;
}
.rail,
.body, .body,
.margin { .margin,
.notes {
min-width: 0; min-width: 0;
} }
@media (--bp-desktop) {
.rail {
grid-column: rail-start / rail-end;
grid-row: 1 / 3;
align-self: stretch;
}
.body {
grid-column: body-start / body-end;
}
.margin {
grid-column: margin-start / margin-end;
grid-row: 1 / 3;
}
.notes {
grid-column: body-start / body-end;
}
}
</style> </style>

View File

@@ -50,22 +50,16 @@ export function createTags(state: NodeState, resolveLink: ResolveLink): Record<s
}) })
state.markers.add(marker) state.markers.add(marker)
const n = state.sidenotes.length + 1 const n = state.sidenotes.length + 1
state.sidenotes.push({ const children = inlineOf(text(node.attributes.note))
n, state.sidenotes.push({ n, marker, type: noteType, children })
marker, return [
type: noteType, new Tag('a', { class: 'sidenote-ref', id: `snref-${n}`, href: `#sn-${n}` }, [marker]),
children: inlineOf(text(node.attributes.note)), new Tag(
}) 'span',
return new Tag( { class: `sidenote sidenote--${noteType}`, id: `sn-${n}`, role: 'note' },
'a', [new Tag('span', { class: 'sidenote__marker' }, [marker]), ...children],
{ ),
class: 'sidenote-ref', ]
id: `snref-${n}`,
href: `#sn-${n}`,
style: `anchor-name: --sn-${n}`,
},
[marker],
)
}, },
}, },
xref: { xref: {

View File

@@ -0,0 +1,3 @@
@import url("foundation.css");
@import url("colors.css");
@import url("primitives.css");

View File

@@ -179,6 +179,10 @@
--slot-modifier: 1.125rem; /* the +2 / −1 column in contributions */ --slot-modifier: 1.125rem; /* the +2 / −1 column in contributions */
--slot-marker: var(--size-5); /* footnote marker column */ --slot-marker: var(--size-5); /* footnote marker column */
--slot-roll-range: 1.5rem; /* the 4–5 column in a table row */ --slot-roll-range: 1.5rem; /* the 4–5 column in a table row */
--slot-section-number: 5ch;
--slot-prose-marker: 1.625rem;
--slot-prose-gap: var(--size-6);
--slot-prose-bar: var(--size-8);
/* ═══ MOTION ═══ */ /* ═══ MOTION ═══ */
--motion-fast: 150ms; /* state change: hover, focus, press */ --motion-fast: 150ms; /* state change: hover, focus, press */
@@ -189,15 +193,12 @@
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
/* ═══ LAYOUT ═══ */ /* ═══ LAYOUT ═══ */
--layout-max-width: 74rem; /* the tool shell */ --layout-wrapper-width: 74rem;
--layout-content-width: minmax(0, 62ch); /* article measure */ --layout-rail-width: 12.5rem;
--layout-margin-width: minmax(10ch, 1fr); --layout-content-width: minmax(0, 62ch);
--layout-step-columns: 2; --layout-margin-width: 18rem;
--layout-gutter: var(--size-6); /* between step cards */ --layout-gutter: var(--size-12);
--layout-section-gap: var(--size-9); /* header to grid */
--layout-page-margin: var(--size-10); --layout-page-margin: var(--size-10);
--layout-tier-height: 2.25rem;
--layout-header-height: 3.125rem; --layout-header-height: 3.125rem;
--layout-card-min-height: 6.5rem; /* stops grid reflow on resolve */
} }
} }

View File

@@ -0,0 +1,2 @@
@layer components {
}

View File

@@ -0,0 +1,2 @@
@import url("prose.css");
@import url("blocks.css");

View File

@@ -0,0 +1,357 @@
@property --prose-line {
inherits: true;
initial-value: 0;
syntax: "<length>";
}
@layer components {
.prose {
@mixin typo_body;
counter-reset: sec sub tbl fig;
& > :first-child {
margin-block-start: var(--size-0);
}
& > :last-child {
margin-block-end: var(--size-0);
}
& ::selection {
color: var(--color-text-on-accent);
background-color: var(--color-primary);
}
/* === Paragraphs === */
& p {
@mixin my var(--space-6);
text-wrap: pretty;
}
/* === HEADINGS === */
& h2 {
@mixin typo_heading-2;
@mixin mt var(--space-11);
@mixin mb var(--space-8);
@mixin ps var(--size-9);
counter-set: sub 0 fig 0;
counter-increment: sec;
clear: both;
display: flex;
flex-direction: column;
gap: var(--space-4);
border-inline-start: var(--slot-prose-bar) solid var(--color-text-primary);
text-wrap: balance;
&::before {
@mixin pt var(--space-4);
content: "§ " counter(sec, upper-roman);
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: var(--font-weight-emphasis);
line-height: 1;
letter-spacing: var(--tracking-label);
}
}
& h3 {
@mixin typo_heading-3;
@mixin mt var(--space-10);
@mixin mb var(--space-7);
@mixin py var(--size-3);
@mixin px var(--size-7);
counter-increment: sub;
display: flex;
gap: var(--size-7);
align-items: baseline;
color: var(--color-text-inverse);
background-color: var(--color-surface-inverse);
&::before {
content: counter(sec, upper-roman) "." counter(sub);
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: var(--font-weight-emphasis);
color: var(--color-primary);
}
}
& h4 {
@mixin typo_heading-4;
@mixin mt var(--space-9);
@mixin mb var(--space-6);
@mixin py var(--size-2);
border-block: var(--border-shell) solid var(--color-text-primary);
}
/* === LISTS === */
& ul,
& ol {
@mixin my var(--space-6);
@mixin pl var(--size-0);
display: flex;
flex-direction: column;
gap: var(--space-4);
list-style: none;
& li {
--prose-line: 1lh;
display: grid;
grid-template-columns: var(--slot-prose-marker) minmax(0, 1fr);
column-gap: var(--slot-prose-gap);
& > * {
grid-column: 2;
}
& > ul,
& > ol {
@mixin mt var(--space-3);
@mixin mb var(--size-0);
gap: var(--space-3);
}
}
}
& ul {
& > li {
&::before {
content: "";
justify-self: center;
width: 0.625rem;
height: 0.625rem;
margin-block-start: calc((var(--prose-line) - 0.625rem) / 2);
background-color: var(--color-text-primary);
}
& ul {
& > li {
&::before {
background-color: transparent;
box-shadow: inset 0 0 0 var(--border-card) var(--color-text-primary);
}
}
& ul {
& > li {
&::before {
height: var(--size-2);
margin-block-start: calc((var(--prose-line) - var(--size-2)) / 2);
background-color: var(--color-text-primary);
box-shadow: none;
}
}
}
}
}
}
& ol {
counter-reset: step;
& > li {
counter-increment: step;
&::before {
content: counter(step, decimal-leading-zero);
display: grid;
place-items: center;
height: 1.375rem;
margin-block-start: calc((var(--prose-line) - 1.375rem) / 2);
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: var(--font-weight-emphasis);
line-height: 1;
color: var(--color-text-inverse);
background-color: var(--color-surface-inverse);
}
& ol {
& > li {
&::before {
content: counter(step, lower-alpha);
color: var(--color-text-primary);
background-color: transparent;
box-shadow: inset 0 0 0 var(--border-card) var(--color-text-primary);
}
}
}
}
}
/* === QUOTES & RULES === */
& blockquote {
@mixin my var(--space-6);
@mixin ps 3ch;
font-style: italic;
color: var(--color-text-secondary);
}
& hr {
@mixin my var(--space-10);
@mixin border-t var(--border-hairline), solid, var(--color-border-strong);
border-block-end: none;
border-inline: none;
}
/* === INLINE === */
& :not(pre) {
& > code {
@mixin px var(--space-2);
@mixin py var(--space-px);
font-family: var(--font-mono);
font-size: 0.875em;
background-color: var(--color-surface-elevated-3);
}
}
& sup {
font-family: var(--font-mono);
font-size: 0.7em;
}
& pre {
@mixin typo_code;
@mixin my var(--space-9);
@mixin py var(--size-7);
@mixin px var(--size-8);
overflow: auto;
border: var(--border-card) solid var(--color-border-strong);
tab-size: 2;
background-color: var(--color-surface-elevated-1);
& code {
@mixin pa var (--size-0);
font: inherit;
background-color: transparent;
}
}
& a {
color: var(--color-text-link);
text-decoration: underline;
text-decoration-thickness: var(--size-1);
text-underline-offset: var(--size-2);
transition: color var(--motion-fast) var(--ease-out);
&:visited {
color: var(--color-text-link-visited);
}
&:hover {
color: var(--color-text-link-hover);
}
&:focus-visible {
@mixin focus-ring;
}
}
& em {
font-style: italic;
}
& b,
& strong {
font-weight: var(--font-weight-emphasis);
}
& mark {
@mixin px var(--size-1);
color: var(--color-text-on-accent);
background-color: var(--color-tertiary);
}
& del,
& s {
color: var(--color-text-tertiary);
text-decoration: line-through;
}
/* === TABLES === */
table {
@mixin typo_table-data;
@mixin my var(--space-10);
border-spacing: 0;
border-collapse: separate;
width: 100%;
& th,
& td {
@mixin py var(--space-4);
@mixin px var(--space-6);
@mixin border-b var(--border-hairline), solid, var(--color-border-subtle);
}
& thead {
& th {
@mixin typo_table-header;
@mixin border-b var(--border-card), solid, var(--color-border-strong);
}
}
& tbody {
& tr {
&:last-child {
& :is(th, td) {
@mixin border-b var(--border-card), solid, var(--color-border-strong);
}
}
&:hover {
background-color: var(--color-surface-hover);
}
}
& th[scope="row"] {
font-family: var(--font-header);
font-weight: var(--font-weight-heading);
text-transform: uppercase;
}
}
}
& :is(tfoot, tr.total) {
& :is(th, td) {
@mixin border-t var(--border-card), solid, var(--color-border-strong);
}
}
& .num {
font-variant-numeric: tabular-nums;
text-align: right;
white-space: nowrap;
}
& .key {
font-weight: var(--font-weight-emphasis);
}
td.text {
font-family: var(--font-body);
font-size: 1.077em;
line-height: 1.55;
color: var(--color-text-secondary);
text-wrap: pretty;
}
}
}

View File

@@ -1,8 +1,5 @@
@layer reset, tokens, base, components, utilities; @import url("base/index.css");
@import url("components/index.css");
@import url("base/foundation.css");
@import url("base/colors.css");
@import url("base/primitives.css");
@layer base { @layer base {
body { body {

View File

@@ -3,7 +3,7 @@
@mixin px var(--layout-page-margin); @mixin px var(--layout-page-margin);
width: 100%; width: 100%;
max-width: var(--layout-max-width); max-width: var(--layout-wrapper-width);
@media screen and (--bp-desktop) { @media screen and (--bp-desktop) {
@mixin px var(--space-0); @mixin px var(--space-0);

View File

@@ -8,29 +8,29 @@
@define-mixin typo_heading-2 { @define-mixin typo_heading-2 {
font-family: var(--font-header); font-family: var(--font-header);
font-size: var(--type-7); font-size: var(--type-4);
font-weight: var(--font-weight-heading); font-weight: var(--font-weight-heading);
line-height: var(--leading-7); line-height: var(--leading-4);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: var(--tracking-7); letter-spacing: var(--tracking-4);
} }
@define-mixin typo_heading-3 { @define-mixin typo_heading-3 {
font-family: var(--font-header); font-family: var(--font-header);
font-size: var(--type-6); font-size: var(--type-1);
font-weight: var(--font-weight-heading); font-weight: var(--font-weight-heading);
line-height: var(--leading-6); line-height: var(--leading-1);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: var(--tracking-6); letter-spacing: var(--tracking-1);
} }
@define-mixin typo_heading-4 { @define-mixin typo_heading-4 {
font-family: var(--font-header); font-family: var(--font-header);
font-size: var(--type-5); font-size: var(--type-0);
font-weight: var(--font-weight-emphasis); font-weight: var(--font-weight-label);
line-height: var(--leading-5); line-height: var(--leading--1);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: var(--tracking-5); letter-spacing: var(--tracking-label);
} }
@define-mixin typo_code { @define-mixin typo_code {
@@ -43,11 +43,11 @@
@define-mixin typo_table-header { @define-mixin typo_table-header {
font-family: var(--font-header); font-family: var(--font-header);
font-size: var(--type--1); font-size: var(--type--2);
font-weight: var(--font-weight-emphasis); font-weight: var(--font-weight-emphasis);
line-height: var(--leading-0); line-height: var(--leading-0);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: var(--tracking-0); letter-spacing: var(--tracking-label);
} }
@define-mixin typo_table-data { @define-mixin typo_table-data {
@@ -68,7 +68,7 @@
} }
@define-mixin typo_definition-data { @define-mixin typo_definition-data {
font-family: var(--font-mono); font-family: var(--font-body);
font-size: var(--type--1); font-size: var(--type--1);
font-weight: var(--font-weight-body); font-weight: var(--font-weight-body);
line-height: var(--leading--1); line-height: var(--leading--1);