Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/palette/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type palette struct {
TargetTouch int `toml:"target_touch"`
Phone, Tablet, Desktop int
WidthPage int `toml:"width_page"`
WidthPanelMin int `toml:"width_panel_min"`
}
// A control's geometry: the heights of a button or an input, the icon in it, and the gap
// between an icon and its word.
Expand Down
6 changes: 6 additions & 0 deletions cmd/palette/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func tokensCSS(p palette, dark, light map[string]rgb) string {
fmt.Fprintf(&b, "\t--spin-control-sm: %dpx;\n\t--spin-control-md: %dpx;\n\t--spin-control-lg: %dpx;\n", p.Controls.Sm, p.Controls.Md, p.Controls.Lg)
fmt.Fprintf(&b, "\t--spin-control-icon: %dpx;\n\t--spin-control-gap: %dpx;\n", p.Controls.Icon, p.Controls.Gap)
fmt.Fprintf(&b, "\t--spin-width-page: %dpx;\n", p.Layout.WidthPage)
fmt.Fprintf(&b, "\t--spin-width-panel-min: %dpx;\n", p.Layout.WidthPanelMin)
fmt.Fprintf(&b, "\t--spin-duration-fast: %dms;\n\t--spin-duration: %dms;\n", p.Motion.Fast, p.Motion.Base)
fmt.Fprintf(&b, "\t--spin-duration-slow: %dms;\n\t--spin-duration-draw: %dms;\n", p.Motion.Slow, p.Motion.Draw)
fmt.Fprintf(&b, "\t--spin-ease: %s;\n", p.Motion.Ease)
Expand Down Expand Up @@ -221,6 +222,9 @@ func dashboardCSS(p palette, dark, light map[string]rgb) string {
// by its own lightness rather than by a shadow; white in light.
{"popover", hsl(c["overlay"])}, {"popover-foreground", hsl(c["text"])},
{"muted", hsl(c["raised"])}, {"muted-foreground", hsl(c["muted"])},
// A panel's header: the surface, a step off the page in either theme, so a thing's name
// and its action read as one band without a second border saying so.
{"surface", hsl(c["surface"])},
{"border", hsl(c["border"])}, {"input", hsl(c["border_strong"])}, {"ring", hsl(c["accent"])},
{"primary", hsl(c["accent"])}, {"primary-foreground", hsl(c["on_accent"])},
// The accent as text - a link, an action written as words: accent_text, held at 4.5:1 on
Expand Down Expand Up @@ -264,6 +268,8 @@ func dashboardCSS(p palette, dark, light map[string]rgb) string {
fmt.Fprintf(&b, "\t--spin-control-icon: %dpx;\n\t--spin-control-gap: %dpx;\n", p.Controls.Icon, p.Controls.Gap)
// How wide a page may grow (the dashboard's page-width.ts reads it).
fmt.Fprintf(&b, "\t--spin-width-page: %dpx;\n", p.Layout.WidthPage)
// The least a panel is as panels tile across the column (layout.width_panel_min).
fmt.Fprintf(&b, "\t--spin-width-panel-min: %dpx;\n", p.Layout.WidthPanelMin)
// The dim behind a dialog: the dark page, in either theme, at the palette's scrim - a neutral
// dim, not the mark's navy, which tinted everything behind a dialog blue.
dim := dark["page"]
Expand Down
3 changes: 3 additions & 0 deletions dist/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
--popover-foreground: 220.9 39.3% 11.0%;
--muted: 216.0 33.3% 91.2%;
--muted-foreground: 219.4 16.3% 38.4%;
--surface: 217.5 40.0% 96.1%;
--border: 216.0 20.4% 80.8%;
--input: 221.1 14.8% 53.5%;
--ring: 200.4 88.4% 33.9%;
Expand Down Expand Up @@ -45,6 +46,7 @@
--popover-foreground: 215.0 31.6% 92.5%;
--muted: 221.8 34.7% 18.6%;
--muted-foreground: 218.0 17.4% 66.3%;
--surface: 220.8 36.2% 13.5%;
--border: 221.1 26.8% 27.8%;
--input: 221.0 16.1% 50.0%;
--ring: 201.6 73.9% 40.6%;
Expand Down Expand Up @@ -81,6 +83,7 @@
--spin-control-icon: 16px;
--spin-control-gap: 6px;
--spin-width-page: 1600px;
--spin-width-panel-min: 560px;
--scrim: rgb(14 20 33 / 0.64);
--terminal-background: #0A0C10;
--terminal-foreground: #E6EBF2;
Expand Down
1 change: 1 addition & 0 deletions dist/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
--spin-control-icon: 16px;
--spin-control-gap: 6px;
--spin-width-page: 1600px;
--spin-width-panel-min: 560px;
--spin-duration-fast: 120ms;
--spin-duration: 200ms;
--spin-duration-slow: 400ms;
Expand Down
3 changes: 2 additions & 1 deletion dist/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"Phone": 390,
"Tablet": 768,
"Desktop": 1280,
"WidthPage": 1600
"WidthPage": 1600,
"WidthPanelMin": 560
},
"light": {
"accent": "#0A6FA3",
Expand Down
31 changes: 28 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,15 @@
.state::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.state.ok { color: var(--spin-success); } .state.warn { color: var(--spin-warning); } .state.err { color: var(--spin-error); } .state.idle { color: var(--spin-muted); }
.state.warn::before { border-radius: 1px; transform: rotate(45deg); } .state.err::before { border-radius: 0; }
.panel { background: var(--spin-raised); border: 1px solid var(--spin-border); border-radius: var(--spin-radius); padding: 1rem; }
.panels { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--spin-width-panel-min)), 1fr)); gap: 1rem; }
.panel { background: var(--spin-page); border: 1px solid var(--spin-border); border-radius: var(--spin-radius); overflow: hidden; }
.panel header { display: flex; align-items: center; gap: .6rem; padding: .5rem .75rem; background: var(--spin-surface); border-bottom: 1px solid var(--spin-border); font-size: 13px; }
.panel header b { font: 600 14px var(--spin-font-mono); }
.panel header .grow { flex: 1; }
.panel .grid { table-layout: fixed; }
.panel .grid td, .panel .grid th { white-space: normal; }
.panel .grid tr:last-child td { border-bottom: 0; }
.panel .grid .id { color: var(--spin-text); }
.dialog { background: var(--spin-overlay); border: 1px solid var(--spin-border); border-radius: var(--spin-radius-lg); padding: 1.25rem; max-width: 26rem; box-shadow: var(--spin-shadow-overlay); }
.toast { display: inline-flex; gap: .6rem; align-items: center; background: var(--spin-overlay); border: 1px solid var(--spin-border); border-radius: var(--spin-radius); padding: .6rem .9rem; box-shadow: var(--spin-shadow-overlay); }
.row { display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-start; margin-top: 1rem; }
Expand Down Expand Up @@ -250,7 +258,7 @@ <h1>Spinbox brand system</h1>
<nav class="index only-full" aria-label="On this page">
<div class="wrap">
<a href="#in-use">1 · In use</a><a href="#site">Site</a><a href="#dashboard">Dashboard</a><a href="#app">App</a>
<a href="#decisions">2 · Decisions</a><a href="#mark">Mark</a><a href="#buttons">Buttons</a><a href="#states">States</a><a href="#fields">Fields</a><a href="#tables">Tables</a><a href="#palette-section">Palette</a>
<a href="#decisions">2 · Decisions</a><a href="#mark">Mark</a><a href="#buttons">Buttons</a><a href="#states">States</a><a href="#fields">Fields</a><a href="#tables">Tables</a><a href="#panels">Panels</a><a href="#palette-section">Palette</a>
<a href="#checks">3 · Checks</a><a href="#widths">Widths</a><a href="#perception">Perception</a><a href="screens.html">Every screen →</a>
</div>
</nav>
Expand Down Expand Up @@ -402,6 +410,23 @@ <h3>Sizes</h3>
<p class="muted" style="font-size:13px">Headers in the muted sans, never uppercase; ids in the mono; figures right-aligned in tabular digits (<code>--spin-numeric</code>); agent-1 under the pointer, ci-job selected. On a phone a table scrolls inside its frame, the page does not.</p>
</section>

