Sovereign Software
Cards are notched, hairline-bordered, and turn brass on hover or focus.
The Feathermode family
A strict, small vocabulary for a site built from floating islands. Token values on this page are read live from the stylesheet.
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.
Every island is ordinary HTML that works with no JavaScript and with motion reduced. Dynamism is an enhancement layered on top, never a requirement.
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.
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.
An island appears only while what it offers is relevant to what is on screen, and leaves when it is not. Quiet by default.
Markup declares what an island is; data attributes carry its state; CSS does all presentation. JavaScript only computes state, with pure functions.
Nothing is rounded. Corners are square, or bitten out as concave arcs, and the border always follows the arc.
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.
An island is a small, self-contained piece of interface with a zone, a trigger and a fallback. This is the whole model.
Each zone is defined once. Islands in a zone inherit its placement, its hidden state and its narrow-screen behaviour.
| Zone | 70rem and up | Below 70rem | Used by |
|---|---|---|---|
top-left | Header, left edge | Same | Wordmark |
top-center | Header, centred | Same; header, right edge on phones (40rem) | Family |
top-right | Header, right edge | Same; a bottom pill on phones | Nav |
left | Left gutter, just beneath the header | In flow, as an ordinary block (a bottom dock on a stage page) | Contents, scene list |
right | Right gutter, beneath the header, or centred with data-anchor="middle" | Bottom pill | Filters, 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.
flowIn its natural place in the page. The static baseline, and the state when motion is reduced or JavaScript is off.
dockedFloating in its zone, visible, because its trigger says what it offers is relevant now.
awayFloating but hidden: faded, visibility: hidden, out of the tab order and the accessibility tree.
| Trigger | Fires when |
|---|---|
scrolled | The page has moved more than 48px. Collapses the wordmark and boxes the nav. |
band | The target overlaps the middle band of the viewport (between 40% and 60% of its height). |
near-end | The bottom of the target has risen above 85% of the viewport: the end is coming. |
always | Every time the page is dynamic. Used by the stage's scene list, which stays visible. |
scrollspy | The last [data-context] target whose top has passed the reading line at 30%. At the page end the final target wins. |
| Island | Zone | Trigger | Target | Static fallback |
|---|---|---|---|---|
| Wordmark | top-left | scrolled | — | Full wordmark in the header |
| Family | top-center | scrolled | — | A dropdown in the header (a native <details>; works with no JavaScript) |
| Nav | top-right · bottom on phones | scrolled + scrollspy | [data-context] | Plain link row in the header |
| Filters | right · bottom pill under 70rem | band | #writing | Horizontal row above the list |
| Contents | left · in flow under 70rem | band | page body | Inline list under the title |
| Next essay | right (middle) · bottom pill under 70rem | near-end | article body | Card at the end of the article |
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 };
prefers-reduced-motion live, and the footer toggle overrides it and is remembered.aria-hidden because it repeats the page; counts that matter use a polite live region.scroll-padding.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.
inert and off-screen.| Input | Effect |
|---|---|
| Arrow keys, Page Up/Down | Previous or next scene. Home and End jump to the first and last. |
| Wheel or trackpad | One scene per burst of scrolling. |
| Swipe | Along the stage axis, past a 48px threshold. |
| Scene list, dock, step buttons | Jump to a scene, or step back and forward. |
Links to #scene | Intercepted, and focus moves to the scene heading. A direct link opens that scene. |
| Inside a picker | Arrow keys move between tabs, not scenes. |
prefers-reduced-motion is set, the same markup is an ordinary scrolling page.inert, so keyboard focus and screen readers only see the active one. A polite live region announces the scene.#fragment jump, which would otherwise shove it out of place.Warm near-black, warm cream, and one brass accent used sparingly: labels, the active item, hover.
--night-950--night-900--night-800--night-700--cream-100--cream-300--stone-500--brass-400--brass-600--bg--surface--surface-hover--text--text-soft--text-muted--accent--hairline--hairline-strongA 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
--step-4--step-3--step-2--step-1--step-0--step--1--step--2Generous spacing, hairline rules, and corners that are square or scooped, never rounded.
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--measure--wrap--gutter--section-space--header-h--island-h--speedLive examples built from the same classes the site uses.
Cards are notched, hairline-bordered, and turn brass on hover or focus.
Rows list things that are not essays: a name, a line, a status.
Prototype Research Concept
The cloud is somebody else's castle.