Added ArticleFooter and SequenceNav
This commit is contained in:
17
src/components/post/Footer/ArticleFooter.astro
Normal file
17
src/components/post/Footer/ArticleFooter.astro
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
import type { Sequence } from '@/lib/site/navigation'
|
||||
|
||||
import SequenceNav from './SequenceNav.astro'
|
||||
|
||||
type Props = {
|
||||
sequence: Sequence | null
|
||||
}
|
||||
|
||||
const { sequence } = Astro.props
|
||||
---
|
||||
|
||||
{sequence && (
|
||||
<footer class="article-footer">
|
||||
<SequenceNav sequence={sequence} />
|
||||
</footer>
|
||||
)}
|
||||
66
src/components/post/Footer/SequenceNav.astro
Normal file
66
src/components/post/Footer/SequenceNav.astro
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
import { toRoman } from '@/lib/content/numerals'
|
||||
import type { Sequence } from '@/lib/site/navigation'
|
||||
|
||||
type Props = {
|
||||
sequence: Sequence
|
||||
}
|
||||
|
||||
const { parent, siblings, position, previous, next } = Astro.props.sequence
|
||||
---
|
||||
|
||||
<nav class="sequence-nav" aria-label={`${parent.title} sequence`}>
|
||||
<div class="sequence-nav__step sequence-nav__step--previous">
|
||||
{previous && (
|
||||
<a class="sequence-nav__link" href={previous.path} rel="prev">
|
||||
<span class="sequence-nav__label">
|
||||
Previous
|
||||
<span class="sequence-nav__numeral" aria-hidden="true">
|
||||
{toRoman(position)}
|
||||
</span>
|
||||
</span>
|
||||
<span class="sequence-nav__title">{previous.title}</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="sequence-nav__overview">
|
||||
<p class="sequence-nav__label">
|
||||
<a class="sequence-nav__parent" href={parent.path}>
|
||||
{parent.title}
|
||||
</a>
|
||||
<span class="sequence-nav__count">
|
||||
{toRoman(position + 1)} of {toRoman(siblings.length)}
|
||||
</span>
|
||||
</p>
|
||||
<ol class="sequence-nav__squares">
|
||||
{siblings.map((sibling, i) => (
|
||||
<li>
|
||||
<a
|
||||
class="sequence-nav__square"
|
||||
href={sibling.path}
|
||||
aria-label={sibling.title}
|
||||
title={sibling.title}
|
||||
aria-current={i === position ? 'page' : undefined}
|
||||
>
|
||||
{toRoman(i + 1)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="sequence-nav__step sequence-nav__step--next">
|
||||
{next && (
|
||||
<a class="sequence-nav__link" href={next.path} rel="next">
|
||||
<span class="sequence-nav__label">
|
||||
<span class="sequence-nav__numeral" aria-hidden="true">
|
||||
{toRoman(position + 2)}
|
||||
</span>
|
||||
Next
|
||||
</span>
|
||||
<span class="sequence-nav__title">{next.title}</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
21
src/content/general/articles/calcination.mdoc
Normal file
21
src/content/general/articles/calcination.mdoc
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Calcination
|
||||
subtitle: Where we burn the people
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: stages
|
||||
index: 2
|
||||
sequence: false
|
||||
summary:
|
||||
cover:
|
||||
effect: none
|
||||
ratio: original
|
||||
dates:
|
||||
published: 2026-09-30T12:27:00.000Z
|
||||
tags:
|
||||
- alchemical-materialism
|
||||
- genesis
|
||||
- the-crucible
|
||||
---
|
||||
21
src/content/general/articles/coagulation.mdoc
Normal file
21
src/content/general/articles/coagulation.mdoc
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Coagulation
|
||||
subtitle: Where we burn the politicians
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: stages
|
||||
index: 6
|
||||
sequence: false
|
||||
summary:
|
||||
cover:
|
||||
effect: none
|
||||
ratio: original
|
||||
dates:
|
||||
published: 2026-09-30T12:27:00.000Z
|
||||
tags:
|
||||
- alchemical-materialism
|
||||
- genesis
|
||||
- the-crucible
|
||||
---
|
||||
21
src/content/general/articles/conjunction.mdoc
Normal file
21
src/content/general/articles/conjunction.mdoc
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Conjunction
|
||||
subtitle: Where we burn the state
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: stages
|
||||
index: 5
|
||||
sequence: false
|
||||
summary:
|
||||
cover:
|
||||
effect: none
|
||||
ratio: original
|
||||
dates:
|
||||
published: 2026-09-30T12:27:00.000Z
|
||||
tags:
|
||||
- alchemical-materialism
|
||||
- genesis
|
||||
- the-crucible
|
||||
---
|
||||
21
src/content/general/articles/dissolution.mdoc
Normal file
21
src/content/general/articles/dissolution.mdoc
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Dissolution
|
||||
subtitle: Where we act
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: stages
|
||||
index: 7
|
||||
sequence: false
|
||||
summary:
|
||||
cover:
|
||||
effect: none
|
||||
ratio: original
|
||||
dates:
|
||||
published: 2026-09-30T12:27:00.000Z
|
||||
tags:
|
||||
- alchemical-materialism
|
||||
- genesis
|
||||
- the-crucible
|
||||
---
|
||||
21
src/content/general/articles/fermentation.mdoc
Normal file
21
src/content/general/articles/fermentation.mdoc
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Fermentation
|
||||
subtitle: Where we burn the priests
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: stages
|
||||
index: 3
|
||||
sequence: false
|
||||
summary:
|
||||
cover:
|
||||
effect: none
|
||||
ratio: original
|
||||
dates:
|
||||
published: 2026-09-30T12:27:00.000Z
|
||||
tags:
|
||||
- alchemical-materialism
|
||||
- genesis
|
||||
- the-crucible
|
||||
---
|
||||
@@ -3,8 +3,11 @@ title: Prima Materia
|
||||
subtitle: Where we lay the ground
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: stages
|
||||
index: 1
|
||||
sequence: false
|
||||
summary:
|
||||
cover:
|
||||
image: /src/assets/content/articles/prima-materia/summary/cover/image.jpg
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
title: Stages
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: the-crucible
|
||||
sequence: true
|
||||
summary:
|
||||
cover: {}
|
||||
cover:
|
||||
effect: none
|
||||
ratio: original
|
||||
dates:
|
||||
published: 2026-09-21T12:34:00.000Z
|
||||
tags: []
|
||||
|
||||
21
src/content/general/articles/sublimation.mdoc
Normal file
21
src/content/general/articles/sublimation.mdoc
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Sublimation
|
||||
subtitle: Where we burn the witches
|
||||
status: draft
|
||||
noindex: false
|
||||
stage: stub
|
||||
route:
|
||||
parent: stages
|
||||
index: 4
|
||||
sequence: false
|
||||
summary:
|
||||
cover:
|
||||
effect: none
|
||||
ratio: original
|
||||
dates:
|
||||
published: 2026-09-30T12:27:00.000Z
|
||||
tags:
|
||||
- alchemical-materialism
|
||||
- genesis
|
||||
- the-crucible
|
||||
---
|
||||
@@ -35,6 +35,11 @@ export const articleRoute = fields.object(
|
||||
label: 'Index',
|
||||
description: 'Sorts among siblings. Empty sorts by date, newest first',
|
||||
}),
|
||||
sequence: fields.checkbox({
|
||||
label: 'Sequence',
|
||||
description:
|
||||
'Children form a numbered series with previous/next navigation. Give each child an index',
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Route',
|
||||
|
||||
@@ -36,7 +36,7 @@ const previewUrl = preview ? new URL(preview.src, Astro.site) : undefined
|
||||
{description && <meta property="og:description" content={description} />}
|
||||
{previewUrl && <meta property="og:image" content={previewUrl} />}
|
||||
<meta name="twitter:card" content={previewUrl ? 'summary_large_image' : 'summary'} />
|
||||
|
||||
<slot name="head" />
|
||||
<Font cssVariable="--font-geist" preload />
|
||||
<Font cssVariable="--font-geist-mono" preload />
|
||||
<Font cssVariable="--font-blaka" preload />
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
import DevBanner from '@/components/dev/DevBanner.astro'
|
||||
import FootnoteList from '@/components/markdoc/blocks/general/FootnoteList.astro'
|
||||
import Node from '@/components/markdoc/Node.astro'
|
||||
import ArticleFooter from '@/components/post/Footer/ArticleFooter.astro'
|
||||
import ArticleHeader from '@/components/post/Header/ArticleHeader.astro'
|
||||
import ToC from '@/components/post/ToC.astro'
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||
import type { Rendered } from '@/lib/markdoc/types'
|
||||
import { metaOf } from '@/lib/site/meta'
|
||||
import { sequenceOf } from '@/lib/site/navigation'
|
||||
import type { Article } from '@/lib/site/types'
|
||||
|
||||
type Props = {
|
||||
@@ -16,6 +18,7 @@ type Props = {
|
||||
|
||||
const { article, rendered } = Astro.props
|
||||
const meta = metaOf(article)
|
||||
const sequence = sequenceOf(article)
|
||||
---
|
||||
|
||||
<BaseLayout {...meta}>
|
||||
@@ -35,6 +38,7 @@ const meta = metaOf(article)
|
||||
<FootnoteList footnotes={rendered.footnotes} />
|
||||
</div>
|
||||
</div>
|
||||
<ArticleFooter sequence={sequence} />
|
||||
</article>
|
||||
<DevBanner
|
||||
collection={article.collection}
|
||||
|
||||
27
src/lib/content/numerals.ts
Normal file
27
src/lib/content/numerals.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
const NUMERALS = [
|
||||
[1000, 'M'],
|
||||
[900, 'CM'],
|
||||
[500, 'D'],
|
||||
[400, 'CD'],
|
||||
[100, 'C'],
|
||||
[90, 'XC'],
|
||||
[50, 'L'],
|
||||
[40, 'XL'],
|
||||
[10, 'X'],
|
||||
[9, 'IX'],
|
||||
[5, 'V'],
|
||||
[4, 'IV'],
|
||||
[1, 'I'],
|
||||
] as const
|
||||
|
||||
export function toRoman(value: number): string {
|
||||
let rest = value
|
||||
let result = ''
|
||||
for (const [amount, numeral] of NUMERALS) {
|
||||
while (rest >= amount) {
|
||||
result += numeral
|
||||
rest -= amount
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,5 +1,30 @@
|
||||
import type { Article } from './types'
|
||||
|
||||
export type Sequence = {
|
||||
parent: Article
|
||||
siblings: Article[]
|
||||
position: number
|
||||
previous: Article | null
|
||||
next: Article | null
|
||||
}
|
||||
|
||||
export function sequenceOf(article: Article): Sequence | null {
|
||||
const parent = article.parent
|
||||
if (!parent?.route.sequence) return null
|
||||
|
||||
const siblings = parent.children
|
||||
const position = siblings.indexOf(article)
|
||||
if (position === -1 || siblings.length < 2) return null
|
||||
|
||||
return {
|
||||
parent,
|
||||
siblings,
|
||||
position,
|
||||
previous: siblings[position - 1] ?? null,
|
||||
next: siblings[position + 1] ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
export function trailOf(article: Article): Article[] {
|
||||
const trail: Article[] = []
|
||||
for (let node: Article | null = article; node; node = node.parent) trail.unshift(node)
|
||||
|
||||
9
src/styles/components/post/article-footer.css
Normal file
9
src/styles/components/post/article-footer.css
Normal file
@@ -0,0 +1,9 @@
|
||||
@layer components.blocks {
|
||||
.article-footer {
|
||||
@mixin layout-panel;
|
||||
@mixin my var(--size-12);
|
||||
|
||||
display: grid;
|
||||
gap: var(--size-10);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
@layer components.blocks {
|
||||
.article-header__panel {
|
||||
@mixin mx auto;
|
||||
@mixin mt var(--size-10);
|
||||
@mixin layout-panel;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--border-card);
|
||||
|
||||
width: min(100% - 2 * var(--layout-page-margin), var(--layout-wrapper-width));
|
||||
border: var(--border-card) solid var(--color-border-strong);
|
||||
|
||||
background-color: var(--color-border-strong);
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
@import url("article-header.css");
|
||||
@import url("stage-badge.css");
|
||||
@import url("article-footer.css");
|
||||
@import url("sequence-nav.css");
|
||||
|
||||
188
src/styles/components/post/sequence-nav.css
Normal file
188
src/styles/components/post/sequence-nav.css
Normal file
@@ -0,0 +1,188 @@
|
||||
@layer components.blocks {
|
||||
.sequence-nav {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"overview"
|
||||
"previous"
|
||||
"next";
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--border-card);
|
||||
|
||||
border: var(--border-card) solid var(--color-border-strong);
|
||||
|
||||
background-color: var(--color-border-strong);
|
||||
|
||||
@media (--bp-tablet-up) {
|
||||
grid-template-areas: "previous overview next";
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__step {
|
||||
background-color: var(--color-surface-elevated-1);
|
||||
|
||||
&:empty {
|
||||
@media (--bp-mobile) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__step--previous {
|
||||
grid-area: previous;
|
||||
}
|
||||
|
||||
.sequence-nav__step--next {
|
||||
grid-area: next;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.sequence-nav__overview {
|
||||
@mixin pa var(--size-9);
|
||||
|
||||
display: flex;
|
||||
grid-area: overview;
|
||||
flex-direction: column;
|
||||
gap: var(--size-6);
|
||||
|
||||
background-color: var(--color-surface-elevated-1);
|
||||
}
|
||||
|
||||
.sequence-nav__link {
|
||||
@mixin pa var(--size-9);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-6);
|
||||
|
||||
height: 100%;
|
||||
|
||||
color: var(--color-text-primary);
|
||||
|
||||
transition: background-color var(--motion-fast) var(--ease-out);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@mixin focus-ring 2px var(--color-focus-ring) -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__label {
|
||||
@mixin typo_eyebrow;
|
||||
@mixin ma var(--size-0);
|
||||
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
.sequence-nav__overview & {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.sequence-nav__step--previous & {
|
||||
&::before {
|
||||
content: "⟪" / "";
|
||||
margin-inline-end: var(--size-2);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__step--next & {
|
||||
&::after {
|
||||
content: "⟫" / "";
|
||||
margin-inline-start: var(--size-2);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__numeral {
|
||||
.sequence-nav__step--previous & {
|
||||
&::before {
|
||||
content: "⟫" / "";
|
||||
margin-inline: var(--size-2);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__step--next & {
|
||||
&::after {
|
||||
content: "⟫" / "";
|
||||
margin-inline: var(--size-2);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__count {
|
||||
&::before {
|
||||
content: "⟫" / "";
|
||||
margin-inline: var(--size-2);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__parent {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@mixin focus-ring;
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-nav__title {
|
||||
@mixin typo_heading-2;
|
||||
}
|
||||
|
||||
.sequence-nav__squares {
|
||||
@mixin ma var(--size-0);
|
||||
@mixin pa var(--size-0);
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--size-2);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sequence-nav__square {
|
||||
@mixin px var(--size-1);
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
min-width: var(--size-10);
|
||||
height: var(--size-10);
|
||||
border: var(--border-hairline) solid var(--color-border-normal);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--type--3);
|
||||
line-height: 1;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
background-color: var(--color-surface-elevated-1);
|
||||
|
||||
&:visited {
|
||||
border-color: var(--color-surface-inverse);
|
||||
color: var(--color-text-inverse);
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
|
||||
&[aria-current="page"] {
|
||||
border-color: var(--color-primary);
|
||||
font-weight: var(--font-weight-emphasis);
|
||||
color: var(--color-text-on-accent);
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@mixin focus-ring;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,3 +9,9 @@
|
||||
@mixin px var(--space-0);
|
||||
}
|
||||
}
|
||||
|
||||
@define-mixin layout-panel {
|
||||
@mixin mx auto;
|
||||
|
||||
width: min(100% - 2 * var(--layout-page-margin), var(--layout-wrapper-width));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user