Design system

The Oofytronic system

A strict, small vocabulary for a site built from floating islands. Token values on this page are read live from the stylesheet.

Principles

The aim is to find the end state of a layout built from islands: contextual panels that appear when useful, in places that never fight the content. These are the rules the site follows.

  1. Static first

    Every island is ordinary HTML that works with no JavaScript and with motion reduced. Dynamism is an enhancement layered on top, never a requirement.

  2. One element, two presentations

    An island is a single DOM element: an in-flow block when static, a floating island when dynamic. Controls are never duplicated for the sake of layout.

  3. Islands live in zones

    An island only names its zone; where that zone is, how wide, and what it becomes on narrow screens is defined once in CSS. Primary content never sits underneath an island on wide screens.

  4. Context is earned

    An island appears only while what it offers is relevant to what is on screen, and leaves when it is not. Quiet by default.

  5. State is declared, not scripted

    Markup declares what an island is; data attributes carry its state; CSS does all presentation. JavaScript only computes state, with pure functions.

  6. Square, or scooped

    Nothing is rounded. Corners are square, or bitten out as concave arcs, and the border always follows the arc.

  7. Access, don't scroll

    Long-form text scrolls. Everything else is accessed, not scrolled to: a site can be a fixed stage of full-screen scenes, with the islands as its controls. A scrolling layout is always available.

Islands

An island is a small, self-contained piece of interface with a zone, a trigger and a fallback. This is the whole model.

Zones

Wordmarktop-left
Familytop-center
Navtop-right
Contentsleft gutter
Content column
FiltersNext essayright gutter
Compact pillbottom, narrow screens
Wordmark top-left, family switcher top-centre, nav top-right, contents in the left gutter, filters and next essay in the right gutter, a compact pill along the bottom on narrow screens.

Zone definitions

Each zone is defined once. Islands in a zone inherit its placement, its hidden state and its narrow-screen behaviour.

Zone70rem and upBelow 70remUsed by
top-leftHeader, left edgeSameWordmark
top-centerHeader, centredSame; header, right edge on phones (40rem)Family
top-rightHeader, right edgeSame; a bottom pill on phonesNav
leftLeft gutter, just beneath the headerIn flow, as an ordinary block (a bottom dock on a stage page)Contents, scene list
rightRight gutter, beneath the header, or centred with data-anchor="middle"Bottom pillFilters, Next essay

One island per zone is visible at a time. Triggers are mutually exclusive on any given page, so islands in the same zone never collide.

States

  • flow

    In its natural place in the page. The static baseline, and the state when motion is reduced or JavaScript is off.

  • docked

    Floating in its zone, visible, because its trigger says what it offers is relevant now.

  • away

    Floating but hidden: faded, visibility: hidden, out of the tab order and the accessibility tree.

Triggers

TriggerFires when
scrolledThe page has moved more than 48px. Collapses the wordmark and boxes the nav.
bandThe target overlaps the middle band of the viewport (between 40% and 60% of its height).
near-endThe bottom of the target has risen above 85% of the viewport: the end is coming.
alwaysEvery time the page is dynamic. Used by the stage's scene list, which stays visible.
scrollspyThe last [data-context] target whose top has passed the reading line at 30%. At the page end the final target wins.

Registry

IslandZoneTriggerTargetStatic fallback
Wordmarktop-leftscrolled—Full wordmark in the header
Familytop-centerscrolled—A dropdown in the header (a native <details>; works with no JavaScript)
Navtop-right · bottom on phonesscrolled + scrollspy[data-context]Plain link row in the header
Filtersright · bottom pill under 70remband#writingHorizontal row above the list
Contentsleft · in flow under 70rembandpage bodyInline list under the title
Next essayright (middle) · bottom pill under 70remnear-endarticle bodyCard at the end of the article

The contract

Markup declares the island; the script reads it. Adding an island means adding attributes and CSS, not editing a list.

<aside class="next-island"
       data-island="next"          <!-- name, for the registry -->
       data-zone="right"           <!-- which zone: placement is defined once, in CSS -->
       data-anchor="middle"        <!-- optional: vertical position inside the zone -->
       data-trigger="near-end"     <!-- rule that decides visibility -->
       data-target=".article__body" <!-- what the rule measures -->
       data-dock="flow">           <!-- state: flow | docked | away -->
  …ordinary, accessible HTML…
</aside>

Visibility is a pure function of measurements, so rules are trivial to read and test.

// A trigger rule is a pure function: measurements in, state out.
const inBand = ({ top, bottom, viewport }) =>
  top <= viewport * 0.6 && bottom >= viewport * 0.4 ? "docked" : "away";

