Contour typography
Text can become a contour object without ever stopping being text. The architecture is the
Body Matter Interaction law: the element absorbs field matter; the visual layer shows
what that absorption means; the semantic text remains the source of meaning. This page
is the Contour Sink tier — the text/vector form of the sink contract.
The quick form — live text, stroked
For simple typographic outlines, keep the text live and stroke the glyphs. Selectable, accessible, zero tooling:
.title {
color: transparent;
-webkit-text-stroke: 2px currentColor;
paint-order: stroke fill;
} The vector form — generated glyph outlines
For true contour behavior the glyphs become vector paths — from any font the author
applies to the body. The platform primitive is font-agnostic: Fundamental ships no parser
(the zero-dependency rule); you hand contourSvgFor a parsed font — anything
matching the ContourFont shape, which opentype.js satisfies directly — and it
lays out the element's own text at its computed size, generates the ring SVG, and binds it:
import { load } from 'opentype.js'; // or any parser — you bring the font
import { contourSvgFor } from '@fundamental-engine/platform';
const title = document.querySelector('#hero-title'); // your sink body, YOUR font
const font = await load('/fonts/your-font.woff'); // whatever face the element uses
contourSvgFor(title, font); // aria-hidden SVG, bound + mirrored Prefer zero font work in the browser? Run the same primitive at build time and commit the output — this site does exactly that with its self-hosted face:
node apps/site/scripts/gen-contours.mjs # the same primitive at build time — parses the site's face, commits the output
The generated SVG is a representation, never a replacement: it binds to its semantic
source with data-field-visual-for, stays aria-hidden, and the
platform mirrors the body's live feedback channels onto it
(MIRRORED_CHANNELS — density, sink load, the measured metrics):
<h1 id="hero-title" data-body="sink attract" data-absorb="80" data-max="36" data-feedback>
<span class="sr-only">Contour</span>
</h1>
<svg data-field-visual-for="hero-title" data-field-visual-role="representation"
aria-hidden="true" focusable="false" viewBox="…">
<path class="ring ring-1" d="…" />
<path class="ring ring-2" d="…" />
<path class="ring ring-3" d="…" />
</svg> /* the rings read the MIRRORED body state — no wiring beyond the binding */
.ring { fill: none; stroke: currentColor; vector-effect: non-scaling-stroke; }
.ring-1 { stroke-width: calc(1.4px + var(--load, 0) * 4px); }
.ring-2 { stroke-width: calc(4px + var(--load, 0) * 8px); opacity: calc(0.3 + var(--d, 0) * 0.4); }
.ring-3 { stroke-width: calc(8px + var(--load, 0) * 14px); opacity: calc(0.1 + var(--d, 0) * 0.25); } Live — the title is a sink; the rings are its readout
The heading below is a real sink attract body on the page field. The outlines
are the generated paths. As the body gathers and captures matter, its mirrored
--d and --load thicken and brighten the rings — hover and dwell to
feed it.
Contour
--d 0.000 · --load 0.000
True offset contours — rings at a mathematically constant distance from the glyph boundary — need polygon offsetting (a Clipper-class pass, best done in the same build step). The rings here are stacked strokes of the one outline: the documented practical form.