| Destinations | Ports |
|---|---|
| Any address | 80/tcp, 443/tcp |
| github.com | 443/tcp |
diff --git a/cmd/palette/main.go b/cmd/palette/main.go index 871f895..9c16ced 100644 --- a/cmd/palette/main.go +++ b/cmd/palette/main.go @@ -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. diff --git a/cmd/palette/outputs.go b/cmd/palette/outputs.go index 83c2836..7ca58e0 100644 --- a/cmd/palette/outputs.go +++ b/cmd/palette/outputs.go @@ -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) @@ -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 @@ -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"] diff --git a/dist/dashboard.css b/dist/dashboard.css index 85b5ecc..94a9ccb 100644 --- a/dist/dashboard.css +++ b/dist/dashboard.css @@ -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%; @@ -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%; @@ -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; diff --git a/dist/tokens.css b/dist/tokens.css index b7b9848..8be2bae 100644 --- a/dist/tokens.css +++ b/dist/tokens.css @@ -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; diff --git a/dist/tokens.json b/dist/tokens.json index 203369d..e42a788 100644 --- a/dist/tokens.json +++ b/dist/tokens.json @@ -47,7 +47,8 @@ "Phone": 390, "Tablet": 768, "Desktop": 1280, - "WidthPage": 1600 + "WidthPage": 1600, + "WidthPanelMin": 560 }, "light": { "accent": "#0A6FA3", diff --git a/index.html b/index.html index 6b92f72..fe980e6 100644 --- a/index.html +++ b/index.html @@ -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; } @@ -250,7 +258,7 @@
Headers in the muted sans, never uppercase; ids in the mono; figures right-aligned in tabular digits (--spin-numeric); agent-1 under the pointer, ci-job selected. On a phone a table scrolls inside its frame, the page does not.
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 --spin-radius, its header - the name in the mono, its state, the one action and ⋯ - on surface, its body on the page, no shadow: it does not float. Panels tile across the column, none narrower than --spin-width-panel-min; 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.
| Destinations | Ports |
|---|---|
| Any address | 80/tcp, 443/tcp |
| github.com | 443/tcp |
| Destinations | Ports |
|---|---|
| Any address | ping |
A Starlight page: one column at the reading measure, the sidebar at --spin-sidebar, headings in the mono. A claim links to the row of the status page that proves it.
The dashboard, read for diagnosis. Every page is one centred column --spin-width-page 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 --spin-wordmark, which the catalog audit holds the sidebar to.
The dashboard, read for diagnosis. Every page is one centred column --spin-width-page 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 panel, and panels tile across the column; the name beside the mark is --spin-wordmark, which the catalog audit holds the sidebar to.
One primary action per view, solid in the accent and in words alone: New workspace on a list, Open terminal 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 ⋯. 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.