Skip to main content

max / makenotwork

server: design-system shadow + hover/active state overhaul Splits the shadow scale into two intent classes: - Hard-offset (everyday affordance): --shadow-raised, --shadow-card, --shadow-inset with --shadow-edge / --shadow-edge-soft. Buttons, tabs, cards, inputs. - Blurred (true elevation, floats over page): --shadow-1, --shadow-2, --shadow-3 for subtle / raised / modal layers. Hover gets three explicit options (surface lift / dim / shift), picked per component type, never mixed on the same primitive. style.css updated alongside the design-system.md token reference.
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-24 21:06 UTC
Commit: 80cc34249f89f84debebcb89b00f984425c0b2d5
Parent: 1dcc23b
2 files changed, +105 insertions, -15 deletions
@@ -14,7 +14,7 @@ Every visual value is a token defined in `:root` (`static/style.css`). No raw he
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 16 | Radius | `--radius-sm` (2px), `--radius-md` (4px), `--radius-round` (50%) | `style.css:105-108` |
17 - | Shadow | `--shadow-1` (subtle lift), `--shadow-2` (raised card), `--shadow-3` (overlay/modal) | `style.css:110-113` |
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` |
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,7 +112,10 @@ When a primitive needs an HTMX endpoint (e.g. server-rendered confirm dialogs re
112 112
113 113 Exactly one spelling for each interaction state, applied to every interactive primitive:
114 114
115 - - **Hover** — `:hover` lifts background one surface step (`--background` -> `--light-background` -> `--surface-muted`) **or** dims opacity to `0.8` for solid-fill buttons. Pick by component type; do not mix on the same component.
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).
116 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; }`.
117 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.
118 121 - **Disabled** — `:disabled` and `[aria-disabled="true"]` show `opacity: 0.5` and `cursor: not-allowed`.
@@ -205,7 +205,17 @@
205 205 --radius-md: 4px;
206 206 --radius-round: 50%;
207 207
208 - /* Shadow scale */
208 + /* Shadow scale.
209 + --shadow-1/-2/-3 are blurred elevation (true floating: popovers,
210 + dropdowns, modals). --shadow-raised/-card/-inset are hard-offset
211 + "sticker on paper" depth for everyday affordance — buttons feel
212 + pressable, cards feel layered, inputs feel recessed. Pick by role:
213 + blurred = floats over page, hard-offset = is part of page. */
214 + --shadow-edge: #bbb; /* heavier — pressable (button, tab) */
215 + --shadow-edge-soft: #ddd; /* lighter — surface (card, input) */
216 + --shadow-raised: 2px 2px var(--shadow-edge);
217 + --shadow-card: 2px 2px var(--shadow-edge-soft);
218 + --shadow-inset: inset 2px 2px var(--shadow-edge-soft);
209 219 --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.06);
210 220 --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.10);
211 221 --shadow-3: 0 4px 12px rgba(0, 0, 0, 0.15);
@@ -359,11 +369,17 @@ button {
359 369 border: 1px solid var(--detail);
360 370 font-family: inherit;
361 371 cursor: pointer;
362 - transition: opacity 0.2s ease;
372 + box-shadow: var(--shadow-raised);
373 + transition: box-shadow 0.1s ease, transform 0.05s ease;
363 374 }
364 375
365 376 button:hover {
366 - opacity: 0.8;
377 + box-shadow: 3px 3px var(--shadow-edge);
378 + }
379 +
380 + button:active {
381 + box-shadow: none;
382 + transform: translate(2px, 2px);
367 383 }
368 384
369 385 .btn-primary {
@@ -374,16 +390,22 @@ button:hover {
374 390 font-family: inherit;
375 391 font-size: 1rem;
376 392 cursor: pointer;
377 - transition: opacity 0.2s ease;
393 + box-shadow: var(--shadow-raised);
394 + transition: box-shadow 0.1s ease, transform 0.05s ease;
378 395 display: inline-block;
379 396 text-decoration: none;
380 397 }
381 398
382 399 .btn-primary:hover {
383 - opacity: 0.8;
400 + box-shadow: 3px 3px var(--shadow-edge);
384 401 text-decoration: none;
385 402 }
386 403
404 + .btn-primary:active {
405 + box-shadow: none;
406 + transform: translate(2px, 2px);
407 + }
408 +
387 409 .btn-secondary {
388 410 background: var(--surface-muted);
389 411 color: var(--detail);
@@ -392,16 +414,22 @@ button:hover {
392 414 font-family: var(--font-mono);
393 415 font-size: 1rem;
394 416 cursor: pointer;
395 - transition: opacity 0.2s ease;
417 + box-shadow: var(--shadow-raised);
418 + transition: box-shadow 0.1s ease, transform 0.05s ease;
396 419 display: inline-block;
397 420 text-decoration: none;
398 421 }
399 422
400 423 .btn-secondary:hover {
401 - opacity: 0.8;
424 + box-shadow: 3px 3px var(--shadow-edge);
402 425 text-decoration: none;
403 426 }
404 427
428 + .btn-secondary:active {
429 + box-shadow: none;
430 + transform: translate(2px, 2px);
431 + }
432 +
405 433 .btn-danger {
406 434 background: var(--danger);
407 435 color: var(--primary-light);
@@ -410,16 +438,22 @@ button:hover {
410 438 font-family: var(--font-mono);
411 439 font-size: 1rem;
412 440 cursor: pointer;
413 - transition: opacity 0.2s ease;
441 + box-shadow: var(--shadow-raised);
442 + transition: box-shadow 0.1s ease, transform 0.05s ease;
414 443 display: inline-block;
415 444 text-decoration: none;
416 445 }
417 446
418 447 .btn-danger:hover {
419 - opacity: 0.8;
448 + box-shadow: 3px 3px var(--shadow-edge);
420 449 text-decoration: none;
421 450 }
422 451
452 + .btn-danger:active {
453 + box-shadow: none;
454 + transform: translate(2px, 2px);
455 + }
456 +
423 457 /* Disabled state for all button variants (charter rule).
424 458 Applies whether disabled via attribute or aria-disabled. */
425 459 button:disabled,
@@ -429,6 +463,8 @@ button[aria-disabled="true"],
429 463 .btn-danger:disabled {
430 464 opacity: 0.5;
431 465 cursor: not-allowed;
466 + box-shadow: none;
467 + transform: none;
432 468 }
433 469
434 470 /* Button size + style modifiers (charter: docs/design-system.md).
@@ -470,11 +506,47 @@ button[aria-disabled="true"],
470 506 font-family: var(--font-mono);
471 507 font-size: 1rem;
472 508 cursor: pointer;
509 + /* Link semantics — flat, no depth. Overrides the shadow that would
510 + otherwise cascade from `button` / `form button` when a .btn--link
511 + happens to be a <button> inside a <form> (e.g. logout). */
512 + box-shadow: none;
473 513 transition: opacity 0.2s ease;
474 514 }
475 515
476 516 .btn--link:hover {
477 517 opacity: 0.6;
518 + box-shadow: none;
519 + }
520 +
521 + .btn--link:active {
522 + box-shadow: none;
523 + transform: none;
524 + }
525 +
526 + /* Menu / disclosure openers — buttons whose job is to reveal a menu,
527 + filter panel, expander, or popover. They navigate UI, they don't
528 + commit anything, so they override the base `button` depth-lift back
529 + to flat. Add new opener selectors here as they appear. */
530 + .context-menu-btn,
531 + .discover-filter-toggle,
532 + .tip-toggle,
533 + .bundle-toggle {
534 + box-shadow: none;
535 + }
536 +
537 + .context-menu-btn:hover,
538 + .discover-filter-toggle:hover,
539 + .tip-toggle:hover,
540 + .bundle-toggle:hover {
541 + box-shadow: none;
542 + }
543 +
544 + .context-menu-btn:active,
545 + .discover-filter-toggle:active,
546 + .tip-toggle:active,
547 + .bundle-toggle:active {
548 + box-shadow: none;
549 + transform: none;
478 550 }
479 551
480 552 /* ===========================================
@@ -509,11 +581,12 @@ textarea,
509 581 select {
510 582 width: 100%;
511 583 background: var(--input-background);
512 - border: none;
584 + border: 1px solid var(--border);
513 585 padding: 0.75rem;
514 586 font-family: var(--font-mono);
515 587 font-size: 0.9rem;
516 588 color: var(--detail);
589 + box-shadow: var(--shadow-inset);
517 590 }
518 591
519 592 input::placeholder,
@@ -528,7 +601,7 @@ input[type="number"]:focus,
528 601 textarea:focus,
529 602 select:focus {
530 603 outline: none;
531 - border: 2px solid var(--highlight);
604 + border: 1px solid var(--highlight);
532 605 }
533 606
534 607 textarea {
@@ -549,11 +622,20 @@ form button {
549 622 padding: 12px 24px;
550 623 border: 1px solid var(--detail);
551 624 cursor: pointer;
625 + box-shadow: var(--shadow-raised);
626 + transition: box-shadow 0.1s ease, transform 0.05s ease, background 0.2s ease;
552 627 }
553 628
554 629 input[type="submit"]:hover,
555 630 form button:hover {
556 631 background: var(--background);
632 + box-shadow: 3px 3px var(--shadow-edge);
633 + }
634 +
635 + input[type="submit"]:active,
636 + form button:active {
637 + box-shadow: none;
638 + transform: translate(2px, 2px);
557 639 }
558 640
559 641 .form-group {
@@ -671,6 +753,9 @@ form button:hover {
671 753 font-family: var(--font-mono);
672 754 font-size: 1rem;
673 755 cursor: pointer;
756 + /* Tabs navigate, they don't commit — stay flat. Overrides the base
757 + `button` shadow if a tab is a <button>. */
758 + box-shadow: none;
674 759 transition:
675 760 background 0.2s ease,
676 761 opacity 0.2s ease;
@@ -701,7 +786,7 @@ form button:hover {
701 786 background: var(--background);
702 787 border: 1px solid var(--border);
703 788 min-width: 180px;
704 - box-shadow: 0 2px 8px rgba(0,0,0,0.1);
789 + box-shadow: var(--shadow-2);
705 790 }
706 791
707 792 .tab-overflow-menu .tab {
@@ -1001,6 +1086,8 @@ form button:hover {
1001 1086 text-decoration: none;
1002 1087 color: var(--detail);
1003 1088 display: block;
1089 + border: 1px solid var(--border);
1090 + box-shadow: var(--shadow-card);
1004 1091 transition: background 0.2s ease;
1005 1092 }
1006 1093
@@ -10135,7 +10222,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
10135 10222 top: 100%;
10136 10223 background: var(--light-background);
10137 10224 border: 1px solid var(--border);
10138 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
10225 + box-shadow: var(--shadow-2);
10139 10226 min-width: 160px;
10140 10227 z-index: 100;
10141 10228 }