max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
17 files changed,
+185 insertions,
-77 deletions
| @@ -8,6 +8,11 @@ | |||
| 8 | 8 | multithreaded/static/dist/ | |
| 9 | 9 | multithreaded/frontend/node_modules/ | |
| 10 | 10 | ||
| 11 | + | # Generated stylesheets (makeover-build emits these from makeover-geometry and | |
| 12 | + | # makeover-webview; the crates are the source, a checked-in copy would drift) | |
| 13 | + | server/static/geometry.css | |
| 14 | + | server/static/layout.css | |
| 15 | + | ||
| 11 | 16 | # Environment files (contain secrets) | |
| 12 | 17 | server/.env | |
| 13 | 18 | server/.env.local |
| @@ -4396,6 +4396,7 @@ | |||
| 4396 | 4396 | "lightningcss", | |
| 4397 | 4397 | "log", | |
| 4398 | 4398 | "makeover", | |
| 4399 | + | "makeover-build", | |
| 4399 | 4400 | "memmap2", | |
| 4400 | 4401 | "metrics", | |
| 4401 | 4402 | "metrics-exporter-prometheus", | |
| @@ -4450,15 +4451,47 @@ | |||
| 4450 | 4451 | ||
| 4451 | 4452 | [[package]] | |
| 4452 | 4453 | name = "makeover" | |
| 4453 | - | version = "2.3.0" | |
| 4454 | + | version = "2.4.1" | |
| 4454 | 4455 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4455 | - | checksum = "e35cc903581eea6df09a3a102f101d85a6b941ea18431fcd0a70738762271fdf" | |
| 4456 | + | checksum = "b0a796c931419e54dd71a8aaa7d62230730e958f3101d37d4c1d3ab7b541b38e" | |
| 4456 | 4457 | dependencies = [ | |
| 4457 | 4458 | "include_dir", | |
| 4458 | 4459 | "serde", | |
| 4459 | 4460 | "toml 1.1.3+spec-1.1.0", | |
| 4460 | 4461 | ] | |
| 4461 | 4462 | ||
| 4463 | + | [[package]] | |
| 4464 | + | name = "makeover-build" | |
| 4465 | + | version = "0.2.0" | |
| 4466 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4467 | + | checksum = "2de96b65f668a690dc86768338af38779692c23ccc116f4df5746f4700b15d9a" | |
| 4468 | + | dependencies = [ | |
| 4469 | + | "makeover", | |
| 4470 | + | "makeover-geometry", | |
| 4471 | + | "makeover-webview", | |
| 4472 | + | ] | |
| 4473 | + | ||
| 4474 | + | [[package]] | |
| 4475 | + | name = "makeover-geometry" | |
| 4476 | + | version = "0.2.0" | |
| 4477 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4478 | + | checksum = "fee41544ab308cc2b66da57ec381d19ca763e85627410b0e27361539e3836873" | |
| 4479 | + | ||
| 4480 | + | [[package]] | |
| 4481 | + | name = "makeover-layout" | |
| 4482 | + | version = "0.6.0" | |
| 4483 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4484 | + | checksum = "ad745601b5220b2244d73a5da3f3854aefe4cdc955dd45c6d3aa5781ad645f5a" | |
| 4485 | + | ||
| 4486 | + | [[package]] | |
| 4487 | + | name = "makeover-webview" | |
| 4488 | + | version = "0.9.0" | |
| 4489 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4490 | + | checksum = "d4ae8b0857cfa95b5d698e22d5df17eb19bae978839d568ae5de02344892962b" | |
| 4491 | + | dependencies = [ | |
| 4492 | + | "makeover-layout", | |
| 4493 | + | ] | |
| 4494 | + | ||
| 4462 | 4495 | [[package]] | |
| 4463 | 4496 | name = "maplit" | |
| 4464 | 4497 | version = "1.0.2" |
| @@ -125,7 +125,7 @@ | |||
| 125 | 125 | tagtree = { path = "../shared/tagtree" } | |
| 126 | 126 | ||
| 127 | 127 | # Shared theme palette + the bundled theme set (Tier 0 creator theming). | |
| 128 | - | makeover = "2.3.0" | |
| 128 | + | makeover = "2.4.1" | |
| 129 | 129 | ||
| 130 | 130 | # Git source browser | |
| 131 | 131 | git2 = { version = "0.21", features = ["vendored-libgit2"] } | |
| @@ -168,6 +168,12 @@ | |||
| 168 | 168 | name = "mnw-admin" | |
| 169 | 169 | path = "src/bin/mnw-admin.rs" | |
| 170 | 170 | ||
| 171 | + | [build-dependencies] | |
| 172 | + | # Emits static/geometry.css (makeover-geometry) and static/layout.css | |
| 173 | + | # (makeover-webview). The same generator GO and BB run; only the output paths | |
| 174 | + | # differ, since the server serves its stylesheets rather than bundling them. | |
| 175 | + | makeover-build = "0.2.0" | |
| 176 | + | ||
| 171 | 177 | [dev-dependencies] | |
| 172 | 178 | tower = { version = "0.5.3", features = ["util"] } | |
| 173 | 179 | http-body-util = "0.1" |
| @@ -21,6 +21,20 @@ | |||
| 21 | 21 | // Compile the TypeScript frontend to static/dist/ (best-effort, see fn). | |
| 22 | 22 | build_frontend(); | |
| 23 | 23 | ||
| 24 | + | // The two generated stylesheets. Spacing comes from makeover-geometry and | |
| 25 | + | // composition from makeover-webview, the same way colour already comes | |
| 26 | + | // from makeover through theming.rs. Written into static/ rather than a | |
| 27 | + | // bundle directory because the server serves its stylesheets; both are | |
| 28 | + | // gitignored, since the crates are the source and a checked-in copy would | |
| 29 | + | // drift from the pin. | |
| 30 | + | // | |
| 31 | + | // No explicit touch selector: density hangs off (hover: none), | |
| 32 | + | // (pointer: coarse) alone, because the server has no mode class to key it | |
| 33 | + | // on. GO passes `.ui-mode-mobile` because it has one. | |
| 34 | + | makeover_build::geometry_css("static/geometry.css", None); | |
| 35 | + | makeover_build::layout_css("static/layout.css", &makeover_build::Emit::default()); | |
| 36 | + | println!("cargo::rerun-if-changed=build.rs"); | |
| 37 | + | ||
| 24 | 38 | // --- Static asset fingerprinting --- | |
| 25 | 39 | // Hash the content of key static files to produce a version suffix. | |
| 26 | 40 | // When any watched file changes, URLs in templates get a new ?v= param, | |
| @@ -44,6 +58,14 @@ | |||
| 44 | 58 | // the TypeScript changes. Read-only: the inputs under frontend/src are the | |
| 45 | 59 | // watched trigger (in build_frontend); watching the outputs would loop. | |
| 46 | 60 | hash_dir_js(Path::new("static/dist"), &mut hasher); | |
| 61 | + | // Same treatment for the two generated stylesheets: read-only, so a bump | |
| 62 | + | // of makeover-geometry or makeover-webview busts `?v=` without the build | |
| 63 | + | // script watching a file it writes itself. | |
| 64 | + | for path in ["static/geometry.css", "static/layout.css"] { | |
| 65 | + | if let Ok(content) = fs::read(path) { | |
| 66 | + | content.hash(&mut hasher); | |
| 67 | + | } | |
| 68 | + | } | |
| 47 | 69 | let static_hash = format!("{:016x}", hasher.finish()); | |
| 48 | 70 | let version = &static_hash[..8]; | |
| 49 | 71 | ||
| @@ -52,6 +74,8 @@ | |||
| 52 | 74 | let partial = format!( | |
| 53 | 75 | r#" <link rel="preload" href="/static/fonts/Lato-Regular.woff2" as="font" type="font/woff2" crossorigin> | |
| 54 | 76 | <link rel="preload" href="/static/fonts/ysrf.woff2" as="font" type="font/woff2" crossorigin> | |
| 77 | + | <link rel="stylesheet" href="/static/geometry.css?v={version}"> | |
| 78 | + | <link rel="stylesheet" href="/static/layout.css?v={version}"> | |
| 55 | 79 | <link rel="stylesheet" href="/static/style.css?v={version}"> | |
| 56 | 80 | <link rel="icon" href="/static/images/favicon.ico" type="image/x-icon"> | |
| 57 | 81 | <script src="/static/htmx.min.js"></script> |
| @@ -13,8 +13,8 @@ | |||
| 13 | 13 | | Color | `--background`, `--detail`, `--highlight`, `--light-background`, surface family, `--text-muted`, `--border`, semantic (`--success/-bg`, `--warning/-bg/-border`, `--danger/-bg`, `--error/-bg`), `--stripe`, health (`--health-ok/-warn/-error/-unknown`), diff (`--diff-add/-bg`, `--diff-del/-bg`), `--focus-ring`, `--highlight-faint`, `--overlay` | `style.css:40-95` | | |
| 14 | 14 | | Type | `--font-heading` (Young Serif), `--font-mono` (IBM Plex Mono), `--font-body` (Lato) | `style.css:42-44` | | |
| 15 | 15 | | Spacing | `--space-1`...`--space-6` mapping to `0.25 / 0.5 / 0.75 / 1 / 1.5 / 2 rem` | `style.css:96-103` | | |
| 16 | - | | Radius | `--radius-sm` (2px), `--radius-md` (4px), `--radius-round` (50%) | `style.css:105-108` | | |
| 17 | - | | Shadow | Hard-offset (everyday affordance): `--shadow-raised` (buttons, tabs), `--shadow-card` (cards, panels), `--shadow-inset` (inputs, recessed). Edge colors `--shadow-edge` / `--shadow-edge-soft`. Blurred (true elevation, floats over page): `--shadow-1` (subtle), `--shadow-2` (raised), `--shadow-3` (modal/overlay). | `style.css:208-222` | | |
| 16 | + | | Radius | `--radius-sm` (4px, controls: buttons, inputs, chips, badges), `--radius-md` (0, containers: cards, panels, dropdowns), `--radius-round` (50%) | `style.css:216-222` | | |
| 17 | + | | Shadow | Everyday depth: `--shadow-raised` (buttons, tabs), `--shadow-card` (cards, panels), `--shadow-inset` (inputs, recessed). All three alias the bevel pair `--bevel-raised` / `--bevel-inset` from `layout.css`, which makeover derives from each theme's own raised surface. Blurred (true elevation, floats over page): `--shadow-1` (subtle), `--shadow-2` (raised), `--shadow-3` (modal/overlay). | `style.css:224-243` | | |
| 18 | 18 | ||
| 19 | 19 | Pure `#000` and `#fff` are forbidden outside the token table. Bootstrap-derived yellows (`#fff3cd`, `#ffc107`) are forbidden. Use `--warning-bg` / `--warning-border`. | |
| 20 | 20 | ||
| @@ -112,10 +112,10 @@ | |||
| 112 | 112 | ||
| 113 | 113 | Exactly one spelling for each interaction state, applied to every interactive primitive: | |
| 114 | 114 | ||
| 115 | - | - **Hover**: three options, picked by component type; do not mix on the same component: | |
| 116 | - | - Surface lift: shift background one step (`--background` -> `--light-background` -> `--surface-muted`). For inline lists, cards-as-links, table rows, and navigation controls (tabs, pagination, breadcrumbs), anything that moves the camera without committing. | |
| 117 | - | - Depth lift: shadow extends from `var(--shadow-raised)` to `3px 3px var(--shadow-edge)`. Reserved for controls that **cause a state change**, DB write, payment, file upload, delete, save. Pair with `:active { box-shadow: none; transform: translate(2px, 2px); }` so the press feels tactile. Default carriers: solid-fill buttons (`.btn-primary` / `-secondary` / `-danger`, bare `button`, `input[type="submit"]`, `form button`). The shadow is an opt-in signal that "this commits something," so a `.btn-primary` used purely for navigation (e.g. "Go to checkout" link) is the exception, not the rule. Those keep depth because they lead to a commit, but pure view-switching uses of these classes should override to `box-shadow: none`. | |
| 118 | - | - Opacity fade to `0.6`: reserved for `.btn--link` (semantically a link, no surface). | |
| 115 | + | - **Hover**: fill shift to `var(--hover-surface)`, on every interactive primitive. One spelling, no component-type branch. The exception is `.btn--link`, which is semantically a link and has no surface to shift: it fades to opacity `0.6`. | |
| 116 | + | - **Press**: `:active` inverts the bevel, `box-shadow: var(--shadow-inset)`. Nothing moves; there is no `transform: translate(2px, 2px)` and no `box-shadow: none`. | |
| 117 | + | ||
| 118 | + | Hover used to have three options picked by component type, and the second of them was a depth lift: the shadow grew from `var(--shadow-raised)` to `3px 3px var(--shadow-edge)`, reserved for controls that commit a write, so that pressing "Pay" looked different from pressing "Next". That signal is gone rather than moved, and it was spent deliberately. A bevel is an edge and not a distance, so it has no growth axis to extend along; keeping a commit-versus-navigate cue would have meant a local override on top of the `layout.css` every other make-family app takes unmodified. Decided 2026-07-31 (wiki `mnw-platinum`). A control that commits a write is distinguished by its label and its placement, the same way it is in GoingsOn and Balanced Breakfast. | |
| 119 | 119 | - **Focus**: `:focus-visible` shows the `--focus-ring` violet outline. Custom interactive containers (`.type-card`, `.pricing-card`, sort headers) must opt in by adding `:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }`. | |
| 120 | 120 | - **Selected / active**: `.is-selected` modifier applies `background: var(--highlight-faint)` plus the focus-ring border. Migrate `.tab.active`, `.filter-item.active`, `.view-btn.active`, `.badge.active`, and the `:checked + .type-card-inner` recipe onto this single class. | |
| 121 | 121 | - **Disabled**: `:disabled` and `[aria-disabled="true"]` show `opacity: 0.5` and `cursor: not-allowed`. |
| @@ -116,6 +116,11 @@ | |||
| 116 | 116 | "/sso/login", | |
| 117 | 117 | "/sso/callback", | |
| 118 | 118 | "/static/style.css", | |
| 119 | + | // Generated by build.rs. style.css reads the bevel pair out of | |
| 120 | + | // layout.css, so gating either would strip depth from the login | |
| 121 | + | // page rather than merely slowing it. | |
| 122 | + | "/static/geometry.css", | |
| 123 | + | "/static/layout.css", | |
| 119 | 124 | "/static/images/favicon.ico", | |
| 120 | 125 | "/rustdoc/index.html", | |
| 121 | 126 | "/favicon.ico", |
| @@ -143,10 +143,15 @@ | |||
| 143 | 143 | fn default_css_carries_intent_tokens() { | |
| 144 | 144 | let css = theme_css(None); | |
| 145 | 145 | // Intent vocabulary the page's brand aliases derive from. | |
| 146 | - | assert!(css.contains("--surface-page: #ede8e1;")); | |
| 146 | + | // The two surface values are the re-ramped parchment (makeover 2.4.1): | |
| 147 | + | // the original sat 2.8 L* between page and raised, which no bevel can | |
| 148 | + | // read. Pinning them here is what makes the style.css :root default and | |
| 149 | + | // the injected block provably the same theme rather than two drifting | |
| 150 | + | // copies of it. | |
| 151 | + | assert!(css.contains("--surface-page: #ddd6c9;")); | |
| 147 | 152 | assert!(css.contains("--content: #3d3530;")); | |
| 148 | 153 | assert!(css.contains("--action: #6c5ce7;")); | |
| 149 | - | assert!(css.contains("--border: #d0cbb8;")); | |
| 154 | + | assert!(css.contains("--border: #b3ab97;")); | |
| 150 | 155 | // Derived states + the scrim are present too. | |
| 151 | 156 | assert!(css.contains("--action-hover: ")); | |
| 152 | 157 | assert!(css.contains("--overlay: rgba(")); |
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | "description": "Sell your work directly. 0% platform fee, only Stripe's ~3% processing.", | |
| 5 | 5 | "start_url": "/", | |
| 6 | 6 | "display": "minimal-ui", | |
| 7 | - | "background_color": "#ede8e1", | |
| 7 | + | "background_color": "#ddd6c9", | |
| 8 | 8 | "theme_color": "#3d3530", | |
| 9 | 9 | "icons": [ | |
| 10 | 10 | { |
| @@ -12,19 +12,19 @@ | |||
| 12 | 12 | modal.id = 'media-picker-modal'; | |
| 13 | 13 | modal.style.cssText = 'display:none; position:fixed; inset:0; z-index:1000; background:rgba(0,0,0,0.5); align-items:center; justify-content:center;'; | |
| 14 | 14 | modal.innerHTML = | |
| 15 | - | '<div style="background:var(--background-color,#ede8e1); border:1px solid var(--border-color); border-radius:6px; width:90%; max-width:700px; max-height:80vh; display:flex; flex-direction:column; overflow:hidden;">' + | |
| 16 | - | '<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.25rem; border-bottom:1px solid var(--border-color);">' + | |
| 15 | + | '<div style="background:var(--surface-page); border:1px solid var(--border); border-radius:6px; width:90%; max-width:700px; max-height:80vh; display:flex; flex-direction:column; overflow:hidden;">' + | |
| 16 | + | '<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem 1.25rem; border-bottom:1px solid var(--border);">' + | |
| 17 | 17 | '<h3 style="margin:0;">Insert from Media Library</h3>' + | |
| 18 | - | '<button id="media-picker-close" style="background:none; border:none; font-size:1.5rem; cursor:pointer; padding:0 0.25rem; color:var(--text-color);">×</button>' + | |
| 18 | + | '<button id="media-picker-close" style="background:none; border:none; font-size:1.5rem; cursor:pointer; padding:0 0.25rem; color:var(--content);">×</button>' + | |
| 19 | 19 | '</div>' + | |
| 20 | - | '<div style="padding:0.75rem 1.25rem; border-bottom:1px solid var(--border-color); display:flex; gap:0.5rem; align-items:center; flex-wrap:wrap;">' + | |
| 20 | + | '<div style="padding:0.75rem 1.25rem; border-bottom:1px solid var(--border); display:flex; gap:0.5rem; align-items:center; flex-wrap:wrap;">' + | |
| 21 | 21 | '<input type="text" id="media-picker-search" placeholder="Filter by name..." style="flex:1; min-width:140px; padding:0.35rem 0.5rem; font-size:0.85rem;">' + | |
| 22 | 22 | '<select id="media-picker-folder" style="padding:0.35rem; font-size:0.85rem;"><option value="">All folders</option></select>' + | |
| 23 | 23 | '</div>' + | |
| 24 | 24 | '<div id="media-picker-grid" style="flex:1; overflow-y:auto; padding:1rem 1.25rem; display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:0.75rem;">' + | |
| 25 | 25 | '<p class="form-hint">Loading...</p>' + | |
| 26 | 26 | '</div>' + | |
| 27 | - | '<div style="padding:0.75rem 1.25rem; border-top:1px solid var(--border-color); font-size:0.85rem; opacity:0.7;">Click an image to insert its Markdown reference at the cursor.</div>' + | |
| 27 | + | '<div style="padding:0.75rem 1.25rem; border-top:1px solid var(--border); font-size:0.85rem; opacity:0.7;">Click an image to insert its Markdown reference at the cursor.</div>' + | |
| 28 | 28 | '</div>'; | |
| 29 | 29 | document.body.appendChild(modal); | |
| 30 | 30 | ||
| @@ -88,7 +88,7 @@ | |||
| 88 | 88 | card.className = 'mp-card'; | |
| 89 | 89 | card.dataset.name = name; | |
| 90 | 90 | card.dataset.folder = f.folder || ''; | |
| 91 | - | card.style.cssText = 'cursor:pointer; border:1px solid var(--border-color); padding:0.5rem; background:var(--light-background); transition:outline 0.1s;'; | |
| 91 | + | card.style.cssText = 'cursor:pointer; border:1px solid var(--border); padding:0.5rem; background:var(--surface-raised); transition:outline 0.1s;'; | |
| 92 | 92 | (function(r) { | |
| 93 | 93 | card.addEventListener('click', function() { window._mediaPickerSelect(r); }); | |
| 94 | 94 | })(ref); | |
| @@ -97,7 +97,7 @@ | |||
| 97 | 97 | ||
| 98 | 98 | var thumb = document.createElement('div'); | |
| 99 | 99 | if (isImage) { | |
| 100 | - | thumb.style.cssText = 'width:100%; height:80px; overflow:hidden; display:flex; align-items:center; justify-content:center; background:var(--surface-muted); margin-bottom:0.4rem;'; | |
| 100 | + | thumb.style.cssText = 'width:100%; height:80px; overflow:hidden; display:flex; align-items:center; justify-content:center; background:var(--surface-sunken); margin-bottom:0.4rem;'; | |
| 101 | 101 | var img = document.createElement('img'); | |
| 102 | 102 | img.src = f.cdn_url || ''; | |
| 103 | 103 | img.alt = name; | |
| @@ -105,7 +105,7 @@ | |||
| 105 | 105 | img.loading = 'lazy'; | |
| 106 | 106 | thumb.appendChild(img); | |
| 107 | 107 | } else { | |
| 108 | - | thumb.style.cssText = 'width:100%; height:80px; display:flex; align-items:center; justify-content:center; background:var(--surface-muted); margin-bottom:0.4rem; font-size:1.5rem; opacity:0.4;'; | |
| 108 | + | thumb.style.cssText = 'width:100%; height:80px; display:flex; align-items:center; justify-content:center; background:var(--surface-sunken); margin-bottom:0.4rem; font-size:1.5rem; opacity:0.4;'; | |
| 109 | 109 | thumb.textContent = '\u{1F4C4}'; | |
| 110 | 110 | } | |
| 111 | 111 | card.appendChild(thumb); |
| @@ -147,10 +147,20 @@ | |||
| 147 | 147 | pages inject a <style> that supersedes this block with the chosen theme. | |
| 148 | 148 | Generated. Keep in sync with `cargo run --example dump_intents makenotwork`. | |
| 149 | 149 | -------------------------------------------------------------------- */ | |
| 150 | - | --surface-page: #ede8e1; | |
| 151 | - | --surface-raised: #f5f0eb; | |
| 152 | - | --surface-sunken: #ddd7c5; | |
| 153 | - | --surface-overlay: #f4f0eb; | |
| 150 | + | --surface-page: #ddd6c9; | |
| 151 | + | --surface-raised: #efe9e0; | |
| 152 | + | --surface-sunken: #cdc5b5; | |
| 153 | + | --surface-overlay: #efe9e0; | |
| 154 | + | /* The content surface cut into a raised one, so a list reads as content in a | |
| 155 | + | container rather than as bands on a panel. Derived by makeover from | |
| 156 | + | surface-raised, away from resolved content, so it goes lighter on a light | |
| 157 | + | theme and darker on a dark one. */ | |
| 158 | + | --surface-well: #fffef5; | |
| 159 | + | /* The two edges of a bevel. makeover derives both from the theme's raised | |
| 160 | + | surface, so they arrive with the rest of the intent set through | |
| 161 | + | intent_css_vars() and every creator theme gains them automatically. */ | |
| 162 | + | --bevel-light: #fffef5; | |
| 163 | + | --bevel-dark: #b5afa6; | |
| 154 | 164 | --content: #3d3530; | |
| 155 | 165 | --content-secondary: #5c554f; | |
| 156 | 166 | --content-muted: #8a8480; | |
| @@ -161,17 +171,17 @@ | |||
| 161 | 171 | --success: #27ae60; | |
| 162 | 172 | --warning: #f59e0b; | |
| 163 | 173 | --info: #17a2b8; | |
| 164 | - | --border: #d0cbb8; | |
| 165 | - | --border-strong: #c0bba8; | |
| 174 | + | --border: #b3ab97; | |
| 175 | + | --border-strong: #a39c88; | |
| 166 | 176 | --focus-ring: #6c5ce7; | |
| 167 | - | --hover-surface: #ddd7c5; | |
| 177 | + | --hover-surface: #cdc5b5; | |
| 168 | 178 | --category-one: #c0392b; | |
| 169 | 179 | --category-two: #27ae60; | |
| 170 | 180 | --category-three: #6c5ce7; | |
| 171 | 181 | --category-four: #f59e0b; | |
| 172 | 182 | --category-five: #a29bfe; | |
| 173 | 183 | --category-six: #17a2b8; | |
| 174 | - | --overlay: rgba(3, 2, 0, 0.5); | |
| 184 | + | --overlay: rgba(3, 1, 0, 0.5); | |
| 175 | 185 | ||
| 176 | 186 | /* -------------------------------------------------------------------- | |
| 177 | 187 | APP-LOCAL CONSTANTS, not theme colors, not derivable from intents. | |
| @@ -203,22 +213,31 @@ | |||
| 203 | 213 | --space-7: 3rem; | |
| 204 | 214 | --space-8: 4rem; | |
| 205 | 215 | ||
| 206 | - | /* Radius scale */ | |
| 207 | - | --radius-sm: 2px; | |
| 208 | - | --radius-md: 4px; | |
| 216 | + | /* Radius scale. Containers are square and controls keep a corner: the | |
| 217 | + | rounding survives only where a thing is meant to be pressed, so the | |
| 218 | + | corner reads as an affordance rather than as decoration. --radius-md is | |
| 219 | + | kept at 0 rather than deleted so the container call sites stay named. */ | |
| 220 | + | --radius-sm: 4px; /* controls: buttons, inputs, chips, badges */ | |
| 221 | + | --radius-md: 0; /* containers: cards, panels, dropdowns */ | |
| 209 | 222 | --radius-round: 50%; | |
| 210 | 223 | ||
| 211 | 224 | /* Shadow scale. | |
| 212 | 225 | --shadow-1/-2/-3 are blurred elevation (true floating: popovers, | |
| 213 | - | dropdowns, modals). --shadow-raised/-card/-inset are hard-offset | |
| 214 | - | "sticker on paper" depth for everyday affordance, buttons feel | |
| 215 | - | pressable, cards feel layered, inputs feel recessed. Pick by role: | |
| 216 | - | blurred = floats over page, hard-offset = is part of page. */ | |
| 217 | - | --shadow-edge: #bbb; /* heavier, pressable (button, tab) */ | |
| 218 | - | --shadow-edge-soft: #ddd; /* lighter, surface (card, input) */ | |
| 219 | - | --shadow-raised: 2px 2px var(--shadow-edge); | |
| 220 | - | --shadow-card: 2px 2px var(--shadow-edge-soft); | |
| 221 | - | --shadow-inset: inset 2px 2px var(--shadow-edge-soft); | |
| 226 | + | dropdowns, modals). --shadow-raised/-card/-inset are everyday depth, | |
| 227 | + | buttons feel pressable, cards feel layered, inputs feel recessed. Pick | |
| 228 | + | by role: blurred = floats over page, bevel = is part of page. | |
| 229 | + | ||
| 230 | + | The three used to be hard offsets over two literal greys, #bbb and #ddd, | |
| 231 | + | picked against parchment and then applied to all 31 themes a creator can | |
| 232 | + | select; on nord they were a light smear on a dark panel. They now alias | |
| 233 | + | the bevel pair from layout.css, which makeover derives from each theme's | |
| 234 | + | own raised surface, so no literal survives and every consumer re-themes | |
| 235 | + | without being opened. --shadow-raised and --shadow-card collapse onto | |
| 236 | + | one value: a bevel is an edge rather than a distance, so there is no | |
| 237 | + | heavier and lighter version of it to pick between. */ | |
| 238 | + | --shadow-raised: var(--bevel-raised); | |
| 239 | + | --shadow-card: var(--bevel-raised); | |
| 240 | + | --shadow-inset: var(--bevel-inset); | |
| 222 | 241 | --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.06); | |
| 223 | 242 | --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.10); | |
| 224 | 243 | --shadow-3: 0 4px 12px rgba(0, 0, 0, 0.15); | |
| @@ -373,16 +392,15 @@ | |||
| 373 | 392 | font-family: inherit; | |
| 374 | 393 | cursor: pointer; | |
| 375 | 394 | box-shadow: var(--shadow-raised); | |
| 376 | - | transition: box-shadow 0.1s ease, transform 0.05s ease; | |
| 395 | + | transition: box-shadow 0.1s ease, background 0.2s ease; | |
| 377 | 396 | } | |
| 378 | 397 | ||
| 379 | 398 | button:hover { | |
| 380 | - | box-shadow: 3px 3px var(--shadow-edge); | |
| 399 | + | background: var(--hover-surface); | |
| 381 | 400 | } | |
| 382 | 401 | ||
| 383 | 402 | button:active { | |
| 384 | - | box-shadow: none; | |
| 385 | - | transform: translate(2px, 2px); | |
| 403 | + | box-shadow: var(--shadow-inset); | |
| 386 | 404 | } | |
| 387 | 405 | ||
| 388 | 406 | .btn-primary { | |
| @@ -394,19 +412,22 @@ | |||
| 394 | 412 | font-size: 1rem; | |
| 395 | 413 | cursor: pointer; | |
| 396 | 414 | box-shadow: var(--shadow-raised); | |
| 397 | - | transition: box-shadow 0.1s ease, transform 0.05s ease; | |
| 415 | + | transition: box-shadow 0.1s ease, background 0.2s ease; | |
| 398 | 416 | display: inline-block; | |
| 399 | 417 | text-decoration: none; | |
| 400 | 418 | } | |
| 401 | 419 | ||
| 420 | + | /* A solid-fill button shifts its own fill rather than taking --hover-surface. | |
| 421 | + | The charter's hover rule is "one step lighter than the resting fill", and on | |
| 422 | + | a neutral control that step is --hover-surface; on this one it is not, since | |
| 423 | + | the label is --primary-light and a parchment fill under it is unreadable. */ | |
| 402 | 424 | .btn-primary:hover { | |
| 403 | - | box-shadow: 3px 3px var(--shadow-edge); | |
| 425 | + | background: color-mix(in oklch, var(--primary-dark) 85%, var(--primary-light)); | |
| 404 | 426 | text-decoration: none; | |
| 405 | 427 | } | |
| 406 | 428 | ||
| 407 | 429 | .btn-primary:active { | |
| 408 | - | box-shadow: none; | |
| 409 | - | transform: translate(2px, 2px); | |
| 430 | + | box-shadow: var(--shadow-inset); | |
| 410 | 431 | } | |
| 411 | 432 | ||
| 412 | 433 | .btn-secondary { | |
| @@ -418,19 +439,18 @@ | |||
| 418 | 439 | font-size: 1rem; | |
| 419 | 440 | cursor: pointer; | |
| 420 | 441 | box-shadow: var(--shadow-raised); | |
| 421 | - | transition: box-shadow 0.1s ease, transform 0.05s ease; | |
| 442 | + | transition: box-shadow 0.1s ease, background 0.2s ease; | |
| 422 | 443 | display: inline-block; | |
| 423 | 444 | text-decoration: none; | |
| 424 | 445 | } | |
| 425 | 446 | ||
| 426 | 447 | .btn-secondary:hover { | |
| 427 | - | box-shadow: 3px 3px var(--shadow-edge); | |
| 448 | + | background: var(--hover-surface); | |
| 428 | 449 | text-decoration: none; | |
| 429 | 450 | } | |
| 430 | 451 | ||
| 431 | 452 | .btn-secondary:active { | |
| 432 | - | box-shadow: none; | |
| 433 | - | transform: translate(2px, 2px); | |
| 453 | + | box-shadow: var(--shadow-inset); | |
| 434 | 454 | } | |
| 435 | 455 | ||
| 436 | 456 | .btn-danger { | |
| @@ -442,19 +462,19 @@ | |||
| 442 | 462 | font-size: 1rem; | |
| 443 | 463 | cursor: pointer; | |
| 444 | 464 | box-shadow: var(--shadow-raised); | |
| 445 | - | transition: box-shadow 0.1s ease, transform 0.05s ease; | |
| 465 | + | transition: box-shadow 0.1s ease, background 0.2s ease; | |
| 446 | 466 | display: inline-block; | |
| 447 | 467 | text-decoration: none; | |
| 448 | 468 | } | |
| 449 | 469 | ||
| 470 | + | /* Solid fill, so the same exception as .btn-primary applies. */ | |
| 450 | 471 | .btn-danger:hover { | |
| 451 | - | box-shadow: 3px 3px var(--shadow-edge); | |
| 472 | + | background: color-mix(in oklch, var(--danger) 85%, var(--primary-light)); | |
| 452 | 473 | text-decoration: none; | |
| 453 | 474 | } | |
| 454 | 475 | ||
| 455 | 476 | .btn-danger:active { | |
| 456 | - | box-shadow: none; | |
| 457 | - | transform: translate(2px, 2px); | |
| 477 | + | box-shadow: var(--shadow-inset); | |
| 458 | 478 | } | |
| 459 | 479 | ||
| 460 | 480 | /* Disabled state for all button variants (charter rule). | |
| @@ -626,19 +646,17 @@ | |||
| 626 | 646 | border: 1px solid var(--content); | |
| 627 | 647 | cursor: pointer; | |
| 628 | 648 | box-shadow: var(--shadow-raised); | |
| 629 | - | transition: box-shadow 0.1s ease, transform 0.05s ease, background 0.2s ease; | |
| 649 | + | transition: box-shadow 0.1s ease, background 0.2s ease; | |
| 630 | 650 | } | |
| 631 | 651 | ||
| 632 | 652 | input[type="submit"]:hover, | |
| 633 | 653 | form button:hover { | |
| 634 | - | background: var(--surface-page); | |
| 635 | - | box-shadow: 3px 3px var(--shadow-edge); | |
| 654 | + | background: var(--hover-surface); | |
| 636 | 655 | } | |
| 637 | 656 | ||
| 638 | 657 | input[type="submit"]:active, | |
| 639 | 658 | form button:active { | |
| 640 | - | box-shadow: none; | |
| 641 | - | transform: translate(2px, 2px); | |
| 659 | + | box-shadow: var(--shadow-inset); | |
| 642 | 660 | } | |
| 643 | 661 | ||
| 644 | 662 | .form-group { | |
| @@ -1528,7 +1546,7 @@ | |||
| 1528 | 1546 | width: 100px; | |
| 1529 | 1547 | padding: 8px; | |
| 1530 | 1548 | border: 1px solid var(--border); | |
| 1531 | - | border-radius: var(--radius-md); | |
| 1549 | + | border-radius: var(--radius-sm); | |
| 1532 | 1550 | font-size: 1rem; | |
| 1533 | 1551 | } | |
| 1534 | 1552 | ||
| @@ -10118,7 +10136,7 @@ | |||
| 10118 | 10136 | background: var(--action); | |
| 10119 | 10137 | color: var(--primary-light); | |
| 10120 | 10138 | border: none; | |
| 10121 | - | border-radius: var(--radius-md); | |
| 10139 | + | border-radius: var(--radius-sm); | |
| 10122 | 10140 | cursor: pointer; | |
| 10123 | 10141 | white-space: nowrap; | |
| 10124 | 10142 | } | |
| @@ -11382,18 +11400,19 @@ | |||
| 11382 | 11400 | color: var(--content); | |
| 11383 | 11401 | background: var(--surface-raised); | |
| 11384 | 11402 | border: 1px solid var(--content); | |
| 11385 | - | border-radius: 4px; | |
| 11386 | - | box-shadow: 2px 2px var(--shadow-edge); | |
| 11403 | + | border-radius: var(--radius-sm); | |
| 11404 | + | box-shadow: var(--shadow-raised); | |
| 11387 | 11405 | cursor: pointer; | |
| 11388 | 11406 | } | |
| 11389 | 11407 | ||
| 11390 | 11408 | .carousel-nav:hover { | |
| 11391 | - | background: var(--surface-page); | |
| 11409 | + | background: var(--hover-surface); | |
| 11392 | 11410 | } | |
| 11393 | 11411 | ||
| 11412 | + | /* Keeps the translateY that centres it vertically; the press is the bevel, | |
| 11413 | + | so nothing is added to the transform. */ | |
| 11394 | 11414 | .carousel-nav:active { | |
| 11395 | - | transform: translateY(-50%) translate(2px, 2px); | |
| 11396 | - | box-shadow: none; | |
| 11415 | + | box-shadow: var(--shadow-inset); | |
| 11397 | 11416 | } | |
| 11398 | 11417 | ||
| 11399 | 11418 | .carousel-nav:focus-visible { |
| @@ -8,7 +8,7 @@ | |||
| 8 | 8 | * { margin: 0; padding: 0; box-sizing: border-box; } | |
| 9 | 9 | body { | |
| 10 | 10 | font-family: Lato, -apple-system, sans-serif; | |
| 11 | - | background: #ede8e1; color: #3d3530; | |
| 11 | + | background: #ddd6c9; color: #3d3530; | |
| 12 | 12 | display: flex; align-items: center; | |
| 13 | 13 | height: 100vh; padding: 8px 12px; | |
| 14 | 14 | } |