This rune is part of @refrakt-md/marketing. Install with npm install @refrakt-md/marketing and add "@refrakt-md/marketing" to the plugins array in your refrakt.config.json.
Hero
Full-width intro section for the top of a page. Headings and paragraphs become the header, links become action buttons, and code fences become copyable command blocks. The first link is styled as a primary button. For smaller, focused action blocks that can appear anywhere, use CTA instead.
Basic usage
A centered hero section with headline, description, and action buttons.
{% hero %}
Whats new [Version 1.0](#)
# Build with refrakt.md
A content framework that turns Markdown into rich, semantic pages. Write standard Markdown — runes decide how it's interpreted.
- [Get started](/docs/getting-started)
- [View on GitHub](https://github.com/refrakt-md/refrakt)
{% /hero %}<section data-field="content-section" data-rune="hero" data-rune-fields="{"align":"center","media-position":"bottom"}">
<div data-name="content">
<header>
<p data-name="eyebrow">
Whats new
<a href="#">Version 1.0</a>
</p>
<h1 id="build-with-refrakt.md" data-name="headline">Build with refrakt.md</h1>
<p data-name="blurb">A content framework that turns Markdown into rich, semantic pages. Write standard Markdown — runes decide how it's interpreted.</p>
</header>
<div data-name="actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Get started</span>
</a>
</li>
<li data-name="action">
<a href="https://github.com/refrakt-md/refrakt">
<span>View on GitHub</span>
</a>
</li>
</ul>
</div>
</div>
</section>Whats new Version 1.0
Build with refrakt.md
A content framework that turns Markdown into rich, semantic pages. Write standard Markdown — runes decide how it's interpreted.
<section data-field="content-section" class="rf-hero rf-hero--center rf-hero--full" data-media-position="bottom" data-align="center" data-width="full" data-rune="hero" data-density="full">
<div data-name="content" class="rf-hero__content">
<header data-name="preamble" class="rf-hero__preamble" data-section="preamble">
<p data-name="eyebrow" class="rf-hero__eyebrow">
Whats new
<a href="#">Version 1.0</a>
</p>
<h1 id="build-with-refrakt.md" data-name="headline" class="rf-hero__headline" data-section="title">Build with refrakt.md</h1>
<p data-name="blurb" class="rf-hero__blurb" data-section="description">A content framework that turns Markdown into rich, semantic pages. Write standard Markdown — runes decide how it's interpreted.</p>
</header>
<div data-name="actions" class="rf-hero__actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Get started</span>
</a>
</li>
<li data-name="action">
<a href="https://github.com/refrakt-md/refrakt">
<span>View on GitHub</span>
</a>
</li>
</ul>
</div>
</div>
</section>With command block
Code fences inside a hero become copyable command blocks — great for install commands on landing pages.
{% hero %}
# Get started in seconds
Scaffold a project and start writing.
```shell
npm create refrakt
```
- [Documentation](/docs/getting-started)
{% /hero %}<section data-field="content-section" data-rune="hero" data-rune-fields="{"align":"center","media-position":"bottom"}">
<div data-name="content">
<header>
<h1 id="get-started-in-seconds" data-name="headline">Get started in seconds</h1>
<p data-name="blurb">Scaffold a project and start writing.</p>
</header>
<div data-name="actions">
<div data-name="command">
<div class="rf-codeblock" data-name="command">
<pre data-language="shell">
<code data-language="shell">npm create refrakt
</code>
</pre>
</div>
</div>
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Documentation</span>
</a>
</li>
</ul>
</div>
</div>
</section>Get started in seconds
Scaffold a project and start writing.
npm create refrakt
<section data-field="content-section" class="rf-hero rf-hero--center rf-hero--full" data-media-position="bottom" data-align="center" data-width="full" data-rune="hero" data-density="full">
<div data-name="content" class="rf-hero__content">
<header data-name="preamble" class="rf-hero__preamble" data-section="preamble">
<h1 id="get-started-in-seconds" data-name="headline" class="rf-hero__headline" data-section="title">Get started in seconds</h1>
<p data-name="blurb" class="rf-hero__blurb" data-section="description">Scaffold a project and start writing.</p>
</header>
<div data-name="actions" class="rf-hero__actions">
<div data-name="command" class="rf-hero__command">
<div class="rf-hero__command rf-codeblock" data-name="command">
<pre data-language="shell"><code data-language="shell">npm create refrakt
</code></pre>
</div>
</div>
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Documentation</span>
</a>
</li>
</ul>
</div>
</div>
</section>Left-aligned
Use align="left" for a more editorial feel.
{% hero align="left" %}
# Documentation that writes itself
Semantic runes transform your Markdown into structured, accessible content.
- [Quick start](/docs/getting-started)
{% /hero %}<section data-field="content-section" data-rune="hero" data-rune-fields="{"align":"left","media-position":"bottom"}">
<div data-name="content">
<header>
<h1 id="documentation-that-writes-itself" data-name="headline">Documentation that writes itself</h1>
<p data-name="blurb">Semantic runes transform your Markdown into structured, accessible content.</p>
</header>
<div data-name="actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Quick start</span>
</a>
</li>
</ul>
</div>
</div>
</section>Documentation that writes itself
Semantic runes transform your Markdown into structured, accessible content.
<section data-field="content-section" class="rf-hero rf-hero--left rf-hero--full" data-media-position="bottom" data-align="left" data-width="full" data-rune="hero" data-density="full">
<div data-name="content" class="rf-hero__content">
<header data-name="preamble" class="rf-hero__preamble" data-section="preamble">
<h1 id="documentation-that-writes-itself" data-name="headline" class="rf-hero__headline" data-section="title">Documentation that writes itself</h1>
<p data-name="blurb" class="rf-hero__blurb" data-section="description">Semantic runes transform your Markdown into structured, accessible content.</p>
</header>
<div data-name="actions" class="rf-hero__actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Quick start</span>
</a>
</li>
</ul>
</div>
</div>
</section>Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
align | string | center | Horizontal alignment of headline + body text: left, center, right |
Cover — content over a full-bleed backdrop
media-position="cover" turns the media zone into a full-bleed backdrop: the media fills the section interior and the headline, blurb, and actions overlay it. A legibility scrim and a light-on-dark foreground are applied automatically — it's a one-attribute switch on the same content.
{% hero media-position="cover" %}

---
# Documentation that reads like it was designed
Semantic runes turn plain Markdown into structured pages.
- [Get started](/docs/getting-started)
- [Explore the runes](/runes/rune-catalog)
{% /hero %}<section data-field="content-section" data-rune="hero" data-rune-fields="{"align":"center","media-position":"cover"}">
<div data-name="content">
<header>
<h1 id="documentation-that-reads-like-it-was-designed" data-name="headline">Documentation that reads like it was designed</h1>
<p data-name="blurb">Semantic runes turn plain Markdown into structured pages.</p>
</header>
<div data-name="actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Get started</span>
</a>
</li>
<li data-name="action">
<a href="/runes/rune-catalog">
<span>Explore the runes</span>
</a>
</li>
</ul>
</div>
</div>
<div data-name="media">
<img src="https://assets.refrakt.md/media-text-valley.jpg" alt="Mountain valley at dusk">
</div>
</section>Documentation that reads like it was designed
Semantic runes turn plain Markdown into structured pages.

<section data-field="content-section" class="rf-hero rf-hero--center rf-hero--cover rf-hero--full" data-media-position="cover" data-align="center" data-width="full" data-rune="hero" data-density="full" data-cover-scope="full">
<div data-name="content" class="rf-hero__content" data-color-scheme="dark">
<header data-name="preamble" class="rf-hero__preamble" data-section="preamble">
<h1 id="documentation-that-reads-like-it-was-designed" data-name="headline" class="rf-hero__headline" data-section="title">Documentation that reads like it was designed</h1>
<p data-name="blurb" class="rf-hero__blurb" data-section="description">Semantic runes turn plain Markdown into structured pages.</p>
</header>
<div data-name="actions" class="rf-hero__actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>Get started</span>
</a>
</li>
<li data-name="action">
<a href="/runes/rune-catalog">
<span>Explore the runes</span>
</a>
</li>
</ul>
</div>
</div>
<div data-name="media" class="rf-hero__media" data-section="media" data-media="hero" data-guest-posture="presentational">
<img src="https://assets.refrakt.md/media-text-valley.jpg" alt="Mountain valley at dusk" />
</div>
</section>The band's height comes from height (named scale) or aspect (a ratio); with neither, a wide default aspect with a viewport floor applies. content-place anchors the overlay — by default the content sits as a centred band.
Animated background — a live program as the backdrop
The media zone holds any media guest — including a running sandbox. Drop a three.js scene in the media zone and the hero gets an animated background: the sandbox becomes an inert presentational backdrop (no pointer events, no focus stops), the content overlays it, and the scrim keeps the text legible. Here a wireframe terrain rolls in slow swells, its crests picking up the site's own niwaki accent colours:
{% hero media-position="cover" height="lg" %}
{% sandbox src="wireframe-waves" /%}
---
# Markdown in, meaning out
Plain text becomes structured, semantic pages.
- [See how it works](/docs/getting-started)
{% /hero %}<section data-field="content-section" data-rune="hero" data-rune-fields="{"align":"center","media-position":"cover","height":"lg"}">
<div data-name="content">
<header>
<h1 id="markdown-in,-meaning-out" data-name="headline">Markdown in, meaning out</h1>
<p data-name="blurb">Plain text becomes structured, semantic pages.</p>
</header>
<div data-name="actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>See how it works</span>
</a>
</li>
</ul>
</div>
</div>
<div data-name="media">
<rf-sandbox data-rune="sandbox" data-source-content="<div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div>" data-height="auto" data-source-origins="HTML wireframe-waves/index.html" data-security-mode="trusted" data-allow-js="true">
<template data-content="fallback">
<pre data-language="html">
<code data-language="html"><div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div></code>
</pre>
</template>
<template data-content="source"><div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div></template>
</rf-sandbox>
</div>
</section>Markdown in, meaning out
Plain text becomes structured, semantic pages.
<div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div><div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div><section data-field="content-section" class="rf-hero rf-hero--center rf-hero--cover rf-hero--full" data-media-position="cover" data-align="center" data-height="lg" data-width="full" data-rune="hero" data-density="full" data-cover-scope="full">
<div data-name="content" class="rf-hero__content" data-color-scheme="dark">
<header data-name="preamble" class="rf-hero__preamble" data-section="preamble">
<h1 id="markdown-in,-meaning-out" data-name="headline" class="rf-hero__headline" data-section="title">Markdown in, meaning out</h1>
<p data-name="blurb" class="rf-hero__blurb" data-section="description">Plain text becomes structured, semantic pages.</p>
</header>
<div data-name="actions" class="rf-hero__actions">
<ul>
<li data-name="action">
<a href="/docs/getting-started">
<span>See how it works</span>
</a>
</li>
</ul>
</div>
</div>
<div data-name="media" class="rf-hero__media" data-section="media" data-media="hero" data-guest-posture="presentational">
<rf-sandbox class="rf-sandbox" data-source-content="<div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div>" data-height="fill" data-source-origins="HTML wireframe-waves/index.html" data-security-mode="trusted" data-allow-js="true" data-rune="sandbox" data-density="compact">
<template data-content="fallback">
<pre data-language="html"><code data-language="html"><div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div></code></pre>
</template>
<template data-content="source"><div data-source="HTML">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
/* Boot backdrop — the scene fades in over this, so the first paint looks
designed even before three.js arrives (and remains if it never does). */
body { background: linear-gradient(rgb(247 246 242) 0%, rgb(236 234 227) 100%); }
html.dark body, body.dark { background: linear-gradient(rgb(20 20 23) 0%, rgb(13 13 16) 100%); }
/* Pinned to the iframe viewport, NOT height: 100% — directory-mode sandbox
content ships inside a plain wrapper div (auto height), which breaks a
percentage-height chain: the canvas would collapse to its attribute
height and render as a fixed-size strip at the top of the band. */
#scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 0.8s ease; }
#scene.on { opacity: 1; }
/* No-JS / no-WebGL / blocked-CDN fallback: a few soft horizon lines keep
the wave suggestion without competing with overlaid hero content. */
#fallback { display: none; position: fixed; inset: 0; overflow: hidden; }
#fallback.on { display: block; }
#fallback i {
position: absolute; left: -5%; right: -5%; height: 2px; display: block;
background: linear-gradient(90deg, transparent, var(--c), transparent);
border-radius: 2px;
}
</style>
<canvas id="scene"></canvas>
<div id="fallback" aria-hidden="true">
<i style="--c:rgb(138 133 125 / 0.30); top: 58%"></i>
<i style="--c:rgb(138 133 125 / 0.22); top: 66%; transform: scaleX(0.92)"></i>
<i style="--c:rgb(61 107 61 / 0.20); top: 74%; transform: scaleX(0.96)"></i>
<i style="--c:rgb(138 133 125 / 0.16); top: 82%"></i>
</div>
<script type="module">
// ── Wireframe waves (SPEC-101 / WORK-401) ──────────────────────────────
// The hero backdrop: a vertex-displaced wireframe plane rolling in slow
// swells — karesansui by way of a vector terrain. Crests pick up a niwaki
// accent over the stone base. Ambient by contract: the cover posture
// demotion blocks pointer events, so the scene carries itself with no
// interaction.
// Palette of record: packages/lumina/src/presets/niwaki.ts (light/dark pairs).
// Base = ishi (stone); crest accent = matsu (light) / wakaba (dark).
const NIWAKI = {
light: { base: 0x8a857d, crest: 0x3d6b3d, fog: 0xeceae3, opacity: 0.5 },
dark: { base: 0x7d7062, crest: 0xb3d475, fog: 0x101013, opacity: 0.55 },
};
const dark =
document.documentElement.classList.contains('dark') ||
document.body.classList.contains('dark');
const P = dark ? NIWAKI.dark : NIWAKI.light;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = document.getElementById('scene');
const showFallback = () => {
canvas.style.display = 'none';
document.getElementById('fallback').classList.add('on');
};
try {
// Version-pinned for reproducibility.
const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
// Backdrop budget: cap the pixel ratio — the scrim sits over us anyway.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
const scene = new THREE.Scene();
// Fog fades the far edge into the CSS backdrop, so the grid has a horizon
// instead of a hard geometric edge.
scene.fog = new THREE.Fog(P.fog, 9, 26);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 2.4, 9.5);
camera.lookAt(0, 0.2, 0);
// ── The displaced plane ──
const SEG_X = 88, SEG_Y = 46;
const geo = new THREE.PlaneGeometry(38, 22, SEG_X, SEG_Y);
geo.rotateX(-Math.PI / 2);
const count = geo.attributes.position.count;
const colors = new Float32Array(count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const base = new THREE.Color(P.base);
const crest = new THREE.Color(P.crest);
const tmp = new THREE.Color();
const mat = new THREE.MeshBasicMaterial({
wireframe: true, vertexColors: true,
transparent: true, opacity: P.opacity,
fog: true, depthWrite: false,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.y = -0.6;
scene.add(mesh);
// Swell: a few travelling sines at different scales — cheap, organic, no
// noise library. Amplitude stays modest; this is a ground, not a storm.
const AMP = 0.9;
function heightAt(x, z, t) {
return AMP * (
0.45 * Math.sin(x * 0.32 + t * 0.50) * Math.cos(z * 0.28 + t * 0.32)
+ 0.30 * Math.sin((x + z) * 0.18 - t * 0.40)
+ 0.15 * Math.sin(x * 0.85 + t * 0.90) * Math.sin(z * 0.55 - t * 0.55)
);
}
function displace(t) {
const pos = geo.attributes.position;
for (let i = 0; i < count; i++) {
const x = pos.getX(i), z = pos.getZ(i);
const h = heightAt(x, z, t);
pos.setY(i, h);
// Crest tint: 0 at the troughs, accent at the peaks.
const k = Math.min(Math.max((h / AMP + 1) / 2, 0), 1);
tmp.copy(base).lerp(crest, k * k); // square biases the accent to true crests
colors[i * 3] = tmp.r;
colors[i * 3 + 1] = tmp.g;
colors[i * 3 + 2] = tmp.b;
}
pos.needsUpdate = true;
geo.attributes.color.needsUpdate = true;
}
function resize() {
const w = canvas.clientWidth, h = canvas.clientHeight;
if (!w || !h) return;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener('resize', resize);
canvas.classList.add('on');
if (reduce) {
// Reduced motion: one composed static frame — a mid-swell moment.
displace(2.4);
renderer.render(scene, camera);
} else {
// The RAF loop pauses with the tab — a backdrop must not drain a
// backgrounded battery.
let raf = 0;
let t = 0;
let last = performance.now();
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
t += dt;
displace(t);
renderer.render(scene, camera);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cancelAnimationFrame(raf);
raf = 0;
} else if (!raf) {
last = performance.now();
raf = requestAnimationFrame(loop);
}
});
}
} catch (err) {
// CDN unreachable / WebGL unavailable → the quiet CSS horizon.
showFallback();
}
</script>
</div></template>
<meta data-field="design-tokens" content="{"fonts":[{"role":"heading","family":"Inter","weights":[400,600,700],"category":"sans-serif"},{"role":"body","family":"Source Sans Pro","weights":[400,600],"category":"sans-serif"},{"role":"mono","family":"Fira Code","weights":[400],"category":"monospace"}],"colors":[{"name":"Primary","value":"#2563EB","group":"Brand"},{"name":"Secondary","value":"#7C3AED","group":"Brand"},{"name":"Accent","value":"#F59E0B","group":"Brand"},{"name":"Gray","value":"#F9FAFB","group":"Neutral"},{"name":"Gray","value":"#E5E7EB","group":"Neutral"},{"name":"Gray","value":"#9CA3AF","group":"Neutral"},{"name":"Gray","value":"#374151","group":"Neutral"},{"name":"Gray","value":"#111827","group":"Neutral"}],"spacing":{"unit":"4px","scale":["4","8","12","16","24","32","48","64"]},"radii":[{"name":"sm","value":"4px"},{"name":"md","value":"8px"},{"name":"lg","value":"12px"},{"name":"full","value":"9999px"}]}" />
</rf-sandbox>
</div>
</section>The mechanism: the cover layout stacks the content over the media well; the interaction-posture contract demotes any cover guest to presentational; and the sandbox automatically switches to height="fill" so the iframe fills the band. No sandbox-side configuration is needed.
The authoring contract for an animated backdrop:
- Eager only.
activation="visible"or"click"contradicts an inert backdrop — the Run control would be unreachable — and produces a build warning. The cost lands on first paint, so keep the scene lean. - Design dim. The scene sits under the scrim; darker than feels right in isolation is right.
- Respect motion. Render a single static frame under
prefers-reduced-motion(the waves scene does). - Cap the budget. Pin the dependency version, cap
devicePixelRatio, pause the animation loop when the tab is hidden, and put a CSS gradient behind the canvas so the boot frame looks designed. Ship a static fallback for no-WebGL / blocked-CDN visitors. - Pin the canvas to the viewport. Size it with
position: fixed; inset: 0rather than aheight: 100%chain — sandbox content renders inside a plain wrapper element with auto height, which breaks percentage heights and leaves the canvas as a fixed-size strip.
Cover attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
content-place | string | auto | Overlay anchor: "<block> <inline>" (e.g. "end start"), or auto — a centred band |
height | string | — | Band height (named scale): sm, md, lg, xl |
aspect | string | — | Band aspect ratio (e.g. "21/9"); without height/aspect, a wide default with a viewport floor applies |
scrim | string | on | Scrim edge (top, bottom, left, right) or none to opt out |
scrim-type | string | gradient | Scrim treatment: gradient or frost (with scrim-blur: none, sm, md, lg) |
Section header
Hero supports an optional eyebrow, headline, and blurb above the headline and description. Place a short paragraph or heading before the main content to use them. See Page sections for the full syntax.
Layout attributes
The body splits on --- into media → content zones (media-first in source). media-position controls visual placement independently of source order — a landing-page hero typically uses media-position="end" to put the screenshot on the right.
| Attribute | Type | Default | Description |
|---|---|---|---|
media-position | string | bottom | Where the media sits: top, bottom (the default — media beneath the text, the classic hero), start (left), end (right), or cover (full-bleed backdrop — see Cover) |
media-ratio | string | — | Media's share of the row when beside content (start/end): 1/3, 2/5, 1/2, 3/5, 2/3 |
valign | string | — | Cross-axis alignment when media is beside content: top, center, bottom, stretch |
collapse | string | — | Breakpoint at which beside layouts collapse to a stack: sm, md, lg, never |
Common attributes
All block runes share these attributes for layout and theming.
| Attribute | Type | Default | Description |
|---|---|---|---|
width | string | content | Page grid width: content, wide, or full |
spacing | string | — | Vertical spacing: flush, tight, default, loose, or breathe |
inset | string | — | Horizontal padding: flush, tight, default, loose, or breathe |
tint | string | — | Named colour tint from theme configuration |
tint-mode | string | auto | Colour scheme override: auto, dark, or light |
bg | string | — | Named background preset from theme configuration |