<section class="only-full" id="panels">
<span class="label">Panels</span>
<div class="rules">
<p class="muted">A section of a page is a heading and its rows; a thing with its own identity and its single action is a panel. A hairline edge at <code>--spin-radius</code>, its header - the name in the mono, its state, the one action and <code>⋯</code> - on <code>surface</code>, its body on the page, no shadow: it does not float. Panels tile across the column, none narrower than <code>--spin-width-panel-min</code>; inside one, a table has fixed tracks, so a column is at one x in every panel. A list of many is a table, never a panel per row.</p>
<div class="panels">
<article class="panel">
<header><b>web</b><span class="state warn">pending</span><span class="grow"></span><button class="btn sm secondary">Edit</button><button class="btn sm ghost icon" aria-label="More actions"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg></button></header>
<table class="grid"><colgroup><col style="width:65%"><col></colgroup><thead><tr><th>Destinations</th><th>Ports</th></tr></thead><tbody><tr><td class="muted">Any address</td><td class="id">80/tcp, 443/tcp</td></tr><tr><td class="id">github.com</td><td class="id">443/tcp</td></tr></tbody></table>
</article>
<article class="panel">
<header><b>ping</b><span class="state ok">enforced on 2 runners</span><span class="grow"></span><button class="btn sm secondary">Edit</button><button class="btn sm ghost icon" aria-label="More actions"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg></button></header>
<table class="grid"><colgroup><col style="width:65%"><col></colgroup><thead><tr><th>Destinations</th><th>Ports</th></tr></thead><tbody><tr><td class="muted">Any address</td><td class="id">ping</td></tr></tbody></table>
</article>
</div>
</div>
</section>