const TRIGGER_RULES = { band: inBand, "near-end": nearEnd };

Motion and access

  • The page follows prefers-reduced-motion live, and the footer toggle overrides it and is remembered.
  • Reduced motion means the static baseline, not a slower animation: nothing floats and nothing changes on scroll.
  • Context text inside an island is aria-hidden because it repeats the page; counts that matter use a polite live region.
  • Away islands leave the accessibility tree. Anchor jumps leave room for the islands via scroll-padding.

Stage

An alternative to the scrolling page for sites whose content is not long-form text. The page becomes a fixed stage of full-screen scenes, like a game or a film, and the islands are its controls.

When to use it

  • Long-form text scrolls. Articles, essays and documentation stay ordinary pages. This site is one: it scrolls.
  • Everything else is accessed. A company site, a product page or a portfolio is a set of short pieces, and a visitor should reach each one without scrolling to it. Feathermode is built this way.

The model

  • Scenes. Each scene fills the screen. Exactly one is active; the others are inert and off-screen.
  • Axis. Landscape screens move along x, like film frames. Portrait screens move along y, like a reel. The page itself never scrolls.
  • Pickers. More than one item in a scene (platforms, principles) becomes a level select: a tablist chooses, one panel shows. Without JavaScript every panel is visible.
  • The HUD is the islands. The wordmark collapses after the title scene, the nav names the current scene, and the contents island becomes the scene list: a left gutter list on wide screens, a numbered dock with previous and next buttons below 70rem.
  • Fit. Type follows the viewport height as well as the width. Secondary text (intros, notes, tags) steps aside on short screens but stays available to assistive technology. A scene can still scroll inside itself on very small screens, such as a landscape phone.

Controls

InputEffect
Arrow keys, Page Up/DownPrevious or next scene. Home and End jump to the first and last.
Wheel or trackpadOne scene per burst of scrolling.
SwipeAlong the stage axis, past a 48px threshold.
Scene list, dock, step buttonsJump to a scene, or step back and forward.
Links to #sceneIntercepted, and focus moves to the scene heading. A direct link opens that scene.
Inside a pickerArrow keys move between tabs, not scenes.

Access

  • The stage is an enhancement. With JavaScript off, or when prefers-reduced-motion is set, the same markup is an ordinary scrolling page.
  • The first tab stop on a stage page is a button that switches to the scrolling layout; the footer toggle does the same and reverses it.
  • Inactive scenes are inert, so keyboard focus and screen readers only see the active one. A polite live region announces the scene.
  • The stage cannot be scrolled, even by a #fragment jump, which would otherwise shove it out of place.

Color

Warm near-black, warm cream, and one brass accent used sparingly: labels, the active item, hover.

Pigments

  • --night-950
  • --night-900
  • --night-800
  • --night-700
  • --cream-100
  • --cream-300
  • --stone-500
  • --brass-400
  • --brass-600

Roles

  • --bg
  • --surface
  • --surface-hover
  • --text
  • --text-soft
  • --text-muted
  • --accent
  • --hairline
  • --hairline-strong

Type

A classical serif for display, a clean sans for interface, a book serif for reading.

  • Software you own

    --font-display · Cormorant Garamond · headlines, wordmark

  • Networks you share

    --font-ui · Inter · interface, metadata, labels

  • Hold your own keys

    --font-text · Newsreader · long-form reading

Scale

  • Display--step-4
  • Headline--step-3
  • Subhead--step-2
  • Lede--step-1
  • Body copy sets the base measure--step-0
  • Interface text and metadata--step--1
  • LABELS AND EYEBROWS--step--2

Space & shape

Generous spacing, hairline rules, and corners that are square or scooped, never rounded.

Spacing

  • --space-1
  • --space-2
  • --space-3
  • --space-4
  • --space-5
  • --space-6
  • --space-7

Notched corners

Concave quarter-circle bites cut with a mask and stroked in the border colour, so the outline follows the arc. Two sizes; apply with .notched and .notched--lg.

--notch-sm
--notch-lg

Layout tokens

  • --measure
  • --wrap
  • --gutter
  • --section-space
  • --header-h
  • --island-h
  • --speed

Components

Live examples built from the same classes the site uses.

Card

  • Local-First
  • Software

Sovereign Software

Cards are notched, hairline-bordered, and turn brass on hover or focus.

12 Oct 20267 min read

Project row

  • Quill

    Rows list things that are not essays: a name, a line, a status.

    PrototypeCRDTs · WebRTC

Status

Prototype Research Concept

Note

NoteNotes frame an aside, with the same notched edge.

Quote

The cloud is somebody else's castle.