Colophon
[COPY NEEDED: colophon intro, one or two sentences in Shoro’s voice]
Stack
Next.js 16 (App Router, Turbopack), React 19, TypeScript, and hand-written CSS Modules on a token layer. No Tailwind, no UI library. Motion for animation, Lenis for smooth scrolling, next-mdx-remote for the case studies, and a hand-ported GLSL dithering shader.
The port
This site is a rebuild of the Framer original at shoro.framer.website, matched against it section by section. The canvas supplied the values; every component here is written from scratch to hit them.
Tokens
Colors, type sizes, spacing, radii, motion values, and shader parameters are tokens with exactly one source file each; components consume tokens and never restate values. Tap a swatch to copy its value.
Fonts
Syne Tactile and Averia Libre, self-hosted as pinned v16 files. Google’s current Syne Tactile release has different glyph metrics (headings measured about 20% narrower than the published site), so the exact files the original serves are pinned here.
Breakpoints
Exactly two media queries, mirroring the Framer canvas: tablet from 810px to 1199.98px and phone up to 809.98px. Desktop is the base.
The shader
The dithering backgrounds are a GLSL port of the Framer site’s own shader, rendered at CSS-pixel resolution on purpose. This one runs the hero preset.
Decisions
The foundations and layout records from the same decisions list /system renders in full.
Four secondary text colors were raised to WCAG AA contrast (4.5:1) against white, by Shoro: --color-gray 110,110,110 to 118,118,118; --color-purple-gray 138,138,191 to 108,108,167; --color-purple-grayer 91,91,122 to 72,72,117; --color-disabled 126,128,247 (blue-violet) to 108,108,117 (neutral gray). Every consumer (case-study body copy, /music body and captions, card eyebrows/values, the coming-soon CTA) inherits the new values through the tokens.
DITHERING_PRESETS lives in lib/shader-presets.ts; the FramerDithering component and the shader lab import the same object, and the lab's copy button emits objects in that shape.
--radius-card (16px), --radius-inset (12px), --radius-control (8px), and --hairline (1px) are tokens; one-offs (10px hamburger bars, 6px lab chrome, the Featured card's authored 2px border) stay literal.
The production shader is the hand-ported GLSL of the Framer site's own DitheringShader (FramerDithering), rendered at CSS-pixel resolution.
components/DitheringBackground.tsx and @paper-design/shaders-react are deleted. (Shoro: 'Delete components/DitheringBackground.tsx and remove @paper-design/shaders-react from package.json and the lockfile: YES.')
Fonts are self-hosted pinned copies of the published site's files (Google Fonts v16 builds of Syne Tactile and Averia Libre) via next/font/local.
The palette is light-only; no prefers-color-scheme dark styles.
Exactly two media queries site-wide: tablet (810 to 1199.98px) and phone (up to 809.98px); desktop is the base.
One global class per Framer text style, composed into modules, with per-breakpoint size variables in type.css.
html/body use overflow-x: clip, never hidden.
--text-nav-link (1.4rem) is flat across breakpoints and skips the global -0.04em tracking.
heading-3b and heading-4c are larger on tablet than desktop, reproduced deliberately.
heading-3b and heading-4c tablet/phone slots follow the canvas's normalized scale (3b 1.28/1.02rem, 4c 0.96/0.77rem) — the presets were re-authored in Framer after the 2026-08-20 token read, removing the larger-on-tablet exception.
Five layout primitives (PageShell, Section, Stack, Grid, Sidebar) encode existing page structure; gaps are --space-gap multiples only, and every new page composes from them. Authored one-off values (the /play 32px grid gap and asymmetric padding) stay in page modules rather than widening the primitives.
An existing page migrates to primitives only when before/after screenshots at 375, 810, and 1200 are pixel-identical (scripts/screendiff.ts protocol); otherwise the migration reverts and the diff is recorded. First application: /play's PageShell migration passed at all three widths, then was deferred before merge because a concurrent session's uncommitted Navigation refactor owns app/play/page.tsx; re-apply after that refactor lands.
Spacing on /music is systematized into page-scoped tiers (--m-pad, --m-gap-*) defined at the top of music.module.css, stepping down per breakpoint; every gap and padding on the page picks a tier, never a literal. Owner decision superseding the canvas's one-off values (documented in CLAUDE.md, Content locations).
The shader lab chrome's 6px radius stays a literal, outside the radius tokens (Phase 2 radius grep: three uses, one module; the lab chrome is deliberately not on system tokens).
fonts/ carries .ttf twins of the pinned .woff2 files (fontTools decompression, identical v16 glyph data) because satori, the ImageResponse renderer behind the OG images, does not parse woff2. The .woff2 files stay the ones the site serves.
Preset text colors in styles/type.css are declared at zero specificity (:where), separate from the typography rules. Any module or page rule that colors the same element wins regardless of bundler stylesheet order. Found when Turbopack chunk order put type.css after the CSS modules and every equal-specificity module color silently lost: case-study headings rendered Primary and body copy Lighter blue instead of the authored Lighter/Purple-grayer, with the same suppression on /quick, /music, /colophon, Lab detail, the gate, testimonials, and the footer. Shoro: "body text should not be blue... it should be purple-grayer... other text styles are all showing primary color when that should not be the case."