Blog #1
@@ -21,7 +21,8 @@
|
||||
"astro": "^7.0.4",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"sharp": "^0.35.4"
|
||||
"sharp": "^0.35.4",
|
||||
"shiki": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.10",
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -41,6 +41,9 @@ importers:
|
||||
sharp:
|
||||
specifier: ^0.35.4
|
||||
version: 0.35.4(@types/node@22.20.4)
|
||||
shiki:
|
||||
specifier: ^4.4.3
|
||||
version: 4.4.3
|
||||
devDependencies:
|
||||
'@astrojs/check':
|
||||
specifier: ^0.9.10
|
||||
|
||||
@@ -7,6 +7,9 @@ import ContentImage from '@/components/media/ContentImage.astro'
|
||||
|
||||
import Figure from '../media/Figure.astro'
|
||||
import Callout from './blocks/general/Callout.astro'
|
||||
import Code from './blocks/general/Code.astro'
|
||||
import Definition from './blocks/general/Definitions/Definition.astro'
|
||||
import DefinitionList from './blocks/general/Definitions/DefinitionList.astro'
|
||||
import Lead from './blocks/general/Lead.astro'
|
||||
import Quote from './blocks/general/Quote.astro'
|
||||
import Reveal from './blocks/general/Reveal.astro'
|
||||
@@ -17,9 +20,12 @@ type Props = { node: RenderableTreeNode | RenderableTreeNode[] }
|
||||
const COMPONENTS: Record<string, AstroComponentFactory> = {
|
||||
Callout,
|
||||
Reveal,
|
||||
Code,
|
||||
Figure,
|
||||
TableWrapper,
|
||||
Quote,
|
||||
Definition,
|
||||
DefinitionList,
|
||||
Lead,
|
||||
Image: ContentImage,
|
||||
}
|
||||
|
||||
14
src/components/markdoc/blocks/general/Code.astro
Normal file
14
src/components/markdoc/blocks/general/Code.astro
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
type Props = {
|
||||
code: string
|
||||
lang?: string
|
||||
html?: string
|
||||
}
|
||||
|
||||
const { code, lang, html } = Astro.props
|
||||
---
|
||||
|
||||
<div class="code" data-language={lang}>
|
||||
{lang && <span class="code__lang">{lang}</span>}
|
||||
{html ? <Fragment set:html={html} /> : <pre tabindex="0"><code>{code}</code></pre>}
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
import TermEntry, { type SeeLink } from '@/components/terms/TermEntry.astro'
|
||||
import type { TermKind } from '@/lib/constants/terms'
|
||||
import { slugify } from '@/lib/content/slug'
|
||||
|
||||
type Props = {
|
||||
term: string
|
||||
kind?: TermKind
|
||||
see?: string
|
||||
}
|
||||
|
||||
const { term, kind = 'entry', see = '' } = Astro.props
|
||||
|
||||
const links: SeeLink[] = see
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean)
|
||||
.map((name) => ({ title: name, href: `#${slugify(name)}` }))
|
||||
---
|
||||
|
||||
<TermEntry id={slugify(term)} title={term} kind={kind} see={links} ink>
|
||||
<slot />
|
||||
</TermEntry>
|
||||
@@ -0,0 +1,3 @@
|
||||
<dl class="terms terms--cross">
|
||||
<slot />
|
||||
</dl>
|
||||
@@ -7,4 +7,4 @@ type Props = { cover: Article['summary']['cover'] }
|
||||
const { image, ...rest } = Astro.props.cover
|
||||
---
|
||||
|
||||
{image && <Figure image={image} {...rest} size="full" eager />}
|
||||
{image && <Figure image={image} {...rest} size="wide" eager />}
|
||||
|
||||
42
src/components/terms/TermEntry.astro
Normal file
42
src/components/terms/TermEntry.astro
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
import { TERM_KINDS, type TermKind } from '@/lib/constants/terms'
|
||||
|
||||
export type SeeLink = {
|
||||
title: string
|
||||
href: string
|
||||
}
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
title: string
|
||||
kind: TermKind
|
||||
see?: SeeLink[]
|
||||
ink?: boolean
|
||||
}
|
||||
|
||||
const { id, title, kind, see = [], ink } = Astro.props
|
||||
|
||||
const label = TERM_KINDS.find((option) => option.value === kind)?.label ?? kind
|
||||
---
|
||||
|
||||
<dt class:list={['term', ink && 'dark']} id={id}>
|
||||
<span class="term__name">{title}</span>
|
||||
<span class="term__kind">{label}</span>
|
||||
</dt>
|
||||
|
||||
<dd class="term__body">
|
||||
<p class="term__definition">
|
||||
<slot />
|
||||
</p>
|
||||
|
||||
{see.length > 0 && (
|
||||
<p class="term__see">
|
||||
<span class="term__see-label">See</span>
|
||||
{see.map((related) => (
|
||||
<a class="term__link" href={related.href}>
|
||||
{related.title}
|
||||
</a>
|
||||
))}
|
||||
</p>
|
||||
)}
|
||||
</dd>
|
||||
@@ -144,8 +144,67 @@ for (const [key, value] of Object.entries(crucible)) {
|
||||
credit="Dave Damage"
|
||||
effect="xeroxed"
|
||||
ratio="16-9"
|
||||
size="wide" /%}
|
||||
size="inline" /%}
|
||||
|
||||
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 %}.
|
||||
|
||||
## Code
|
||||
|
||||
```ts
|
||||
const crucible = { stage: 'prima-materia', heat: 3 }
|
||||
|
||||
function temper(work: typeof crucible): 'calcined' | 'raw' {
|
||||
return work.heat > 5 ? 'calcined' : 'raw'
|
||||
}
|
||||
```
|
||||
|
||||
A fence with no language, which should render unhighlighted and with no label:
|
||||
|
||||
```
|
||||
Prima materia — the undifferentiated ground.
|
||||
Nothing is named yet.
|
||||
```
|
||||
|
||||
A long line, which should scroll inside the block rather than widen the article:
|
||||
|
||||
```css
|
||||
.prose :is(h2, h3, h4) + .table-wrapper thead th { border-block-end: var(--border-card) solid var(--color-border-strong); color: inherit; background-color: transparent; }
|
||||
```
|
||||
|
||||
An unknown language, which should render plain and raise a warning in the dev banner:
|
||||
|
||||
```brainfuck
|
||||
+[----->+++<]>+.
|
||||
```
|
||||
|
||||
{% callout type="example" title="Code inside a block" %}
|
||||
The frame should sit inside the callout body, not fight it:
|
||||
|
||||
```js
|
||||
export const isTag = (node) => Markdoc.Tag.isTag(node)
|
||||
```
|
||||
{% /callout %}
|
||||
|
||||
## Spoiler
|
||||
|
||||
Duis aute irure dolor in reprehenderit, and then {% spoiler %}the prima materia was the vessel all along{% /spoiler %}, which changes the reading of everything above.
|
||||
|
||||
A spoiler long enough to wrap across a line break: {% spoiler %}Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua{% /spoiler %}. And text after it.
|
||||
|
||||
## Definitions
|
||||
|
||||
{% definitions %}
|
||||
{% definition term="Prima Materia" kind="prime" see="Calcination, Solutio" %}
|
||||
The undifferentiated ground. Nothing is named yet, which is precisely what makes it workable.
|
||||
{% /definition %}
|
||||
|
||||
{% definition term="Calcination" kind="stage" see="Prima Materia" %}
|
||||
Burned down to what will not burn.
|
||||
{% /definition %}
|
||||
|
||||
{% definition term="Solutio" kind="stage" %}
|
||||
Dissolved back into flow, so that forms loosen.
|
||||
{% /definition %}
|
||||
{% /definitions %}
|
||||
|
||||
27
src/keystatic/blocks/general/definition-list/definition.ts
Normal file
27
src/keystatic/blocks/general/definition-list/definition.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { fields } from '@keystatic/core'
|
||||
import { wrapper } from '@keystatic/core/content-components'
|
||||
|
||||
import { TERM_KINDS } from '@/lib/constants/terms'
|
||||
|
||||
export const definition = wrapper({
|
||||
label: 'Definition',
|
||||
description: 'One term and its meaning',
|
||||
forSpecificLocations: true,
|
||||
schema: {
|
||||
term: fields.text({
|
||||
label: 'Term',
|
||||
validation: {
|
||||
isRequired: true,
|
||||
},
|
||||
}),
|
||||
kind: fields.select({
|
||||
label: 'Kind',
|
||||
options: TERM_KINDS,
|
||||
defaultValue: 'entry',
|
||||
}),
|
||||
see: fields.text({
|
||||
label: 'See also',
|
||||
description: 'Other terms in this list, comma separated',
|
||||
}),
|
||||
},
|
||||
})
|
||||
13
src/keystatic/blocks/general/definition-list/definitions.ts
Normal file
13
src/keystatic/blocks/general/definition-list/definitions.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { repeating } from '@keystatic/core/content-components'
|
||||
|
||||
export const definitions = repeating({
|
||||
label: 'Definitions',
|
||||
description: 'A list of terms defined for this article only',
|
||||
children: ['definition'],
|
||||
validation: {
|
||||
children: {
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
schema: {},
|
||||
})
|
||||
@@ -1,21 +1,27 @@
|
||||
import { callout } from './callout'
|
||||
import { definition } from './definition-list/definition'
|
||||
import { definitions } from './definition-list/definitions'
|
||||
import { figure } from './figure'
|
||||
import { footnote } from './footnote'
|
||||
import { lead } from './lead'
|
||||
import { quote } from './quote'
|
||||
import { reveal } from './reveal'
|
||||
import { sidenote } from './sidenote'
|
||||
import { spoiler } from './spoiler'
|
||||
import { tableWrapper } from './table-wrapper'
|
||||
import { xref } from './xref'
|
||||
|
||||
export const generalBlocks = (collection: string) => ({
|
||||
figure: figure(collection),
|
||||
callout,
|
||||
definition,
|
||||
definitions,
|
||||
lead,
|
||||
footnote,
|
||||
sidenote,
|
||||
tableWrapper,
|
||||
quote,
|
||||
reveal,
|
||||
spoiler,
|
||||
xref: xref(),
|
||||
})
|
||||
|
||||
15
src/keystatic/blocks/general/spoiler.ts
Normal file
15
src/keystatic/blocks/general/spoiler.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { mark } from '@keystatic/core/content-components'
|
||||
|
||||
import { eyeOffIcon } from '@keystar/ui/icon/icons/eyeOffIcon'
|
||||
|
||||
export const spoiler = mark({
|
||||
label: 'Spoiler',
|
||||
icon: eyeOffIcon,
|
||||
tag: 'span',
|
||||
schema: {},
|
||||
style: {
|
||||
backgroundColor: '#2A2A2A',
|
||||
color: '#F5F5F5',
|
||||
padding: '0 2px',
|
||||
},
|
||||
})
|
||||
11
src/lib/constants/terms.ts
Normal file
11
src/lib/constants/terms.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const TERM_KINDS = [
|
||||
{ label: 'Entry', value: 'entry' },
|
||||
{ label: 'Prime', value: 'prime' },
|
||||
{ label: 'Essence', value: 'essence' },
|
||||
{ label: 'Element', value: 'element' },
|
||||
{ label: 'Stage', value: 'stage' },
|
||||
{ label: 'Tier', value: 'tier' },
|
||||
{ label: 'Premise', value: 'premise' },
|
||||
] as const
|
||||
|
||||
export type TermKind = (typeof TERM_KINDS)[number]['value']
|
||||
38
src/lib/markdoc/highlight.ts
Normal file
38
src/lib/markdoc/highlight.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createHighlighter, type Highlighter } from 'shiki'
|
||||
|
||||
const THEMES = { light: 'github-light', dark: 'github-dark' } as const
|
||||
|
||||
const LANGS = [
|
||||
'astro',
|
||||
'bash',
|
||||
'css',
|
||||
'diff',
|
||||
'html',
|
||||
'ini',
|
||||
'js',
|
||||
'json',
|
||||
'jsonc',
|
||||
'jsx',
|
||||
'md',
|
||||
'python',
|
||||
'sql',
|
||||
'ts',
|
||||
'tsx',
|
||||
'xml',
|
||||
'yaml',
|
||||
]
|
||||
|
||||
export type Highlight = (code: string, lang: string) => string | null
|
||||
|
||||
let ready: Promise<Highlighter> | null = null
|
||||
|
||||
export async function createHighlight(): Promise<Highlight> {
|
||||
ready ??= createHighlighter({ themes: Object.values(THEMES), langs: LANGS })
|
||||
const highlighter = await ready
|
||||
const loaded = new Set(highlighter.getLoadedLanguages())
|
||||
|
||||
return (code, lang) =>
|
||||
loaded.has(lang)
|
||||
? highlighter.codeToHtml(code, { lang, themes: THEMES, defaultColor: false })
|
||||
: null
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import Markdoc from '@markdoc/markdoc'
|
||||
|
||||
import { createDiagnostics } from '../diagnostics'
|
||||
import { createHighlight } from './highlight'
|
||||
import { createNodes } from './nodes'
|
||||
import { createTags } from './tags'
|
||||
import type { NodeState, Renderable, Rendered, ResolveLink } from './types'
|
||||
@@ -21,11 +22,13 @@ export async function render(
|
||||
ids: new Set(),
|
||||
footnotes: [],
|
||||
sidenoteCount: 0,
|
||||
spoilerCount: 0,
|
||||
markers: new Set(),
|
||||
problems: [],
|
||||
}
|
||||
const highlight = await createHighlight()
|
||||
const config = {
|
||||
nodes: createNodes(state),
|
||||
nodes: createNodes(state, highlight),
|
||||
tags: createTags(state, resolveLink),
|
||||
}
|
||||
const problems = createDiagnostics()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Markdoc, { type Node, type Schema } from '@markdoc/markdoc'
|
||||
|
||||
import { slugify } from '../content/slug'
|
||||
import type { Highlight } from './highlight'
|
||||
import type { NodeState } from './types'
|
||||
|
||||
const { Tag } = Markdoc
|
||||
@@ -18,8 +19,27 @@ function uniqueId(text: string, ids: Set<string>): string {
|
||||
return id
|
||||
}
|
||||
|
||||
export function createNodes(state: NodeState): Record<string, Schema> {
|
||||
export function createNodes(state: NodeState, highlight: Highlight): Record<string, Schema> {
|
||||
return {
|
||||
fence: {
|
||||
...Markdoc.nodes.fence,
|
||||
transform(node) {
|
||||
/* transformAttributes renames `language` to `data-language`, so the
|
||||
raw attribute is the honest source. The content carries a trailing
|
||||
newline that would render as a blank last line. */
|
||||
const lang = String(node.attributes.language ?? '').trim()
|
||||
const code = String(node.attributes.content ?? '').replace(/\n$/, '')
|
||||
const html = lang ? highlight(code, lang) : null
|
||||
|
||||
if (lang && !html)
|
||||
state.problems.push({
|
||||
level: 'warning',
|
||||
message: `No highlighting for language "${lang}" — add it to LANGS in highlight.ts.`,
|
||||
})
|
||||
|
||||
return new Tag('Code', { code, lang: lang || undefined, html: html ?? undefined }, [])
|
||||
},
|
||||
},
|
||||
document: {
|
||||
...Markdoc.nodes.document,
|
||||
transform: (node, config) => node.transformChildren(config),
|
||||
|
||||
@@ -60,6 +60,8 @@ const generated =
|
||||
components: generalBlocks(ARTICLES),
|
||||
render: {
|
||||
tags: {
|
||||
definitions: 'DefinitionList',
|
||||
definition: 'Definition',
|
||||
callout: 'Callout',
|
||||
figure: 'Figure',
|
||||
tableWrapper: 'TableWrapper',
|
||||
@@ -84,6 +86,16 @@ export function createTags(state: NodeState, resolveLink: ResolveLink): Record<s
|
||||
])
|
||||
},
|
||||
},
|
||||
spoiler: {
|
||||
...generated.spoiler,
|
||||
transform(node, config) {
|
||||
const id = `spoiler-${(state.spoilerCount += 1)}`
|
||||
return new Tag('span', { class: 'spoiler' }, [
|
||||
new Tag('input', { id, class: 'spoiler__toggle', type: 'checkbox' }, []),
|
||||
new Tag('label', { class: 'spoiler__text', for: id }, node.transformChildren(config)),
|
||||
])
|
||||
},
|
||||
},
|
||||
sidenote: {
|
||||
...generated.sidenote,
|
||||
transform(node, config) {
|
||||
|
||||
@@ -23,6 +23,7 @@ export type NodeState = {
|
||||
ids: Set<string>
|
||||
footnotes: Footnote[]
|
||||
sidenoteCount: number
|
||||
spoilerCount: number
|
||||
markers: Set<string>
|
||||
problems: Problem[]
|
||||
}
|
||||
|
||||
49
src/styles/components/code.css
Normal file
49
src/styles/components/code.css
Normal file
@@ -0,0 +1,49 @@
|
||||
@layer components.blocks {
|
||||
.code {
|
||||
@mixin my var(--space-9);
|
||||
border: var(--border-card) solid var(--color-border-strong);
|
||||
background-color: var(--color-surface-elevated-1);
|
||||
}
|
||||
|
||||
.code__lang {
|
||||
@mixin typo_label;
|
||||
@mixin py var(--size-3);
|
||||
@mixin px var(--size-7);
|
||||
@mixin border-b var(--border-card), solid, var(--color-border-strong);
|
||||
display: block;
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--type--3);
|
||||
color: var(--color-text-primary);
|
||||
|
||||
background-color: var(--color-surface-base);
|
||||
}
|
||||
|
||||
.code pre {
|
||||
@mixin ma var(--size-0);
|
||||
@mixin py var(--size-7);
|
||||
@mixin px var(--size-8);
|
||||
|
||||
overflow-x: auto;
|
||||
border: 0;
|
||||
tab-size: 2;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.code pre code {
|
||||
@mixin pa var(--size-0);
|
||||
|
||||
font: inherit;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.code .shiki,
|
||||
.code .shiki span {
|
||||
color: var(--shiki-light);
|
||||
}
|
||||
|
||||
.dark .code .shiki,
|
||||
.dark .code .shiki span {
|
||||
color: var(--shiki-dark);
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,6 @@
|
||||
@import url("footnote.css");
|
||||
@import url("sidenote.css");
|
||||
@import url("figure.css");
|
||||
@import url("code.css");
|
||||
@import url("terms.css");
|
||||
@import url("spoiler.css");
|
||||
|
||||
35
src/styles/components/spoiler.css
Normal file
35
src/styles/components/spoiler.css
Normal file
@@ -0,0 +1,35 @@
|
||||
@layer components.blocks {
|
||||
.spoiler__toggle {
|
||||
position: absolute;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
clip-path: inset(50%);
|
||||
}
|
||||
|
||||
.spoiler__text {
|
||||
@mixin px var(--space-1);
|
||||
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: transparent;
|
||||
background-color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.spoiler__toggle:checked + .spoiler__text {
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
color: inherit;
|
||||
background-color: var(--color-surface-elevated-3);
|
||||
}
|
||||
|
||||
.spoiler__toggle:focus-visible + .spoiler__text {
|
||||
@mixin focus-ring;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
@layer components.blocks {
|
||||
.table-wrapper {
|
||||
/* The header treatment. Ink by default: a standalone wrapper is a
|
||||
citable object. Position overrides these — nothing is authored. */
|
||||
--table-header-fg: var(--color-text-inverse);
|
||||
--table-header-bg: var(--color-surface-inverse);
|
||||
--table-header-rule: 0;
|
||||
@@ -59,11 +57,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Underline header: a table introduced by a heading, or one inside a
|
||||
block, is part of the argument rather than a reference. A block with
|
||||
its own surface redeclares --table-header-bg so the sticky first
|
||||
column on mobile paints the right colour. */
|
||||
|
||||
.prose :is(h2, h3, h4) + .table-wrapper,
|
||||
:is(.callout, .reveal) .table-wrapper {
|
||||
--table-header-fg: inherit;
|
||||
@@ -72,47 +65,6 @@
|
||||
--table-header-edge: var(--color-border-strong);
|
||||
}
|
||||
|
||||
/* === MATRIX ===
|
||||
A correspondence table: the cells carry hits, not values, so it has to
|
||||
read as a grid — rules on both axes, everything centred, and a miss
|
||||
stated rather than left looking like an oversight. */
|
||||
|
||||
.table-wrapper--matrix {
|
||||
& :is(th, td) {
|
||||
@mixin px var(--space-4);
|
||||
@mixin border-r var(--border-hairline), solid, var(--color-border-subtle);
|
||||
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& :is(th, td):last-child {
|
||||
border-inline-end: 0;
|
||||
}
|
||||
|
||||
/* The first column is the subject; it keeps its reading direction. */
|
||||
& :is(thead, tbody) :is(th, td):first-child {
|
||||
@mixin border-r var(--border-card), solid, var(--color-border-strong);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Axis labels are short. Let them wrap instead of widening a column. */
|
||||
& thead th {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* A hit: the cell itself is the mark. */
|
||||
& tbody td.is-set {
|
||||
background-color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* A miss, said out loud. */
|
||||
& tbody td:empty::before {
|
||||
content: "·";
|
||||
color: var(--color-text-quiet);
|
||||
}
|
||||
}
|
||||
|
||||
@media (--bp-mobile) {
|
||||
.table-wrapper .table-scroll thead th:first-child {
|
||||
border-inline-end-color: var(--table-header-edge);
|
||||
|
||||
103
src/styles/components/terms.css
Normal file
103
src/styles/components/terms.css
Normal file
@@ -0,0 +1,103 @@
|
||||
@layer components.blocks {
|
||||
.terms {
|
||||
@mixin ma var(--size-0);
|
||||
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.term {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-8);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.term__name {
|
||||
@mixin typo_heading-3;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.term__kind {
|
||||
@mixin typo_label;
|
||||
|
||||
font-size: var(--type--2);
|
||||
color: var(--color-text-quarternary);
|
||||
}
|
||||
|
||||
.term__body {
|
||||
@mixin ma var(--size-0);
|
||||
}
|
||||
|
||||
.term__definition {
|
||||
@mixin typo_body;
|
||||
@mixin ma var(--size-0);
|
||||
}
|
||||
|
||||
.term__see {
|
||||
@mixin typo_label;
|
||||
@mixin mt var(--size-8);
|
||||
@mixin mb var(--size-0);
|
||||
@mixin pt var(--size-5);
|
||||
@mixin border-t var(--border-hairline), solid, var(--color-border-subtle);
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--size-5);
|
||||
|
||||
font-size: var(--type--2);
|
||||
color: var(--color-text-quarternary);
|
||||
}
|
||||
|
||||
.term__see-label::after {
|
||||
content: " ⟫";
|
||||
}
|
||||
|
||||
.terms .term__link {
|
||||
color: var(--color-text-primary);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--size-2);
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.terms--cross {
|
||||
@mixin my var(--space-9);
|
||||
|
||||
grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
|
||||
gap: var(--border-card);
|
||||
border: var(--border-card) solid var(--color-border-strong);
|
||||
background-color: var(--color-border-strong);
|
||||
}
|
||||
|
||||
.terms--cross .term {
|
||||
@mixin py var(--size-8);
|
||||
@mixin px var(--size-9);
|
||||
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-surface-base);
|
||||
|
||||
&:not(:first-of-type) {
|
||||
box-shadow: 0 calc(-1 * var(--border-card)) 0 var(--color-border-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.terms--cross .term:target {
|
||||
color: var(--color-text-on-accent);
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.terms--cross .term:target .term__kind {
|
||||
color: var(--color-text-on-accent);
|
||||
}
|
||||
|
||||
.terms--cross .term__body {
|
||||
@mixin py var(--size-8);
|
||||
@mixin px var(--size-9);
|
||||
|
||||
background-color: var(--color-surface-elevated-1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user