Skip to main content

max / makenotwork

Give stacking order a named scale instead of eight numbers up to 10000
Author: Max Johnson <me@maxj.phd> · 2026-08-02 22:00 UTC
Signed with PGP, not checked
Commit: 910defdabc2d381dac4ac89bf3c99cec68b9469f
Parent: b366d66
3 files changed, +41 insertions, -21 deletions
@@ -15,6 +15,7 @@
15 15 | Type size | `--text-fine` / `--text-note` / `--text-body` / `--text-lead` / `--text-subhead` / `--text-head` / `--text-title` / `--text-display` / `--text-hero`, named for what the text is, not the size | `geometry.css` (generated by makeover-build) |
16 16 | Spacing | `--gap-bound` / `--gap-peer` / `--gap-group` / `--gap-section` / `--gap-pane` / `--gap-page`, named for the relationship they express, not the size | `geometry.css` (generated by makeover-build) |
17 17 | Radius | `--radius-sm` (4px, controls: buttons, inputs, chips, badges), `--radius-md` (0, containers: cards, panels, dropdowns), `--radius-round` (50%) | `style.css:216-222` |
18 + | Stacking | `--z-raised` / `--z-nav` / `--z-header` / `--z-dropdown` / `--z-picker` / `--z-context` / `--z-overlay` / `--z-modal` / `--z-toast` / `--z-banner` / `--z-skip-link`, in that order, named for what floats over what | `style.css:213-228` |
18 19 | 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` |
19 20
20 21 Pure `#000` and `#fff` are forbidden outside the token table. Bootstrap-derived yellows (`#fff3cd`, `#ffc107`) are forbidden. Use `--warning-bg` / `--warning-border`.
@@ -178,10 +179,11 @@
178 179 7. **Destructive actions** use `.danger` button class plus the `confirm_dialog` macro. No bare destructive buttons.
179 180 8. **Spacing values come from `--gap-*` tokens.** Pick the one that names what the rule separates: `bound` inside a thing, `peer` between siblings, `group` between related controls, `section` between parts of a pane, `pane` between panes, `page` at page level. Raw values and off-scale values are bugs. The tokens are generated from makeover-geometry and shift under a coarse pointer, so a hardcoded value silently opts that rule out of touch density.
180 181 9. **Font sizes come from `--text-*` tokens.** Pick the one that names what the text is: `fine` for timestamps and badges, `note` for metadata and table cells, `body` for running copy, `lead` for emphasised copy and card titles, `subhead` / `head` / `title` for the three heading levels, `display` and `hero` above the hierarchy. Raw values and off-scale values are bugs, including a value that differs from a tier by a fraction of a pixel. Nothing sits below `fine`: text that should recede does it with colour or weight.
181 - 10. **Page-scoped CSS is a last resort.** Default to composing layout + component primitives. Page-scoped sections exist for genuinely page-specific layout (grids, long-form typography, marketing heroes), not for shapes that are cards or list rows in disguise.
182 - 11. **No bare `<h1>` / `<h2>` in templates.** Pick a heading class: `.brand-h1` (wordmark), `.page-title` (page h1), `.subtitle-h2` (page subtitle on auth/wizards), `.subsection-title` (default h2 inside dashboards/tabs/partials), or `.section-header` (prose sub-section with bottom border). Two exceptions, both because the heading classes are the wrong tool rather than because the rule is optional. **Prose containers** style their own descendant headings — `.doc-body`, `.article-body`, `.markdown-preview` — so a heading inside one stays bare whether the prose was rendered from markdown or hand-written in the template (`pages/economics.html`). **Standalone creator pages** (`templates/custom/*.html`, `templates/embed/*.html`) do not load `style.css` at all, so no heading class exists to pick.
183 - 12. **Button class is `.btn-primary` / `.btn-secondary` / `.btn-danger`.** Works on both `<button>` and `<a>` (the `<a><button>` antipattern is banned. Use `<a class="btn-primary">…</a>`). The bare `button.primary` shorthand has been retired.
184 - 13. **Class names use kebab-case only.** No BEM `__` separator (retired 2026-05-20, was 112 sites across 20 prefixes, now flattened to single-dash). Modifier `--` (e.g. `.card--bordered`) is still allowed.
182 + 10. **`z-index` values come from `--z-*` tokens.** Pick the one that names what the thing is, and read the block in `:root` to see what it will float over. A raw number is a bug: it is how the old range reached 10000, each new floating thing picking a value bigger than whatever it happened to need to beat. If nothing in the scale describes the new thing, add a step to the scale rather than a number to the rule.
183 + 11. **Page-scoped CSS is a last resort.** Default to composing layout + component primitives. Page-scoped sections exist for genuinely page-specific layout (grids, long-form typography, marketing heroes), not for shapes that are cards or list rows in disguise.
184 + 12. **No bare `<h1>` / `<h2>` in templates.** Pick a heading class: `.brand-h1` (wordmark), `.page-title` (page h1), `.subtitle-h2` (page subtitle on auth/wizards), `.subsection-title` (default h2 inside dashboards/tabs/partials), or `.section-header` (prose sub-section with bottom border). Two exceptions, both because the heading classes are the wrong tool rather than because the rule is optional. **Prose containers** style their own descendant headings — `.doc-body`, `.article-body`, `.markdown-preview` — so a heading inside one stays bare whether the prose was rendered from markdown or hand-written in the template (`pages/economics.html`). **Standalone creator pages** (`templates/custom/*.html`, `templates/embed/*.html`) do not load `style.css` at all, so no heading class exists to pick.
185 + 13. **Button class is `.btn-primary` / `.btn-secondary` / `.btn-danger`.** Works on both `<button>` and `<a>` (the `<a><button>` antipattern is banned. Use `<a class="btn-primary">…</a>`). The bare `button.primary` shorthand has been retired.
186 + 14. **Class names use kebab-case only.** No BEM `__` separator (retired 2026-05-20, was 112 sites across 20 prefixes, now flattened to single-dash). Modifier `--` (e.g. `.card--bordered`) is still allowed.
185 187
186 188 ## How to extend
187 189
@@ -211,6 +211,24 @@
211 211 --radius-md: 0; /* containers: cards, panels, dropdowns */
212 212 --radius-round: 50%;
213 213
214 + /* Stacking order, named for what floats over what. Local rather than
215 + generated: this is a claim about MNW's own furniture, not an invariant
216 + like spacing. The numbers are spaced so a value can be slotted between
217 + two of them without renumbering, and nothing outside this block should
218 + write a z-index literal. Anything not listed here does not float. */
219 + --z-raised: 1; /* lifted within the flow: a saved badge on a card */
220 + --z-nav: 10; /* the nav panel that drops out of the mobile header */
221 + --z-header: 11; /* the header's own toggle and search panel, both of
222 + which have to stay above the panel they open */
223 + --z-dropdown: 20; /* menus and suggestion lists opened from a control */
224 + --z-picker: 30; /* pickers that open over a dropdown */
225 + --z-context: 40; /* right-click / overflow context menus */
226 + --z-overlay: 50; /* full-page scrims */
227 + --z-modal: 60; /* dialogs, above their scrim */
228 + --z-toast: 70; /* transient messages, above a dialog */
229 + --z-banner: 80; /* page-level interruptions, above everything */
230 + --z-skip-link: 90; /* the keyboard escape hatch outranks all of it */
231 +
214 232 /* Shadow scale.
215 233 --shadow-1/-2/-3 are blurred elevation (true floating: popovers,
216 234 dropdowns, modals). --shadow-raised/-card/-inset are everyday depth,
@@ -763,7 +781,7 @@
763 781 position: absolute;
764 782 top: 100%;
765 783 right: 0;
766 - z-index: 10;
784 + z-index: var(--z-dropdown);
767 785 background: var(--surface-page);
768 786 border: 1px solid var(--border);
769 787 min-width: 180px;
@@ -4540,7 +4558,7 @@
4540 4558 display: none;
4541 4559 cursor: pointer;
4542 4560 padding: var(--gap-peer);
4543 - z-index: 11;
4561 + z-index: var(--z-header);
4544 4562 }
4545 4563
4546 4564 .nav-toggle-label span {
@@ -4698,7 +4716,7 @@
4698 4716 font-family: var(--font-mono);
4699 4717 padding: var(--gap-bound) var(--gap-peer);
4700 4718 white-space: nowrap;
4701 - z-index: 10;
4719 + z-index: var(--z-dropdown);
4702 4720 pointer-events: none;
4703 4721 margin-bottom: var(--gap-bound);
4704 4722 }
@@ -4839,7 +4857,7 @@
4839 4857 position: fixed;
4840 4858 bottom: 1.5rem;
4841 4859 right: 1.5rem;
4842 - z-index: 1000;
4860 + z-index: var(--z-toast);
4843 4861 display: flex;
4844 4862 flex-direction: column;
4845 4863 gap: var(--gap-peer);
@@ -4967,7 +4985,7 @@
4967 4985 top: 0;
4968 4986 left: 0;
4969 4987 right: 0;
4970 - z-index: 10000;
4988 + z-index: var(--z-banner);
4971 4989 padding: var(--gap-group) var(--gap-section);
4972 4990 animation: banner-slide-down 0.3s ease-out;
4973 4991 }
@@ -5079,7 +5097,7 @@
5079 5097 background: var(--primary-dark);
5080 5098 color: var(--primary-light);
5081 5099 padding: 8px;
5082 - z-index: 100;
5100 + z-index: var(--z-skip-link);
5083 5101 transition: top 0.2s ease;
5084 5102 }
5085 5103
@@ -5165,7 +5183,7 @@
5165 5183 display: flex;
5166 5184 align-items: center;
5167 5185 justify-content: center;
5168 - z-index: 1000;
5186 + z-index: var(--z-modal);
5169 5187 }
5170 5188
5171 5189 .modal {
@@ -6629,7 +6647,7 @@
6629 6647 border-top: none;
6630 6648 max-height: 300px;
6631 6649 overflow-y: auto;
6632 - z-index: 20;
6650 + z-index: var(--z-dropdown);
6633 6651 box-shadow: 0 4px 8px rgba(0,0,0,0.08);
6634 6652 }
6635 6653 .docs-search-result {
@@ -7253,7 +7271,7 @@
7253 7271 padding: var(--gap-section) var(--gap-pane);
7254 7272 background: var(--surface-page);
7255 7273 border-bottom: 1px solid var(--border);
7256 - z-index: 11;
7274 + z-index: var(--z-header);
7257 7275 }
7258 7276
7259 7277 .site-header nav {
@@ -7265,7 +7283,7 @@
7265 7283 background: var(--surface-page);
7266 7284 border-bottom: 1px solid var(--border);
7267 7285 padding: var(--gap-section) var(--gap-pane);
7268 - z-index: 10;
7286 + z-index: var(--z-nav);
7269 7287 }
7270 7288
7271 7289 .site-header {
@@ -7691,7 +7709,7 @@
7691 7709 border-radius: 0 0 var(--radius-md) var(--radius-md);
7692 7710 max-height: 200px;
7693 7711 overflow-y: auto;
7694 - z-index: 10;
7712 + z-index: var(--z-dropdown);
7695 7713 box-shadow: var(--shadow-2);
7696 7714 }
7697 7715
@@ -7805,7 +7823,7 @@
7805 7823
7806 7824 .collection-picker-anchor { position: relative; }
7807 7825 .collection-picker {
7808 - position: absolute; left: 0; right: 0; top: 100%; z-index: 50;
7826 + position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
7809 7827 background: var(--surface-page); border: 1px solid var(--border);
7810 7828 box-shadow: 0 2px 8px rgba(0,0,0,0.12); min-width: 220px;
7811 7829 }
@@ -7829,7 +7847,7 @@
7829 7847 }
7830 7848 .row-save:hover, .grid-card-save:hover { opacity: 1; }
7831 7849 .grid-card-save {
7832 - position: absolute; top: 0.4rem; right: 0.4rem; z-index: 2;
7850 + position: absolute; top: 0.4rem; right: 0.4rem; z-index: var(--z-raised);
7833 7851 background: var(--surface-page); border-radius: 3px; padding: var(--gap-bound) var(--gap-peer);
7834 7852 box-shadow: 0 1px 3px rgba(0,0,0,0.15); opacity: 0;
7835 7853 }
@@ -7846,7 +7864,7 @@
7846 7864 .search-wrapper { position: relative; flex: 1; min-width: 0; }
7847 7865 .search-suggestions,
7848 7866 .tag-suggest-list {
7849 - display: none; position: absolute; left: 0; right: 0; top: 100%; z-index: 50;
7867 + display: none; position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
7850 7868 background: var(--surface-page); border: 1px solid var(--border); border-top: none;
7851 7869 box-shadow: 0 4px 12px rgba(0,0,0,0.1); max-height: 280px; overflow-y: auto;
7852 7870 }
@@ -8414,7 +8432,7 @@
8414 8432 .tag-search-wrap { position: relative; }
8415 8433 .tag-suggestions {
8416 8434 position: absolute;
8417 - z-index: 10;
8435 + z-index: var(--z-dropdown);
8418 8436 background: var(--surface-sunken);
8419 8437 border: 1px solid var(--border);
8420 8438 width: 100%;
@@ -10491,7 +10509,7 @@
10491 10509 border: 1px solid var(--border);
10492 10510 box-shadow: var(--shadow-2);
10493 10511 min-width: 160px;
10494 - z-index: 100;
10512 + z-index: var(--z-context);
10495 10513 }
10496 10514 .context-menu.open { display: block; }
10497 10515 .context-menu-item {
@@ -162,7 +162,7 @@
162 162 border-radius: 0 0 4px 4px;
163 163 max-height: 200px;
164 164 overflow-y: auto;
165 - z-index: 10;
165 + z-index: var(--z-dropdown);
166 166 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
167 167 }
168 168