← GlyphicaThe guide · how it was built
[ Parable build guide ]

Four sliders, one typeface.

Glyphica is a digital type foundry concept built as a Next.js static export. The whole site is typography demonstrating itself: no images, no chart library, no stock anything — the product is the rendering engine, so the flourish is a live variable-font instrument you can actually pull on.

Next.js 14 · static exportvariable fonts · 4 axesFraunces / Inter / Spline Sans Monolight + darkreduced-motion honest

The idea

A type foundry site has an unfair advantage: its product needs no photography, because every paragraph is already a product demo. The risk is the opposite — that the site reads like a PDF specimen. So Glyphica’s centerpiece is an instrument, not a gallery: four real variable axes (weight, optical size, softness, wonk) wired to a giant specimen, with size, tracking, italic, editable text, presets with foundry names, and a readout that hands you the exact CSS you just made.

The fictional flagship, Cathedra, is played by Fraunces — chosen precisely because it exposes eccentric custom axes (SOFT, WONK) alongside the registered pair (opsz, wght). The voice follows the product: opinionated, precise, a little liturgical. “The alphabet is an argument. We publish ours.”

The stack

const display = Fraunces({
  subsets: ['latin'],
  style: ['normal', 'italic'],
  axes: ['SOFT', 'WONK', 'opsz'],   // + wght, variable by default
  variable: '--font-display',
});

Signature technique — the axis instrument

Everything reduces to one string. The playground keeps axis state and renders it into font-variation-settings on the specimen; the browser’s font engine does the rest. This is the actual render path from components/AxisPlayground.tsx:

const settings =
  `"opsz" ${opsz}, "wght" ${wght}, "SOFT" ${soft}, "WONK" ${wonk ? 1 : 0}`;

<p
  className="spec-word"
  style={{
    fontVariationSettings: settings,
    fontSize: `min(${size}px, 16vw)`,        // giant, but never overflowing
    letterSpacing: `${(tracking / 100).toFixed(3)}em`,
    fontStyle: italic ? 'italic' : 'normal',
  }}
>
  {text}
</p>

Two details make it feel like an instrument instead of a form. First, the specimen carries transition: font-variation-settings 0.45s var(--ease) — the ink appears to settle into each new weight rather than snapping (axis lists must keep the same order for the interpolation to work). Second, until you touch a control, an auto-specimen loop breathes through weight and optical size, so the section demonstrates itself:

useEffect(() => {
  if (reduced || interacted || !stageInView) return;  // never under
  let raf = 0, last = 0;                              // reduced motion
  const t0 = performance.now();
  const loop = (t: number) => {
    if (interactedRef.current) return;                // yours now
    if (t - last >= 42) {                             // ~24fps is plenty
      const s = (t - t0) / 1000;
      setAxes((p) => ({
        ...p,
        wght: Math.round(560 + 320 * Math.sin(s * 0.55)),
        opsz: Math.round(80 + 64 * Math.sin(s * 0.31 + 1.4)),
      }));
      last = t;
    }
    raf = requestAnimationFrame(loop);
  };
  raf = requestAnimationFrame(loop);
  return () => cancelAnimationFrame(raf);
}, [reduced, interacted, stageInView]);

The loop is throttled to ~24fps, pauses when the stage scrolls out of view (an IntersectionObserver flips stageInView), stops forever on first interaction, and under prefers-reduced-motion it simply never starts — the sliders still work, the changes just land without easing. The waterfall below the readout re-renders the same settings at four fixed sizes with opsz mapped to each — the argument for optical sizing, made visually instead of verbally.

Details that matter

The sign-in form is a demo — it validates and confirms in place but sends nothing and stores nothing. Wire it to your own auth (and your own cart) before selling fonts with it.

Ship it on GitHub Pages

Four commands once the export is configured:

npm run build                  # next build → out/ (static export)
rm -rf docs && cp -r out docs  # Pages serves /docs on main
touch docs/.nojekyll           # keep _next/ out of Jekyll's mouth
gh api --method POST /repos/bswxyz/glyphica/pages \
  -f 'source[branch]=main' -f 'source[path]=/docs'

With trailingSlash: true this page exports as /guide/index.html, so the URL works with no server-side routing. Local dev is npm run dev — note the basePath means the site serves at localhost:3000/glyphica.

← Back to Glyphica