Site: roro9stack.net, phase 1 (the home page, Install with a browser flasher, Downloads)

A Zola site in site/, from the design: both themes on the device's
palette, notched shapes, self-hosted fonts, the wordmark and icons, two
generated hero drawings, nine App cards (the mesh messenger marked
planned), real screenshots, the updates and build sections. The Install
page builds ESP Web Tools' manifest in the browser from the Gitea API (it
needs Caddy to allow the origin), refuses any download that isn't on the
project's server, and falls back to the esptool steps. Downloads lists the
releases. The focus ring shows on notched controls. A page checker fails
the build if a page loads from another origin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-06 18:20:18 +02:00
co-authored by Claude Sonnet 5.5
parent b92fc6e2e5
commit 8f4b5e0ddc
48 changed files with 2939 additions and 0 deletions
+234
View File
@@ -0,0 +1,234 @@
/* roro9stack.net: the tokens and shapes of the design (docs/milestones/W1.md). Every colour is on the
device's RGB332 grid. Dark is the default; a visitor who prefers light gets light, and the switch
in the header overrides both. */
@font-face { font-family: "DM Mono"; src: url(/fonts/DMMono-Regular.woff2) format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "DM Mono"; src: url(/fonts/DMMono-Medium.woff2) format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "Hanken Grotesk"; src: url(/fonts/HankenGrotesk.woff2) format("woff2"); font-weight: 100 900; font-style: normal; font-display: swap; }
:root {
--mono: "DM Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
--sans: "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
color-scheme: dark;
--s1: #494955; --s2: #000000; --s3: #6d6d55; --line: #6d6d55; --ls: #b6b6aa;
--ink: #ffffff; --muted: #b6dbff;
--cyan: #00dbff; --onfill: #000055; --cyandeep: #00dbff; --cyantext: #00dbff;
--mag: #ff00ff; --magtext: #ff92ff; --orange: #ff9200; --orangetext: #ffb655;
--icons: url(/img/icons-dark.svg);
--logo-dark: block; --logo-light: none;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]) {
color-scheme: light;
--s1: #ffffff; --s2: #dbffff; --s3: #b6dbff; --line: #92b6ff; --ls: #6d6daa;
--ink: #000055; --muted: #4949aa;
--cyandeep: #0092aa; --cyantext: #006daa; --magtext: #b600aa; --orangetext: #b64900;
--icons: url(/img/icons-light.svg);
--logo-dark: none; --logo-light: block;
}
}
:root[data-theme="light"] {
color-scheme: light;
--s1: #ffffff; --s2: #dbffff; --s3: #b6dbff; --line: #92b6ff; --ls: #6d6daa;
--ink: #000055; --muted: #4949aa;
--cyandeep: #0092aa; --cyantext: #006daa; --magtext: #b600aa; --orangetext: #b64900;
--icons: url(/img/icons-light.svg);
--logo-dark: none; --logo-light: block;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body { margin: 0; background: var(--s1); color: var(--ink); font: 400 16px/24px var(--sans); }
a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 4px; }
img { max-width: 100%; height: auto; }
h1, h2, h3, p, ul, ol, figure, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
/* A focus ring must show. clip-path clips an outline along with everything else outside the shape,
so a focused notched control drops its notches and shows square corners and its ring. */
:focus-visible { outline: 2px solid var(--cyandeep); outline-offset: 2px; }
.n-sm:focus-visible, .n-md:focus-visible, .n-lg:focus-visible { clip-path: none; }
.skip { position: absolute; left: 8px; top: -60px; z-index: 10; background: var(--cyan); color: var(--onfill); padding: 12px 16px; font: 500 14px/20px var(--mono); }
.skip:focus { top: 8px; }
/* Notched corners: whole steps on a 4px grid (the device draws no curves). */
.n-sm { clip-path: polygon(0 4px,4px 4px,4px 0,calc(100% - 4px) 0,calc(100% - 4px) 4px,100% 4px,100% calc(100% - 4px),calc(100% - 4px) calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,4px calc(100% - 4px),0 calc(100% - 4px)); }
.n-md { clip-path: polygon(0 8px,4px 8px,4px 4px,8px 4px,8px 0,calc(100% - 8px) 0,calc(100% - 8px) 4px,calc(100% - 4px) 4px,calc(100% - 4px) 8px,100% 8px,100% calc(100% - 8px),calc(100% - 4px) calc(100% - 8px),calc(100% - 4px) calc(100% - 4px),calc(100% - 8px) calc(100% - 4px),calc(100% - 8px) 100%,8px 100%,8px calc(100% - 4px),4px calc(100% - 4px),4px calc(100% - 8px),0 calc(100% - 8px)); }
.n-lg { clip-path: polygon(0 16px,8px 16px,8px 8px,16px 8px,16px 0,calc(100% - 16px) 0,calc(100% - 16px) 8px,calc(100% - 8px) 8px,calc(100% - 8px) 16px,100% 16px,100% calc(100% - 16px),calc(100% - 8px) calc(100% - 16px),calc(100% - 8px) calc(100% - 8px),calc(100% - 16px) calc(100% - 8px),calc(100% - 16px) 100%,16px 100%,16px calc(100% - 8px),8px calc(100% - 8px),8px calc(100% - 16px),0 calc(100% - 16px)); }
.wrap { width: 100%; max-width: 1200px; margin: 0 auto; padding-left: 32px; padding-right: 32px; }
.mono { font-family: var(--mono); }
/* Header */
.bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; padding-top: 24px; padding-bottom: 24px; }
.brand { display: flex; align-items: center; min-height: 44px; }
.logo { height: 28px; width: auto; }
.logo-dark { display: var(--logo-dark); }
.logo-light { display: var(--logo-light); }
.bar nav { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.bar nav a, .link-btn { display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; font: 400 14px/20px var(--mono); }
.link-btn { background: none; border: 0; color: inherit; cursor: pointer; }
.link-btn:hover { text-decoration: underline; text-underline-offset: 4px; }
.js-only { display: none; }
.js .js-only { display: inline-flex; }
/* Buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; min-height: 48px; padding: 0 24px; font: 500 14px/20px var(--mono); border: 0; cursor: pointer; }
.btn:hover { text-decoration: none; filter: brightness(1.08); }
.btn-primary { background: var(--cyan); color: var(--onfill); }
.btn-secondary { background: var(--s2); color: var(--ink); }
.bar nav a.btn { min-height: 44px; padding: 0 20px; }
.bar nav a.btn:hover { text-decoration: none; }
/* Type */
.eyebrow { font: 500 12px/16px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--cyantext); }
.eyebrow-row { display: flex; align-items: center; gap: 8px; }
.eyebrow .v { text-transform: none; }
.dot { width: 8px; height: 8px; background: var(--cyan); flex: none; }
h1, h2 { font-family: var(--mono); font-weight: 500; }
h1 { font-size: 64px; line-height: 64px; letter-spacing: -.02em; }
h2 { font-size: 40px; line-height: 44px; letter-spacing: -.01em; }
h3 { font-size: 24px; line-height: 30px; font-weight: 600; }
.accent { color: var(--cyantext); }
.lead { font-size: 18px; line-height: 28px; max-width: 520px; }
.muted { color: var(--muted); }
.small { font-size: 14px; line-height: 20px; }
.caption { font: 400 12px/16px var(--mono); color: var(--muted); }
code, .code { font-family: var(--mono); font-size: .9em; }
section { padding-top: 64px; padding-bottom: 64px; }
.section-head { display: flex; flex-direction: column; gap: 12px; max-width: 680px; }
.stack { display: flex; flex-direction: column; gap: 24px; }
/* Hero */
.hero { padding-top: 48px; padding-bottom: 32px; display: flex; flex-direction: column; gap: 48px; }
.hero-grid { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 32px 56px; }
.hero-grid > div { flex: 1 1 360px; min-width: 0; display: flex; flex-direction: column; gap: 24px; }
.hero-grid > div:first-child { flex-basis: 480px; }
.actions { display: flex; flex-wrap: wrap; gap: 16px; }
.facts { display: flex; flex-wrap: wrap; gap: 8px 24px; }
.facts li { display: flex; align-items: center; gap: 8px; font: 400 14px/20px var(--mono); color: var(--muted); }
.facts li::before { content: ""; width: 8px; height: 8px; background: var(--mag); display: block; }
.slab { background: var(--cyan); padding: 40px; display: flex; flex-wrap: wrap; gap: 32px; }
.slab .device { flex: 1 1 440px; min-width: 0; }
.illustration { display: block; width: 100%; height: auto; }
.illustration .mono { font-family: var(--mono); }
.hero .caption { margin-top: -36px; }
/* Panels */
.panel { background: var(--s2); padding: 32px; }
.status-box { background: var(--s2); padding: 24px 32px; display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 24px; }
.status-box .eyebrow { color: var(--orangetext); }
.status-box p { flex: 1 1 420px; min-width: 0; }
.status-box a, .prose a, .steps a, .need a { color: var(--cyantext); text-decoration: underline; text-underline-offset: 4px; }
.need { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 24px; margin-top: 32px; }
.need li { display: flex; flex-direction: column; gap: 4px; padding-left: 16px; border-left: 4px solid var(--cyan); }
.need strong { font-weight: 600; }
.need span { color: var(--muted); }
/* App cards */
.cards { display: flex; flex-wrap: wrap; gap: 24px; margin-top: 40px; }
.card { flex: 1 1 320px; min-width: 0; background: var(--s2); padding: 32px; display: flex; flex-direction: column; gap: 16px; }
.card-top { display: flex; justify-content: space-between; align-items: center; }
.card .tag { font: 500 12px/16px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--cyantext); }
.card .num { font: 500 12px/16px var(--mono); letter-spacing: .08em; color: var(--muted); }
.card p { color: var(--muted); }
.card .fact { margin-top: auto; padding-top: 8px; display: flex; align-items: center; gap: 8px; font: 500 12px/16px var(--mono); letter-spacing: .04em; }
.card .fact::before { content: ""; width: 8px; height: 8px; background: var(--cyan); flex: none; }
.card.planned .tag { color: var(--orangetext); }
.card.planned .fact::before { background: var(--orange); }
.ic { width: 48px; height: 48px; flex: none; background-image: var(--icons); background-repeat: no-repeat; background-size: 144px 192px; image-rendering: pixelated; }
.i0 { background-position: 0 0; } .i1 { background-position: -48px 0; } .i2 { background-position: -96px 0; }
.i3 { background-position: 0 -48px; } .i4 { background-position: -48px -48px; } .i5 { background-position: -96px -48px; }
.i6 { background-position: 0 -96px; } .i7 { background-position: -48px -96px; } .i8 { background-position: -96px -96px; }
.i9 { background-position: 0 -144px; }
.cards-note { margin-top: 24px; font-size: 14px; line-height: 20px; color: var(--muted); }
/* Real screenshots */
.shots { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 24px; margin-top: 40px; }
.shot { background: var(--s2); padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.shot img { display: block; width: 100%; height: auto; } /* shown at about half size: smoothed, not pixel-doubled */
.shot figcaption { font: 500 12px/16px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
/* Updates */
.split { display: flex; flex-wrap: wrap; gap: 48px; }
.split > div { flex: 1 1 380px; min-width: 0; display: flex; flex-direction: column; gap: 24px; }
.split > .aside { flex-basis: 420px; justify-content: center; gap: 32px; }
.updates { background: var(--s2); padding: 48px; }
.terminal { background: var(--s1); padding: 16px 20px; font: 400 14px/20px var(--mono); overflow-x: auto; white-space: pre; }
.terminal .p { color: var(--cyantext); }
.terminal .c { color: var(--muted); }
.num-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 24px; }
.num-row .big { flex: none; min-width: 160px; font: 500 64px/64px var(--mono); letter-spacing: -.02em; }
.num-row p { flex: 1 1 200px; min-width: 0; color: var(--muted); }
.big.c1 { color: var(--cyantext); } .big.c2 { color: var(--magtext); }
.split > .blocks { display: flex; flex-direction: column; gap: 4px; background: var(--s2); padding: 32px; font: 400 14px/20px var(--mono); overflow-x: auto; white-space: pre; }
.split > .blocks .gap { height: 16px; }
.split > .blocks .c { color: var(--muted); }
.split > .blocks .p { color: var(--cyantext); }
/* Call to action */
.cta { background: var(--cyan); color: var(--onfill); padding: 48px; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 40px; }
.cta > div:not(.nine) { flex: 1 1 360px; min-width: 0; display: flex; flex-direction: column; gap: 20px; }
.cta p { font-size: 18px; line-height: 28px; max-width: 480px; }
.cta .btn { background: #000055; color: #ffffff; }
.nine { display: grid; grid-template-columns: repeat(5, 24px); gap: 4px; }
.nine i { display: block; width: 24px; height: 24px; }
.nine i.on { background: var(--s1); }
/* Footer */
footer .wrap { padding-top: 32px; padding-bottom: 64px; display: flex; flex-direction: column; gap: 24px; }
.foot-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; }
.foot-row span, footer small { font: 400 14px/20px var(--mono); color: var(--muted); }
footer nav { display: flex; flex-wrap: wrap; gap: 8px; }
footer nav a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; font: 400 14px/20px var(--mono); }
footer small { display: block; max-width: 760px; }
/* Pages: Install, Downloads, 404 */
.page { padding-top: 32px; padding-bottom: 64px; display: flex; flex-direction: column; gap: 40px; }
.page h1 { font-size: 48px; line-height: 52px; }
.page h2 { font-size: 32px; line-height: 36px; }
.prose { max-width: 720px; display: flex; flex-direction: column; gap: 16px; }
.prose h2 { margin-top: 24px; }
.prose ul { list-style: disc; padding-left: 24px; display: flex; flex-direction: column; gap: 8px; }
.prose ol, .steps { list-style: decimal; padding-left: 24px; display: flex; flex-direction: column; gap: 12px; }
.prose pre { margin: 0; padding: 16px 20px; background: var(--s2); font: 400 14px/20px var(--mono); overflow-x: auto; clip-path: polygon(0 8px,4px 8px,4px 4px,8px 4px,8px 0,calc(100% - 8px) 0,calc(100% - 8px) 4px,calc(100% - 4px) 4px,calc(100% - 4px) 8px,100% 8px,100% calc(100% - 8px),calc(100% - 4px) calc(100% - 8px),calc(100% - 4px) calc(100% - 4px),calc(100% - 8px) calc(100% - 4px),calc(100% - 8px) 100%,8px 100%,8px calc(100% - 4px),4px calc(100% - 4px),4px calc(100% - 8px),0 calc(100% - 8px)); }
.prose pre code { font-size: 14px; }
.prose :not(pre) > code { background: var(--s2); padding: 0 6px; }
.prose table { border-collapse: collapse; width: 100%; }
.prose th, .prose td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
.flasher { background: var(--s2); padding: 32px; display: flex; flex-direction: column; gap: 20px; max-width: 720px; }
.flasher dl { display: grid; grid-template-columns: max-content 1fr; gap: 4px 20px; font: 400 14px/20px var(--mono); }
.flasher dt { color: var(--muted); }
.flasher dd { overflow-wrap: anywhere; }
.flasher [hidden] { display: none !important; }
.flasher .msg { color: var(--orangetext); }
.hash { word-break: break-all; }
.releases { display: flex; flex-direction: column; gap: 24px; }
.release { background: var(--s2); padding: 24px 32px; display: flex; flex-direction: column; gap: 12px; max-width: 880px; }
.release h2 { font-size: 28px; line-height: 32px; }
.release .meta { font: 400 14px/20px var(--mono); color: var(--muted); }
.release .files { display: flex; flex-wrap: wrap; gap: 8px 20px; font: 400 14px/20px var(--mono); }
.release .files a { color: var(--cyantext); text-decoration: underline; text-underline-offset: 4px; }
@media (max-width: 720px) {
h1 { font-size: 40px; line-height: 44px; }
h2 { font-size: 32px; line-height: 36px; }
.wrap { padding-left: 20px; padding-right: 20px; }
section { padding-top: 48px; padding-bottom: 48px; }
.slab, .updates, .cta { padding: 24px; }
.panel, .card { padding: 24px; }
.hero .caption { margin-top: -24px; }
.num-row .big { font-size: 48px; line-height: 48px; min-width: 120px; }
}
.accent-link { color: var(--cyantext); text-decoration: underline; text-underline-offset: 4px; }
.page > header { display: flex; flex-direction: column; gap: 12px; }
.page .lead { max-width: 720px; }