<section class="only-full" id="compositions">
<span class="label">Compositions</span>
<div class="rules">
Expand All @@ -411,7 +436,7 @@ <h3>Marketing</h3>
<h3>Documentation</h3>
<p class="muted">A Starlight page: one column at the reading measure, the sidebar at <code>--spin-sidebar</code>, headings in the mono. A claim links to the row of the status page that proves it.</p>
<h3>Application</h3>
<p class="muted">The dashboard, read for diagnosis. Every page is one centred column <code>--spin-width-page</code> wide, so its edges stay where the page before had them; an explorer takes the viewport. A page is a header - the title, its state, its context and its one primary action on one row - and sections of rows between hairlines; the name beside the mark is <code>--spin-wordmark</code>, which the catalog audit holds the sidebar to.</p>
<p class="muted">The dashboard, read for diagnosis. Every page is one centred column <code>--spin-width-page</code> wide, so its edges stay where the page before had them; an explorer takes the viewport. A page is a header - the title, its state, its context and its one primary action on one row - and sections of rows between hairlines; a thing with its own identity and its single action is a <a href="#panels">panel</a>, and panels tile across the column; the name beside the mark is <code>--spin-wordmark</code>, which the catalog audit holds the sidebar to.</p>
<h3>Actions</h3>
<p class="muted">One primary action per view, solid in the accent and in words alone: <em>New workspace</em> on a list, <em>Open terminal</em> on a workspace. A row's actions are quiet: its most likely one as a secondary button (the raised fill, a hairline edge, no icon) and the rest behind <code>⋯</code>. An icon goes alone, with a tooltip, only where the toolbar has no room for words; never an icon and a word on every row. What destroys is in the destructive fill, in a confirmation, after Cancel.</p>
<h3>Elevation</h3>
Expand Down
11 changes: 11 additions & 0 deletions palette.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ desktop = 1280
# (2026-11-08). An explorer - logs, traces - takes the viewport. The dashboard's page-width.ts
# reads this.
width_page = 1600 # px
# What a page is made of, inside that column. A section of a page is a heading, a hairline and
# its rows, on the page's own plane: a border around every section made the console a grid of
# cards (the October review). A thing with its own identity and its single action - a network
# policy, a resource preset, a forge's app - is a panel: a hairline edge at `radius`, its header (name, state,
# the action and its menu) on `surface`, its body on the page, no shadow, since it does not
# float. A list of many is a table, never a panel per row. Panels tile across the column, each
# at least this wide: at 1600 px two rules tables side by side, where one stretched to the
# column's width put a port 1000 px from its destination (2026-11-08). In the column a table has
# fixed tracks, so a column is at one x in every row and every panel, and a label and its value
# sit together, never split across the width.
width_panel_min = 560 # px

# Controls: a button's or an input's height, small (a row's action, a toolbar), medium (a form, a
# page's header) and large (a dialog's footer, the site); the icon in it and the gap to its word.
Expand Down
Loading