Skip to main content

max / makenotwork

314.6 KB · 11764 lines History Blame Raw
1 /* ============================================================================
2 MNW global stylesheet. Charter: docs/design-system.md.
3
4 Build new features by COMPOSING these primitives, in this order of preference:
5
6 1. Use a UTILITY class for one-off adjustments (.mb-section, .text-sm, .nowrap)
7 2. Use a LAYOUT PRIMITIVE to position content (.container, .stack-row, .field-row, .list-row)
8 3. Use a COMPONENT PRIMITIVE for a UI element (.card, .callout, .badge, .progress-bar, .empty-state, .input--sm, .small)
9 4. Extend a primitive with a modifier (.card--bordered, .callout--warning, .stack-row--bordered)
10 5. ONLY THEN consider a new class, and add it to the right section below
11
12 A new class is a smell, not a goal. Before writing one:
13 - grep this file for the visual shape you want; almost everything is here
14 - if you can't find it, consider whether the shape belongs in the design system
15 (then add it to the appropriate section here AND to docs/design-system.md)
16 - page-scoped rules (`.foo-page .bar`) are a last resort, not a first move
17
18 --------------------------------------------------------------------------
19 FILE STRUCTURE (search by section name for anchor)
20
21 FOUNDATIONS
22 @font-face declarations (top of file)
23 :root tokens color, type, space, radius, shadow
24 Element base h1/h2/h3, p, a, button, input, table
25
26 UTILITIES
27 Spacing (.m-0, .mt-*, .mb-*, .ml-*, .my-*)
28 Sizing (.col-*, .w-*, .maxw-*, .minw-*)
29 Text (.text-sm, .text-xs, .text-center, .text-right, .fw-bold,
30 .nowrap, .muted, .dimmed, .dimmer, .is-faded, .danger-text)
31 State (.hidden, .is-selected, .is-active, .unstyled-link)
32 Shapes (.square-cover, .scroll-x)
33
34 LAYOUT PRIMITIVES (page-scaffolding shapes)
35 .container width set by body class
36 .stack-row + --bordered / --tight / --top toolbar / header bar
37 .field-row + .form-group.is-grow inline form row
38 .list-row + .list-row-title vertical item list
39 .form-row 2-col grid form
40 .cover-row + .cover-thumb + .cover-empty image picker
41
42 COMPONENT PRIMITIVES (reusable UI blocks)
43 .content-section light-bg page section box
44 .card / .card-muted / .card--bordered / .card--selectable
45 .form-group / .form-section / details.form-section
46 .section-header / .section-lead / .section-group-label
47 .badge + data-tone (success/warning/danger)
48 .callout + --danger / --warning / --solid-warning
49 .banner + --info / --warning full-bleed page-top notice
50 .alert + -note / -tip / -warning / -caution left-border inline notice
51 .modal + .modal-overlay
52 .empty-state + --compact / --chart / --lg
53 .progress-bar-container + .progress-bar (--slim, --rounded, --highlight)
54 .upload-status + -row + -msg.is-success / .is-error
55 .field-status / .save-status + .success / .error / .saving
56 .toast + .toast--success / --error / --warning
57 .breadcrumb / .pagination
58 .tabs + .tab.chosen (chosen is makeover's)
59
60 CONTROL MODIFIERS (compose onto buttons/inputs)
61 .small / .btn-compact / .btn-link / .btn-tiny button sizes
62 .btn-danger / .saved button intents
63 .input--xs / --sm / --mono / --upper / --numeric input sizes/shapes
64
65 FEATURE SUBAPPS (their own CSS scope)
66 .git-* (git source browser at /source/*)
67 Pricing calculator on /pricing
68 Markdown preview / docs-ui in /docs/*
69
70 PAGE-SCOPED LAYOUTS (when truly page-specific)
71 .item-page / .project-page / .article-page / .purchase-page / .cart-* /
72 .library-page / .feed-page / .discover-page / .landing / .creators-page /
73 .health-page / .import-page / .delete-account-page / .export-page /
74 .receipt-page / .fan-plus-page / .stripe-disclaimer-page / .buy-page /
75 .user-page / .collection-page / .changelog-page / .policy-page /
76 .tag-tree-page / .project-blog-page / .project-paywall-page /
77 .confirm-delete-page / .admin-page / .dashboard-page
78
79 DASHBOARD TABS (one section per tab partial)
80 Per-tab classes scoped under their canonical body class. Use modifier
81 composition where possible; only add tab-specific rules when the shape
82 genuinely diverges from a primitive.
83
84 RESPONSIVE (@media at end of file)
85 768px tablet
86 480px phone
87
88 --------------------------------------------------------------------------
89 When in doubt: read docs/design-system.md first. Update both files together
90 when you add a primitive.
91 ============================================================================ */
92
93 @font-face {
94 font-family: "Young Serif";
95 src: url("/static/fonts/ysrf.woff2") format("woff2"),
96 url("/static/fonts/ysrf.ttf") format("truetype");
97 font-display: swap;
98 }
99
100 @font-face {
101 font-family: "IBM Plex Mono";
102 src: url("/static/fonts/IBMPlexMono-Regular.woff2") format("woff2"),
103 url("/static/fonts/IBMPlexMono-Regular.ttf") format("truetype");
104 font-weight: normal;
105 font-display: swap;
106 }
107
108 @font-face {
109 font-family: "IBM Plex Mono";
110 src: url("/static/fonts/IBMPlexMono-Bold.woff2") format("woff2"),
111 url("/static/fonts/IBMPlexMono-Bold.ttf") format("truetype");
112 font-weight: bold;
113 font-display: swap;
114 }
115
116 @font-face {
117 font-family: "Lato";
118 src: url("/static/fonts/Lato-Regular.woff2") format("woff2"),
119 url("/static/fonts/Lato-Regular.ttf") format("truetype");
120 font-weight: normal;
121 font-display: swap;
122 }
123
124 @font-face {
125 font-family: "Lato";
126 src: url("/static/fonts/Lato-Bold.woff2") format("woff2"),
127 url("/static/fonts/Lato-Bold.ttf") format("truetype");
128 font-weight: bold;
129 font-display: swap;
130 }
131
132 /* Everything below is in the `components` cascade layer.
133
134 _head_assets.html declares the order once, `@layer makeover, base,
135 components, responsive`, and until now three of those four names were
136 empty: this file adopted none of them, so 11,788 unlayered lines outranked
137 every named layer by construction and the generated sheets lost every
138 contest they entered.
139
140 Adopting `components` changes no rendering today. `components` already sits
141 after `makeover` in the declared order, so this file wins the same contests
142 it was winning before; what changes is that it wins them by a stated rule
143 rather than by being unlayered. wizard.css and media-player.css adopt the
144 same layer, and they still beat this file on a tie because a layer resolves
145 internally by source order and they load later.
146
147 Two things stay OUT of the layer, deliberately. The @font-face rules above
148 are not style rules and take no part in the cascade. And the theme block
149 theming.rs injects into <head> is unlayered, which is load-bearing: an
150 unlayered rule outranks every layer, so a creator's chosen theme keeps
151 overriding this file's :root without needing to know the order. */
152 @layer components {
153 :root {
154 /* Typography (three-tier system) */
155 --font-heading: "Young Serif", serif;
156 --font-mono: "IBM Plex Mono", monospace;
157 --font-body: "Lato", sans-serif;
158
159 /* --------------------------------------------------------------------
160 INTENT LAYER, the shared theme vocabulary (themeable)
161
162 These are the intent tokens emitted by theme_common::intent_css_vars():
163 colors declared by ROLE (surface/content/action/status/line/category),
164 plus the derived interactive states (hover/active/selection/row-stripe/
165 contrast) the crate computes once for every product. The values below
166 are the platform default (shared/themes/makenotwork.toml resolved) so
167 non-creator pages render without injection; creator profile/project/item
168 pages inject a <style> that supersedes this block with the chosen theme.
169 Generated. Keep in sync with `cargo run --example dump_intents makenotwork`.
170 -------------------------------------------------------------------- */
171 --surface-page: #ddd6c9;
172 --surface-raised: #efe9e0;
173 --surface-sunken: #cdc5b5;
174 --surface-overlay: #efe9e0;
175 /* The content surface cut into a raised one, so a list reads as content in a
176 container rather than as bands on a panel. Derived by makeover from
177 surface-raised, away from resolved content, so it goes lighter on a light
178 theme and darker on a dark one. */
179 --surface-well: #fffef5;
180 /* The two edges of a bevel. makeover derives both from the theme's raised
181 surface, so they arrive with the rest of the intent set through
182 intent_css_vars() and every creator theme gains them automatically. */
183 --bevel-light: #fffef5;
184 --bevel-dark: #b5afa6;
185 --content: #3d3530;
186 --content-secondary: #5c554f;
187 --content-muted: #8a8480;
188 --content-on-action: #ffffff;
189 --action: #6c5ce7;
190 --action-hover: #7a6cf8;
191 --danger: #c0392b;
192 --success: #27ae60;
193 --warning: #f59e0b;
194 --info: #17a2b8;
195 --border: #b3ab97;
196 --border-strong: #a39c88;
197 --focus-ring: #6c5ce7;
198 --hover-surface: #cdc5b5;
199 --category-one: #c0392b;
200 --category-two: #27ae60;
201 --category-three: #6c5ce7;
202 --category-four: #f59e0b;
203 --category-five: #a29bfe;
204 --category-six: #17a2b8;
205 --overlay: rgba(3, 1, 0, 0.5);
206 /* What a surface floating over the page casts onto it. Same tone as the
207 scrim above and pinned near-black on every theme, because a shadow is
208 absence of light rather than a tint. layout.css composes the geometry
209 into --elevation-overlay; a consumer that needs a different geometry
210 builds its own from this token rather than from a literal. */
211 --elevation: rgba(3, 1, 0, 0.18);
212
213 /* --------------------------------------------------------------------
214 APP-LOCAL CONSTANTS, not theme colors, not derivable from intents.
215
216 Component CSS references the intent tokens above directly (no brand
217 aliases). Status backgrounds derive from the intents inline via
218 color-mix(); these few stay fixed:
219 - pure black/white contrast anchors
220 - Stripe's brand color (an external constant)
221 - diff add/del, a hard readability convention that must not re-theme
222 -------------------------------------------------------------------- */
223 --primary-dark: #000000;
224 --primary-light: #ffffff;
225 --stripe: #635BFF;
226
227 /* The faint accent tint every selection and highlight recipe fills with.
228 Derived from --action, so it re-tints with the creator's theme. Named
229 because the charter names it and because a dozen rules read it. */
230 --highlight-faint: color-mix(in oklch, var(--action) 8%, transparent);
231
232 /* Diff / code review, fixed green-add / red-del convention */
233 --diff-add: #2d7d2d;
234 --diff-add-bg: rgba(45, 125, 45, 0.08);
235 --diff-del: #a83232;
236 --diff-del-bg: rgba(168, 50, 50, 0.08);
237
238 /* Radius lives in geometry.css now, generated from makeover-geometry:
239 --radius-square / -fine / -control / -panel / -round. Containers are
240 square and controls keep a corner, so the rounding survives only where a
241 thing is meant to be pressed. */
242
243 /* Syntax highlighting for the git source browser: base16 Tomorrow, used
244 whole rather than assembled colour by colour. It stays fixed across all
245 31 themes on purpose, the same argument as the diff add/del pair above:
246 a reader recognises a highlighting palette, and re-tinting it per theme
247 costs that recognition to gain nothing. Named so it is legible as one
248 palette instead of seven hex literals scattered through the git rules. */
249 --syntax-keyword: #8959a8;
250 --syntax-string: #718c00;
251 --syntax-comment: #8e908c;
252 --syntax-constant: #f5871f;
253 --syntax-entity: #4271ae;
254 --syntax-variable: #c82829;
255 --syntax-support: #3e999f;
256
257 /* Stacking order, named for what floats over what. Local rather than
258 generated: this is a claim about MNW's own furniture, not an invariant
259 like spacing. The numbers are spaced so a value can be slotted between
260 two of them without renumbering, and nothing outside this block should
261 write a z-index literal. Anything not listed here does not float. */
262 --z-raised: 1; /* lifted within the flow: a saved badge on a card */
263 --z-nav: 10; /* the nav panel that drops out of the mobile header */
264 --z-header: 11; /* the header's own toggle and search panel, both of
265 which have to stay above the panel they open */
266 --z-dropdown: 20; /* menus and suggestion lists opened from a control */
267 --z-picker: 30; /* pickers that open over a dropdown */
268 --z-context: 40; /* right-click / overflow context menus */
269 --z-overlay: 50; /* full-page scrims */
270 --z-modal: 60; /* dialogs, above their scrim */
271 --z-toast: 70; /* transient messages, above a dialog */
272 --z-banner: 80; /* page-level interruptions, above everything */
273 --z-skip-link: 90; /* the keyboard escape hatch outranks all of it */
274
275 /* Depth, and there are only two kinds of it.
276
277 A surface IN the page takes a bevel: an edge, not a distance. That is
278 --shadow-raised/-card/-inset, aliasing the pair layout.css derives from
279 each theme's own raised surface. --shadow-raised and --shadow-card
280 collapse onto one value because an edge has no heavier and lighter
281 version to pick between.
282
283 A surface floating OVER the page takes --elevation-overlay, also from
284 layout.css, derived from the theme's page colour. Menus, toasts,
285 popovers, dropdowns and pickers float. A card, a plate and a framed
286 image do not; they are .raised.
287
288 There used to be a third kind, --shadow-1/-2/-3, a blurred scale of
289 rgba-black literals picked against parchment and then applied to all 31
290 themes a creator can select. It was the same defect the bevel trio was
291 rewritten to undo, one scale later, and the elevation intent is what
292 replaced it. No literal survives here now, so every consumer re-themes
293 without being opened. Geometry stays consumer-side: a site that needs a
294 different blur composes it from var(--elevation), never from a literal. */
295 --shadow-raised: var(--bevel-raised);
296 --shadow-card: var(--bevel-raised);
297 --shadow-inset: var(--bevel-inset);
298 }
299
300 /* Reset */
301 * {
302 margin: 0;
303 padding: 0;
304 box-sizing: border-box;
305 }
306
307 /* Base styles */
308 body {
309 margin: 0;
310 background-color: var(--surface-page);
311 color: var(--content);
312 font-family: var(--font-body);
313 line-height: 1.6;
314 }
315
316 /* Padded page layout for most content pages */
317 .padded-page {
318 padding: var(--gap-pane);
319 }
320
321 /* Centered page layout for landing, login, signup */
322 .centered-page {
323 display: flex;
324 flex-direction: column;
325 justify-content: center;
326 align-items: center;
327 min-height: 100vh;
328 gap: var(--gap-page);
329 }
330
331 .centered-page h1 {
332 font-size: var(--text-hero);
333 }
334
335 .message-container {
336 max-width: 500px;
337 text-align: center;
338 }
339
340 .centered-wrapper {
341 justify-content: center;
342 text-align: center;
343 }
344
345 h1 {
346 font-family: var(--font-heading);
347 font-weight: normal;
348 color: var(--content);
349 text-align: center;
350 font-size: var(--text-display);
351 }
352
353 h2,
354 h3 {
355 font-family: var(--font-mono);
356 font-weight: normal;
357 color: var(--content);
358 }
359
360 /* Heading classes (charter: docs/design-system.md, every h1/h2 in a
361 template must carry one of these so the role is explicit).
362
363 .brand-h1 the "Makenot.work" wordmark on auth/wizard pages.
364 .page-title page-level h1 (Young Serif, centered).
365 .subtitle-h2 page subtitle h2 used under .brand-h1 in auth/wizards.
366 .subsection-title, h2 inside dashboards, tabs, and prose partials
367 (mono, no border, the default-h2 role made explicit).
368 .section-header h2 prose sub-section heading with bottom border
369 (existing rule, see SECTIONS block). */
370 .brand-h1,
371 .page-title {
372 font-family: var(--font-heading);
373 font-weight: normal;
374 color: var(--content);
375 text-align: center;
376 font-size: var(--text-display);
377 }
378
379 .subtitle-h2 {
380 font-family: var(--font-mono);
381 font-weight: normal;
382 color: var(--content);
383 text-align: center;
384 font-size: var(--text-head);
385 }
386
387 .subsection-title {
388 font-family: var(--font-mono);
389 font-weight: normal;
390 color: var(--content);
391 }
392
393 p {
394 font-family: var(--font-body);
395 color: var(--content);
396 }
397
398 a {
399 color: var(--content);
400 text-decoration: none;
401 }
402
403 a:hover {
404 text-decoration: underline;
405 }
406
407 /* The signature dot */
408 .dot {
409 color: var(--action);
410 }
411
412 /* Container, default 1200px max-width. Modifier classes for narrower pages,
413 or comma-grouped page-scoped overrides below for body-class-based control. */
414 .container {
415 max-width: 1200px;
416 margin: 0 auto;
417 padding: var(--gap-page);
418 }
419
420 /* Per-page container width overrides (selected via body class). One rule
421 per width tier; new pages should add their body class here rather than
422 defining a new .foo-page .container rule. */
423 .delete-account-page .container,
424 .receipt-page .container,
425 .user-page .container { max-width: 600px; margin: 0 auto; }
426
427 .import-page .container,
428 .export-page .container { max-width: 800px; margin: 0 auto; }
429
430 .fan-plus-page .container,
431 .feed-page .container,
432 .creators-page .container { max-width: 900px; margin: 0 auto; }
433
434 /* ===========================================
435 BUTTONS
436 =========================================== */
437
438 button {
439 color: var(--content);
440 background: var(--surface-overlay);
441 padding: 6px 12px;
442 border: 1px solid var(--content);
443 font-family: inherit;
444 cursor: pointer;
445 box-shadow: var(--shadow-raised);
446 transition: box-shadow 0.1s ease, background 0.2s ease;
447 }
448
449 button:hover {
450 background: var(--hover-surface);
451 }
452
453 button:active {
454 box-shadow: var(--shadow-inset);
455 }
456
457 .btn-primary {
458 background: var(--primary-dark);
459 color: var(--primary-light);
460 border: none;
461 padding: var(--gap-section) var(--gap-pane);
462 font-family: inherit;
463 font-size: var(--text-body);
464 cursor: pointer;
465 box-shadow: var(--shadow-raised);
466 transition: box-shadow 0.1s ease, background 0.2s ease;
467 display: inline-block;
468 text-decoration: none;
469 }
470
471 /* A solid-fill button shifts its own fill rather than taking --hover-surface.
472 The charter's hover rule is "one step lighter than the resting fill", and on
473 a neutral control that step is --hover-surface; on this one it is not, since
474 the label is --primary-light and a parchment fill under it is unreadable. */
475 .btn-primary:hover {
476 background: color-mix(in oklch, var(--primary-dark) 85%, var(--primary-light));
477 text-decoration: none;
478 }
479
480 .btn-primary:active {
481 box-shadow: var(--shadow-inset);
482 }
483
484 .btn-secondary {
485 background: var(--surface-sunken);
486 color: var(--content);
487 border: none;
488 padding: var(--gap-section) var(--gap-pane);
489 font-family: var(--font-mono);
490 font-size: var(--text-body);
491 cursor: pointer;
492 box-shadow: var(--shadow-raised);
493 transition: box-shadow 0.1s ease, background 0.2s ease;
494 display: inline-block;
495 text-decoration: none;
496 }
497
498 .btn-secondary:hover {
499 background: var(--hover-surface);
500 text-decoration: none;
501 }
502
503 .btn-secondary:active {
504 box-shadow: var(--shadow-inset);
505 }
506
507 .btn-danger {
508 background: var(--danger);
509 color: var(--primary-light);
510 border: none;
511 padding: var(--gap-section) var(--gap-pane);
512 font-family: var(--font-mono);
513 font-size: var(--text-body);
514 cursor: pointer;
515 box-shadow: var(--shadow-raised);
516 transition: box-shadow 0.1s ease, background 0.2s ease;
517 display: inline-block;
518 text-decoration: none;
519 }
520
521 /* Solid fill, so the same exception as .btn-primary applies. */
522 .btn-danger:hover {
523 background: color-mix(in oklch, var(--danger) 85%, var(--primary-light));
524 text-decoration: none;
525 }
526
527 .btn-danger:active {
528 box-shadow: var(--shadow-inset);
529 }
530
531 /* Disabled state for all button variants (charter rule).
532 Applies whether disabled via attribute or aria-disabled. */
533 button:disabled,
534 button[aria-disabled="true"],
535 .btn-primary:disabled,
536 .btn-secondary:disabled,
537 .btn-danger:disabled {
538 opacity: 0.5;
539 cursor: not-allowed;
540 box-shadow: none;
541 transform: none;
542 }
543
544 /* Button size + style modifiers (charter: docs/design-system.md).
545 Use these on top of .btn-primary / -secondary / -danger.
546
547 .btn--large large CTA padding bump.
548 .btn--icon square icon-only / micro button (small padding,
549 tight line-height).
550 .btn--link visually a link, semantically a button (no bg/
551 border, opacity-fade hover, mono).
552
553 Some surfaces have tuned button variants that are NOT compositions
554 of these modifiers and keep their own classes:
555 .big-button Young Serif 200×60 anchor on splash pages.
556 .order-btn list reorder up/down (with active flash).
557 .play-button circular media-player play control.
558 .speed-button segment in media-player speed group.
559 .shortcuts-help-btn, 1.5rem square help glyph in toolbars.
560 .toast-retry-btn inline bordered button inside a toast,
561 uses currentColor to inherit toast tone.
562 These are documented variants, not deprecation targets. */
563 .btn--large {
564 padding: var(--gap-section) var(--gap-page);
565 font-size: var(--text-lead);
566 }
567
568 .btn--icon {
569 padding: var(--gap-bound) var(--gap-peer);
570 font-size: var(--text-note);
571 line-height: 1;
572 min-width: 1.75rem;
573 }
574
575 .btn--link {
576 background: none;
577 color: var(--content);
578 border: none;
579 padding: 0;
580 font-family: var(--font-mono);
581 font-size: var(--text-body);
582 cursor: pointer;
583 /* Link semantics, flat, no depth. Overrides the shadow that would
584 otherwise cascade from `button` / `form button` when a .btn--link
585 happens to be a <button> inside a <form> (e.g. logout). */
586 box-shadow: none;
587 transition: opacity 0.2s ease;
588 }
589
590 .btn--link:hover {
591 opacity: 0.6;
592 box-shadow: none;
593 }
594
595 .btn--link:active {
596 box-shadow: var(--shadow-inset);
597 }
598
599 /* Menu / disclosure openers, buttons whose job is to reveal a menu,
600 filter panel, expander, or popover. They sit flat rather than raised.
601 Add new opener selectors here as they appear. */
602 .context-menu-btn,
603 .discover-filter-toggle,
604 .tip-toggle,
605 .bundle-toggle {
606 box-shadow: none;
607 }
608
609 .context-menu-btn:hover,
610 .discover-filter-toggle:hover,
611 .tip-toggle:hover,
612 .bundle-toggle:hover {
613 box-shadow: none;
614 }
615
616 .context-menu-btn:active,
617 .discover-filter-toggle:active,
618 .tip-toggle:active,
619 .bundle-toggle:active {
620 box-shadow: var(--shadow-inset);
621 }
622
623 /* ===========================================
624 FORMS
625 =========================================== */
626
627 form {
628 display: flex;
629 flex-direction: column;
630 gap: var(--gap-section);
631 width: 100%;
632 }
633
634 .form-container {
635 max-width: 400px;
636 background: var(--surface-overlay);
637 padding: var(--gap-page);
638 }
639
640 label {
641 font-family: var(--font-mono);
642 color: var(--content);
643 font-size: var(--text-note);
644 }
645
646 input[type="text"],
647 input[type="email"],
648 input[type="password"],
649 input[type="number"],
650 input[type="date"],
651 textarea,
652 select {
653 width: 100%;
654 background: var(--surface-sunken);
655 border: 1px solid var(--border);
656 padding: var(--gap-section);
657 font-family: var(--font-mono);
658 font-size: var(--text-note);
659 color: var(--content);
660 box-shadow: var(--shadow-inset);
661 }
662
663 input::placeholder,
664 textarea::placeholder {
665 opacity: 0.5;
666 }
667
668 input[type="text"]:focus,
669 input[type="email"]:focus,
670 input[type="password"]:focus,
671 input[type="number"]:focus,
672 textarea:focus,
673 select:focus {
674 outline: none;
675 border: 1px solid var(--action);
676 }
677
678 textarea {
679 min-height: 100px;
680 resize: vertical;
681 }
682
683 select {
684 cursor: pointer;
685 }
686
687 input[type="submit"],
688 form button {
689 font-family: var(--font-heading);
690 font-size: var(--text-body);
691 color: var(--content);
692 background: var(--surface-overlay);
693 padding: 12px 24px;
694 border: 1px solid var(--content);
695 cursor: pointer;
696 box-shadow: var(--shadow-raised);
697 transition: box-shadow 0.1s ease, background 0.2s ease;
698 }
699
700 input[type="submit"]:hover,
701 form button:hover {
702 background: var(--hover-surface);
703 }
704
705 input[type="submit"]:active,
706 form button:active {
707 box-shadow: var(--shadow-inset);
708 }
709
710 .form-group {
711 margin-bottom: var(--gap-pane);
712 }
713
714 .form-group label {
715 display: block;
716 margin-bottom: var(--gap-peer);
717 font-size: var(--text-note);
718 }
719
720 .form-group input,
721 .form-group textarea,
722 .form-group select {
723 width: 100%;
724 }
725
726 .form-row {
727 display: grid;
728 grid-template-columns: 1fr 1fr;
729 gap: var(--gap-section);
730 }
731
732 .hint {
733 font-size: var(--text-note);
734 opacity: 0.6;
735 margin-top: var(--gap-peer);
736 }
737
738 /* Field-level validation error, paired with .form-group--error.
739 Canonical: `partials/_ui.html` ui::form_field macro. */
740 .form-group--error label {
741 color: var(--danger);
742 }
743
744 .form-group--error input,
745 .form-group--error textarea,
746 .form-group--error select {
747 border-color: var(--danger);
748 }
749
750 .field-error {
751 font-size: var(--text-note);
752 color: var(--danger);
753 margin-top: var(--gap-peer);
754 }
755
756 /* Checkbox group */
757 .checkbox-group {
758 display: flex;
759 align-items: start;
760 gap: var(--gap-section);
761 cursor: pointer;
762 transition: background 0.2s ease;
763 margin-bottom: var(--gap-peer);
764 }
765
766 .checkbox-group input[type="checkbox"] {
767 width: auto;
768 margin-top: var(--gap-bound);
769 margin-bottom: var(--gap-bound);
770 cursor: pointer;
771 }
772 /* Radio group */
773 .radio-group {
774 display: flex;
775 flex-direction: column;
776 gap: var(--gap-section);
777 margin-top: var(--gap-peer);
778 }
779 /* ===========================================
780 TABS
781 =========================================== */
782
783 .tabs {
784 display: flex;
785 flex-wrap: nowrap;
786 gap: 0;
787 margin-bottom: 0;
788 }
789
790 /* Geometry and type only. The surface is layout.css's: `.tab` is sunken,
791 `:hover` steps to --hover-surface on a fine pointer, `:active` insets. This
792 rule used to restate the sunken background verbatim and then carry a second,
793 redundant selection signal on top of it — opacity 0.6 unselected, 1 selected
794 — so a tab said "not chosen" twice and the generated surface step did
795 nothing. The opacity idiom is gone and the surface contrast is the signal. */
796 .tab {
797 color: var(--content);
798 border: none;
799 padding: var(--gap-section) var(--gap-page);
800 font-family: var(--font-mono);
801 font-size: var(--text-body);
802 cursor: pointer;
803 /* Tabs navigate, they don't commit, stay flat. Overrides the base
804 `button` shadow if a tab is a <button>. */
805 box-shadow: none;
806 transition: background 0.2s ease;
807 white-space: nowrap;
808 flex-shrink: 0;
809 }
810
811 /* A tab's selected state is `.tab.chosen` in layout.css and nowhere else now.
812 This file used to carry a `.tab.is-selected` rule setting the same two
813 declarations byte for byte, on the argument that `.is-selected` is the
814 site's own idiom across git nav links, editor tabs, section tabs, issue tabs
815 and filter buttons, so renaming the tabs alone would split it. That is true
816 and it is still the wrong trade: a second name for a state the design system
817 already names is invisible only while both say the same thing, and stops
818 being invisible the moment makeover changes what a chosen tab looks like.
819 The other components keep `.is-selected`, because makeover has no primitive
820 for a git nav link; the tab has one.
821
822 The chosen arm's box-shadow comes back through the layer rather than being
823 restated. `.tab` below zeroes the base `button` shadow, and this file is
824 unlayered, so it outranks @layer makeover whatever the specificity: without
825 the revert, the generated bevel never applies and a chosen tab reads flat.
826 `revert-layer` hands the property back instead of naming a value, which is
827 the difference between deferring to the design system and copying it. */
828 .tab.chosen {
829 /* respec-ok: a layer handoff, not a value. See the comment above. */
830 box-shadow: revert-layer;
831 }
832
833 .tab-more-wrap {
834 position: relative;
835 flex-shrink: 0;
836 }
837
838 .tab-overflow-menu {
839 position: absolute;
840 top: 100%;
841 right: 0;
842 z-index: var(--z-dropdown);
843 background: var(--surface-page);
844 border: 1px solid var(--border);
845 min-width: 180px;
846 box-shadow: var(--elevation-overlay);
847 }
848
849 /* The overflow menu restates the tab as a menu row: block, full width, left
850 aligned, tighter. It carried the same opacity pair the strip did and it goes
851 for the same reason; hover and selection are the generated surface steps. */
852 .tab-overflow-menu .tab {
853 display: block;
854 width: 100%;
855 text-align: left;
856 padding: var(--gap-group) var(--gap-section);
857 }
858
859
860 .tab-content {
861 display: none;
862 background: var(--surface-overlay);
863 padding: var(--gap-page);
864 }
865
866 .tab-content.active {
867 display: block;
868 }
869
870 /* ===========================================
871 TABLES
872 =========================================== */
873
874 .data-table {
875 width: 100%;
876 border-collapse: collapse;
877 font-size: var(--text-note);
878 font-family: var(--font-body);
879 margin-bottom: var(--gap-section);
880 }
881
882 .data-table thead {
883 background: var(--surface-sunken);
884 }
885
886 .data-table th {
887 text-align: left;
888 padding: var(--gap-section);
889 font-weight: normal;
890 }
891
892 /* `.data-table th.sortable` and its four rules were here and had no consumer:
893 nothing in templates/ ever put `sortable` on a `th`. The one sortable table
894 in the app is templates/partials/tabs/project_content.html, which used
895 `.sortable-th` further down this file. Both are gone; the affordance and the
896 caret are `.table-heading[data-sortable]` in the generated static/layout.css.
897 See the block that replaced `.sortable-th` for what stayed behind. */
898
899 .data-table tbody tr {
900 transition: background 0.1s ease;
901 }
902
903 /* No zebra. There were two rules here, :nth-child(odd) and (even), and both
904 set --surface-overlay — the same value, so the stripe never existed and the
905 effect was every row painted the panel's own colour. The table is a .well
906 now and the rows sit on it unpainted, which is what the banding was
907 reaching for. makeover derives no row-stripe intent, so if one is ever
908 wanted it is a makeover-side question, not a literal here. */
909
910 .data-table tbody tr:hover {
911 background: var(--surface-sunken);
912 }
913
914 .data-table td {
915 padding: var(--gap-section);
916 border-top: 1px solid var(--border);
917 }
918
919 /* Marks the row a table is about, used across the dashboard and library
920 tables. Lived in the pricing block until the fee calculator replaced it. */
921 .data-table .highlight {
922 background: var(--surface-overlay);
923 border-left: 3px solid var(--action);
924 }
925
926 /* ===========================================
927 COMPACT TABLES (dashboard/admin, lighter than .data-table)
928 =========================================== */
929
930 .compact-table {
931 width: 100%;
932 border-collapse: collapse;
933 }
934
935 .compact-table thead tr {
936 font-size: var(--text-note);
937 opacity: 0.7;
938 }
939
940 .compact-table th {
941 padding: var(--gap-peer) var(--gap-section);
942 text-align: left;
943 }
944
945 .compact-table td {
946 padding: var(--gap-section);
947 }
948
949 .compact-table tbody tr {
950 border-bottom: 1px solid var(--border);
951 }
952
953 /* ===========================================
954 UTILITIES
955 =========================================== */
956
957 .text-sm { font-size: var(--text-note); }
958 .text-xs { font-size: var(--text-fine); }
959 .muted { opacity: 0.7; }
960 .dimmed { opacity: 0.6; }
961 .dimmer { opacity: 0.5; }
962 .meta { font-size: var(--text-note); opacity: 0.7; }
963 .nowrap { white-space: nowrap; }
964 .scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
965 .text-center { text-align: center; }
966 .text-right { text-align: right; }
967 .fw-bold { font-weight: bold; }
968 .unstyled-link { text-decoration: none; color: inherit; }
969 .square-cover { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
970
971 /* Spacing utilities.
972 mt = margin-top, mb = margin-bottom, my = vertical, py = vertical padding.
973 Named for the relationship, same vocabulary as the --gap-* tokens they
974 read. Use sparingly; prefer parent layout primitives. */
975 .m-0 { margin: 0; }
976 .mt-0 { margin-top: 0; }
977 .mb-0 { margin-bottom: 0; }
978 .mt-peer { margin-top: var(--gap-peer); }
979 .mt-section { margin-top: var(--gap-section); }
980 .mt-pane { margin-top: var(--gap-pane); }
981 .mt-page { margin-top: var(--gap-page); }
982 .mb-peer { margin-bottom: var(--gap-peer); }
983 .mb-section { margin-bottom: var(--gap-section); }
984 .mb-pane { margin-bottom: var(--gap-pane); }
985 .my-peer { margin-block: var(--gap-peer); }
986 .my-section { margin-block: var(--gap-section); }
987 .py-section { padding-block: var(--gap-section); }
988
989 /* Min-width utilities for tables inside .scroll-x wrappers. */
990 .minw-300 { min-width: 300px; }
991 .minw-400 { min-width: 400px; }
992 .minw-500 { min-width: 500px; }
993 .minw-600 { min-width: 600px; }
994 .minw-700 { min-width: 700px; }
995 .minw-800 { min-width: 800px; }
996
997 /* Compact button sizes. Canonical: .small (broad use, 0.25rem 0.6rem 0.8rem).
998 The tiny / row-btn / cart-row-btn aliases are visually equivalent, within
999 sub-pixel of each other in the original templates, and folded into .small. */
1000 .small { padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); }
1001
1002 /* Canonical empty-state primitive. Use modifiers for tight/sized contexts.
1003 Markup: <div class="empty-state">…</div> (optionally with .empty-state--compact
1004 inside dropdowns or .empty-state--chart inside a fixed-height chart slot). */
1005 .empty-state { text-align: center; padding: var(--gap-page); opacity: 0.6; }
1006 .empty-state--compact { padding: var(--gap-section); font-size: var(--text-note); }
1007 .empty-state--chart { height: 200px; padding: 0; display: flex; align-items: center; justify-content: center; opacity: 0.5; }
1008 .empty-state-hint { font-family: var(--font-mono); font-size: var(--text-fine); margin-top: var(--gap-peer); opacity: 0.6; }
1009
1010 .tab-docs {
1011 display: flex;
1012 justify-content: flex-end;
1013 margin-bottom: var(--gap-section);
1014 }
1015
1016 .tab-docs a {
1017 font-family: var(--font-mono);
1018 font-size: var(--text-fine);
1019 opacity: 0.5;
1020 transition: opacity 0.2s ease;
1021 }
1022
1023 .tab-docs a:hover {
1024 opacity: 1;
1025 }
1026
1027 /* ===========================================
1028 BADGES
1029 =========================================== */
1030
1031 /* Geometry and type only. The colour is layout.css's: a badge is coloured
1032 text on the page, and its status colour comes from `data-tone` — see
1033 src/types/badge.rs, which maps every status enum onto a tone name.
1034
1035 This used to be a filled pill (near-black ground, --primary-light label)
1036 with a local `.badge--live/pending/failed` set painting the fill per
1037 status. All of it went in wave 2 tier B2: the fill was carrying exactly
1038 what the generated `color` carries, so it was the same thing said in a
1039 different channel, and keeping it meant the tone attribute could never do
1040 anything. */
1041 .badge {
1042 display: inline-block;
1043 padding: var(--gap-bound) var(--gap-peer);
1044 font-size: var(--text-fine);
1045 font-family: var(--font-mono);
1046 }
1047
1048 /* AI disclosure tier badges. See site-docs/public/about/generative-ai.md.
1049 Colours track the brand palette: violet for the cleanest case, warm-tan for
1050 disclosed AI use, charcoal for primarily generated.
1051
1052 respec-ok: this is a taxonomy makeover has no vocabulary for. Its four
1053 tones are info / success / warning / danger, which all read as alarm, and
1054 disclosure is not alarm — a handmade item is not a "success" and a
1055 generated one is not a "danger". So the three hues stay local. They set
1056 `color` rather than a background, because the badge form is the generated
1057 one now and only the hue is ours. */
1058 .badge.ai-tier {
1059 letter-spacing: 0.02em;
1060 }
1061 .badge.ai-tier-handmade {
1062 color: var(--action);
1063 }
1064 .badge.ai-tier-assisted {
1065 color: var(--warning);
1066 }
1067 .badge.ai-tier-generated {
1068 color: var(--content);
1069 }
1070
1071 .item-ai-tier {
1072 margin: var(--gap-bound) 0 var(--gap-section);
1073 }
1074
1075 .ai-disclosure {
1076 margin: var(--gap-section) 0;
1077 padding: var(--gap-section);
1078 background: var(--surface-sunken);
1079 border-left: 3px solid var(--warning);
1080 }
1081 .ai-disclosure-label {
1082 font-family: var(--font-mono);
1083 font-size: var(--text-fine);
1084 text-transform: uppercase;
1085 letter-spacing: 0.05em;
1086 color: var(--content-muted);
1087 margin-bottom: var(--gap-bound);
1088 }
1089 .ai-disclosure-text {
1090 font-size: var(--text-note);
1091 color: var(--content);
1092 }
1093
1094 /* Discover row: plain text mention per design decision 2026-06-03:
1095 no pill in the row, so fans skim the listing without disclosure noise
1096 and use the explicit filter when they care. */
1097 .discover-row-ai-tier {
1098 font-family: var(--font-mono);
1099 font-size: var(--text-fine);
1100 color: var(--content-muted);
1101 }
1102
1103 /* ===========================================
1104 TAGS
1105 =========================================== */
1106
1107 .tag {
1108 display: inline-block;
1109 background: var(--primary-dark);
1110 color: var(--primary-light);
1111 padding: var(--gap-bound) var(--gap-peer);
1112 font-size: var(--text-fine);
1113 margin-right: var(--gap-bound);
1114 }
1115
1116 .tag-input {
1117 background: var(--surface-sunken);
1118 padding: var(--gap-peer);
1119 margin-bottom: var(--gap-peer);
1120 display: flex;
1121 flex-wrap: wrap;
1122 gap: var(--gap-peer);
1123 min-height: 40px;
1124 }
1125
1126 .tag-input .tag {
1127 display: flex;
1128 align-items: center;
1129 gap: var(--gap-peer);
1130 padding: var(--gap-bound) var(--gap-peer);
1131 font-size: var(--text-note);
1132 }
1133
1134 .tag-input .tag button {
1135 background: none;
1136 border: none;
1137 color: var(--primary-light);
1138 cursor: pointer;
1139 padding: 0;
1140 font-size: var(--text-body);
1141 }
1142
1143 /* ===========================================
1144 CARDS
1145 =========================================== */
1146
1147 /* Canonical card primitive (charter: docs/design-system.md).
1148 Variants:
1149 .card filled, hover-step (default content card).
1150 .card.card-muted surface-muted fill, no hover (dashboard stat / analytics blocks).
1151 .card.card--bordered bordered, padded, no fill (marketing cards: feature/tier/use-case).
1152 .card.card--selectable radio-card pattern; pair with .is-selected.
1153 .card.card--grid grid-cell card (discover grid).
1154 No aliases. A template names the modifier it wants and adds its own
1155 component class beside it; a recipe reached only through a per-page name is
1156 a recipe nobody can find from the charter. */
1157 /* Fill and depth come from layout.css, generated by makeover-webview: the card
1158 is `--surface-raised` plus `--bevel-raised`, and `:hover` goes to
1159 `--hover-surface` on a fine pointer only. Declaring either here would take
1160 them back, since style.css is unlayered and unlayered beats the `makeover`
1161 layer whatever the specificity. The box-shadow this used to carry was
1162 `--shadow-card`, which already aliases `--bevel-raised`, so only the fill
1163 actually moved: `--surface-overlay` to `--surface-raised`. Those are equal on
1164 the platform default and a theme may set them apart. */
1165 .card {
1166 padding: var(--gap-pane);
1167 margin-bottom: var(--gap-section);
1168 text-decoration: none;
1169 color: var(--content);
1170 display: block;
1171 border: 1px solid var(--border);
1172 transition: background 0.2s ease;
1173 }
1174
1175 /* Muted card primitive. */
1176 .card-muted {
1177 background: var(--surface-sunken);
1178 padding: var(--gap-pane);
1179 }
1180 /* Bordered card. Templates put this modifier on the element alongside any
1181 component class of their own (.tier-card, .use-case-card, .fork-card), so
1182 the name the charter teaches is the name in the markup. */
1183 .card--bordered {
1184 background: transparent;
1185 padding: var(--gap-section);
1186 border: 1px solid var(--border);
1187 margin-bottom: 0;
1188 }
1189 .use-case-card { padding: var(--gap-section); }
1190 .fork-card { padding: var(--gap-pane); display: flex; flex-direction: column; }
1191
1192 .card-title {
1193 font-size: var(--text-subhead);
1194 margin-bottom: var(--gap-peer);
1195 font-family: var(--font-heading);
1196 }
1197
1198 .card-meta {
1199 font-size: var(--text-note);
1200 color: var(--content-muted);
1201 margin-bottom: var(--gap-peer);
1202 font-family: var(--font-mono);
1203 }
1204
1205 .card-description {
1206 font-size: var(--text-note);
1207 opacity: 0.8;
1208 font-family: var(--font-body);
1209 }
1210
1211 /* ===========================================
1212 STATS
1213 =========================================== */
1214
1215 .stats-grid {
1216 display: grid;
1217 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1218 gap: var(--gap-section);
1219 margin-bottom: var(--gap-pane);
1220 }
1221
1222 .stats-row {
1223 display: flex;
1224 gap: var(--gap-pane);
1225 margin: var(--gap-pane) 0;
1226 }
1227
1228 .stat-box {
1229 background: var(--surface-overlay);
1230 padding: var(--gap-section) var(--gap-pane);
1231 flex: 1;
1232 text-align: center;
1233 }
1234
1235 .stat-box .number {
1236 font-family: var(--font-heading);
1237 font-weight: bold;
1238 font-size: var(--text-head);
1239 }
1240
1241 .stat-box .label {
1242 font-size: var(--text-fine);
1243 opacity: 0.7;
1244 }
1245
1246 /* .stat-card base recipe defined above with .card-muted */
1247
1248 .stat-label {
1249 font-size: var(--text-note);
1250 opacity: 0.7;
1251 margin-bottom: var(--gap-bound);
1252 }
1253
1254 .stat-value {
1255 font-size: var(--text-head);
1256 }
1257
1258 .stat-change {
1259 font-size: var(--text-note);
1260 opacity: 0.6;
1261 }
1262
1263 .stat-change.positive {
1264 color: var(--success);
1265 }
1266
1267 .stat-change.negative {
1268 color: var(--danger);
1269 }
1270
1271 /* ===========================================
1272 FILTER BAR
1273 =========================================== */
1274
1275 .filter-bar {
1276 display: flex;
1277 gap: var(--gap-peer);
1278 margin: var(--gap-pane) 0;
1279 flex-wrap: wrap;
1280 align-items: center;
1281 }
1282
1283 .filter-bar > .label {
1284 font-size: var(--text-note);
1285 opacity: 0.7;
1286 margin-right: var(--gap-peer);
1287 }
1288
1289 .filter-bar button {
1290 padding: var(--gap-bound) var(--gap-section);
1291 font-size: var(--text-note);
1292 }
1293
1294 /* ===========================================
1295 DISCUSSION SECTION
1296 =========================================== */
1297
1298 /* Sits inside the parent container; previously had max-width: 720px and
1299 margin: 2rem auto, which looked indented on wide pages (.library-page,
1300 .item-page) where every neighbouring card filled the container. Let the
1301 parent dictate width, article-page already constrains via
1302 .article-container; player pages have their own narrow layout. */
1303 .discussion-section {
1304 margin: var(--gap-page) 0;
1305 padding: var(--gap-pane);
1306 border: 1px solid var(--border);
1307 font-family: var(--font-mono);
1308 }
1309
1310 .discussion-section h3 {
1311 font-family: var(--font-heading);
1312 font-size: var(--text-subhead);
1313 margin: 0 0 var(--gap-peer);
1314 }
1315
1316 .discussion-section p {
1317 margin: 0 0 var(--gap-section);
1318 color: var(--content-muted);
1319 font-size: var(--text-note);
1320 }
1321
1322 .discussion-section a {
1323 color: var(--action);
1324 text-decoration: none;
1325 font-size: var(--text-note);
1326 }
1327
1328 /* ===========================================
1329 INFO & WARNING BOXES
1330 =========================================== */
1331
1332 /* Notification surface roles (charter: docs/design-system.md).
1333 Five distinct components. Keep them distinct, don't merge:
1334 .toast transient, JS-dismissible, bottom-right corner.
1335 .banner full-bleed page-top notice (sandbox, restart,
1336 founder pricing). One short sentence + optional link.
1337 .alert inline directive callout with uppercase mono title
1338 (NOTE / TIP / IMPORTANT / WARNING / CAUTION). Used in
1339 docs and longer-form bodies.
1340 .info-box informational headed block with h3 + bullet list
1341 inside forms / wizards (distinct register from .alert).
1342 .warning-box loud yellow-on-yellow attention-grabber for
1343 page-level warnings (delete confirms, account
1344 warnings). Louder than .alert-warning intentionally.
1345 .error-message inline form-field error; hidden by default, shown
1346 with .is-active. Distinct from page-level .alert. */
1347 .info-box {
1348 background: var(--surface-sunken);
1349 padding: var(--gap-section);
1350 margin-bottom: var(--gap-section);
1351 font-size: var(--text-note);
1352 font-family: var(--font-body);
1353 }
1354
1355 .info-box h3 {
1356 font-size: var(--text-body);
1357 font-weight: normal;
1358 margin-bottom: var(--gap-peer);
1359 }
1360
1361 .info-box ul {
1362 list-style: none;
1363 margin-top: var(--gap-peer);
1364 }
1365
1366 .info-box li {
1367 padding: var(--gap-bound) 0;
1368 }
1369
1370 .info-box li::before {
1371 content: "- ";
1372 opacity: 0.5;
1373 }
1374
1375 .warning-box {
1376 background: var(--warning);
1377 color: var(--primary-light);
1378 padding: var(--gap-section);
1379 margin-bottom: var(--gap-section);
1380 font-size: var(--text-note);
1381 font-family: var(--font-body);
1382 }
1383
1384 .error-message {
1385 font-family: var(--font-mono);
1386 color: var(--danger);
1387 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
1388 padding: 12px;
1389 border: 1px solid var(--danger);
1390 border-radius: var(--radius-fine);
1391 text-align: center;
1392 font-size: var(--text-note);
1393 display: none;
1394 }
1395
1396 /* Full-page error layout (templates/pages/error.html). Distinct from
1397 the inline `.error-message` form-error class above; named to avoid
1398 collision. */
1399 .error-page {
1400 min-height: 100vh;
1401 display: flex;
1402 align-items: center;
1403 justify-content: center;
1404 padding: var(--gap-page);
1405 background: var(--surface-page);
1406 }
1407
1408 .error-container {
1409 text-align: center;
1410 max-width: 560px;
1411 }
1412
1413 .error-status {
1414 font-size: var(--text-note);
1415 font-weight: 600;
1416 letter-spacing: 0.08em;
1417 text-transform: uppercase;
1418 color: var(--content-muted);
1419 margin-bottom: var(--gap-section);
1420 }
1421
1422 .error-page-message {
1423 font-size: var(--text-head);
1424 line-height: 1.4;
1425 color: var(--content);
1426 margin-bottom: var(--gap-page);
1427 }
1428
1429 .error-actions {
1430 display: flex;
1431 gap: var(--gap-section);
1432 justify-content: center;
1433 }
1434
1435
1436 /* Minimal direct-purchase page (templates/pages/buy.html). Standalone
1437 page used for link-in-bio sharing, no site chrome, just a card.
1438 Scoped under .buy-page so the rules don't bleed into the rest of
1439 the app. Tokenized; previously lived as a page-isolated <style>. */
1440 .buy-page {
1441 min-height: 100vh;
1442 display: flex;
1443 align-items: center;
1444 justify-content: center;
1445 padding: var(--gap-page);
1446 }
1447
1448 /* The one former --shadow-3 site that is not a float. The card is the whole
1449 page's content rather than something laid over it, so it takes .raised
1450 from layout.css for its surface and its edge, and keeps only the geometry
1451 the generated primitive does not carry. */
1452 .buy-page .buy-card {
1453 border-radius: var(--radius-panel);
1454 padding: var(--gap-page);
1455 max-width: 420px;
1456 width: 100%;
1457 }
1458
1459 .buy-page .cover,
1460 .buy-page .no-cover {
1461 width: 100%;
1462 aspect-ratio: 1;
1463 max-height: 280px;
1464 border-radius: var(--radius-panel);
1465 margin-bottom: var(--gap-pane);
1466 background: var(--surface-raised);
1467 }
1468
1469 .buy-page .cover {
1470 object-fit: cover;
1471 }
1472
1473 .buy-page .no-cover {
1474 display: flex;
1475 align-items: center;
1476 justify-content: center;
1477 font-size: var(--text-hero);
1478 opacity: 0.2;
1479 }
1480
1481 .buy-page h1 {
1482 text-align: left;
1483 font-size: var(--text-head);
1484 margin-bottom: var(--gap-bound);
1485 }
1486
1487 .buy-page .creator {
1488 font-family: var(--font-mono);
1489 font-size: var(--text-note);
1490 opacity: 0.6;
1491 margin-bottom: var(--gap-pane);
1492 }
1493
1494 .buy-page .creator a {
1495 color: inherit;
1496 }
1497
1498 .buy-page .price {
1499 font-family: var(--font-mono);
1500 font-size: var(--text-subhead);
1501 margin-bottom: var(--gap-pane);
1502 }
1503
1504 .buy-page .description {
1505 font-size: var(--text-note);
1506 line-height: 1.5;
1507 opacity: 0.8;
1508 margin-bottom: var(--gap-pane);
1509 max-height: 4.5em;
1510 overflow: hidden;
1511 }
1512
1513 .buy-page .buy-btn {
1514 display: block;
1515 width: 100%;
1516 padding: 14px;
1517 background: var(--action);
1518 color: var(--primary-light);
1519 border: none;
1520 border-radius: var(--radius-panel);
1521 font-size: var(--text-body);
1522 font-weight: 600;
1523 cursor: pointer;
1524 text-align: center;
1525 text-decoration: none;
1526 transition: opacity 0.2s ease;
1527 }
1528
1529 .buy-page .buy-btn:hover {
1530 opacity: 0.85;
1531 }
1532
1533 .buy-page .buy-btn:disabled {
1534 opacity: 0.6;
1535 cursor: wait;
1536 }
1537
1538 .buy-page .pwyw-input {
1539 display: flex;
1540 align-items: center;
1541 gap: var(--gap-bound);
1542 margin-bottom: var(--gap-section);
1543 }
1544
1545 .buy-page .pwyw-input input {
1546 width: 100px;
1547 padding: 8px;
1548 border: 1px solid var(--border);
1549 border-radius: var(--radius-control);
1550 font-size: var(--text-body);
1551 }
1552
1553 .buy-page .note {
1554 font-size: var(--text-fine);
1555 opacity: 0.5;
1556 text-align: center;
1557 margin-top: var(--gap-section);
1558 }
1559
1560 .buy-page .note a {
1561 color: var(--action);
1562 }
1563
1564 .buy-page .footer {
1565 text-align: center;
1566 margin-top: var(--gap-pane);
1567 padding-top: var(--gap-section);
1568 border-top: 1px solid var(--border);
1569 font-size: var(--text-fine);
1570 opacity: 0.5;
1571 }
1572
1573 .buy-page .footer a {
1574 color: inherit;
1575 }
1576
1577 /* Sectioned content (templates/pages/item.html, project.html,
1578 library_downloads.html). Tab+panel pattern shared by all three
1579 pages, kept global, NOT scoped under .item-page. */
1580 .section-tabs {
1581 display: flex;
1582 gap: 0;
1583 border-bottom: 1px solid var(--border);
1584 margin-bottom: var(--gap-pane);
1585 }
1586
1587 .section-tab {
1588 background: none;
1589 border: none;
1590 padding: var(--gap-group) var(--gap-section);
1591 cursor: pointer;
1592 font-size: var(--text-body);
1593 opacity: 0.6;
1594 border-bottom: 2px solid transparent;
1595 font-family: var(--font-body);
1596 color: var(--content);
1597 }
1598
1599 .section-tab.is-selected {
1600 opacity: 1;
1601 border-bottom-color: var(--content);
1602 }
1603
1604 .section-tab:hover { opacity: 0.9; }
1605
1606 /* .section-panel.active is a visibility toggle (display: none / block),
1607 not a selection, kept as .active. */
1608 .section-panel { display: none; }
1609 .section-panel.active { display: block; }
1610 .section-panel p { margin-bottom: var(--gap-section); }
1611 .section-panel ul,
1612 .section-panel ol {
1613 margin-left: var(--gap-pane);
1614 margin-bottom: var(--gap-section);
1615 }
1616 .section-panel li { margin-bottom: var(--gap-peer); }
1617 .section-panel h1,
1618 .section-panel h2,
1619 .section-panel h3 {
1620 margin-top: var(--gap-section);
1621 margin-bottom: var(--gap-peer);
1622 }
1623 .section-panel pre {
1624 background: var(--surface-sunken);
1625 padding: var(--gap-section);
1626 overflow-x: auto;
1627 margin-bottom: var(--gap-section);
1628 }
1629 .section-panel code { font-size: var(--text-note); }
1630
1631 /* Item detail page (templates/pages/item.html). Scoped under .item-page
1632 body class so the .item-title / .item-meta / .item-price / .item-footer
1633 names don't collide with the store-front item-card on project.html. */
1634 .item-page .item-layout {
1635 display: grid;
1636 grid-template-columns: 400px 1fr;
1637 gap: var(--gap-page);
1638 margin-bottom: var(--gap-page);
1639 }
1640
1641 .item-page .item-layout.no-media { grid-template-columns: 1fr; }
1642
1643 .item-page .item-title {
1644 font-size: var(--text-title);
1645 margin-bottom: var(--gap-peer);
1646 }
1647
1648 .item-page .item-creator {
1649 font-size: var(--text-body);
1650 opacity: 0.7;
1651 margin-bottom: var(--gap-pane);
1652 }
1653
1654 .item-page .item-creator a { color: var(--content); }
1655
1656 .item-page .item-price {
1657 font-size: var(--text-display);
1658 margin-bottom: var(--gap-pane);
1659 }
1660
1661 .item-page .item-meta {
1662 display: grid;
1663 grid-template-columns: 1fr 1fr;
1664 gap: var(--gap-section);
1665 margin-bottom: var(--gap-pane);
1666 padding-bottom: var(--gap-pane);
1667 border-bottom: 1px solid var(--border);
1668 }
1669
1670 .item-page .meta-item { font-size: var(--text-note); }
1671 .item-page .meta-label { opacity: 0.7; margin-bottom: var(--gap-bound); }
1672 .item-page .meta-value { font-weight: bold; }
1673
1674 .item-page .item-tags { margin-bottom: var(--gap-pane); }
1675
1676 /* purchase-box uses .card-muted for the box; only its h3/ul/li specifics remain. */
1677 .purchase-box h3 {
1678 font-size: var(--text-body);
1679 font-weight: normal;
1680 margin-bottom: var(--gap-section);
1681 }
1682 .purchase-box ul { list-style: none; margin-bottom: var(--gap-section); }
1683 .purchase-box li { padding: var(--gap-bound) 0; }
1684 .purchase-box li::before {
1685 content: "→ ";
1686 opacity: 0.5;
1687 }
1688
1689 .item-page .btn-primary {
1690 width: 100%;
1691 padding: var(--gap-section) var(--gap-page);
1692 font-size: var(--text-lead);
1693 }
1694
1695 .item-page .btn-secondary {
1696 width: 100%;
1697 margin-top: var(--gap-peer);
1698 }
1699
1700 .item-page .payment-note {
1701 font-size: var(--text-note);
1702 opacity: 0.6;
1703 text-align: center;
1704 margin-top: var(--gap-section);
1705 }
1706
1707 /* bundle-child uses .list-row co-class; only the gap-4 override remains. */
1708 .bundle-child { gap: var(--gap-section); padding: var(--gap-section) 0; }
1709
1710 .item-page .bundle-child-type {
1711 font-size: var(--text-fine);
1712 padding: var(--gap-bound) var(--gap-group);
1713 background: var(--surface-sunken);
1714 white-space: nowrap;
1715 }
1716
1717 .item-page .bundle-child-title { flex: 1; }
1718 .item-page .bundle-child-title a { color: var(--content); }
1719 .item-page .bundle-child-price { font-size: var(--text-note); opacity: 0.7; }
1720
1721 .item-page .bundle-notice {
1722 background: var(--surface-sunken);
1723 padding: var(--gap-pane);
1724 margin-bottom: var(--gap-pane);
1725 text-align: center;
1726 }
1727
1728 .item-page .bundle-notice a { color: var(--content); }
1729
1730 .item-page .item-description p { margin-bottom: var(--gap-section); text-align: left; }
1731 .item-page .item-description ul { margin-left: var(--gap-pane); margin-bottom: var(--gap-section); }
1732 .item-page .item-description li { margin-bottom: var(--gap-peer); }
1733
1734 .item-page .features-grid {
1735 display: grid;
1736 grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
1737 gap: var(--gap-pane);
1738 }
1739 .item-page .version-number { font-size: var(--text-lead); }
1740 .item-page .item-footer {
1741 margin-top: var(--gap-page);
1742 padding-top: var(--gap-page);
1743 border-top: 1px solid var(--border);
1744 text-align: center;
1745 opacity: 0.6;
1746 font-size: var(--text-note);
1747 }
1748
1749 .item-page .item-footer a { color: var(--content); }
1750
1751 @media (max-width: 839px) {
1752 .item-page .item-layout { grid-template-columns: 1fr; }
1753 }
1754
1755 @media (max-width: 599px) {
1756 .item-page .item-title { font-size: var(--text-head); }
1757 .item-page .item-price { font-size: var(--text-title); }
1758 .item-page .item-meta { grid-template-columns: 1fr; gap: var(--gap-peer); }
1759 .item-page .item-media,
1760 .item-page .item-details { padding: var(--gap-section); }
1761 .item-page .item-description { padding: var(--gap-section); }
1762 .item-page .item-description h2 { font-size: var(--text-subhead); }
1763 .item-page .purchase-box { padding: var(--gap-section); }
1764 }
1765
1766 /* Project store-front page (templates/pages/project.html). Scoped under
1767 .project-page body class, `.item-card`, `.item-title`, `.item-meta`,
1768 `.item-price`, etc. here mean store-front item cards, distinct from
1769 the item-detail-page versions scoped under .item-page above. */
1770 .project-page .store-header { margin-bottom: var(--gap-page); }
1771 .project-page .store-title { font-size: var(--text-hero); margin-bottom: var(--gap-peer); }
1772 .project-page .store-meta {
1773 font-size: var(--text-note);
1774 opacity: 0.7;
1775 margin-bottom: var(--gap-pane);
1776 }
1777 .project-page .store-meta a { color: var(--content); }
1778 .project-page .store-description {
1779 font-size: var(--text-lead);
1780 max-width: 800px;
1781 margin-bottom: var(--gap-page);
1782 text-align: left;
1783 }
1784 .project-page .store-actions {
1785 display: flex;
1786 gap: var(--gap-section);
1787 flex-wrap: wrap;
1788 }
1789
1790 .project-page .items-section { margin-bottom: var(--gap-page); }
1791
1792 .project-page .items-grid {
1793 display: grid;
1794 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1795 gap: var(--gap-page);
1796 }
1797
1798 .project-page .item-card {
1799 background: var(--surface-overlay);
1800 transition: background 0.2s ease;
1801 cursor: pointer;
1802 display: flex;
1803 flex-direction: column;
1804 height: 100%;
1805 }
1806
1807 .project-page .item-card:hover { background: var(--surface-sunken); }
1808
1809 .project-page a.item-thumbnail {
1810 width: 100%;
1811 height: 300px;
1812 background: var(--border);
1813 display: flex;
1814 align-items: center;
1815 justify-content: center;
1816 font-size: var(--text-hero);
1817 text-decoration: none;
1818 overflow: hidden;
1819 }
1820
1821 .project-page a.item-thumbnail img {
1822 width: 100%;
1823 height: 100%;
1824 object-fit: cover;
1825 display: block;
1826 }
1827
1828 /* The dimming belongs to the placeholder, not to the slot. It was on the slot,
1829 so the day covers started rendering here they would have rendered at 30%. */
1830 .project-page a.item-thumbnail-empty { opacity: 0.3; }
1831 .project-page a.item-thumbnail-empty:hover { opacity: 0.5; }
1832
1833 .project-page .item-content {
1834 padding: var(--gap-pane);
1835 display: flex;
1836 flex-direction: column;
1837 flex-grow: 1;
1838 }
1839
1840 .project-page .item-title {
1841 font-size: var(--text-subhead);
1842 margin-bottom: var(--gap-peer);
1843 font-family: var(--font-heading);
1844 font-weight: bold;
1845 }
1846
1847 .project-page .item-title a:hover { text-decoration: underline; }
1848
1849 .project-page .item-meta {
1850 font-size: var(--text-note);
1851 opacity: 0.7;
1852 margin-bottom: var(--gap-section);
1853 }
1854
1855 .project-page .item-tags { margin-bottom: var(--gap-section); }
1856
1857 .project-page .item-description {
1858 font-size: var(--text-note);
1859 margin-bottom: var(--gap-section);
1860 opacity: 0.8;
1861 text-align: left;
1862 flex-grow: 1;
1863 }
1864
1865 .project-page .item-footer {
1866 display: flex;
1867 justify-content: space-between;
1868 align-items: center;
1869 margin-top: auto;
1870 }
1871
1872 .project-page .item-price { font-size: var(--text-subhead); }
1873 .project-page .item-stats { font-size: var(--text-note); opacity: 0.6; }
1874
1875 .project-page .store-footer {
1876 margin-top: var(--gap-page);
1877 padding-top: var(--gap-page);
1878 border-top: 1px solid var(--border);
1879 opacity: 0.6;
1880 }
1881
1882 .project-page .store-footer a { color: var(--content); }
1883
1884 .project-page .item-content .btn-primary,
1885 .project-page .item-content .btn-secondary {
1886 width: 100%;
1887 margin-top: var(--gap-section);
1888 }
1889
1890 /* project.html allows .section-tabs to wrap (other pages don't). */
1891 .project-page .section-tabs { flex-wrap: wrap; }
1892
1893 .project-page .tiers-section { margin-bottom: var(--gap-page); }
1894
1895 .project-page .tiers-grid {
1896 display: grid;
1897 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
1898 gap: var(--gap-pane);
1899 }
1900
1901 .project-page .tier-card {
1902 background: var(--surface-overlay);
1903 padding: var(--gap-page);
1904 display: flex;
1905 flex-direction: column;
1906 }
1907
1908 .project-page .tier-name {
1909 font-family: var(--font-heading);
1910 font-weight: bold;
1911 font-size: var(--text-subhead);
1912 margin-bottom: var(--gap-peer);
1913 }
1914
1915 .project-page .tier-price {
1916 font-size: var(--text-head);
1917 margin-bottom: var(--gap-section);
1918 }
1919
1920 .project-page .tier-description {
1921 font-size: var(--text-note);
1922 opacity: 0.8;
1923 margin-bottom: var(--gap-pane);
1924 flex-grow: 1;
1925 text-align: left;
1926 }
1927
1928 .project-page .tier-card form { margin-top: auto; }
1929 .project-page .tier-card button,
1930 .project-page .tier-card .btn-primary { width: 100%; }
1931
1932 .project-page .tier-active-badge {
1933 font-size: var(--text-note);
1934 padding: var(--gap-peer) var(--gap-section);
1935 background: var(--surface-sunken);
1936 text-align: center;
1937 opacity: 0.7;
1938 }
1939
1940 @media (max-width: 599px) {
1941 .project-page .store-title { font-size: var(--text-title); }
1942 .project-page a.item-thumbnail { height: 200px; }
1943 .project-page .store-description { font-size: var(--text-body); }
1944 }
1945
1946 /* Library downloads page (templates/pages/library_downloads.html).
1947 Scoped under .library-page body class. Class names like .item-footer,
1948 .bundle-child, etc. overlap with .item-page; the scope keeps them
1949 distinct. */
1950 .library-page .library-header {
1951 display: grid;
1952 grid-template-columns: 200px 1fr;
1953 gap: var(--gap-pane);
1954 margin-bottom: var(--gap-page);
1955 align-items: center;
1956 }
1957
1958 .library-page .library-header.no-cover { grid-template-columns: 1fr; }
1959
1960 .library-page .library-cover img {
1961 width: 100%;
1962 aspect-ratio: 1 / 1;
1963 object-fit: cover;
1964 display: block;
1965 }
1966
1967 .library-page .library-title {
1968 font-size: var(--text-title);
1969 margin-bottom: var(--gap-bound);
1970 }
1971
1972 /* Byline sits under the centered H1 (global `h1 { text-align: center }`),
1973 so center it too, otherwise it floats flush-left while the title is
1974 centered above. Same for the back-link breadcrumb. When a cover is
1975 present the grid scope below restores left-alignment in the text column. */
1976 .library-page .library-creator {
1977 font-size: var(--text-body);
1978 opacity: 0.7;
1979 margin-bottom: var(--gap-section);
1980 text-align: center;
1981 }
1982
1983 .library-page .library-creator a { color: var(--content); }
1984
1985 .library-page .library-back {
1986 font-size: var(--text-note);
1987 opacity: 0.7;
1988 text-align: center;
1989 }
1990 .library-page .library-back a { color: var(--content); }
1991
1992 /* When a cover is present, the title block sits in the right grid column;
1993 left-align in that context so the column reads as a coherent block next
1994 to the cover instead of a centered island. */
1995 .library-page .library-header:not(.no-cover) .library-title,
1996 .library-page .library-header:not(.no-cover) .library-creator {
1997 text-align: left;
1998 }
1999
2000 .library-page .downloads-hero h2 {
2001 font-size: var(--text-head);
2002 margin-bottom: var(--gap-section);
2003 padding-bottom: var(--gap-peer);
2004 border-bottom: 1px solid var(--border);
2005 }
2006
2007 .library-page .download-row {
2008 display: flex;
2009 align-items: center;
2010 gap: var(--gap-section);
2011 padding: var(--gap-group) 0;
2012 border-bottom: 1px solid var(--border);
2013 }
2014
2015 .library-page .download-row:last-child { border-bottom: none; }
2016
2017 .library-page .download-version {
2018 min-width: 4em;
2019 font-family: var(--font-mono);
2020 font-size: var(--text-note);
2021 }
2022
2023 .library-page .download-label { flex: 1; }
2024
2025 .library-page .download-size {
2026 font-size: var(--text-note);
2027 opacity: 0.6;
2028 min-width: 5em;
2029 text-align: right;
2030 }
2031
2032 .library-page .download-notice {
2033 background: var(--surface-sunken);
2034 padding: var(--gap-pane);
2035 margin-bottom: var(--gap-page);
2036 font-size: var(--text-note);
2037 opacity: 0.8;
2038 }
2039
2040 .library-page .download-notice strong { font-family: var(--font-mono); }
2041
2042 /* Inline download list on library_audio.html / library_video.html
2043 (below the media player). */
2044
2045 .library-page .library-downloads h3 {
2046 font-size: var(--text-lead);
2047 margin-bottom: var(--gap-section);
2048 }
2049
2050 .library-page .library-section h2 {
2051 font-size: var(--text-subhead);
2052 margin-bottom: var(--gap-section);
2053 padding-bottom: var(--gap-peer);
2054 border-bottom: 1px solid var(--border);
2055 }
2056
2057 .library-page .bundle-child {
2058 display: flex;
2059 align-items: center;
2060 gap: var(--gap-section);
2061 padding: var(--gap-section) 0;
2062 border-bottom: 1px solid var(--border);
2063 }
2064
2065 .library-page .bundle-child:last-child { border-bottom: none; }
2066
2067 .library-page .bundle-child-type {
2068 font-size: var(--text-fine);
2069 padding: var(--gap-bound) var(--gap-group);
2070 background: var(--surface-sunken);
2071 white-space: nowrap;
2072 }
2073
2074 .library-page .bundle-child-title { flex: 1; }
2075 .library-page .bundle-child-title a { color: var(--content); }
2076
2077 .library-page .item-footer {
2078 margin-top: var(--gap-page);
2079 padding-top: var(--gap-page);
2080 border-top: 1px solid var(--border);
2081 text-align: center;
2082 opacity: 0.6;
2083 font-size: var(--text-note);
2084 }
2085
2086 .library-page .item-footer a { color: var(--content); }
2087
2088 @media (max-width: 599px) {
2089 .library-page .library-header { grid-template-columns: 1fr; }
2090 .library-page .library-cover img { max-width: 200px; }
2091 }
2092
2093 /* Article reader pages (templates/pages/text_reader.html, blog_post.html,
2094 library_text.html). Long-form article layout, centered narrow column,
2095 author header, large heading, magazine-style body type. Scoped under
2096 .article-page body class. */
2097 .article-page .article-container {
2098 max-width: 680px;
2099 margin: 0 auto;
2100 padding: var(--gap-page) var(--gap-pane) var(--gap-page);
2101 }
2102
2103 .article-page .author-header {
2104 display: flex;
2105 align-items: center;
2106 gap: var(--gap-section);
2107 margin-bottom: var(--gap-page);
2108 }
2109
2110 .article-page .author-avatar {
2111 width: 48px;
2112 height: 48px;
2113 background: var(--surface-sunken);
2114 border-radius: var(--radius-round);
2115 display: flex;
2116 align-items: center;
2117 justify-content: center;
2118 font-family: var(--font-heading);
2119 font-size: var(--text-subhead);
2120 color: var(--content);
2121 }
2122
2123 .article-page .author-info { flex: 1; }
2124
2125 .article-page .author-name {
2126 font-family: var(--font-mono);
2127 font-size: var(--text-body);
2128 color: var(--content);
2129 }
2130
2131
2132 .article-page .author-name a:hover { text-decoration: underline; }
2133
2134 .article-page .article-meta {
2135 font-family: var(--font-mono);
2136 font-size: var(--text-note);
2137 color: var(--content-muted);
2138 }
2139
2140 .article-page .article-title {
2141 font-family: var(--font-heading);
2142 font-size: var(--text-display);
2143 font-weight: normal;
2144 line-height: 1.2;
2145 margin-bottom: var(--gap-pane);
2146 color: var(--content);
2147 text-align: left;
2148 }
2149
2150 .article-page .article-deck {
2151 font-family: var(--font-mono);
2152 font-size: var(--text-subhead);
2153 color: var(--content-muted);
2154 margin-bottom: var(--gap-page);
2155 line-height: 1.6;
2156 }
2157
2158 .article-page .article-body {
2159 font-family: var(--font-body);
2160 font-size: var(--text-lead);
2161 line-height: 1.9;
2162 }
2163
2164 .article-page .article-body p { margin-bottom: var(--gap-pane); }
2165
2166 .article-page .article-body h1 {
2167 font-family: var(--font-heading);
2168 font-size: var(--text-title);
2169 font-weight: normal;
2170 margin-top: var(--gap-page);
2171 margin-bottom: var(--gap-section);
2172 color: var(--content);
2173 text-align: left;
2174 }
2175
2176 .article-page .article-body h2 {
2177 font-family: var(--font-mono);
2178 font-size: var(--text-head);
2179 font-weight: normal;
2180 margin-top: var(--gap-page);
2181 margin-bottom: var(--gap-section);
2182 color: var(--content);
2183 }
2184
2185 .article-page .article-body h3 {
2186 font-family: var(--font-mono);
2187 font-size: var(--text-subhead);
2188 font-weight: normal;
2189 margin-top: var(--gap-page);
2190 margin-bottom: var(--gap-section);
2191 color: var(--content);
2192 }
2193
2194 .article-page .article-body blockquote {
2195 border-left: 3px solid var(--action);
2196 padding-left: var(--gap-pane);
2197 margin: var(--gap-page) 0;
2198 font-style: italic;
2199 color: var(--content-muted);
2200 }
2201
2202 .article-page .article-body ul,
2203 .article-page .article-body ol {
2204 margin-bottom: var(--gap-pane);
2205 padding-left: var(--gap-pane);
2206 }
2207
2208 .article-page .article-body li { margin-bottom: var(--gap-peer); }
2209
2210 .article-page .article-body a {
2211 color: var(--action);
2212 text-decoration: underline;
2213 text-decoration-color: color-mix(in oklch, var(--action) 40%, transparent);
2214 text-underline-offset: 2px;
2215 }
2216
2217 .article-page .article-body a:hover {
2218 text-decoration-color: var(--action);
2219 }
2220
2221 .article-page .article-body strong { font-weight: bold; }
2222 .article-page .article-body em { font-style: italic; }
2223
2224 .article-page .article-body code {
2225 font-family: var(--font-mono);
2226 background: var(--surface-sunken);
2227 padding: var(--gap-bound) var(--gap-peer);
2228 font-size: var(--text-body);
2229 }
2230
2231 .article-page .article-body pre {
2232 background: var(--surface-sunken);
2233 padding: var(--gap-section);
2234 overflow-x: auto;
2235 margin-bottom: var(--gap-pane);
2236 }
2237
2238 .article-page .article-body pre code {
2239 background: none;
2240 padding: 0;
2241 }
2242
2243 .article-page .article-body hr {
2244 border: none;
2245 text-align: center;
2246 margin: var(--gap-page) 0;
2247 }
2248
2249 .article-page .article-body hr::before {
2250 content: "* * *";
2251 color: var(--content-muted);
2252 letter-spacing: 1rem;
2253 }
2254
2255 .article-page .article-footer {
2256 margin-top: var(--gap-page);
2257 padding-top: var(--gap-page);
2258 border-top: 1px solid var(--border);
2259 }
2260
2261 .article-page .author-bio {
2262 display: flex;
2263 gap: var(--gap-section);
2264 padding: var(--gap-pane);
2265 background: var(--surface-overlay);
2266 }
2267
2268 .article-page .author-bio .author-avatar {
2269 width: 64px;
2270 height: 64px;
2271 font-size: var(--text-head);
2272 flex-shrink: 0;
2273 }
2274
2275 .article-page .author-bio-content { flex: 1; }
2276
2277 .article-page .author-bio-name {
2278 font-family: var(--font-mono);
2279 font-size: var(--text-lead);
2280 margin-bottom: var(--gap-peer);
2281 }
2282
2283
2284 .article-page .author-bio-name a:hover { text-decoration: underline; }
2285
2286 .article-page .article-tags {
2287 margin-top: var(--gap-page);
2288 display: flex;
2289 gap: var(--gap-peer);
2290 flex-wrap: wrap;
2291 }
2292
2293 .article-page .article-tag {
2294 font-family: var(--font-mono);
2295 background: var(--surface-sunken);
2296 color: var(--content);
2297 padding: var(--gap-peer) var(--gap-section);
2298 font-size: var(--text-fine);
2299 text-decoration: none;
2300 }
2301
2302 /* Share controls at the end of the article, inside .article-footer beside
2303 the tags or the blog crumb. Was a page-bottom footer below the comments
2304 until the site-footer consolidation; the control belongs with the piece
2305 it copies, not under the discussion. */
2306 .article-page .reader-actions {
2307 font-family: var(--font-mono);
2308 font-size: var(--text-note);
2309 margin-top: var(--gap-section);
2310 }
2311
2312 .article-page .reader-actions a { color: var(--content); text-decoration: none; }
2313 .article-page .reader-actions a:hover { text-decoration: underline; }
2314
2315 /* Blog-specific: nav crumb above the article. */
2316 .article-page .blog-nav {
2317 font-family: var(--font-mono);
2318 font-size: var(--text-note);
2319 margin-bottom: var(--gap-page);
2320 }
2321
2322 .article-page .blog-nav a { color: var(--content); text-decoration: none; }
2323 .article-page .blog-nav a:hover { text-decoration: underline; }
2324
2325 @media (max-width: 599px) {
2326 .article-page .article-title { font-size: var(--text-title); }
2327 .article-page .article-deck { font-size: var(--text-lead); }
2328 .article-page .article-body { font-size: var(--text-body); }
2329 }
2330
2331 /* Admin dashboards (templates/dashboards/admin-*.html). Every admin
2332 page had a duplicate `h1 { font-size: 2rem; margin-bottom: 0.5rem; }`
2333 inline, consolidated here. */
2334
2335 /* System health dashboard (templates/pages/health.html). */
2336 .health-page {
2337 padding: var(--gap-page);
2338 }
2339
2340 .health-page .health-container {
2341 max-width: 900px;
2342 margin: 0 auto;
2343 }
2344
2345 .health-page .health-grid {
2346 display: grid;
2347 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
2348 gap: var(--gap-pane);
2349 margin-bottom: var(--gap-page);
2350 }
2351
2352 .health-page .health-card {
2353 background: var(--surface-sunken);
2354 padding: var(--gap-pane);
2355 }
2356
2357 .health-page .health-card h3 {
2358 font-family: var(--font-mono);
2359 font-size: var(--text-note);
2360 margin-bottom: var(--gap-section);
2361 display: flex;
2362 align-items: center;
2363 gap: var(--gap-peer);
2364 }
2365
2366 .health-page .status-indicator {
2367 width: 10px;
2368 height: 10px;
2369 border-radius: var(--radius-round);
2370 display: inline-block;
2371 }
2372
2373 .health-page .status-ok { background: var(--success); }
2374 .health-page .status-warn { background: var(--warning); }
2375 .health-page .status-error { background: var(--danger); }
2376 .health-page .status-unknown { background: var(--content-muted); }
2377
2378 .health-page .health-card dl {
2379 display: grid;
2380 grid-template-columns: auto 1fr;
2381 gap: var(--gap-peer) var(--gap-section);
2382 font-size: var(--text-note);
2383 }
2384
2385 .health-page .health-card dt { opacity: 0.7; }
2386 .health-page .health-card dd {
2387 font-family: var(--font-mono);
2388 text-align: right;
2389 }
2390
2391 .health-page .section-title {
2392 font-family: var(--font-mono);
2393 font-size: var(--text-body);
2394 margin: var(--gap-page) 0 var(--gap-section);
2395 padding-bottom: var(--gap-peer);
2396 border-bottom: 1px solid var(--border);
2397 }
2398 .health-page .test-list {
2399 list-style: none;
2400 padding: 0;
2401 margin: 0;
2402 }
2403
2404 .health-page .test-list li {
2405 display: flex;
2406 justify-content: space-between;
2407 padding: var(--gap-peer) 0;
2408 border-bottom: 1px solid var(--border);
2409 font-size: var(--text-note);
2410 }
2411
2412 .health-page .test-list li:last-child { border-bottom: none; }
2413
2414 .health-page .test-name { font-family: var(--font-mono); }
2415
2416 .health-page .test-pass { color: var(--success); }
2417 .health-page .test-fail { color: var(--danger); }
2418 .health-page .summary-bar {
2419 display: flex;
2420 gap: var(--gap-page);
2421 padding: var(--gap-section);
2422 background: var(--surface-sunken);
2423 margin-bottom: var(--gap-page);
2424 font-family: var(--font-mono);
2425 font-size: var(--text-note);
2426 }
2427
2428 .health-page .summary-item {
2429 display: flex;
2430 align-items: center;
2431 gap: var(--gap-peer);
2432 }
2433
2434 .health-page .timestamp {
2435 font-size: var(--text-fine);
2436 opacity: 0.6;
2437 text-align: right;
2438 margin-top: var(--gap-page);
2439 }
2440
2441 .health-page details.check-list {
2442 background: var(--surface-overlay);
2443 padding: var(--gap-pane);
2444 margin-bottom: var(--gap-page);
2445 }
2446
2447 .health-page details.check-list summary {
2448 font-family: var(--font-mono);
2449 font-size: var(--text-note);
2450 cursor: pointer;
2451 list-style: none;
2452 display: flex;
2453 align-items: center;
2454 gap: var(--gap-peer);
2455 }
2456
2457 .health-page details.check-list summary::-webkit-details-marker { display: none; }
2458
2459 .health-page details.check-list summary::before {
2460 content: "â–¶";
2461 font-size: var(--text-fine);
2462 transition: transform 0.15s;
2463 }
2464
2465 .health-page details.check-list[open] summary::before {
2466 transform: rotate(90deg);
2467 }
2468
2469 .health-page details.check-list .test-list {
2470 margin-top: var(--gap-section);
2471 }
2472 /* Import data wizard (templates/dashboards/dashboard-import.html).
2473 Page-specific upload + column-mapping + progress flow. Migration also
2474 fixed token bypasses: --border-color → --border, --accent → --highlight,
2475 --success-background → --success-bg, --error-background → --error-bg. */
2476
2477 .import-page .back-link {
2478 display: inline-block;
2479 margin-bottom: var(--gap-pane);
2480 font-size: var(--text-note);
2481 opacity: 0.7;
2482 }
2483
2484 .import-page .back-link:hover { opacity: 1; }
2485
2486 .import-page .form-group { margin-bottom: var(--gap-pane); }
2487
2488 .import-page .form-group label {
2489 display: block;
2490 font-family: var(--font-mono);
2491 font-size: var(--text-note);
2492 font-weight: bold;
2493 margin-bottom: var(--gap-peer);
2494 }
2495
2496 .import-page .form-group select,
2497 .import-page .form-group input[type="file"] {
2498 width: 100%;
2499 padding: var(--gap-peer);
2500 font-size: var(--text-note);
2501 border: 1px solid var(--border);
2502 background: var(--surface-page);
2503 }
2504
2505 .import-page .csv-preview {
2506 margin-top: var(--gap-section);
2507 overflow-x: auto;
2508 }
2509
2510 .import-page .csv-preview table {
2511 width: 100%;
2512 border-collapse: collapse;
2513 font-size: var(--text-note);
2514 }
2515
2516 .import-page .csv-preview th,
2517 .import-page .csv-preview td {
2518 padding: var(--gap-peer) var(--gap-group);
2519 border: 1px solid var(--border);
2520 text-align: left;
2521 }
2522
2523 .import-page .csv-preview th {
2524 background: var(--surface-sunken);
2525 font-family: var(--font-mono);
2526 }
2527
2528 .import-page .column-mapping { margin-top: var(--gap-section); }
2529
2530 .import-page .mapping-row {
2531 display: flex;
2532 align-items: center;
2533 gap: var(--gap-section);
2534 margin-bottom: var(--gap-peer);
2535 }
2536
2537 .import-page .mapping-row label {
2538 min-width: 100px;
2539 font-size: var(--text-note);
2540 font-family: var(--font-mono);
2541 margin-bottom: 0;
2542 }
2543
2544 .import-page .mapping-row select {
2545 flex: 1;
2546 padding: var(--gap-bound);
2547 font-size: var(--text-note);
2548 }
2549
2550 .import-page .progress-stats {
2551 display: flex;
2552 gap: var(--gap-page);
2553 font-size: var(--text-note);
2554 font-family: var(--font-mono);
2555 }
2556
2557 .import-page .progress-stats .stat-value { font-weight: bold; }
2558
2559 .import-page .import-result {
2560 padding: var(--gap-section);
2561 margin-bottom: var(--gap-section);
2562 font-size: var(--text-note);
2563 }
2564
2565 .import-page .import-result.success { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); }
2566 .import-page .import-result.error { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); }
2567
2568 .import-page .error-log {
2569 margin-top: var(--gap-section);
2570 padding: var(--gap-peer);
2571 background: var(--surface-sunken);
2572 font-family: var(--font-mono);
2573 font-size: var(--text-fine);
2574 max-height: 200px;
2575 overflow-y: auto;
2576 white-space: pre-wrap;
2577 }
2578
2579 .import-page .import-history { margin-top: var(--gap-page); }
2580
2581 .import-page .history-table {
2582 width: 100%;
2583 border-collapse: collapse;
2584 font-size: var(--text-note);
2585 }
2586
2587 .import-page .history-table th,
2588 .import-page .history-table td {
2589 padding: var(--gap-peer);
2590 border-bottom: 1px solid var(--border);
2591 text-align: left;
2592 }
2593
2594 .import-page .history-table th {
2595 font-family: var(--font-mono);
2596 font-weight: bold;
2597 }
2598
2599 .import-page .import-note {
2600 background: var(--surface-sunken);
2601 padding: var(--gap-section);
2602 margin-top: var(--gap-page);
2603 font-size: var(--text-note);
2604 }
2605
2606 .import-page .import-note h3 {
2607 font-family: var(--font-mono);
2608 font-size: var(--text-note);
2609 margin-bottom: var(--gap-peer);
2610 }
2611
2612 /* Delete account page (templates/dashboards/dashboard-delete-account.html).
2613 Overrides .warning-box to use --danger (delete is danger, not warning)
2614 and the .form-status partial styling. Scoped to keep the global versions
2615 intact for other pages. */
2616
2617 .delete-account-page .warning-box {
2618 background: var(--danger);
2619 color: var(--primary-light);
2620 padding: var(--gap-pane);
2621 margin-bottom: var(--gap-page);
2622 }
2623
2624 .delete-account-page .warning-box h3 {
2625 font-family: var(--font-mono);
2626 font-size: var(--text-body);
2627 margin-bottom: var(--gap-peer);
2628 }
2629
2630 .delete-account-page .warning-box ul {
2631 margin: var(--gap-peer) 0 0 var(--gap-pane);
2632 font-size: var(--text-note);
2633 }
2634
2635 .delete-account-page .warning-box li { margin-bottom: var(--gap-bound); }
2636
2637 .delete-account-page .export-prompt {
2638 background: var(--surface-sunken);
2639 padding: var(--gap-pane);
2640 margin-bottom: var(--gap-page);
2641 }
2642
2643 .delete-account-page .export-prompt h3 {
2644 font-family: var(--font-mono);
2645 font-size: var(--text-body);
2646 margin-bottom: var(--gap-peer);
2647 }
2648
2649 .delete-account-page .export-prompt p {
2650 font-size: var(--text-note);
2651 margin-bottom: var(--gap-section);
2652 }
2653
2654 .delete-account-page .delete-form {
2655 background: var(--surface-overlay);
2656 padding: var(--gap-pane);
2657 }
2658
2659 .delete-account-page .delete-form .form-group { margin-bottom: var(--gap-pane); }
2660
2661 .delete-account-page .delete-form label {
2662 display: block;
2663 margin-bottom: var(--gap-peer);
2664 font-family: var(--font-mono);
2665 font-size: var(--text-note);
2666 }
2667
2668 .delete-account-page .delete-form .hint {
2669 font-size: var(--text-note);
2670 opacity: 0.7;
2671 margin-top: var(--gap-bound);
2672 }
2673
2674 .delete-account-page .delete-form input[type="text"] {
2675 width: 100%;
2676 padding: var(--gap-section);
2677 font-size: var(--text-body);
2678 border: 2px solid var(--border);
2679 background: var(--surface-sunken);
2680 }
2681
2682 .delete-account-page .delete-form input[type="text"]:focus {
2683 border-color: var(--danger);
2684 outline: none;
2685 }
2686
2687 .delete-account-page .back-link {
2688 display: inline-block;
2689 margin-bottom: var(--gap-pane);
2690 font-size: var(--text-note);
2691 opacity: 0.7;
2692 }
2693
2694 .delete-account-page .back-link:hover { opacity: 1; }
2695
2696 .delete-account-page .form-actions {
2697 display: flex;
2698 gap: var(--gap-section);
2699 align-items: center;
2700 }
2701
2702 .delete-account-page .form-status { font-size: var(--text-note); }
2703 .delete-account-page .form-status.error { color: var(--danger); }
2704 .delete-account-page .form-status.success { color: var(--success); }
2705
2706 /* Export data page (templates/dashboards/dashboard-export.html).
2707 `.export-card` and its inner classes (`-info`, `-title`, `-desc`, `-meta`)
2708 are intentionally page-scoped: this is a row-layout card (info on left,
2709 action button on right, flex justify-between) that doesn't fit `.card-muted`
2710 cleanly. Background uses `--light-background` rather than `--surface-muted`
2711 for visual distinction from the surrounding dashboard surface. */
2712
2713 .export-page .export-cards {
2714 display: grid;
2715 gap: var(--gap-section);
2716 }
2717
2718 .export-page .export-card {
2719 background: var(--surface-overlay);
2720 padding: var(--gap-pane);
2721 display: flex;
2722 justify-content: space-between;
2723 align-items: flex-start;
2724 gap: var(--gap-section);
2725 }
2726
2727 .export-page .export-card-info { flex: 1; }
2728
2729 .export-page .export-card-title {
2730 font-family: var(--font-mono);
2731 font-weight: bold;
2732 font-size: var(--text-lead);
2733 margin-bottom: var(--gap-peer);
2734 }
2735
2736 .export-page .export-card-desc {
2737 font-size: var(--text-note);
2738 opacity: 0.8;
2739 margin-bottom: var(--gap-peer);
2740 }
2741
2742 .export-page .export-card-meta {
2743 font-size: var(--text-fine);
2744 opacity: 0.6;
2745 }
2746
2747 .export-page .export-card button { white-space: nowrap; }
2748
2749 .export-page .export-note {
2750 background: var(--surface-sunken);
2751 padding: var(--gap-section);
2752 margin-top: var(--gap-page);
2753 font-size: var(--text-note);
2754 }
2755
2756 .export-page .export-note h3 {
2757 font-family: var(--font-mono);
2758 font-size: var(--text-note);
2759 margin-bottom: var(--gap-peer);
2760 }
2761
2762 .export-page .back-link {
2763 display: inline-block;
2764 margin-bottom: var(--gap-pane);
2765 font-size: var(--text-note);
2766 opacity: 0.7;
2767 }
2768
2769 .export-page .back-link:hover { opacity: 1; }
2770
2771 .export-page .export-status {
2772 margin-top: var(--gap-peer);
2773 font-size: var(--text-note);
2774 }
2775
2776 .export-page .export-status.success { color: var(--success); }
2777 .export-page .export-status.error { color: var(--danger); }
2778
2779 /* Receipt page (templates/pages/receipt.html). */
2780
2781 .receipt-page .receipt-header {
2782 display: flex;
2783 justify-content: space-between;
2784 align-items: flex-start;
2785 margin-bottom: var(--gap-page);
2786 padding-bottom: var(--gap-pane);
2787 border-bottom: 1px solid var(--border);
2788 }
2789
2790 .receipt-page .receipt-row {
2791 display: flex;
2792 justify-content: space-between;
2793 padding: var(--gap-peer) 0;
2794 font-size: var(--text-body);
2795 }
2796
2797 .receipt-page .receipt-row.total {
2798 font-weight: bold;
2799 font-size: var(--text-lead);
2800 border-top: 1px solid var(--border);
2801 padding-top: var(--gap-section);
2802 margin-top: var(--gap-peer);
2803 }
2804
2805 .receipt-page .receipt-label { opacity: 0.7; }
2806
2807 .receipt-page .receipt-footer {
2808 margin-top: var(--gap-page);
2809 padding-top: var(--gap-pane);
2810 border-top: 1px solid var(--border);
2811 font-size: var(--text-note);
2812 opacity: 0.6;
2813 }
2814
2815 .receipt-page .back-link {
2816 display: inline-block;
2817 margin-bottom: var(--gap-pane);
2818 font-size: var(--text-note);
2819 opacity: 0.7;
2820 }
2821
2822 .receipt-page .back-link:hover { opacity: 1; }
2823
2824 @media print {
2825 .receipt-page .back-link,
2826 .receipt-page .site-header,
2827 .receipt-page .print-btn { display: none !important; }
2828 .receipt-page .receipt-box { border: 1px solid var(--border); }
2829 }
2830
2831 /* Fan+ subscription page (templates/pages/fan_plus.html). */
2832 .fan-plus-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
2833 .fan-plus-page h2 { font-size: var(--text-head); margin-top: var(--gap-page); margin-bottom: var(--gap-section); }
2834
2835 .fan-plus-page .fan-plus-section { margin-bottom: var(--gap-page); line-height: 1.7; }
2836 .fan-plus-page .fan-plus-section ul { padding-left: var(--gap-pane); margin: var(--gap-section) 0; }
2837 .fan-plus-page .fan-plus-section li { margin-bottom: var(--gap-peer); }
2838
2839 .fan-plus-page .price-callout {
2840 font-size: var(--text-subhead);
2841 font-family: var(--font-mono);
2842 margin: var(--gap-pane) 0;
2843 }
2844
2845 .fan-plus-page .subscribe-form { margin: var(--gap-page) 0; }
2846
2847 .fan-plus-page .subscribe-btn {
2848 display: inline-block;
2849 padding: var(--gap-section) var(--gap-page);
2850 background: var(--action);
2851 color: var(--primary-light);
2852 border: none;
2853 font-size: var(--text-lead);
2854 cursor: pointer;
2855 font-family: var(--font-mono);
2856 }
2857
2858 .fan-plus-page .subscribe-btn:hover { opacity: 0.9; }
2859
2860 .fan-plus-page .status-box {
2861 padding: var(--gap-pane);
2862 background: var(--surface-overlay);
2863 margin: var(--gap-pane) 0;
2864 }
2865
2866 .fan-plus-page .status-box p { margin: var(--gap-peer) 0; }
2867
2868 .fan-plus-page .success-banner {
2869 padding: var(--gap-section) var(--gap-pane);
2870 background: color-mix(in oklch, var(--success) 12%, var(--surface-page));
2871 margin-bottom: var(--gap-pane);
2872 font-family: var(--font-mono);
2873 }
2874
2875 .fan-plus-page .login-link { font-family: var(--font-mono); }
2876
2877 /* Purchase confirmation page (templates/pages/purchase.html). */
2878 .purchase-page .container {
2879 max-width: 800px;
2880 margin: var(--gap-page) auto;
2881 padding: 0 var(--gap-page);
2882 }
2883
2884 .purchase-page .tagline {
2885 font-size: var(--text-note);
2886 opacity: 0.7;
2887 margin-bottom: var(--gap-page);
2888 text-align: center;
2889 }
2890
2891 .purchase-page .checkout-box { margin-bottom: var(--gap-pane); }
2892
2893 .purchase-page h2 { font-size: var(--text-title); margin-bottom: var(--gap-peer); }
2894
2895 .purchase-page .step-indicator {
2896 font-size: var(--text-note);
2897 opacity: 0.6;
2898 margin-bottom: var(--gap-pane);
2899 }
2900
2901 .purchase-page .item-summary {
2902 background: var(--surface-page);
2903 padding: var(--gap-pane);
2904 margin-bottom: var(--gap-pane);
2905 display: flex;
2906 gap: var(--gap-pane);
2907 }
2908
2909 .purchase-page .item-thumbnail {
2910 width: 120px;
2911 height: 120px;
2912 background: var(--surface-sunken);
2913 flex-shrink: 0;
2914 display: flex;
2915 align-items: center;
2916 justify-content: center;
2917 font-size: var(--text-hero);
2918 overflow: hidden;
2919 }
2920
2921 .purchase-page .item-thumbnail img {
2922 width: 100%;
2923 height: 100%;
2924 object-fit: cover;
2925 display: block;
2926 }
2927
2928 /* See the storefront card: the dimming is the placeholder's, not the slot's. */
2929 .purchase-page .item-thumbnail-empty { opacity: 0.3; }
2930
2931 .purchase-page .item-details { flex: 1; }
2932
2933 .purchase-page .item-title {
2934 font-size: var(--text-subhead);
2935 margin-bottom: var(--gap-peer);
2936 font-family: var(--font-heading);
2937 font-weight: bold;
2938 }
2939
2940 .purchase-page .item-creator {
2941 font-size: var(--text-note);
2942 opacity: 0.7;
2943 margin-bottom: var(--gap-section);
2944 }
2945
2946 .purchase-page .item-creator a { color: var(--content); }
2947 .purchase-page .item-description { font-size: var(--text-note); opacity: 0.8; }
2948
2949 .purchase-page .price-breakdown {
2950 background: var(--surface-page);
2951 padding: var(--gap-pane);
2952 margin-bottom: var(--gap-pane);
2953 }
2954
2955 .purchase-page .price-breakdown h2 { font-size: var(--text-lead); margin-bottom: var(--gap-section); }
2956
2957 .purchase-page .price-row {
2958 display: flex;
2959 justify-content: space-between;
2960 padding: var(--gap-peer) 0;
2961 border-bottom: 1px solid var(--border);
2962 }
2963
2964 .purchase-page .price-row:last-child {
2965 border-bottom: none;
2966 margin-top: var(--gap-peer);
2967 font-size: var(--text-subhead);
2968 padding-top: var(--gap-section);
2969 border-top: 2px solid var(--border);
2970 }
2971
2972 .purchase-page .price-row.total { font-weight: bold; }
2973
2974 .purchase-page .payment-section { margin-bottom: var(--gap-pane); }
2975 .purchase-page .payment-section h2 { font-size: var(--text-lead); margin-bottom: var(--gap-section); }
2976
2977 .purchase-page .btn-primary { width: 100%; }
2978 .purchase-page .btn-secondary { width: 100%; margin-top: var(--gap-section); }
2979
2980 .purchase-page .security-note {
2981 font-size: var(--text-note);
2982 opacity: 0.6;
2983 text-align: center;
2984 margin-top: var(--gap-section);
2985 }
2986
2987 /* Replaces inline styles in pages/purchase.html. */
2988 .purchase-page h1 { text-align: center; }
2989
2990 .purchase-page .price-breakdown-note {
2991 margin-top: var(--gap-peer);
2992 font-size: var(--text-fine);
2993 opacity: 0.6;
2994 }
2995
2996 .purchase-page .creator-breakdown {
2997 background: var(--surface-page);
2998 padding: var(--gap-section) var(--gap-pane);
2999 margin-bottom: var(--gap-pane);
3000 font-size: var(--text-note);
3001 }
3002 .purchase-page .creator-breakdown h2 {
3003 font-size: var(--text-body);
3004 margin-bottom: var(--gap-section);
3005 opacity: 0.7;
3006 }
3007 .purchase-page .creator-breakdown .price-row { opacity: 0.8; }
3008 .purchase-page .creator-breakdown .price-row.is-total {
3009 opacity: 1;
3010 font-weight: bold;
3011 border-top: 1px solid var(--border);
3012 margin-top: var(--gap-peer);
3013 padding-top: var(--gap-peer);
3014 /* override .price-row:last-child upsize */
3015 font-size: inherit;
3016 border-bottom: none;
3017 }
3018 .purchase-page .creator-breakdown .platform-fee-amount { color: var(--content); }
3019 .purchase-page .creator-breakdown-note {
3020 margin-top: var(--gap-section);
3021 font-size: var(--text-note);
3022 opacity: 0.6;
3023 }
3024
3025 .purchase-page .payment-section-lead {
3026 margin-bottom: var(--gap-pane);
3027 opacity: 0.8;
3028 }
3029
3030 .purchase-page .pending-checkout {
3031 margin-bottom: var(--gap-pane);
3032 background: var(--surface-sunken);
3033 padding: var(--gap-section);
3034 }
3035 .purchase-page .pending-checkout p { margin: 0 0 var(--gap-section); }
3036 .purchase-page .pending-checkout p.is-sub {
3037 font-size: var(--text-note);
3038 opacity: 0.8;
3039 }
3040 .purchase-page .pending-checkout form { margin: 0; }
3041
3042 /* Checkout form field wrappers. */
3043 .purchase-page .checkout-field { margin-bottom: var(--gap-section); }
3044 .purchase-page .checkout-field-label {
3045 font-size: var(--text-note);
3046 display: block;
3047 margin-bottom: var(--gap-bound);
3048 }
3049 .purchase-page .pwyw-row {
3050 display: flex;
3051 align-items: center;
3052 gap: var(--gap-bound);
3053 }
3054 .purchase-page .pwyw-symbol { font-size: var(--text-lead); }
3055 .purchase-page .pwyw-input { width: 120px; }
3056 .purchase-page .pwyw-min-note {
3057 font-size: var(--text-fine);
3058 opacity: 0.6;
3059 margin-top: var(--gap-bound);
3060 }
3061 .purchase-page .promo-input {
3062 width: 200px;
3063 text-transform: uppercase;
3064 }
3065 .purchase-page .share-contact-label {
3066 font-size: var(--text-note);
3067 display: flex;
3068 align-items: center;
3069 gap: var(--gap-peer);
3070 cursor: pointer;
3071 }
3072 .purchase-page .share-contact-checkbox { width: auto; }
3073
3074 .purchase-page .cart-alt-link {
3075 text-align: center;
3076 margin-top: var(--gap-section);
3077 font-size: var(--text-note);
3078 opacity: 0.7;
3079 }
3080 .purchase-page .guest-hint {
3081 font-size: var(--text-fine);
3082 opacity: 0.6;
3083 margin-top: var(--gap-section);
3084 text-align: center;
3085 }
3086 .purchase-page .guest-login-prompt {
3087 font-size: var(--text-fine);
3088 margin-top: var(--gap-peer);
3089 text-align: center;
3090 }
3091
3092 /* ===========================================
3093 PROJECT SETTINGS TAB
3094 (replaces inline styles in partials/tabs/project_settings.html and
3095 related innerHTML in static/project-sections.js)
3096 =========================================== */
3097
3098 .proj-image-hint {
3099 font-size: var(--text-note);
3100 opacity: 0.7;
3101 margin: 0 0 var(--gap-peer);
3102 }
3103
3104 /* Canonical inline form-status text following a Save / Change button.
3105 Two class names because templates evolved with both. They're aliases.
3106 States via .success / .error / .saving modifier. */
3107 .field-status,
3108 .save-status {
3109 margin-left: var(--gap-peer);
3110 font-size: var(--text-note);
3111 }
3112 .field-status.success, .save-status.success { color: var(--success); }
3113 .field-status.error, .save-status.error { color: var(--danger); }
3114 .field-status.saving, .save-status.saving { opacity: 0.6; }
3115
3116 /* Muted lead paragraph above a form button (monetization / features / delete). */
3117 .section-lead {
3118 margin-bottom: var(--gap-section);
3119 opacity: 0.7;
3120 text-align: left;
3121 }
3122
3123 /* Large intro paragraph under a page h1 (creators, policy, changelog, fan_plus). */
3124 .page-intro {
3125 font-size: var(--text-lead);
3126 margin-bottom: var(--gap-page);
3127 line-height: 1.6;
3128 }
3129
3130 .subscription-note p {
3131 opacity: 0.7;
3132 font-size: var(--text-note);
3133 }
3134
3135 .features-grid-status {
3136 margin-top: var(--gap-peer);
3137 display: inline-block;
3138 }
3139
3140 /* "Pages" disclosure (project-level markdown sections). */
3141 .pages-toggle { margin-top: var(--gap-page); }
3142 .pages-toggle > summary { cursor: pointer; }
3143 .pages-toggle > summary h2 { display: inline; }
3144 .pages-intro {
3145 font-size: var(--text-note);
3146 opacity: 0.7;
3147 margin: var(--gap-section) 0 var(--gap-section);
3148 }
3149
3150 /* psection-* mirrors section-mgmt-* (used by item_details) but psection
3151 class hooks are also queried by static/project-sections.js. */
3152 .psection-row-title { flex: 1; font-weight: bold; }
3153 .psection-row-anchor { font-size: var(--text-fine); opacity: 0.6; }
3154 .psection-row-length { font-size: var(--text-fine); opacity: 0.6; }
3155
3156 .psection-edit-btn,
3157 .psection-del-btn {
3158 padding: var(--gap-bound) var(--gap-group);
3159 font-size: var(--text-fine);
3160 }
3161
3162 .psection-add-details { margin-top: var(--gap-section); }
3163 .psection-add-details > summary {
3164 cursor: pointer;
3165 font-size: var(--text-body);
3166 }
3167 .psection-add-fields { margin-top: var(--gap-section); }
3168
3169 .psection-edit-modal {
3170 margin-top: var(--gap-section);
3171 padding: var(--gap-section);
3172 background: var(--surface-sunken);
3173 }
3174 .psection-edit-actions {
3175 display: flex;
3176 gap: var(--gap-peer);
3177 }
3178
3179 /* ===========================================
3180 CART PAGE (replaces inline styles in pages/cart.html)
3181 =========================================== */
3182
3183 .cart-empty {
3184 text-align: center;
3185 padding: var(--gap-page) var(--gap-section);
3186 }
3187 .cart-empty-hint {
3188 font-size: var(--text-note);
3189 opacity: 0.7;
3190 margin-bottom: var(--gap-pane);
3191 }
3192
3193 /* Multi-creator summary bar (only renders when seller_groups.len() > 1). */
3194 .cart-multi-bar-note {
3195 opacity: 0.7;
3196 margin-left: var(--gap-peer);
3197 }
3198 .cart-multi-bar-form {
3199 display: flex;
3200 align-items: center;
3201 gap: var(--gap-section);
3202 }
3203 .cart-share-label {
3204 font-size: var(--text-note);
3205 display: flex;
3206 align-items: center;
3207 gap: var(--gap-peer);
3208 }
3209
3210 /* Per-seller group card. */
3211 .cart-group { margin-bottom: var(--gap-page); }
3212 .cart-group-title { margin-bottom: var(--gap-bound); }
3213 .cart-group-count {
3214 font-size: var(--text-note);
3215 opacity: 0.6;
3216 margin-bottom: var(--gap-section);
3217 }
3218
3219 /* PWYW editable price cell. */
3220 .cart-pwyw-cell {
3221 display: flex;
3222 align-items: center;
3223 justify-content: flex-end;
3224 gap: var(--gap-bound);
3225 }
3226 .cart-pwyw-symbol { font-size: var(--text-note); }
3227
3228 /* Compact action button used in cart/wishlist tables. */
3229
3230 /* Bottom summary row of each seller group. */
3231 .cart-group-summary {
3232 margin-top: var(--gap-section);
3233 display: flex;
3234 justify-content: space-between;
3235 align-items: center;
3236 flex-wrap: wrap;
3237 gap: var(--gap-section);
3238 }
3239 .cart-subtotal {
3240 font-family: var(--font-heading);
3241 font-size: var(--text-lead);
3242 }
3243 .cart-fee-line {
3244 font-size: var(--text-note);
3245 opacity: 0.6;
3246 }
3247 .cart-savings {
3248 font-size: var(--text-note);
3249 color: var(--action);
3250 margin-top: var(--gap-bound);
3251 }
3252 .cart-stripe-not-ready {
3253 font-size: var(--text-note);
3254 opacity: 0.6;
3255 }
3256
3257 /* Checkout form within a seller group. */
3258 .cart-promo-row { margin-bottom: var(--gap-peer); }
3259 .cart-checkout-share {
3260 display: flex;
3261 align-items: center;
3262 gap: var(--gap-peer);
3263 font-size: var(--text-note);
3264 margin-bottom: var(--gap-peer);
3265 }
3266
3267 /* Wishlist suggestions section below the cart. */
3268 .cart-wishlist { margin-top: var(--gap-page); }
3269
3270 /* Generic "faded out" row state, used by Add-to-Cart click handler in cart.html
3271 to indicate the wishlist row has been moved into the cart. */
3272 .is-faded { opacity: 0.4; }
3273
3274 /* ===========================================
3275 PROFILE TAB (replaces inline styles in partials/tabs/user_profile.html)
3276 =========================================== */
3277
3278 /* Inline form row: input(s) + button laid out horizontally, bottom-aligned.
3279 Distinct from .form-row (which is a 1fr 1fr grid). */
3280 .field-row {
3281 display: flex;
3282 gap: var(--gap-section);
3283 align-items: flex-end;
3284 }
3285 .field-row > .form-group { margin-bottom: 0; }
3286 .field-row > .form-group.is-grow { flex: 1; }
3287 .field-row > .form-group.is-grow-2 { flex: 2; }
3288 .field-row > button { margin-bottom: 0; }
3289
3290 /* Custom domain section. */
3291 .profile-domain-header { margin-bottom: var(--gap-section); }
3292 .profile-domain-status {
3293 margin-left: var(--gap-peer);
3294 }
3295 .profile-domain-status.is-verified { color: var(--success); }
3296 .profile-domain-status.is-pending { color: var(--warning); }
3297
3298 .dns-row {
3299 display: flex;
3300 align-items: center;
3301 gap: var(--gap-peer);
3302 margin-bottom: var(--gap-peer);
3303 }
3304 .dns-row:last-of-type { margin-bottom: 0; }
3305 .dns-row-label {
3306 font-size: var(--text-fine);
3307 opacity: 0.7;
3308 min-width: 70px;
3309 }
3310 .dns-row-value {
3311 font-size: var(--text-note);
3312 flex: 1;
3313 }
3314 .dns-row-copy {
3315 padding: var(--gap-bound) var(--gap-peer);
3316 font-size: var(--text-fine);
3317 }
3318 .profile-domain-actions {
3319 display: flex;
3320 gap: var(--gap-section);
3321 }
3322 .profile-domain-result { margin-top: var(--gap-section); }
3323 .profile-domain-lead {
3324 font-size: var(--text-note);
3325 opacity: 0.8;
3326 }
3327 .profile-domain-add-result { margin-top: var(--gap-section); }
3328
3329 /* Personal feed row: URL input + Copy button. */
3330 .profile-feed-row {
3331 display: flex;
3332 gap: var(--gap-peer);
3333 align-items: center;
3334 }
3335 .profile-feed-input {
3336 flex: 1;
3337 font-size: var(--text-note);
3338 font-family: var(--font-mono);
3339 opacity: 0.8;
3340 }
3341 .profile-feed-lead { margin-bottom: var(--gap-section); }
3342
3343 .purchase-page .checkout-footer {
3344 margin-top: var(--gap-page);
3345 padding-top: var(--gap-pane);
3346 border-top: 1px solid var(--border);
3347 opacity: 0.6;
3348 text-align: center;
3349 font-size: var(--text-note);
3350 }
3351
3352 .purchase-page .checkout-footer a { color: var(--content); }
3353
3354 @media (max-width: 599px) {
3355 .purchase-page .container { padding: 0 var(--gap-section); }
3356 .purchase-page .item-summary { flex-direction: column; }
3357 .purchase-page .item-thumbnail { width: 100%; height: 160px; }
3358 .purchase-page .checkout-box { padding: var(--gap-pane); }
3359 .purchase-page h2 { font-size: var(--text-head); }
3360 }
3361
3362 /* Feed page (templates/pages/feed.html). Compact tabular layout of
3363 items from followed users / projects / tags. */
3364 .feed-page .page-title { font-size: var(--text-head); margin-bottom: var(--gap-section); }
3365 .feed-page .feed-meta { font-size: var(--text-fine); opacity: 0.6; margin-bottom: var(--gap-section); }
3366
3367 .feed-page .table-header {
3368 display: grid;
3369 grid-template-columns: 50px 1fr 100px 70px 70px;
3370 gap: var(--gap-peer);
3371 padding: var(--gap-peer) var(--gap-section);
3372 background: var(--surface-overlay);
3373 font-size: var(--text-fine);
3374 opacity: 0.7;
3375 text-transform: uppercase;
3376 letter-spacing: 0.03em;
3377 }
3378
3379 .feed-page .col-right { text-align: right; }
3380
3381 .feed-page .results-table {
3382 border: 1px solid var(--border);
3383 border-top: none;
3384 }
3385
3386 .feed-page .table-row {
3387 /* respec-ok: a five-column grid template. The generated `.table-row` sets a
3388 display but has no vocabulary for column tracks, so the tracks and the
3389 display that carries them are stated together here. */
3390 display: grid;
3391 grid-template-columns: 50px 1fr 100px 70px 70px;
3392 gap: var(--gap-peer);
3393 padding: var(--gap-peer) var(--gap-section);
3394 align-items: center;
3395 text-decoration: none;
3396 color: var(--content);
3397 font-size: var(--text-note);
3398 border-bottom: 1px solid var(--border);
3399 transition: background 0.1s ease;
3400 }
3401
3402 .feed-page .table-row:last-child { border-bottom: none; }
3403 .feed-page .table-row:hover { background: var(--surface-sunken); }
3404
3405 .feed-page .item-name-cell {
3406 display: flex;
3407 flex-direction: column;
3408 gap: var(--gap-bound);
3409 min-width: 0;
3410 }
3411
3412 .feed-page .item-name {
3413 white-space: nowrap;
3414 overflow: hidden;
3415 text-overflow: ellipsis;
3416 }
3417
3418 .feed-page .item-creator { font-size: var(--text-fine); opacity: 0.5; }
3419
3420 /* The feed's badges shout a little: caps and tracking. The sunken fill that
3421 used to come with them went with the filled-badge form in B2. */
3422 .feed-page .badge {
3423 text-transform: uppercase;
3424 letter-spacing: 0.03em;
3425 }
3426
3427 .feed-page .empty-state {
3428 text-align: center;
3429 padding: var(--gap-page) var(--gap-pane);
3430 background: var(--surface-overlay);
3431 border: 1px solid var(--border);
3432 }
3433
3434 .feed-page .empty-state h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-peer); }
3435 .feed-page .empty-state p { opacity: 0.7; margin-bottom: var(--gap-pane); }
3436
3437 .feed-page .pagination {
3438 display: flex;
3439 gap: var(--gap-bound);
3440 justify-content: center;
3441 margin-top: var(--gap-section);
3442 }
3443
3444 .feed-page .pagination a,
3445 .feed-page .pagination span {
3446 padding: var(--gap-peer) var(--gap-section);
3447 font-size: var(--text-note);
3448 text-decoration: none;
3449 color: var(--content);
3450 border: 1px solid var(--border);
3451 }
3452
3453 .feed-page .pagination span.current {
3454 background: var(--primary-dark);
3455 color: var(--primary-light);
3456 border-color: var(--primary-dark);
3457 }
3458
3459 .feed-page .pagination a:hover { background: var(--surface-sunken); }
3460
3461 @media (max-width: 599px) {
3462 .feed-page .table-header,
3463 .feed-page .table-row { grid-template-columns: 1fr 70px; }
3464 .feed-page .table-header span:nth-child(1),
3465 .feed-page .table-row .badge:first-child,
3466 .feed-page .table-header span:nth-child(3),
3467 .feed-page .table-header span:nth-child(4),
3468 .feed-page .table-row span:nth-child(3),
3469 .feed-page .table-row span:nth-child(3),
3470 }
3471
3472 /* (Stripe Connect disclaimer page rules defined in the canonical
3473 STRIPE DISCLAIMER PAGE section near end of file.) */
3474
3475 /* User profile page (templates/pages/user.html). */
3476 .user-page .profile-header { text-align: center; margin-bottom: var(--gap-page); }
3477
3478 .user-page .profile-avatar {
3479 width: 120px;
3480 height: 120px;
3481 background: var(--surface-overlay);
3482 margin: 0 auto var(--gap-section);
3483 display: flex;
3484 align-items: center;
3485 justify-content: center;
3486 font-size: var(--text-hero);
3487 opacity: 0.5;
3488 }
3489
3490 .user-page .profile-name { font-size: var(--text-title); margin-bottom: var(--gap-peer); }
3491
3492 .user-page .profile-username {
3493 font-size: var(--text-note);
3494 opacity: 0.7;
3495 margin-bottom: var(--gap-section);
3496 }
3497
3498 .user-page .profile-bio {
3499 font-size: var(--text-body);
3500 max-width: 500px;
3501 margin: 0 auto;
3502 text-align: center;
3503 }
3504
3505 .user-page .links-section { margin-bottom: var(--gap-page); }
3506 .user-page .link-item { text-align: center; }
3507
3508 .user-page .link-title {
3509 font-size: var(--text-lead);
3510 margin-bottom: var(--gap-bound);
3511 font-family: var(--font-heading);
3512 font-weight: bold;
3513 }
3514
3515 .user-page .link-description { font-size: var(--text-note); opacity: 0.7; }
3516
3517 .user-page .project-title {
3518 font-size: var(--text-lead);
3519 margin-bottom: var(--gap-peer);
3520 font-family: var(--font-heading);
3521 font-weight: bold;
3522 }
3523
3524 .user-page .project-meta {
3525 font-size: var(--text-note);
3526 opacity: 0.7;
3527 margin-bottom: var(--gap-peer);
3528 }
3529
3530 .user-page .project-description { font-size: var(--text-note); opacity: 0.8; }
3531
3532 /* Creators / become-a-creator page (templates/pages/creators.html). */
3533 .creators-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
3534 .creators-page h2 { font-size: var(--text-head); margin-top: var(--gap-page); margin-bottom: var(--gap-section); }
3535
3536 .creators-page .how-it-works { margin-bottom: var(--gap-page); }
3537 .creators-page .how-it-works ol { padding-left: var(--gap-pane); line-height: 1.8; }
3538
3539 .creators-page .stats-row { gap: var(--gap-page); margin: var(--gap-page) 0; }
3540 .creators-page .stat-box { padding: var(--gap-pane); }
3541 .creators-page .stat-box .number { font-size: var(--text-title); }
3542 .creators-page .stat-box .label { font-size: var(--text-note); margin-top: var(--gap-bound); }
3543
3544 .creators-page .wave-table {
3545 width: 100%;
3546 border-collapse: collapse;
3547 margin: var(--gap-section) 0;
3548 }
3549
3550 .creators-page .wave-table th,
3551 .creators-page .wave-table td {
3552 padding: var(--gap-section);
3553 text-align: left;
3554 border-bottom: 1px solid var(--border);
3555 }
3556
3557 .creators-page .wave-table th {
3558 font-family: var(--font-mono);
3559 font-size: var(--text-note);
3560 opacity: 0.7;
3561 }
3562
3563 .creators-page .cta {
3564 margin-top: var(--gap-page);
3565 padding: var(--gap-pane);
3566 background: var(--surface-overlay);
3567 text-align: center;
3568 }
3569
3570 .creators-page .cta a { margin: 0 var(--gap-peer); }
3571
3572 /* Tag tree browse page (templates/pages/tag_tree.html). */
3573 .tag-tree-page .breadcrumbs {
3574 font-size: var(--text-note);
3575 margin-bottom: var(--gap-pane);
3576 opacity: 0.7;
3577 }
3578 .tag-tree-page .breadcrumbs a:hover { opacity: 0.6; }
3579 .tag-tree-page .breadcrumbs .sep { margin: 0 var(--gap-bound); opacity: 0.4; }
3580
3581 .tag-tree-page .tag-grid {
3582 display: grid;
3583 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
3584 gap: var(--gap-section);
3585 }
3586
3587 .tag-tree-page .tag-card {
3588 background: var(--surface-sunken);
3589 padding: var(--gap-section);
3590 text-decoration: none;
3591 color: var(--content);
3592 border: 1px solid var(--border);
3593 transition: background 0.1s ease;
3594 }
3595
3596 .tag-tree-page .tag-card:hover {
3597 background: var(--surface-sunken);
3598 text-decoration: none;
3599 }
3600
3601 .tag-tree-page .tag-card-name {
3602 font-family: var(--font-heading);
3603 font-weight: bold;
3604 font-size: var(--text-body);
3605 }
3606
3607 .tag-tree-page .tag-card-meta {
3608 font-size: var(--text-fine);
3609 opacity: 0.5;
3610 margin-top: var(--gap-bound);
3611 }
3612
3613 .tag-tree-page .empty-state {
3614 opacity: 0.5;
3615 font-size: var(--text-note);
3616 padding: var(--gap-page) 0;
3617 }
3618
3619 /* Confirm delete prompt (templates/pages/confirm_delete.html). Overrides
3620 the global .warning-box to use --danger-bg (matches the delete-account
3621 semantic). Also tokenized previously-broken fallbacks. */
3622 .confirm-delete-page .warning-box {
3623 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
3624 border: 1px solid var(--danger);
3625 border-radius: var(--radius-panel);
3626 padding: var(--gap-pane);
3627 margin-bottom: var(--gap-pane);
3628 }
3629
3630 .confirm-delete-page .warning-box p {
3631 margin: 0;
3632 color: var(--danger);
3633 }
3634
3635 /* Project paywall page (templates/pages/project_paywall.html). */
3636 .project-paywall-page .paywall-cover { margin-bottom: var(--gap-pane); }
3637 .project-paywall-page .paywall-cover img {
3638 width: 120px;
3639 height: 120px;
3640 border-radius: var(--radius-panel);
3641 object-fit: cover;
3642 }
3643
3644 .project-paywall-page .paywall-title {
3645 font-family: var(--font-heading);
3646 font-size: var(--text-hero);
3647 margin-bottom: var(--gap-peer);
3648 }
3649
3650 .project-paywall-page .paywall-creator {
3651 font-size: var(--text-body);
3652 opacity: 0.7;
3653 margin-bottom: var(--gap-pane);
3654 }
3655
3656 .project-paywall-page .paywall-creator a { color: var(--content); }
3657
3658 .project-paywall-page .paywall-description {
3659 font-size: var(--text-lead);
3660 max-width: 800px;
3661 margin: 0 auto var(--gap-page);
3662 text-align: left;
3663 }
3664
3665 .project-paywall-page .paywall-item-count {
3666 font-size: var(--text-body);
3667 opacity: 0.7;
3668 margin-bottom: var(--gap-peer);
3669 }
3670
3671 .project-paywall-page .paywall-tiers {
3672 display: grid;
3673 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
3674 gap: var(--gap-pane);
3675 margin-top: var(--gap-pane);
3676 text-align: left;
3677 }
3678
3679 .project-paywall-page .paywall-tiers .tier-card {
3680 background: var(--surface-overlay);
3681 padding: var(--gap-page);
3682 display: flex;
3683 flex-direction: column;
3684 }
3685
3686 .project-paywall-page .paywall-tiers .tier-card h3 {
3687 font-family: var(--font-heading);
3688 font-weight: bold;
3689 font-size: var(--text-subhead);
3690 margin-bottom: var(--gap-peer);
3691 }
3692
3693 .project-paywall-page .paywall-tiers .tier-price {
3694 font-size: var(--text-head);
3695 margin-bottom: var(--gap-section);
3696 }
3697
3698 .project-paywall-page .paywall-tiers .tier-card form,
3699 .project-paywall-page .paywall-tiers .tier-card .button { margin-top: auto; }
3700
3701 .project-paywall-page .paywall-tiers .tier-card button,
3702 .project-paywall-page .paywall-tiers .tier-card .button { width: 100%; }
3703
3704 /* Collection detail page (templates/pages/collection.html). */
3705 .collection-page .container { max-width: 700px; }
3706 .collection-page .collection-header { margin-bottom: var(--gap-page); }
3707 .collection-page .collection-title { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
3708
3709 .collection-page .collection-owner {
3710 font-size: var(--text-note);
3711 opacity: 0.7;
3712 margin-bottom: var(--gap-section);
3713 }
3714
3715 .collection-page .collection-description {
3716 font-size: var(--text-body);
3717 opacity: 0.85;
3718 margin-bottom: var(--gap-section);
3719 }
3720
3721 .collection-page .collection-meta { font-size: var(--text-note); opacity: 0.6; }
3722
3723 .collection-page .collection-item {
3724 display: flex;
3725 justify-content: space-between;
3726 align-items: flex-start;
3727 padding: var(--gap-section);
3728 background: var(--surface-overlay);
3729 margin-bottom: var(--gap-peer);
3730 }
3731
3732 .collection-page .collection-item-info { flex: 1; }
3733
3734 .collection-page .collection-item-title {
3735 font-family: var(--font-heading);
3736 font-weight: bold;
3737 font-size: var(--text-body);
3738 margin-bottom: var(--gap-bound);
3739 }
3740
3741 .collection-page .collection-item-title a { text-decoration: none; }
3742
3743 .collection-page .collection-item-meta { font-size: var(--text-note); opacity: 0.7; }
3744
3745 .collection-page .collection-item-price {
3746 font-size: var(--text-note);
3747 white-space: nowrap;
3748 margin-left: var(--gap-section);
3749 }
3750
3751 .collection-page .empty-state {
3752 text-align: center;
3753 padding: var(--gap-page) var(--gap-section);
3754 opacity: 0.6;
3755 }
3756
3757 /* Project blog index page (templates/pages/project_blog.html). */
3758 .project-blog-page .blog-header { margin-bottom: var(--gap-page); }
3759 .project-blog-page .blog-title { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
3760
3761 .project-blog-page .blog-meta {
3762 font-size: var(--text-note);
3763 opacity: 0.7;
3764 margin-bottom: var(--gap-pane);
3765 }
3766
3767 .project-blog-page .blog-meta a { color: var(--content); }
3768
3769 .project-blog-page .blog-actions {
3770 display: flex;
3771 gap: var(--gap-section);
3772 }
3773
3774 .project-blog-page .posts-list { max-width: 800px; }
3775
3776 .project-blog-page .post-entry {
3777 padding: var(--gap-pane) 0;
3778 border-bottom: 1px solid var(--border);
3779 }
3780
3781 .project-blog-page .post-entry:first-child { padding-top: 0; }
3782 .project-blog-page .post-title { font-size: var(--text-head); margin-bottom: var(--gap-peer); }
3783 .project-blog-page .post-title a:hover { text-decoration: underline; }
3784
3785 .project-blog-page .post-date {
3786 font-size: var(--text-note);
3787 opacity: 0.6;
3788 font-family: var(--font-mono);
3789 }
3790
3791 .project-blog-page .empty-state { padding: var(--gap-page) 0; opacity: 0.6; }
3792
3793 /* Canonical "page-level h1", left-aligned, smaller than the global centered
3794 default. Used by dashboards, admin, health, import, delete-account, export,
3795 receipt. Aliased via comma-grouped selectors so each page picks it up via
3796 its body class without redefining. */
3797 .dashboard-page h1,
3798 .admin-page h1,
3799 .health-page h1,
3800 .import-page h1,
3801 .delete-account-page h1,
3802 .export-page h1,
3803 .receipt-page h1 {
3804 font-size: var(--text-title);
3805 margin-bottom: var(--gap-peer);
3806 text-align: left;
3807 }
3808 .delete-account-page h1 { color: var(--danger); }
3809
3810 /* Canonical subtitle under a page h1, muted, with bottom space. */
3811 .health-page .subtitle,
3812 .import-page .subtitle,
3813 .delete-account-page .subtitle,
3814 .export-page .subtitle {
3815 opacity: 0.7;
3816 margin-bottom: var(--gap-page);
3817 text-align: left;
3818 }
3819 .health-page .subtitle { text-align: center; }
3820
3821 /* User Settings tab (templates/partials/tabs/user_settings.html).
3822 Left-rail nav layout for the Settings dashboard tab. Selection uses the
3823 canonical .is-selected modifier. */
3824 .settings-layout { display: flex; gap: var(--gap-page); }
3825 .settings-nav { flex-shrink: 0; min-width: 140px; }
3826 .settings-nav a {
3827 display: block;
3828 padding: var(--gap-peer) var(--gap-section);
3829 text-decoration: none;
3830 color: var(--content);
3831 opacity: 0.6;
3832 font-family: var(--font-mono);
3833 font-size: var(--text-note);
3834 transition: opacity 0.15s ease;
3835 }
3836 .settings-nav a:hover { opacity: 1; }
3837 .settings-nav a.is-selected { opacity: 1; background: var(--surface-sunken); }
3838 .settings-body { flex: 1; min-width: 0; }
3839 @media (max-width: 839px) {
3840 .settings-layout { flex-direction: column; gap: var(--gap-section); }
3841 .settings-nav { display: flex; flex-wrap: wrap; gap: 0; min-width: 0; }
3842 .settings-nav a { padding: var(--gap-peer) var(--gap-group); font-size: var(--text-note); }
3843 }.library-page .feed-table-header {
3844 display: grid;
3845 grid-template-columns: 50px 1fr 100px 70px 70px;
3846 gap: var(--gap-peer);
3847 padding: var(--gap-peer) var(--gap-section);
3848 background: var(--surface-overlay);
3849 font-size: var(--text-fine);
3850 opacity: 0.7;
3851 text-transform: uppercase;
3852 letter-spacing: 0.03em;
3853 }
3854 .library-page .feed-col-right { text-align: right; }
3855 .library-page .feed-results-table { border: 1px solid var(--border); border-top: none; }
3856 .library-page .feed-table-row {
3857 display: grid;
3858 grid-template-columns: 50px 1fr 100px 70px 70px;
3859 gap: var(--gap-peer);
3860 padding: var(--gap-peer) var(--gap-section);
3861 align-items: center;
3862 text-decoration: none;
3863 color: var(--content);
3864 font-size: var(--text-note);
3865 border-bottom: 1px solid var(--border);
3866 transition: background 0.1s ease;
3867 }
3868 .library-page .feed-table-row:last-child { border-bottom: none; }
3869 .library-page .feed-table-row:hover { background: var(--surface-sunken); }
3870 .library-page .feed-item-name-cell {
3871 display: flex;
3872 flex-direction: column;
3873 gap: var(--gap-bound);
3874 min-width: 0;
3875 }
3876 .library-page .feed-item-name {
3877 white-space: nowrap;
3878 overflow: hidden;
3879 text-overflow: ellipsis;
3880 }
3881 .library-page .feed-item-creator { font-size: var(--text-fine); opacity: 0.5; }
3882 .library-page .feed-pagination {
3883 display: flex;
3884 gap: var(--gap-bound);
3885 justify-content: center;
3886 margin-top: var(--gap-section);
3887 }
3888 .library-page .feed-pagination a,
3889 .library-page .feed-pagination span {
3890 padding: var(--gap-peer) var(--gap-section);
3891 font-size: var(--text-note);
3892 text-decoration: none;
3893 color: var(--content);
3894 border: 1px solid var(--border);
3895 }
3896 .library-page .feed-pagination span.current {
3897 background: var(--primary-dark);
3898 color: var(--primary-light);
3899 border-color: var(--primary-dark);
3900 }
3901 .library-page .feed-pagination a:hover { background: var(--surface-sunken); }
3902 @media (max-width: 599px) {
3903 .library-page .feed-table-header,
3904 .library-page .feed-table-row {
3905 grid-template-columns: 1fr 70px;
3906 }
3907 .library-page .feed-table-header span:nth-child(1),
3908 .library-page .feed-table-row .badge:first-child,
3909 .library-page .feed-table-header span:nth-child(3),
3910 .library-page .feed-table-header span:nth-child(4),
3911 .library-page .feed-table-row span:nth-child(3),
3912 .library-page .feed-table-row span:nth-child(4) {
3913 display: none;
3914 }
3915 }
3916
3917 /* User-level dashboard (templates/dashboards/dashboard-user.html).
3918 Scoped under .dashboard-user-page to avoid colliding with .project-card
3919 / .project-title / .project-meta on the public profile (user.html) and
3920 the user_projects tab partial. */
3921 .dashboard-user-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
3922
3923 .dashboard-user-page .project-card {
3924 background: var(--surface-overlay);
3925 padding: var(--gap-pane);
3926 margin-bottom: var(--gap-section);
3927 display: flex;
3928 justify-content: space-between;
3929 align-items: flex-start;
3930 }
3931
3932 .dashboard-user-page .project-info { flex: 1; }
3933
3934 .dashboard-user-page .project-title {
3935 font-family: var(--font-heading);
3936 font-weight: bold;
3937 font-size: var(--text-subhead);
3938 margin-bottom: var(--gap-bound);
3939 }
3940
3941 .dashboard-user-page .project-meta {
3942 font-size: var(--text-note);
3943 opacity: 0.7;
3944 margin-bottom: var(--gap-peer);
3945 }
3946
3947 .dashboard-user-page .project-stats { font-size: var(--text-note); }
3948 .dashboard-user-page .project-actions {
3949 display: flex;
3950 gap: var(--gap-peer);
3951 }
3952
3953 .dashboard-user-page .project-actions button {
3954 padding: var(--gap-peer) var(--gap-section);
3955 font-size: var(--text-note);
3956 }
3957 @media (max-width: 839px) {
3958 .dashboard-user-page .project-card { flex-direction: column; }
3959 .dashboard-user-page .project-actions { margin-top: var(--gap-section); }
3960 }
3961
3962 /* Project dashboard (templates/dashboards/dashboard-project.html). */
3963 .dashboard-project-page .project-meta { font-size: var(--text-note); opacity: 0.7; }
3964 .dashboard-project-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
3965
3966 .dashboard-project-page .content-header {
3967 display: flex;
3968 justify-content: space-between;
3969 align-items: center;
3970 margin-bottom: var(--gap-pane);
3971 }
3972
3973 .dashboard-project-page .content-header h2 { font-size: var(--text-subhead); }
3974
3975 .dashboard-project-page .item-thumbnail {
3976 width: 40px;
3977 height: 40px;
3978 background: var(--border);
3979 display: inline-block;
3980 vertical-align: middle;
3981 margin-right: var(--gap-peer);
3982 }
3983
3984 .dashboard-project-page .item-actions {
3985 display: flex;
3986 gap: var(--gap-peer);
3987 }
3988
3989 .dashboard-project-page .item-actions button {
3990 padding: var(--gap-peer) var(--gap-section);
3991 font-size: var(--text-note);
3992 }
3993
3994 .dashboard-project-page .data-section { margin-bottom: var(--gap-page); }
3995
3996 .dashboard-project-page .data-section h2 {
3997 font-size: var(--text-subhead);
3998 margin-bottom: var(--gap-section);
3999 padding-bottom: var(--gap-peer);
4000 border-bottom: 1px solid var(--border);
4001 }
4002
4003 /* Item dashboard (templates/dashboards/dashboard-item.html). */
4004 .dashboard-item-page .item-meta {
4005 font-size: var(--text-note);
4006 opacity: 0.7;
4007 display: flex;
4008 gap: var(--gap-section);
4009 align-items: center;
4010 }
4011
4012 .dashboard-item-page .stat-value { font-size: var(--text-head); }
4013
4014 .dashboard-item-page .section-header {
4015 display: flex;
4016 justify-content: space-between;
4017 align-items: center;
4018 margin-bottom: var(--gap-pane);
4019 padding-bottom: var(--gap-section);
4020 border-bottom: 1px solid var(--border);
4021 }
4022
4023 .dashboard-item-page .section-header h2 { font-size: var(--text-subhead); }
4024
4025 @media (max-width: 839px) {
4026 .dashboard-item-page .section-header {
4027 flex-direction: column;
4028 align-items: flex-start;
4029 gap: var(--gap-section);
4030 }
4031 .dashboard-item-page .section-header .action-buttons { width: 100%; }
4032 }
4033
4034 /* Changelog page (templates/pages/changelog.html). */
4035 .changelog-page .container { max-width: 900px; margin: 0 auto; }
4036 .changelog-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
4037 .changelog-page .changelog-entry {
4038 margin-bottom: var(--gap-page);
4039 padding-bottom: var(--gap-page);
4040 border-bottom: 1px solid var(--border);
4041 }
4042 .changelog-page .changelog-entry:last-child { border-bottom: none; }
4043 .changelog-page .changelog-date {
4044 font-family: var(--font-mono);
4045 font-size: var(--text-note);
4046 opacity: 0.6;
4047 margin-bottom: var(--gap-peer);
4048 }
4049 .changelog-page .changelog-entry h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
4050 .changelog-page .changelog-entry ul {
4051 padding-left: var(--gap-pane);
4052 margin: var(--gap-peer) 0;
4053 line-height: 1.7;
4054 }
4055 .changelog-page .changelog-entry li { margin-bottom: var(--gap-bound); }
4056
4057 /* Policy page (templates/pages/policy.html). */
4058 .policy-page .container { max-width: 900px; margin: 0 auto; }
4059 .policy-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
4060 .policy-page h2 {
4061 font-size: var(--text-head);
4062 margin-top: var(--gap-page);
4063 margin-bottom: var(--gap-section);
4064 }
4065 .policy-page .policy-section { margin-bottom: var(--gap-page); line-height: 1.7; }
4066 .policy-page .policy-section ul {
4067 padding-left: var(--gap-pane);
4068 margin: var(--gap-section) 0;
4069 }
4070 .policy-page .policy-section li { margin-bottom: var(--gap-bound); }
4071 .policy-page .contact {
4072 margin-top: var(--gap-page);
4073 padding: var(--gap-pane);
4074 background: var(--surface-overlay);
4075 }
4076
4077 /* Admin page extras (in addition to global .admin-page h1). */
4078 .admin-page .metrics-grid {
4079 display: grid;
4080 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
4081 gap: var(--gap-section);
4082 margin-bottom: var(--gap-page);
4083 }
4084
4085 .admin-page .metric-card {
4086 padding: var(--gap-section);
4087 border: 1px solid var(--border);
4088 border-radius: var(--radius-square);
4089 }
4090
4091 .admin-page .metric-card .value {
4092 font-size: var(--text-title);
4093 font-family: var(--font-mono);
4094 font-weight: 600;
4095 }
4096
4097 .admin-page .metric-card .label {
4098 font-size: var(--text-note);
4099 color: var(--content-muted);
4100 margin-top: var(--gap-bound);
4101 }
4102
4103 .admin-page .metric-card.warn .value { color: var(--warning); }
4104 .admin-page .metric-card.error .value { color: var(--danger); }
4105
4106 .admin-page .section-heading {
4107 font-size: var(--text-subhead);
4108 margin: var(--gap-pane) 0 var(--gap-section);
4109 font-family: var(--font-mono);
4110 }
4111
4112 /* Scan pipeline dashboard. See docs/scan-pipeline-audit.md § 5. */
4113
4114 .admin-scan-section {
4115 margin-top: var(--gap-page);
4116 }
4117
4118 .admin-section-header {
4119 display: flex;
4120 justify-content: space-between;
4121 align-items: baseline;
4122 margin-bottom: var(--gap-section);
4123 gap: var(--gap-section);
4124 }
4125
4126 /* Typeface comes from .subsection-title on the heading itself; this sets only
4127 what the flex row needs. */
4128 .admin-section-header h2 {
4129 font-size: var(--text-lead);
4130 margin: 0;
4131 }
4132
4133 .layer-health-grid {
4134 display: grid;
4135 grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
4136 gap: var(--gap-section);
4137 }
4138
4139 .layer-health-card {
4140 padding: var(--gap-section);
4141 border: 1px solid var(--border);
4142 border-radius: var(--radius-square);
4143 border-left-width: 4px;
4144 font-size: var(--text-note);
4145 }
4146
4147 .layer-health-card .layer-name {
4148 font-family: var(--font-mono);
4149 font-weight: 600;
4150 font-size: var(--text-body);
4151 margin-bottom: var(--gap-peer);
4152 }
4153
4154 .layer-health-card .layer-stat {
4155 display: flex;
4156 justify-content: space-between;
4157 margin-bottom: 2px;
4158 }
4159
4160 .layer-health-card .layer-stat .dimmed {
4161 color: var(--content-muted);
4162 }
4163
4164 .layer-health-card.layer-ok { border-left-color: var(--success); }
4165 .layer-health-card.layer-degraded { border-left-color: var(--warning); }
4166 .layer-health-card.layer-down { border-left-color: var(--danger); }
4167
4168 .layer-chips {
4169 display: flex;
4170 flex-wrap: wrap;
4171 gap: 4px;
4172 max-width: 360px;
4173 }
4174
4175 .layer-chip {
4176 display: inline-block;
4177 font-family: var(--font-mono);
4178 font-size: var(--text-fine);
4179 padding: 1px 6px;
4180 border-radius: var(--radius-control);
4181 border: 1px solid transparent;
4182 cursor: help;
4183 line-height: 1.4;
4184 }
4185
4186 .layer-chip-pass { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); border-color: var(--success); color: var(--success); }
4187 .layer-chip-skip { background: var(--surface-page); border-color: var(--border); color: var(--content-muted); }
4188 .layer-chip-fail { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); border-color: var(--danger); color: var(--danger); font-weight: 600; }
4189 .layer-chip-error { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); border-color: var(--warning); color: var(--warning); font-weight: 600; }
4190
4191 .scan-history-grid table { font-size: var(--text-note); }
4192
4193 .scan-history-grid summary {
4194 cursor: pointer;
4195 font-family: var(--font-mono);
4196 padding: var(--gap-peer) 0;
4197 user-select: none;
4198 }
4199
4200 .scan-row-meta {
4201 font-size: var(--text-fine);
4202 color: var(--content-muted);
4203 margin-top: 2px;
4204 }
4205
4206 .audit-filter-form {
4207 display: flex;
4208 flex-wrap: wrap;
4209 align-items: flex-end;
4210 gap: var(--gap-section);
4211 padding: var(--gap-section) 0;
4212 margin-bottom: var(--gap-section);
4213 border-bottom: 1px solid var(--border);
4214 }
4215
4216 .audit-filter-form label {
4217 display: flex;
4218 flex-direction: column;
4219 gap: 2px;
4220 }
4221
4222 .audit-filter-form input,
4223 .audit-filter-form select {
4224 font-size: var(--text-note);
4225 }
4226
4227 .layer-chips-cell { vertical-align: top; }
4228
4229 .layer-detail-disclosure {
4230 margin-top: var(--gap-peer);
4231 }
4232
4233 .layer-detail-disclosure summary {
4234 cursor: pointer;
4235 user-select: none;
4236 padding: 2px 0;
4237 }
4238
4239 .layer-detail-list {
4240 margin: var(--gap-peer) 0 0;
4241 display: grid;
4242 grid-template-columns: max-content 1fr;
4243 gap: 2px var(--gap-section);
4244 font-size: var(--text-fine);
4245 }
4246
4247 .layer-detail-list dt {
4248 font-family: var(--font-mono);
4249 }
4250
4251 .layer-detail-list dd {
4252 margin: 0;
4253 color: var(--content-muted);
4254 word-break: break-word;
4255 }
4256
4257 .admin-page table {
4258 width: 100%;
4259 border-collapse: collapse;
4260 font-size: var(--text-note);
4261 }
4262
4263 .admin-page th,
4264 .admin-page td {
4265 text-align: left;
4266 padding: var(--gap-peer) var(--gap-group);
4267 border-bottom: 1px solid var(--border);
4268 }
4269
4270 .admin-page th {
4271 font-family: var(--font-mono);
4272 font-size: var(--text-fine);
4273 text-transform: uppercase;
4274 color: var(--content-muted);
4275 }
4276
4277 .admin-page td.mono { font-family: var(--font-mono); }
4278
4279 .admin-page .refresh-note {
4280 font-size: var(--text-fine);
4281 color: var(--content-muted);
4282 margin-bottom: var(--gap-section);
4283 font-family: var(--font-mono);
4284 }
4285
4286 .admin-page .appeal-count {
4287 font-size: var(--text-note);
4288 opacity: 0.7;
4289 margin-bottom: var(--gap-pane);
4290 }
4291
4292 .admin-page .decide-form {
4293 display: flex;
4294 gap: var(--gap-peer);
4295 align-items: flex-end;
4296 flex-wrap: wrap;
4297 }
4298
4299 .admin-page .decide-form textarea {
4300 min-width: 200px;
4301 font-size: var(--text-note);
4302 }
4303
4304 .admin-page .suspend-form {
4305 display: flex;
4306 gap: var(--gap-peer);
4307 align-items: flex-end;
4308 }
4309
4310 .admin-page .suspend-form input[type="text"] {
4311 min-width: 200px;
4312 font-size: var(--text-note);
4313 }
4314
4315 .admin-page .lottery-form {
4316 background: var(--surface-overlay);
4317 padding: var(--gap-pane);
4318 margin: var(--gap-pane) 0;
4319 display: flex;
4320 gap: var(--gap-section);
4321 align-items: flex-end;
4322 }
4323
4324 .admin-page .lottery-form .form-group { margin-bottom: 0; }
4325
4326 .admin-page .lottery-form label {
4327 font-size: var(--text-note);
4328 display: block;
4329 margin-bottom: var(--gap-bound);
4330 }
4331
4332 .admin-page .lottery-form input[type="number"] { width: 80px; }
4333
4334 /* Page-top notice (charter primitive, replaces #restart-banner and
4335 the inline sandbox banner in dashboard-project). Full-bleed across
4336 the viewport, monospace, one short sentence + optional action.
4337 Hero callouts on the landing page use `.alert` instead (multi-paragraph). */
4338 .banner {
4339 padding: var(--gap-section) var(--gap-pane);
4340 font-family: var(--font-mono);
4341 font-size: var(--text-note);
4342 text-align: center;
4343 color: var(--content);
4344 }
4345
4346 .banner--info {
4347 background: var(--highlight-faint);
4348 border-bottom: 2px solid var(--focus-ring);
4349 }
4350
4351 .banner--warning {
4352 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
4353 border-bottom: 2px solid var(--warning);
4354 }
4355
4356 .banner a {
4357 color: inherit;
4358 text-decoration: underline;
4359 }
4360
4361 /* ===========================================
4362 SECTIONS
4363 =========================================== */
4364
4365 .section-header {
4366 font-size: var(--text-head);
4367 font-weight: normal;
4368 margin-bottom: var(--gap-section);
4369 padding-bottom: var(--gap-peer);
4370 border-bottom: 1px solid var(--border);
4371 font-family: var(--font-mono);
4372 }
4373
4374 /* Canonical "page section box", light-background card with padding + bottom
4375 space. One of these in the codebase. Aliased to the per-page class names
4376 that were just relabels of this same shape. Templates can use the alias
4377 name OR add `.content-section` as a co-class. */
4378 /* The canonical panel. Every one of these carries `.raised` in the markup
4379 now, so the fill and the bevel come from layout.css and this rule is the
4380 spacing only. The background it used to declare was --surface-overlay,
4381 which .raised spells --surface-raised; equal on the platform default and a
4382 theme may set them apart, the same move .card and .buy-card made. */
4383 .content-section,
4384 .import-page .import-form,
4385 .import-page .import-progress,
4386 .library-page .library-section,
4387 .library-page .library-downloads,
4388 .library-page .downloads-hero,
4389 .receipt-page .receipt-box,
4390 .stripe-disclaimer-page .disclaimer-box,
4391 .project-page .project-sections {
4392 padding: var(--gap-page);
4393 margin-bottom: var(--gap-page);
4394 }
4395
4396 /* Variants of the canonical shape that want top-spacing instead of bottom,
4397 or a tighter --gap-pane padding. */
4398 .library-page .library-downloads,
4399 .receipt-page .receipt-box { margin-bottom: 0; margin-top: var(--gap-pane); }
4400 .import-page .import-progress { padding: var(--gap-pane); margin-bottom: var(--gap-section); }
4401 .stripe-disclaimer-page .disclaimer-box { margin-bottom: var(--gap-pane); }
4402
4403 .form-section {
4404 margin-bottom: var(--gap-page);
4405 }
4406
4407 .section-group-label {
4408 font-family: var(--font-mono);
4409 font-size: var(--text-fine);
4410 font-weight: bold;
4411 text-transform: uppercase;
4412 letter-spacing: 0.05em;
4413 color: var(--content-muted);
4414 margin: var(--gap-page) 0 var(--gap-section) 0;
4415 padding-top: var(--gap-pane);
4416 border-top: 2px solid var(--border);
4417 }
4418
4419 .form-section h2 {
4420 font-size: var(--text-subhead);
4421 font-weight: normal;
4422 margin-bottom: var(--gap-section);
4423 padding-bottom: var(--gap-peer);
4424 border-bottom: 1px solid var(--border);
4425 }
4426
4427 details.form-section > summary {
4428 cursor: pointer;
4429 list-style: disclosure-closed;
4430 }
4431
4432 details.form-section[open] > summary {
4433 list-style: disclosure-open;
4434 margin-bottom: var(--gap-section);
4435 }
4436
4437 details.form-section > summary > h2 {
4438 display: inline;
4439 }
4440
4441 details.form-section > summary:hover > h2 {
4442 color: var(--content);
4443 }
4444
4445 /* ===========================================
4446 NAVIGATION
4447 =========================================== */
4448
4449 nav {
4450 margin-bottom: var(--gap-page);
4451 }
4452
4453 .nav-links {
4454 display: flex;
4455 gap: var(--gap-page);
4456 flex-wrap: wrap;
4457 }
4458
4459 .nav-links a {
4460 color: var(--content);
4461 text-decoration: none;
4462 font-family: var(--font-mono);
4463 transition: opacity 0.2s ease;
4464 }
4465
4466 .nav-links a:hover {
4467 opacity: 0.6;
4468 }
4469
4470 .shortcuts-help-btn {
4471 font-family: var(--font-mono);
4472 color: var(--content-muted);
4473 font-size: var(--text-note);
4474 width: 1.5rem;
4475 height: 1.5rem;
4476 border: 1px solid var(--border);
4477 border-radius: var(--radius-control);
4478 display: inline-flex;
4479 align-items: center;
4480 justify-content: center;
4481 cursor: pointer;
4482 transition: opacity 0.2s ease;
4483 padding: 0;
4484 background: none;
4485 }
4486
4487 .shortcuts-help-btn:hover {
4488 opacity: 0.6;
4489 }
4490
4491 .breadcrumb {
4492 font-size: var(--text-note);
4493 color: var(--content-muted);
4494 margin-bottom: var(--gap-peer);
4495 font-family: var(--font-mono);
4496 }
4497
4498 .breadcrumb a {
4499 color: var(--content);
4500 text-decoration: none;
4501 }
4502
4503 .breadcrumb a:hover {
4504 text-decoration: underline;
4505 }
4506
4507 /* ===========================================
4508 HEADER & FOOTER
4509 =========================================== */
4510
4511 header {
4512 background: transparent;
4513 border-bottom: none;
4514 padding: 0;
4515 margin-bottom: var(--gap-page);
4516 }
4517
4518 footer {
4519 text-align: center;
4520 font-family: var(--font-mono);
4521 color: var(--content-muted);
4522 }
4523
4524 .profile-footer {
4525 margin-top: var(--gap-page);
4526 padding-top: var(--gap-pane);
4527 border-top: 1px solid var(--border);
4528 text-align: center;
4529 opacity: 0.6;
4530 font-size: var(--text-note);
4531 }
4532
4533 .profile-footer a {
4534 color: var(--content);
4535 text-decoration: none;
4536 }
4537
4538 .profile-footer a:hover {
4539 text-decoration: underline;
4540 }
4541
4542 /* Site-wide header */
4543 .site-header {
4544 display: flex;
4545 justify-content: space-between;
4546 align-items: center;
4547 padding: var(--gap-section) var(--gap-pane);
4548 margin-bottom: var(--gap-pane);
4549 }
4550
4551 .site-header nav {
4552 margin: 0;
4553 }
4554
4555 .site-header .nav-links {
4556 gap: var(--gap-pane);
4557 }
4558
4559 .site-logo {
4560 font-family: var(--font-heading);
4561 font-size: var(--text-subhead);
4562 text-decoration: none;
4563 color: var(--content);
4564 }
4565
4566 .site-logo:hover {
4567 opacity: 0.8;
4568 }
4569
4570 .nav-form {
4571 display: contents;
4572 }
4573
4574 /* Header search */
4575 .header-search {
4576 flex: 0 1 220px;
4577 margin: 0 var(--gap-section);
4578 }
4579
4580 .header-search input {
4581 width: 100%;
4582 padding: var(--gap-peer) var(--gap-group);
4583 font-size: var(--text-note);
4584 font-family: var(--font-mono);
4585 border: 1px solid var(--border);
4586 background: var(--surface-page);
4587 color: var(--content);
4588 border-radius: var(--radius-control);
4589 }
4590
4591 .header-search input:focus {
4592 outline: 2px solid var(--focus-ring);
4593 outline-offset: -1px;
4594 }
4595
4596 /* Hamburger toggle, hidden by default, shown at 768px */
4597 .nav-toggle-checkbox {
4598 display: none;
4599 }
4600
4601 .nav-toggle-label {
4602 display: none;
4603 cursor: pointer;
4604 padding: var(--gap-peer);
4605 z-index: var(--z-header);
4606 }
4607
4608 .nav-toggle-label span {
4609 display: block;
4610 width: 22px;
4611 height: 2px;
4612 background: var(--content);
4613 margin: 5px 0;
4614 transition: transform 0.2s ease, opacity 0.2s ease;
4615 }
4616
4617 /* ===========================================
4618 LAYOUT UTILITIES
4619 =========================================== */
4620
4621 .action-buttons {
4622 display: flex;
4623 gap: var(--gap-section);
4624 flex-wrap: wrap;
4625 }
4626 /* ===========================================
4627 SPECIFIC COMPONENTS
4628 =========================================== */
4629
4630 /* Login container */
4631 .login-container {
4632 display: flex;
4633 flex-direction: column;
4634 max-width: 400px;
4635 width: 100%;
4636 margin: 0 auto;
4637 background: var(--surface-overlay);
4638 padding: var(--gap-page);
4639 }
4640 /* Logo */
4641 .logo {
4642 font-family: var(--font-heading);
4643 color: var(--content);
4644 font-size: var(--text-head);
4645 }
4646
4647 .tagline {
4648 font-family: var(--font-mono);
4649 color: var(--content-muted);
4650 text-align: center;
4651 }
4652
4653 .centered-page .tagline {
4654 margin-bottom: var(--gap-bound);
4655 }
4656 /* Skip link */
4657 .skip-link {
4658 text-align: center;
4659 margin-top: var(--gap-section);
4660 }
4661
4662 .skip-link a {
4663 color: var(--primary-dark);
4664 text-decoration: none;
4665 font-size: var(--text-note);
4666 opacity: 0.7;
4667 }
4668
4669 .skip-link a:hover {
4670 text-decoration: underline;
4671 }
4672
4673 /* Footer link */
4674 .foot-link {
4675 margin-top: 0.5em;
4676 }
4677
4678 /* Status indicators */
4679 .status-indicator {
4680 display: inline-block;
4681 width: 8px;
4682 height: 8px;
4683 background: var(--primary-dark);
4684 margin-right: var(--gap-peer);
4685 }
4686
4687 .status-indicator.active {
4688 background: var(--success);
4689 }
4690
4691 /* Amount styling */
4692 .amount-in {
4693 color: var(--success);
4694 }
4695
4696 .amount-out {
4697 opacity: 0.7;
4698 }
4699
4700 /* File upload */
4701 .file-upload-area {
4702 background: var(--surface-sunken);
4703 padding: var(--gap-page);
4704 text-align: center;
4705 cursor: pointer;
4706 transition: background 0.2s ease;
4707 margin-bottom: var(--gap-section);
4708 }
4709
4710 .file-upload-area:hover,
4711 .file-upload-area.dragover {
4712 background: var(--border);
4713 }
4714 /* Chart placeholder */
4715 .chart-container {
4716 background: var(--surface-sunken);
4717 padding: var(--gap-pane);
4718 margin-bottom: var(--gap-pane);
4719 }
4720
4721 .chart-header {
4722 display: flex;
4723 justify-content: space-between;
4724 align-items: center;
4725 margin-bottom: var(--gap-section);
4726 }
4727
4728 .chart-header h2 {
4729 font-size: var(--text-subhead);
4730 font-weight: normal;
4731 }
4732
4733 .chart-bars {
4734 display: flex;
4735 align-items: flex-end;
4736 gap: 2px;
4737 height: 200px;
4738 padding: 0 var(--gap-peer);
4739 }
4740
4741 .chart-bar-col {
4742 flex: 1;
4743 display: flex;
4744 flex-direction: column;
4745 align-items: center;
4746 min-width: 0;
4747 cursor: default;
4748 position: relative;
4749 }
4750
4751 .chart-bar-col[data-tooltip]:hover::before {
4752 content: attr(data-tooltip);
4753 position: absolute;
4754 bottom: 100%;
4755 left: 50%;
4756 transform: translateX(-50%);
4757 background: var(--primary-dark);
4758 color: var(--primary-light);
4759 font-size: var(--text-fine);
4760 font-family: var(--font-mono);
4761 padding: var(--gap-bound) var(--gap-peer);
4762 white-space: nowrap;
4763 z-index: var(--z-dropdown);
4764 pointer-events: none;
4765 margin-bottom: var(--gap-bound);
4766 }
4767
4768 .chart-bar {
4769 width: 100%;
4770 height: var(--fill, 0%);
4771 background: var(--primary-dark);
4772 min-height: 2px;
4773 transition: opacity 0.15s;
4774 }
4775
4776 .chart-bar-col:hover .chart-bar {
4777 opacity: 0.8;
4778 }
4779
4780 .chart-bar-label {
4781 font-size: var(--text-fine);
4782 opacity: 0.5;
4783 margin-top: var(--gap-bound);
4784 white-space: nowrap;
4785 overflow: hidden;
4786 text-overflow: ellipsis;
4787 max-width: 100%;
4788 }
4789
4790 /* Time selector */
4791 .time-selector {
4792 display: flex;
4793 gap: var(--gap-peer);
4794 }
4795
4796 /* The range buttons are makeover's `.segment`, which is what a segmented
4797 control is called there, and B3's question about whether this codebase has
4798 an analogue: it does, and this is it. Fill, bevel, hover, active and focus
4799 are the generated ones; this rule is geometry and type.
4800
4801 Selection was a --primary-dark fill with a --primary-light label, which is
4802 a second thing meaning "chosen" beside the generated `.chosen` (the segment
4803 presses IN — well fill, inset bevel). Taking the generated one; only the
4804 name stays local, same as the tabs. */
4805 .time-selector button {
4806 border: none;
4807 padding: var(--gap-peer) var(--gap-section);
4808 font-family: inherit;
4809 font-size: var(--text-note);
4810 cursor: pointer;
4811 }
4812
4813 .time-selector button.is-selected {
4814 background: var(--surface-well, var(--surface-page));
4815 box-shadow: var(--bevel-inset);
4816 }
4817
4818 /* Analytics */
4819 .analytics-grid {
4820 display: grid;
4821 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4822 gap: var(--gap-pane);
4823 margin-bottom: var(--gap-pane);
4824 }
4825 .analytics-list {
4826 list-style: none;
4827 }
4828
4829 .analytics-list li {
4830 padding: var(--gap-peer) 0;
4831 border-bottom: 1px solid var(--border);
4832 display: flex;
4833 justify-content: space-between;
4834 }
4835
4836 .analytics-list li:last-child {
4837 border-bottom: none;
4838 }
4839
4840 /* Quick actions */
4841 .quick-actions {
4842 background: var(--surface-sunken);
4843 padding: var(--gap-pane);
4844 }
4845
4846 .quick-actions h2 {
4847 font-size: var(--text-subhead);
4848 font-weight: normal;
4849 margin-bottom: var(--gap-section);
4850 }
4851
4852 /* Filter controls */
4853 .filter-controls {
4854 display: flex;
4855 gap: var(--gap-section);
4856 margin-bottom: var(--gap-section);
4857 flex-wrap: wrap;
4858 align-items: center;
4859 }
4860
4861 .filter-group {
4862 display: flex;
4863 gap: var(--gap-peer);
4864 align-items: center;
4865 }
4866 /* UUID styling */
4867 .uuid {
4868 font-family: inherit;
4869 opacity: 0.5;
4870 font-size: var(--text-note);
4871 }
4872 /* ===========================================
4873 HTMX LOADING INDICATORS
4874 =========================================== */
4875
4876 .htmx-indicator {
4877 display: none;
4878 animation: htmx-spin 1s ease-in-out infinite;
4879 }
4880
4881 .htmx-request .htmx-indicator {
4882 display: inline;
4883 }
4884
4885 .htmx-request.htmx-indicator {
4886 display: inline;
4887 }
4888
4889 /* Loading spinner animation. Applied on .htmx-indicator above. */
4890 @keyframes htmx-spin {
4891 0% { opacity: 0.3; }
4892 50% { opacity: 1; }
4893 100% { opacity: 0.3; }
4894 }
4895
4896 /* Disable buttons during request */
4897 .htmx-request button[type="submit"] {
4898 opacity: 0.6;
4899 cursor: wait;
4900 }
4901
4902 /* ===========================================
4903 TOAST NOTIFICATIONS
4904 =========================================== */
4905
4906 .toast-container {
4907 position: fixed;
4908 bottom: 1.5rem;
4909 right: 1.5rem;
4910 z-index: var(--z-toast);
4911 display: flex;
4912 flex-direction: column;
4913 gap: var(--gap-peer);
4914 }
4915
4916 .toast {
4917 background: var(--primary-dark);
4918 color: var(--primary-light);
4919 padding: var(--gap-section);
4920 font-family: var(--font-mono);
4921 font-size: var(--text-note);
4922 box-shadow: var(--elevation-overlay);
4923 animation: toast-in 0.3s ease-out;
4924 max-width: 300px;
4925 display: flex;
4926 align-items: center;
4927 gap: var(--gap-section);
4928 }
4929
4930 .toast-dismiss {
4931 background: none;
4932 border: none;
4933 color: inherit;
4934 font-size: var(--text-lead);
4935 cursor: pointer;
4936 opacity: 0.7;
4937 padding: 0;
4938 line-height: 1;
4939 flex-shrink: 0;
4940 }
4941
4942 .toast-dismiss:hover {
4943 opacity: 1;
4944 }
4945
4946 .toast-success {
4947 background: var(--success);
4948 }
4949
4950 .toast-error {
4951 background: var(--danger);
4952 }
4953
4954 .toast-warning {
4955 background: var(--warning);
4956 }
4957
4958 .toast-info {
4959 background: var(--primary-dark);
4960 }
4961
4962 .toast-retry-btn {
4963 background: none;
4964 border: 1px solid currentColor;
4965 color: inherit;
4966 font-family: var(--font-mono);
4967 font-size: var(--text-fine);
4968 font-weight: 600;
4969 padding: var(--gap-bound) var(--gap-peer);
4970 margin-left: var(--gap-section);
4971 cursor: pointer;
4972 }
4973
4974 .toast-retry-btn:hover {
4975 background: color-mix(in oklch, var(--primary-light) 15%, transparent);
4976 }
4977
4978 .toast.fade-out {
4979 animation: toast-out 0.3s ease-in forwards;
4980 }
4981
4982 @keyframes toast-in {
4983 from {
4984 transform: translateX(100%);
4985 opacity: 0;
4986 }
4987 to {
4988 transform: translateX(0);
4989 opacity: 1;
4990 }
4991 }
4992
4993 @keyframes toast-out {
4994 from {
4995 transform: translateX(0);
4996 opacity: 1;
4997 }
4998 to {
4999 transform: translateX(100%);
5000 opacity: 0;
5001 }
5002 }
5003
5004 /* ===========================================
5005 "NEW" BADGE (data-new-until)
5006 ===========================================
5007 Applied by whats_new.js while today's date is <= the element's
5008 data-new-until. Renders a small violet dot to the right of the
5009 element's content. Pair with relative-positioned parents. */
5010
5011 .is-new {
5012 position: relative;
5013 }
5014
5015 .is-new::after {
5016 content: "";
5017 position: absolute;
5018 top: -2px;
5019 right: -8px;
5020 width: 6px;
5021 height: 6px;
5022 border-radius: var(--radius-round);
5023 background: var(--action);
5024 box-shadow: 0 0 0 2px var(--surface-page);
5025 }
5026
5027 /* ===========================================
5028 RESTART WARNING BANNER
5029 =========================================== */
5030
5031 /* Page-top behavior extensions on top of .banner.banner--warning.
5032 Color / typography come from the .banner primitive. */
5033 #restart-banner {
5034 position: fixed;
5035 top: 0;
5036 left: 0;
5037 right: 0;
5038 z-index: var(--z-banner);
5039 padding: var(--gap-group) var(--gap-section);
5040 animation: banner-slide-down 0.3s ease-out;
5041 }
5042
5043 @keyframes banner-slide-down {
5044 from {
5045 transform: translateY(-100%);
5046 }
5047 to {
5048 transform: translateY(0);
5049 }
5050 }
5051
5052 /* ===========================================
5053 SAVE STATUS INDICATORS
5054 =========================================== */
5055
5056 /* Username availability status */
5057 .username-status {
5058 display: block;
5059 margin-top: var(--gap-bound);
5060 font-size: var(--text-note);
5061 }
5062
5063 .username-status.available {
5064 color: var(--success);
5065 }
5066
5067 .username-status.taken {
5068 color: var(--danger);
5069 }
5070
5071 /* ===========================================
5072 HTMX TRANSITIONS
5073 =========================================== */
5074
5075 /* Fade in for swapped content */
5076 .htmx-added {
5077 animation: htmx-fade-in 0.2s ease-out;
5078 }
5079
5080 @keyframes htmx-fade-in {
5081 from {
5082 opacity: 0;
5083 }
5084 to {
5085 opacity: 1;
5086 }
5087 }
5088
5089 /* Settling animation for swapped content */
5090 .htmx-settling {
5091 opacity: 0.8;
5092 }
5093
5094 /* Swap animation */
5095 .htmx-swapping {
5096 opacity: 0.5;
5097 transition: opacity 0.2s ease-out;
5098 }
5099
5100 /* ===========================================
5101 ACCESSIBILITY - FOCUS STYLES
5102 =========================================== */
5103
5104 /* Focus-visible for keyboard navigation */
5105 *:focus-visible {
5106 outline: 2px solid var(--focus-ring);
5107 outline-offset: 2px;
5108 }
5109
5110 /* Remove default focus outline since we use focus-visible */
5111 *:focus:not(:focus-visible) {
5112 outline: none;
5113 }
5114
5115 /* Ensure buttons and links are keyboard accessible */
5116 button:focus-visible,
5117 a:focus-visible,
5118 input:focus-visible,
5119 select:focus-visible,
5120 textarea:focus-visible {
5121 outline: 2px solid var(--focus-ring);
5122 outline-offset: 2px;
5123 }
5124
5125 /* Tab focus is layout.css's, same ring as everything else at outline-offset
5126 2px. The local rule inset it to -2px, which is only worth doing where a
5127 strip clips its overflow; `.tabs` is flex/nowrap with no overflow set, so
5128 there was nothing to clip and the tabs read differently from every other
5129 focusable thing on the page for no reason. */
5130
5131 /* Canonical "selected" recipe (charter: docs/design-system.md).
5132 Apply `.is-selected` to any interactive container to mark it as the
5133 currently-selected option. A badge never takes it: a badge reports a status
5134 and nobody selects "Enabled", so badges take a `data-tone` instead.
5135 Visibility toggles (`.tab-content.active`, `.section-panel.active`,
5136 `.editor-panel.active`) also keep `.active` since they're not selection
5137 state. */
5138 .is-selected {
5139 background: var(--highlight-faint);
5140 border-color: var(--focus-ring);
5141 }
5142
5143 /* Skip to main content link (for screen readers) */
5144 .skip-to-main {
5145 position: absolute;
5146 top: -100px;
5147 left: 0;
5148 background: var(--primary-dark);
5149 color: var(--primary-light);
5150 padding: 8px;
5151 z-index: var(--z-skip-link);
5152 transition: top 0.2s ease;
5153 }
5154
5155 .skip-to-main:focus {
5156 top: 0;
5157 }
5158
5159 /* Visually hidden but accessible to screen readers */
5160 .sr-only {
5161 position: absolute;
5162 width: 1px;
5163 height: 1px;
5164 padding: 0;
5165 margin: -1px;
5166 overflow: hidden;
5167 clip: rect(0, 0, 0, 0);
5168 white-space: nowrap;
5169 border: 0;
5170 }
5171
5172 /* Status indicator with text for accessibility */
5173 .status-indicator::after {
5174 content: attr(data-status);
5175 position: absolute;
5176 width: 1px;
5177 height: 1px;
5178 padding: 0;
5179 margin: -1px;
5180 overflow: hidden;
5181 clip: rect(0, 0, 0, 0);
5182 white-space: nowrap;
5183 border: 0;
5184 }
5185
5186 /* ===========================================
5187 PAYWALL
5188 =========================================== */
5189
5190 .paywall-section {
5191 margin: var(--gap-page) 0;
5192 padding: var(--gap-page);
5193 background: var(--surface-sunken);
5194 text-align: center;
5195 }
5196
5197 .paywall-box h2 {
5198 font-family: var(--font-heading);
5199 font-weight: normal;
5200 margin-bottom: var(--gap-peer);
5201 }
5202
5203 .paywall-price {
5204 font-family: var(--font-mono);
5205 font-size: var(--text-head);
5206 margin-bottom: var(--gap-peer);
5207 }
5208
5209 .paywall-desc {
5210 font-family: var(--font-body);
5211 color: var(--content-muted);
5212 margin-bottom: var(--gap-pane);
5213 }
5214
5215 .paywall-note {
5216 font-family: var(--font-mono);
5217 font-size: var(--text-note);
5218 color: var(--content-muted);
5219 margin-top: var(--gap-section);
5220 }
5221
5222 .paywall-note a {
5223 color: var(--action);
5224 }
5225
5226 /* ===========================================
5227 MODALS
5228 =========================================== */
5229
5230 .modal-overlay {
5231 position: fixed;
5232 inset: 0;
5233 background: var(--overlay);
5234 display: flex;
5235 align-items: center;
5236 justify-content: center;
5237 z-index: var(--z-modal);
5238 }
5239
5240 .modal {
5241 background: var(--surface-page);
5242 padding: var(--gap-page);
5243 max-width: 500px;
5244 width: 90%;
5245 max-height: 90vh;
5246 overflow-y: auto;
5247 }
5248
5249 .modal h2 {
5250 margin-bottom: var(--gap-pane);
5251 }
5252
5253 .modal .form-actions {
5254 display: flex;
5255 gap: var(--gap-section);
5256 justify-content: flex-end;
5257 margin-top: var(--gap-pane);
5258 }
5259
5260 /* ===========================================
5261 LINK ROWS (Dashboard Links Section)
5262 =========================================== */
5263
5264 .link-row {
5265 display: flex;
5266 gap: var(--gap-section);
5267 align-items: center;
5268 margin-bottom: var(--gap-section);
5269 padding: var(--gap-section);
5270 background: var(--surface-sunken);
5271 transition: opacity 0.2s ease, background 0.2s ease;
5272 }
5273
5274 .link-row input[type="text"],
5275 .link-row input[type="url"] {
5276 flex: 1;
5277 min-width: 0;
5278 }
5279
5280 .link-row .btn-danger {
5281 padding: var(--gap-peer) var(--gap-section);
5282 font-size: var(--text-note);
5283 }
5284
5285 /* Link order buttons */
5286 .link-order-buttons {
5287 display: flex;
5288 flex-direction: column;
5289 gap: 2px;
5290 }
5291
5292 .order-btn {
5293 background: var(--surface-sunken);
5294 border: 1px solid var(--border);
5295 padding: var(--gap-bound) var(--gap-peer);
5296 font-size: var(--text-fine);
5297 line-height: 1;
5298 cursor: pointer;
5299 opacity: 0.6;
5300 transition: opacity 0.15s ease, background 0.15s ease;
5301 }
5302
5303 .order-btn:hover {
5304 opacity: 1;
5305 background: var(--border);
5306 }
5307
5308 .order-btn:active {
5309 box-shadow: var(--shadow-inset);
5310 }
5311
5312 /* ===========================================
5313 TEXT EDITOR
5314 =========================================== */
5315
5316 .text-editor {
5317 width: 100%;
5318 }
5319
5320 .editor-tabs {
5321 display: flex;
5322 gap: 0;
5323 margin-bottom: 0;
5324 }
5325
5326 .editor-tab {
5327 background: var(--surface-sunken);
5328 border: none;
5329 padding: var(--gap-peer) var(--gap-pane);
5330 font-family: var(--font-mono);
5331 font-size: var(--text-note);
5332 cursor: pointer;
5333 opacity: 0.6;
5334 transition: opacity 0.2s ease, background 0.2s ease;
5335 }
5336
5337 .editor-tab.is-selected {
5338 background: var(--surface-sunken);
5339 opacity: 1;
5340 }
5341
5342 .editor-tab:hover {
5343 opacity: 1;
5344 }
5345
5346 .editor-panel {
5347 display: none;
5348 }
5349
5350 .editor-panel.active {
5351 display: block;
5352 }
5353
5354 .editor-panel textarea {
5355 width: 100%;
5356 min-height: 400px;
5357 padding: var(--gap-section);
5358 font-family: var(--font-mono);
5359 font-size: var(--text-note);
5360 line-height: 1.6;
5361 resize: vertical;
5362 border: none;
5363 background: var(--surface-sunken);
5364 }
5365
5366 .editor-footer {
5367 display: flex;
5368 justify-content: space-between;
5369 padding: var(--gap-peer) var(--gap-section);
5370 background: var(--surface-sunken);
5371 font-size: var(--text-note);
5372 opacity: 0.7;
5373 }
5374
5375 .markdown-preview {
5376 min-height: 400px;
5377 padding: var(--gap-pane);
5378 background: var(--surface-overlay);
5379 font-family: var(--font-body);
5380 line-height: 1.8;
5381 }
5382
5383 .markdown-preview h1,
5384 .markdown-preview h2,
5385 .markdown-preview h3 {
5386 margin-top: var(--gap-pane);
5387 margin-bottom: var(--gap-peer);
5388 }
5389
5390 .markdown-preview h1 {
5391 font-size: var(--text-title);
5392 }
5393
5394 .markdown-preview h2 {
5395 font-size: var(--text-head);
5396 }
5397
5398 .markdown-preview h3 {
5399 font-size: var(--text-lead);
5400 }
5401
5402 .markdown-preview p {
5403 margin-bottom: var(--gap-section);
5404 }
5405
5406 .markdown-preview code {
5407 background: var(--surface-sunken);
5408 padding: var(--gap-bound);
5409 font-family: var(--font-mono);
5410 font-size: var(--text-note);
5411 }
5412
5413 /* ===========================================
5414 AUDIO UPLOAD
5415 =========================================== */
5416
5417 .audio-upload {
5418 width: 100%;
5419 }
5420
5421 .current-audio {
5422 display: flex;
5423 justify-content: space-between;
5424 align-items: center;
5425 padding: var(--gap-section);
5426 background: var(--surface-sunken);
5427 margin-bottom: var(--gap-section);
5428 }
5429
5430 .audio-info {
5431 display: flex;
5432 align-items: center;
5433 gap: var(--gap-section);
5434 }
5435
5436 .audio-icon {
5437 font-size: var(--text-head);
5438 opacity: 0.6;
5439 }
5440
5441 .audio-details {
5442 display: flex;
5443 flex-direction: column;
5444 }
5445
5446 .audio-filename {
5447 font-family: var(--font-mono);
5448 font-size: var(--text-note);
5449 }
5450
5451 .audio-duration {
5452 font-size: var(--text-note);
5453 opacity: 0.6;
5454 }
5455
5456 .upload-area .file-upload-area {
5457 padding: var(--gap-page);
5458 }
5459
5460 .upload-icon {
5461 font-size: var(--text-title);
5462 opacity: 0.4;
5463 margin-bottom: var(--gap-peer);
5464 }
5465
5466 .upload-text {
5467 font-size: var(--text-lead);
5468 margin-bottom: var(--gap-peer);
5469 }
5470
5471 .upload-hint {
5472 font-size: var(--text-note);
5473 opacity: 0.6;
5474 }
5475
5476 .upload-progress {
5477 padding: var(--gap-pane);
5478 background: var(--surface-sunken);
5479 }
5480
5481 .progress-info {
5482 display: flex;
5483 justify-content: space-between;
5484 margin-bottom: var(--gap-section);
5485 font-size: var(--text-note);
5486 }
5487
5488 /* Canonical progress bar primitive. One of these in the codebase.
5489 - .progress-bar-container is the track (8px default; --slim = 6px)
5490 - .progress-bar is the fill (success-green default; --highlight = purple)
5491 - Add .progress-bar-container--rounded for the 3px-radius pill variant
5492 Spacing (mb-4 / mt-2 etc.) is added by the caller, not baked in. */
5493 .progress-bar-container {
5494 height: 8px;
5495 background: var(--border);
5496 overflow: hidden;
5497 }
5498 .progress-bar-container--slim { height: 6px; }
5499 .progress-bar-container--rounded { border-radius: var(--radius-control); }
5500
5501 /* Fill proportion comes from the template as --fill, because it is
5502 per-render data. The property that reads it lives here. */
5503 .progress-bar {
5504 width: var(--fill, 0%);
5505 height: 100%;
5506 background: var(--success);
5507 transition: width 0.2s ease;
5508 }
5509 .progress-bar--highlight { background: var(--action); }
5510 .progress-bar--warn { background: var(--warning); }
5511 .progress-bar--danger { background: var(--danger); }
5512
5513 /* Upload status block (filename + percent label + progress bar). One of these
5514 in the codebase. Used by wizard upload flows and standalone audio/video upload partials. */
5515 .upload-status { margin-top: var(--gap-peer); }
5516 .upload-status-row {
5517 display: flex;
5518 align-items: center;
5519 gap: var(--gap-peer);
5520 }
5521 .upload-status-row span { font-size: var(--text-note); }
5522 .upload-status-msg { margin-top: var(--gap-peer); }
5523 .upload-status-msg.is-success { color: var(--success); }
5524 .upload-status-msg.is-error { color: var(--danger); }
5525
5526 /* Inline progress cell rendered inside a table-row status cell (batch uploads). */
5527 .upload-progress-row {
5528 display: flex;
5529 align-items: center;
5530 gap: var(--gap-peer);
5531 }
5532 .upload-progress-row .progress-bar-container { flex: 1; min-width: 60px; }
5533 .upload-progress-pct {
5534 font-size: var(--text-fine);
5535 min-width: 2.5em;
5536 text-align: right;
5537 }
5538
5539 .upload-success {
5540 padding: var(--gap-pane);
5541 background: var(--surface-sunken);
5542 text-align: center;
5543 }
5544 .upload-error {
5545 padding: var(--gap-pane);
5546 background: var(--surface-sunken);
5547 text-align: center;
5548 }
5549
5550 .hidden {
5551 display: none !important;
5552 }
5553
5554 /* ===========================================
5555 VIEW TOGGLE (Grid/List)
5556 =========================================== */
5557
5558 .view-controls {
5559 display: flex;
5560 gap: var(--gap-bound);
5561 margin-left: auto;
5562 }
5563
5564 .view-btn {
5565 background: var(--surface-sunken);
5566 border: none;
5567 padding: var(--gap-peer) var(--gap-group);
5568 font-family: var(--font-mono);
5569 font-size: var(--text-note);
5570 cursor: pointer;
5571 opacity: 0.5;
5572 transition: opacity 0.2s ease;
5573 }
5574
5575 .view-btn:hover {
5576 opacity: 0.8;
5577 }
5578
5579 .view-btn.is-selected {
5580 opacity: 1;
5581 background: var(--primary-dark);
5582 color: var(--primary-light);
5583 }
5584 .results-container.results-list .results-table {
5585 display: block;
5586 }
5587
5588 .results-container.results-list .results-grid {
5589 display: none;
5590 }
5591
5592 .results-container.results-grid .results-table {
5593 display: none;
5594 }
5595
5596 .results-container.results-grid .results-grid {
5597 display: grid;
5598 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
5599 gap: var(--gap-pane);
5600 border: 1px solid var(--border);
5601 border-top: none;
5602 padding: var(--gap-pane);
5603 background: var(--surface-overlay);
5604 }
5605
5606 /* Grid cards */
5607 .grid-card {
5608 background: var(--surface-overlay);
5609 text-decoration: none;
5610 color: var(--content);
5611 display: flex;
5612 flex-direction: column;
5613 transition: background 0.2s ease;
5614 }
5615
5616 .grid-card:hover {
5617 background: var(--surface-sunken);
5618 text-decoration: none;
5619 }
5620
5621 .grid-card-thumbnail {
5622 height: 120px;
5623 background: var(--border);
5624 display: flex;
5625 align-items: center;
5626 justify-content: center;
5627 font-size: var(--text-fine);
5628 opacity: 0.5;
5629 text-transform: uppercase;
5630 }
5631
5632 .grid-card-content {
5633 padding: var(--gap-section);
5634 display: flex;
5635 flex-direction: column;
5636 gap: var(--gap-bound);
5637 }
5638
5639 .grid-card-title {
5640 font-family: var(--font-heading);
5641 font-size: var(--text-lead);
5642 }
5643
5644 .grid-card-meta {
5645 font-size: var(--text-note);
5646 opacity: 0.6;
5647 }
5648
5649 .grid-card-footer {
5650 display: flex;
5651 justify-content: space-between;
5652 align-items: center;
5653 margin-top: var(--gap-peer);
5654 font-size: var(--text-note);
5655 }
5656
5657 .grid-card-category {
5658 opacity: 0.6;
5659 }
5660
5661 .grid-card-price {
5662 font-weight: bold;
5663 }
5664
5665 .grid-card-stat {
5666 opacity: 0.6;
5667 }
5668
5669 /* ===========================================
5670 PROJECT PAGE VIEW TOGGLE
5671 =========================================== */
5672
5673 .items-header {
5674 display: flex;
5675 justify-content: space-between;
5676 align-items: center;
5677 margin-bottom: var(--gap-pane);
5678 }
5679
5680 .items-header .section-header {
5681 margin-bottom: 0;
5682 border-bottom: none;
5683 padding-bottom: 0;
5684 }
5685
5686 /* Grid view is default on project page */
5687 .items-container.items-grid-view .items-grid {
5688 display: grid;
5689 }
5690
5691 .items-container.items-grid-view .items-list {
5692 display: none;
5693 }
5694
5695 .items-container.items-list-view .items-grid {
5696 display: none;
5697 }
5698
5699 .items-container.items-list-view .items-list {
5700 display: block;
5701 }
5702
5703 /* List items styling */
5704 .items-list {
5705 display: none;
5706 }
5707
5708 .list-item {
5709 display: flex;
5710 justify-content: space-between;
5711 align-items: center;
5712 padding: var(--gap-section);
5713 background: var(--surface-overlay);
5714 margin-bottom: var(--gap-peer);
5715 text-decoration: none;
5716 color: var(--content);
5717 transition: background 0.2s ease;
5718 }
5719
5720 .list-item:hover {
5721 background: var(--surface-sunken);
5722 text-decoration: none;
5723 }
5724
5725 .list-item-info {
5726 display: flex;
5727 flex-direction: column;
5728 gap: var(--gap-bound);
5729 }
5730
5731 .list-item-title {
5732 font-family: var(--font-heading);
5733 font-size: var(--text-lead);
5734 }
5735
5736 .list-item-meta {
5737 font-size: var(--text-note);
5738 opacity: 0.6;
5739 }
5740
5741 .list-item-price {
5742 font-size: var(--text-lead);
5743 font-weight: bold;
5744 }
5745
5746 /* ===========================================
5747 LANDING PAGE
5748 =========================================== */
5749
5750 .landing-hero {
5751 max-width: 720px;
5752 width: 100%;
5753 text-align: center;
5754 }
5755
5756 .landing-headline {
5757 font-family: var(--font-heading);
5758 font-size: var(--text-head);
5759 margin-bottom: var(--gap-peer);
5760 }
5761
5762 .landing-sub {
5763 font-size: var(--text-body);
5764 line-height: 1.6;
5765 opacity: 0.75;
5766 margin-bottom: 0;
5767 }
5768
5769 .fork-revenue-line {
5770 font-size: var(--text-note);
5771 opacity: 0.8;
5772 margin: var(--gap-section) 0 var(--gap-peer);
5773 line-height: 1.5;
5774 }
5775
5776 .fork-revenue-line a {
5777 border-bottom: 1px solid transparent;
5778 transition: border-color 0.2s ease;
5779 }
5780
5781 .fork-revenue-line a:hover {
5782 border-bottom-color: currentColor;
5783 }
5784
5785 .landing-fork {
5786 display: grid;
5787 grid-template-columns: 1fr 1fr;
5788 gap: var(--gap-section);
5789 margin-top: var(--gap-page);
5790 text-align: left;
5791 }
5792
5793 .fork-heading {
5794 font-family: var(--font-mono);
5795 font-size: var(--text-note);
5796 font-weight: 600;
5797 text-transform: uppercase;
5798 letter-spacing: 0.05em;
5799 margin-bottom: var(--gap-section);
5800 }
5801
5802 .fork-list {
5803 list-style: none;
5804 margin: 0;
5805 padding: 0;
5806 flex: 1;
5807 }
5808
5809 .fork-list li {
5810 margin-bottom: var(--gap-group);
5811 font-size: var(--text-note);
5812 line-height: 1.5;
5813 }
5814
5815 .fork-list strong {
5816 font-family: var(--font-mono);
5817 font-size: var(--text-fine);
5818 }
5819
5820 .fork-actions {
5821 margin-top: auto;
5822 padding-top: var(--gap-section);
5823 display: flex;
5824 flex-direction: column;
5825 align-items: center;
5826 gap: var(--gap-peer);
5827 }
5828
5829 .fork-actions .big-button {
5830 width: 100%;
5831 }
5832
5833 .fork-secondary-link {
5834 font-family: var(--font-mono);
5835 font-size: var(--text-fine);
5836 opacity: 0.6;
5837 transition: opacity 0.2s ease;
5838 }
5839
5840 .fork-secondary-link:hover {
5841 opacity: 1;
5842 }
5843
5844 .fork-lede {
5845 font-size: var(--text-body);
5846 line-height: 1.55;
5847 margin: 0 0 var(--gap-section);
5848 }
5849
5850 .landing-browse-cta {
5851 text-align: center;
5852 margin: var(--gap-section) 0 0;
5853 }
5854
5855 .landing-mobile-note {
5856 text-align: center;
5857 margin: var(--gap-peer) 0 0;
5858 font-size: var(--text-fine);
5859 color: var(--content-muted);
5860 }
5861
5862 .founder-tagline {
5863 font-family: var(--font-body);
5864 font-size: var(--text-body);
5865 line-height: 1.55;
5866 margin: var(--gap-section) auto 0;
5867 max-width: 560px;
5868 color: var(--content);
5869 display: flex;
5870 flex-direction: column;
5871 align-items: center;
5872 gap: var(--gap-peer);
5873 }
5874
5875 .landing-velocity {
5876 font-family: var(--font-mono);
5877 font-size: var(--text-fine);
5878 text-align: center;
5879 margin: var(--gap-section) auto 0;
5880 color: var(--content-muted);
5881 }
5882
5883 .landing-velocity a {
5884 color: var(--content);
5885 text-decoration: none;
5886 border-bottom: 1px solid var(--border);
5887 }
5888
5889 .landing-velocity a:hover {
5890 border-bottom-color: var(--content);
5891 }
5892
5893 .founder-tagline-detail {
5894 display: block;
5895 }
5896
5897 .founder-tagline-mark {
5898 font-family: var(--font-mono);
5899 font-size: var(--text-fine);
5900 font-weight: 600;
5901 text-transform: uppercase;
5902 letter-spacing: 0.08em;
5903 color: var(--action);
5904 padding: var(--gap-bound) var(--gap-peer);
5905 border: 1px solid var(--action);
5906 border-radius: var(--radius-control);
5907 white-space: nowrap;
5908 }
5909
5910 .fork-card--founder {
5911 border-color: var(--action);
5912 box-shadow: 0 0 0 1px var(--highlight-faint);
5913 }
5914
5915 .founder-tier-grid {
5916 display: grid;
5917 grid-template-columns: 1fr 1fr;
5918 gap: var(--gap-peer) var(--gap-section);
5919 margin-bottom: var(--gap-section);
5920 padding-bottom: var(--gap-section);
5921 border-bottom: 1px dashed var(--border);
5922 }
5923
5924 .founder-tier {
5925 display: flex;
5926 flex-direction: column;
5927 gap: var(--gap-bound);
5928 }
5929
5930 .founder-tier-name {
5931 font-family: var(--font-mono);
5932 font-size: var(--text-fine);
5933 font-weight: 600;
5934 text-transform: uppercase;
5935 letter-spacing: 0.05em;
5936 opacity: 0.7;
5937 }
5938
5939 .founder-tier-price {
5940 font-family: var(--font-heading);
5941 font-size: var(--text-body);
5942 line-height: 1.1;
5943 }
5944
5945 .founder-tier-price del {
5946 opacity: 0.4;
5947 margin-right: var(--gap-bound);
5948 font-weight: 400;
5949 }
5950
5951 .founder-tier-price strong {
5952 color: var(--action);
5953 font-weight: 700;
5954 }
5955
5956 .founder-tier-unit {
5957 font-family: var(--font-mono);
5958 font-size: var(--text-fine);
5959 opacity: 0.6;
5960 margin-left: var(--gap-bound);
5961 }
5962
5963 .founder-slots {
5964 font-family: var(--font-mono);
5965 font-size: var(--text-fine);
5966 margin: 0 0 var(--gap-section);
5967 line-height: 1.3;
5968 }
5969
5970 .founder-slots-number {
5971 font-family: var(--font-heading);
5972 font-size: var(--text-head);
5973 color: var(--action);
5974 font-weight: 700;
5975 margin-right: var(--gap-bound);
5976 }
5977
5978 .founder-slots-cap {
5979 opacity: 0.55;
5980 margin-left: var(--gap-bound);
5981 }
5982
5983 .founder-slots--cap {
5984 opacity: 0.7;
5985 font-size: var(--text-fine);
5986 }
5987
5988 .landing-stats-line {
5989 font-family: var(--font-mono);
5990 font-size: var(--text-note);
5991 opacity: 0.6;
5992 margin-top: var(--gap-page);
5993 }
5994
5995 .landing-stats-line a {
5996 opacity: 0.8;
5997 transition: opacity 0.2s ease;
5998 }
5999
6000 .landing-stats-line a:hover {
6001 opacity: 1;
6002 }
6003
6004 .landing-principles {
6005 margin-top: var(--gap-page);
6006 width: 100%;
6007 }
6008
6009 .landing-use-cases {
6010 margin-top: var(--gap-page);
6011 width: 100%;
6012 }
6013
6014 .use-case-line {
6015 font-family: var(--font-mono);
6016 font-size: var(--text-note);
6017 opacity: 0.7;
6018 letter-spacing: 0.02em;
6019 }
6020
6021 .landing-prose {
6022 font-size: var(--text-body);
6023 line-height: 1.7;
6024 margin-bottom: var(--gap-peer);
6025 }
6026
6027 .notify-form {
6028 display: flex;
6029 gap: var(--gap-peer);
6030 max-width: 400px;
6031 margin: 0 auto;
6032 }
6033
6034 .notify-input {
6035 flex: 1;
6036 padding: var(--gap-peer) var(--gap-section);
6037 border: 1px solid var(--border);
6038 border-radius: var(--radius-control);
6039 font-family: var(--font-body);
6040 font-size: var(--text-note);
6041 background: var(--surface-page);
6042 color: var(--content);
6043 }
6044
6045 .notify-status {
6046 font-family: var(--font-mono);
6047 font-size: var(--text-fine);
6048 text-align: center;
6049 margin-top: var(--gap-peer);
6050 min-height: 1.2em;
6051 }
6052
6053 .notify-status.success {
6054 color: var(--content);
6055 }
6056
6057 .notify-status.error {
6058 color: var(--danger);
6059 }
6060
6061 .section-label {
6062 font-family: var(--font-mono);
6063 font-size: var(--text-note);
6064 letter-spacing: 0.05em;
6065 text-transform: uppercase;
6066 opacity: 0.6;
6067 margin-bottom: var(--gap-section);
6068 }
6069
6070 .tier-section {
6071 margin: var(--gap-page) 0;
6072 width: 100%;
6073 }
6074 /* Marketing pricing variant of .card--bordered. */
6075 .tier-name {
6076 font-family: var(--font-mono);
6077 font-size: var(--text-note);
6078 font-weight: 600;
6079 margin-bottom: var(--gap-bound);
6080 }
6081
6082 .tier-price {
6083 font-family: var(--font-heading);
6084 font-size: var(--text-lead);
6085 margin-bottom: var(--gap-bound);
6086 }
6087
6088 .tier-desc {
6089 font-size: var(--text-note);
6090 opacity: 0.7;
6091 }
6092
6093 .tier-card.planned {
6094 opacity: 0.5;
6095 border-style: dashed;
6096 position: relative;
6097 }
6098 .landing-cta {
6099 display: flex;
6100 align-items: center;
6101 justify-content: center;
6102 gap: var(--gap-page);
6103 width: fit-content;
6104 margin: var(--gap-pane) auto var(--gap-page);
6105 }
6106 .secondary-links {
6107 display: flex;
6108 gap: var(--gap-pane);
6109 justify-content: center;
6110 margin-top: var(--gap-section);
6111 }
6112
6113 .secondary-links a {
6114 font-size: var(--text-note);
6115 opacity: 0.6;
6116 transition: opacity 0.2s ease;
6117 }
6118
6119 .secondary-links a:hover {
6120 opacity: 1;
6121 }
6122 .section-link {
6123 display: block;
6124 margin-top: var(--gap-section);
6125 font-family: var(--font-mono);
6126 font-size: var(--text-fine);
6127 opacity: 0.55;
6128 transition: opacity 0.2s ease;
6129 }
6130
6131 .section-link:hover {
6132 opacity: 1;
6133 }
6134
6135 .feature-grid {
6136 display: grid;
6137 grid-template-columns: 1fr 1fr;
6138 gap: var(--gap-section);
6139 text-align: left;
6140 }
6141
6142 .feature-name {
6143 font-family: var(--font-mono);
6144 font-size: var(--text-note);
6145 font-weight: 600;
6146 margin-bottom: var(--gap-bound);
6147 }
6148
6149 .feature-desc {
6150 font-size: var(--text-note);
6151 opacity: 0.7;
6152 }
6153
6154 .how-list {
6155 list-style: none;
6156 margin: 0;
6157 padding: 0;
6158 text-align: left;
6159 }
6160
6161 .how-list li {
6162 margin-bottom: var(--gap-group);
6163 font-size: var(--text-body);
6164 }
6165
6166 .how-list strong {
6167 font-family: var(--font-mono);
6168 font-size: var(--text-note);
6169 }
6170
6171 .do-rail {
6172 list-style: none;
6173 margin: var(--gap-page) 0 var(--gap-pane);
6174 padding: 0;
6175 display: grid;
6176 /* A do-card stops being readable under about 200px, which is a fact about
6177 the card. Three across is what that yields at full width. */
6178 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
6179 gap: var(--gap-section);
6180 width: 100%;
6181 }
6182
6183 .do-card {
6184 background: transparent;
6185 border: 1px solid var(--border);
6186 padding: var(--gap-section);
6187 text-align: left;
6188 display: flex;
6189 flex-direction: column;
6190 }
6191
6192 .do-card-title {
6193 font-family: var(--font-mono);
6194 font-size: var(--text-note);
6195 font-weight: 600;
6196 text-transform: uppercase;
6197 letter-spacing: 0.05em;
6198 margin: 0 0 var(--gap-peer);
6199 }
6200
6201 .do-card-lede {
6202 font-size: var(--text-note);
6203 line-height: 1.5;
6204 opacity: 0.78;
6205 margin: 0 0 var(--gap-section);
6206 }
6207
6208 .do-card-links {
6209 list-style: none;
6210 margin: auto 0 0;
6211 padding: var(--gap-section) 0 0;
6212 border-top: 1px solid var(--border);
6213 display: flex;
6214 flex-wrap: wrap;
6215 gap: var(--gap-peer) var(--gap-section);
6216 }
6217
6218 .do-card-links a {
6219 font-size: var(--text-note);
6220 border-bottom: 1px solid transparent;
6221 transition: border-color 0.2s ease;
6222 }
6223
6224 .do-card-links a:hover {
6225 border-bottom-color: currentColor;
6226 }
6227
6228 .do-card--wide {
6229 margin-top: var(--gap-page);
6230 width: 100%;
6231 }
6232
6233 .contrast-rail {
6234 list-style: none;
6235 margin: var(--gap-section) 0 0;
6236 padding: 0;
6237 display: grid;
6238 /* Title plus a short link list, so it holds up narrower than a do-card.
6239 Four across at full width. */
6240 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
6241 gap: var(--gap-section);
6242 width: 100%;
6243 }
6244
6245 .contrast-card {
6246 border-top: 1px solid var(--border);
6247 padding: var(--gap-section) var(--gap-bound) 0;
6248 text-align: left;
6249 }
6250
6251 .contrast-card-title {
6252 font-family: var(--font-mono);
6253 font-size: var(--text-fine);
6254 letter-spacing: 0.04em;
6255 text-transform: uppercase;
6256 opacity: 0.5;
6257 text-decoration: line-through;
6258 text-decoration-thickness: 1px;
6259 margin: 0 0 var(--gap-peer);
6260 font-weight: 600;
6261 }
6262
6263 .contrast-card-links {
6264 list-style: none;
6265 margin: 0;
6266 padding: 0;
6267 display: flex;
6268 flex-wrap: wrap;
6269 gap: var(--gap-peer) var(--gap-section);
6270 }
6271
6272 .contrast-card-links a {
6273 font-size: var(--text-note);
6274 border-bottom: 1px solid transparent;
6275 transition: border-color 0.2s ease;
6276 }
6277
6278 .contrast-card-links a:hover {
6279 border-bottom-color: currentColor;
6280 }
6281
6282 .explore-links {
6283 display: flex;
6284 flex-wrap: wrap;
6285 gap: var(--gap-section) var(--gap-pane);
6286 justify-content: center;
6287 }
6288
6289 .explore-links a {
6290 font-family: var(--font-mono);
6291 font-size: var(--text-note);
6292 opacity: 0.7;
6293 transition: opacity 0.2s ease;
6294 }
6295
6296 .explore-links a:hover {
6297 opacity: 1;
6298 }
6299
6300 /* ===========================================
6301 PRICING CALCULATOR
6302 =========================================== */
6303
6304 .pricing-page {
6305 max-width: 800px;
6306 }
6307
6308 .pricing-input-wrap {
6309 display: flex;
6310 align-items: baseline;
6311 justify-content: center;
6312 gap: var(--gap-bound);
6313 }
6314
6315 .pricing-currency {
6316 font-family: var(--font-heading);
6317 font-size: var(--text-display);
6318 opacity: 0.5;
6319 }
6320
6321 .pricing-input {
6322 font-family: var(--font-heading);
6323 font-size: var(--text-display);
6324 width: 5ch;
6325 text-align: center;
6326 background: transparent;
6327 border: none;
6328 border-bottom: 2px solid var(--border);
6329 color: var(--content);
6330 padding: var(--gap-bound) 0;
6331 -moz-appearance: textfield;
6332 }
6333
6334 .pricing-input::-webkit-inner-spin-button,
6335 .pricing-input::-webkit-outer-spin-button {
6336 -webkit-appearance: none;
6337 margin: 0;
6338 }
6339
6340 .pricing-input:focus {
6341 outline: none;
6342 border-bottom-color: var(--action);
6343 }
6344
6345 .pricing-period {
6346 font-family: var(--font-mono);
6347 font-size: var(--text-body);
6348 opacity: 0.5;
6349 }
6350
6351 /* <mnw-price-mode> is a custom element (default display:inline); it wraps the
6352 toggle and the tier grid, so it has to lay out as a block. */
6353 mnw-price-mode {
6354 display: block;
6355 }
6356
6357 /* List-vs-founder toggle above the tier cards, rendered only while the founder
6358 window is open. Segmented control rather than a checkbox: both options are
6359 real prices the visitor can be shown, and neither is an "off" state. Follows
6360 .tier-option's pattern of hiding the radio and styling the label off
6361 :has(:checked), so the selection needs no JS class. */
6362 .price-mode {
6363 display: inline-flex;
6364 gap: 0;
6365 margin-bottom: var(--gap-section);
6366 border: 1px solid var(--border);
6367 border-radius: var(--radius-control);
6368 overflow: hidden;
6369 }
6370
6371 .price-mode-option {
6372 cursor: pointer;
6373 font-family: var(--font-mono);
6374 font-size: var(--text-note);
6375 padding: var(--gap-bound) var(--gap-section);
6376 transition: background-color 0.15s ease;
6377 }
6378
6379 .price-mode-option input[type="radio"] {
6380 position: absolute;
6381 opacity: 0;
6382 pointer-events: none;
6383 }
6384
6385 .price-mode-option:has(input[type="radio"]:checked) {
6386 background: var(--action);
6387 color: var(--content-on-action);
6388 }
6389
6390 .price-mode-option:focus-within {
6391 outline: 2px solid var(--focus-ring);
6392 outline-offset: -2px;
6393 }
6394
6395 .tier-selector {
6396 display: grid;
6397 grid-template-columns: 1fr 1fr;
6398 gap: var(--gap-section);
6399 text-align: left;
6400 }
6401
6402 .tier-option {
6403 cursor: pointer;
6404 transition: border-color 0.15s ease;
6405 }
6406
6407 .tier-option input[type="radio"] {
6408 position: absolute;
6409 opacity: 0;
6410 pointer-events: none;
6411 }
6412
6413 .tier-option:focus-within {
6414 outline: 2px solid var(--focus-ring);
6415 outline-offset: 2px;
6416 }
6417
6418 /* Selected tier, driven purely by the checked radio (no JS toggling a
6419 class). The `.is-selected` alias is retained for any server-rendered use. */
6420 .tier-option.is-selected,
6421 .tier-option:has(input[type="radio"]:checked) {
6422 border-color: var(--action);
6423 border-width: 2px;
6424 padding: calc(var(--gap-section) - 1px);
6425 }
6426
6427 /* Two dials side by side (price + volume, then the other platform's two).
6428 Stacks on narrow screens via the mobile block below. */
6429 .calc-dials {
6430 display: grid;
6431 grid-template-columns: 1fr 1fr;
6432 gap: var(--gap-section);
6433 align-items: end;
6434 }
6435
6436 .calc-dial {
6437 display: flex;
6438 flex-direction: column;
6439 align-items: center;
6440 gap: var(--gap-bound);
6441 }
6442
6443 .calc-dial-label {
6444 font-family: var(--font-mono);
6445 font-size: var(--text-note);
6446 opacity: 0.7;
6447 }
6448
6449 /* Who comes out ahead at the current dials. The three states are the only
6450 place the calculator uses color to make a claim, so they carry the same
6451 weight in both directions: losing is stated as plainly as winning. */
6452 .calc-verdict {
6453 font-family: var(--font-heading);
6454 font-size: var(--text-title);
6455 text-align: center;
6456 padding: var(--gap-pane);
6457 background: var(--surface-overlay);
6458 border: 1px solid var(--border);
6459 border-left-width: 3px;
6460 }
6461
6462 .verdict--ahead {
6463 border-left-color: var(--success);
6464 }
6465
6466 .verdict--even {
6467 border-left-color: var(--border);
6468 }
6469
6470 .verdict--behind {
6471 border-left-color: var(--warning);
6472 }
6473
6474 .calc-table th[scope="row"] {
6475 text-align: left;
6476 font-weight: 400;
6477 opacity: 0.7;
6478 }
6479
6480 .calc-table .calc-gross,
6481 .calc-keep-row td {
6482 font-family: var(--font-mono);
6483 }
6484
6485 .calc-keep-row td {
6486 font-size: var(--text-title);
6487 }
6488
6489 /* The win-region bar. Two segments tile the full width; the server sends both
6490 widths as percentages, so nothing here is computed in the browser. */
6491 .calc-scale {
6492 display: flex;
6493 flex-direction: column;
6494 gap: var(--gap-bound);
6495 }
6496
6497 .calc-bar {
6498 position: relative;
6499 display: flex;
6500 height: 1.5rem;
6501 border: 1px solid var(--border);
6502 overflow: hidden;
6503 }
6504
6505 .calc-bar-seg {
6506 height: 100%;
6507 }
6508
6509 .calc-bar-seg--other {
6510 background: var(--warning);
6511 opacity: 0.35;
6512 }
6513
6514 .calc-bar-seg--mnw {
6515 background: var(--success);
6516 opacity: 0.35;
6517 }
6518
6519 /* "You are here" on the sales axis. */
6520 .calc-marker {
6521 position: absolute;
6522 top: 0;
6523 bottom: 0;
6524 width: 2px;
6525 margin-left: -1px;
6526 background: var(--content);
6527 }
6528
6529 .calc-crossover {
6530 position: absolute;
6531 top: 0;
6532 bottom: 0;
6533 width: 1px;
6534 background: var(--content);
6535 opacity: 0.4;
6536 }
6537
6538 .calc-axis {
6539 display: flex;
6540 justify-content: space-between;
6541 gap: var(--gap-bound);
6542 font-family: var(--font-mono);
6543 font-size: var(--text-fine);
6544 opacity: 0.6;
6545 }
6546
6547 .calc-axis-crossover {
6548 opacity: 0.9;
6549 }
6550
6551 .calc-legend {
6552 display: flex;
6553 flex-wrap: wrap;
6554 gap: var(--gap-section);
6555 font-size: var(--text-fine);
6556 opacity: 0.7;
6557 }
6558
6559 .calc-legend-item::before {
6560 content: "";
6561 display: inline-block;
6562 width: 0.75rem;
6563 height: 0.75rem;
6564 margin-right: var(--gap-bound);
6565 vertical-align: baseline;
6566 }
6567
6568 .calc-legend-item--other::before {
6569 background: var(--warning);
6570 opacity: 0.35;
6571 }
6572
6573 .calc-legend-item--mnw::before {
6574 background: var(--success);
6575 opacity: 0.35;
6576 }
6577
6578 .calc-note {
6579 font-family: var(--font-mono);
6580 font-size: var(--text-note);
6581 padding: var(--gap-section);
6582 background: var(--surface-overlay);
6583 border-left: 3px solid var(--border);
6584 line-height: 1.6;
6585 }
6586
6587 /* ===========================================
6588 DOCUMENTATION PAGES
6589 =========================================== */
6590
6591 .doc-container {
6592 max-width: 680px;
6593 margin: 0 auto;
6594 padding: var(--gap-page) var(--gap-pane) var(--gap-page);
6595 }
6596
6597 .doc-breadcrumb {
6598 font-family: var(--font-mono);
6599 font-size: var(--text-note);
6600 margin-bottom: var(--gap-page);
6601 }
6602
6603 .doc-breadcrumb a { color: var(--content); text-decoration: none; }
6604 .doc-breadcrumb a:hover { text-decoration: underline; }
6605
6606 .doc-title {
6607 font-family: var(--font-heading);
6608 font-size: var(--text-display);
6609 font-weight: normal;
6610 line-height: 1.2;
6611 margin-bottom: var(--gap-pane);
6612 color: var(--content);
6613 text-align: left;
6614 }
6615
6616 .doc-body {
6617 font-family: var(--font-body);
6618 font-size: var(--text-lead);
6619 line-height: 1.9;
6620 }
6621
6622 .doc-body p { margin-bottom: var(--gap-pane); }
6623
6624 .doc-body h1 {
6625 font-family: var(--font-heading);
6626 font-size: var(--text-title);
6627 font-weight: normal;
6628 margin-top: var(--gap-page);
6629 margin-bottom: var(--gap-section);
6630 color: var(--content);
6631 text-align: left;
6632 }
6633
6634 .doc-body h2 {
6635 font-family: var(--font-mono);
6636 font-size: var(--text-head);
6637 font-weight: normal;
6638 margin-top: var(--gap-page);
6639 margin-bottom: var(--gap-section);
6640 color: var(--content);
6641 }
6642
6643 .doc-body h3 {
6644 font-family: var(--font-mono);
6645 font-size: var(--text-subhead);
6646 font-weight: normal;
6647 margin-top: var(--gap-page);
6648 margin-bottom: var(--gap-section);
6649 color: var(--content);
6650 }
6651
6652 .doc-body blockquote {
6653 border-left: 3px solid var(--action);
6654 padding-left: var(--gap-pane);
6655 margin: var(--gap-page) 0;
6656 font-style: italic;
6657 color: var(--content-muted);
6658 }
6659
6660 .doc-body ul, .doc-body ol { margin-bottom: var(--gap-pane); padding-left: var(--gap-pane); }
6661 .doc-body li { margin-bottom: var(--gap-peer); }
6662
6663 .doc-body a {
6664 color: var(--action);
6665 text-decoration: underline;
6666 text-decoration-color: color-mix(in oklch, var(--action) 40%, transparent);
6667 text-underline-offset: 2px;
6668 }
6669
6670 .doc-body a:hover { text-decoration-color: var(--action); }
6671 .doc-body strong { font-weight: bold; }
6672 .doc-body em { font-style: italic; }
6673
6674 .doc-body code {
6675 font-family: var(--font-mono);
6676 background: var(--surface-sunken);
6677 padding: var(--gap-bound) var(--gap-peer);
6678 font-size: var(--text-body);
6679 }
6680
6681 .doc-body pre {
6682 background: var(--surface-sunken);
6683 padding: var(--gap-section);
6684 overflow-x: auto;
6685 margin-bottom: var(--gap-pane);
6686 }
6687
6688 .doc-body pre code { background: none; padding: 0; }
6689
6690 .doc-body hr { border: none; text-align: center; margin: var(--gap-page) 0; }
6691 .doc-body hr::before { content: "* * *"; color: var(--content-muted); letter-spacing: 1rem; }
6692
6693 .doc-body table { width: 100%; border-collapse: collapse; margin-bottom: var(--gap-pane); }
6694 .doc-body th, .doc-body td { padding: var(--gap-section); text-align: left; border-bottom: 1px solid var(--border); }
6695 .doc-body th { font-family: var(--font-mono); font-weight: 600; }
6696
6697 .doc-backlinks {
6698 margin-top: var(--gap-page);
6699 padding-top: var(--gap-pane);
6700 border-top: 1px solid var(--border);
6701 }
6702
6703 .doc-backlinks-title {
6704 font-family: var(--font-mono);
6705 font-size: var(--text-note);
6706 font-weight: 600;
6707 text-transform: uppercase;
6708 letter-spacing: 0.05em;
6709 color: var(--content-muted);
6710 margin-bottom: var(--gap-section);
6711 }
6712
6713 .doc-backlinks-list {
6714 list-style: none;
6715 margin: 0;
6716 padding: 0;
6717 }
6718
6719 .doc-backlinks-list li { margin-bottom: var(--gap-peer); }
6720 .doc-backlinks-list a { color: var(--content); text-decoration: none; }
6721 .doc-backlinks-list a:hover { text-decoration: underline; }
6722
6723 /* Docs index page */
6724 .docs-index {
6725 max-width: 680px;
6726 margin: 0 auto;
6727 padding: var(--gap-page) var(--gap-pane) var(--gap-page);
6728 }
6729
6730 .docs-index-title {
6731 font-family: var(--font-heading);
6732 font-size: var(--text-display);
6733 font-weight: normal;
6734 line-height: 1.2;
6735 margin-bottom: var(--gap-page);
6736 color: var(--content);
6737 }
6738
6739 .docs-section {
6740 margin-bottom: var(--gap-page);
6741 }
6742
6743 .docs-section h2 {
6744 font-family: var(--font-mono);
6745 font-size: var(--text-subhead);
6746 font-weight: normal;
6747 color: var(--content-muted);
6748 margin-bottom: var(--gap-section);
6749 }
6750
6751 .docs-section ul {
6752 list-style: none;
6753 padding: 0;
6754 margin: 0;
6755 }
6756
6757 .docs-section li {
6758 margin-bottom: var(--gap-peer);
6759 }
6760
6761 .docs-section a {
6762 font-family: var(--font-body);
6763 font-size: var(--text-lead);
6764 color: var(--content);
6765 text-decoration: none;
6766 }
6767
6768 .docs-section a:hover {
6769 color: var(--action);
6770 text-decoration: underline;
6771 }
6772
6773 .docs-section details {
6774 margin-bottom: var(--gap-section);
6775 }
6776
6777 .docs-section summary {
6778 font-family: var(--font-mono);
6779 font-size: var(--text-body);
6780 color: var(--content-muted);
6781 cursor: pointer;
6782 padding: var(--gap-bound) 0;
6783 list-style: disclosure-closed;
6784 }
6785
6786 .docs-section details[open] > summary {
6787 list-style: disclosure-open;
6788 margin-bottom: var(--gap-bound);
6789 }
6790
6791 .docs-section summary:hover {
6792 color: var(--content);
6793 }
6794
6795 .docs-section details ul {
6796 padding-left: var(--gap-section);
6797 }
6798
6799 /* Docs search */
6800 .docs-search-container {
6801 position: relative;
6802 margin-bottom: var(--gap-page);
6803 }
6804 .docs-search-inline {
6805 display: inline-block;
6806 margin-bottom: 0;
6807 margin-left: auto;
6808 float: right;
6809 }
6810 .docs-search-input {
6811 width: 100%;
6812 max-width: 320px;
6813 padding: var(--gap-peer) var(--gap-section);
6814 font-family: var(--font-body);
6815 font-size: var(--text-body);
6816 background: var(--surface-sunken);
6817 border: 1px solid var(--border);
6818 color: var(--content);
6819 }
6820 .docs-search-input:focus {
6821 outline: 2px solid var(--action);
6822 border-color: var(--action);
6823 }
6824 .docs-search-input::placeholder { opacity: 0.5; }
6825 .docs-search-results {
6826 position: absolute;
6827 top: 100%;
6828 left: 0;
6829 right: 0;
6830 max-width: 320px;
6831 background: var(--surface-page);
6832 border: 1px solid var(--border);
6833 border-top: none;
6834 max-height: 300px;
6835 overflow-y: auto;
6836 z-index: var(--z-dropdown);
6837 box-shadow: var(--elevation-overlay);
6838 }
6839 .docs-search-result {
6840 display: flex;
6841 justify-content: space-between;
6842 align-items: baseline;
6843 padding: var(--gap-peer) var(--gap-section);
6844 text-decoration: none;
6845 color: var(--content);
6846 font-size: var(--text-note);
6847 border-bottom: 1px solid var(--border);
6848 }
6849 .docs-search-result:last-child { border-bottom: none; }
6850 .docs-search-result:hover { background: var(--surface-sunken); }
6851 .docs-search-result-title { font-family: var(--font-body); }
6852 .docs-search-result-section { font-family: var(--font-mono); font-size: var(--text-fine); opacity: 0.5; }.alert .alert-title {
6853 font-family: var(--font-mono);
6854 font-size: var(--text-note);
6855 font-weight: bold;
6856 text-transform: uppercase;
6857 letter-spacing: 0.04em;
6858 margin-bottom: var(--gap-bound);
6859 }
6860 .alert p:last-child { margin-bottom: 0; }
6861 .alert-note { border-left-color: var(--focus-ring); }
6862 .alert-note .alert-title { color: var(--focus-ring); }
6863 .alert-tip { border-left-color: var(--success); }
6864 .alert-tip .alert-title { color: var(--success); }
6865 .alert-important { border-left-color: var(--action); }
6866 .alert-important .alert-title { color: var(--action); }
6867 .alert-warning { border-left-color: var(--warning); }
6868 .alert-warning .alert-title { color: var(--warning); }
6869 .alert-caution { border-left-color: var(--danger); }
6870 .alert-caution .alert-title { color: var(--danger); }
6871
6872 /* ===========================================
6873 USE CASES PAGE
6874 =========================================== */
6875
6876 .use-cases-page {
6877 max-width: 900px;
6878 margin: 0 auto;
6879 }
6880
6881 .use-cases-intro {
6882 font-size: var(--text-lead);
6883 margin-bottom: var(--gap-page);
6884 line-height: 1.6;
6885 }
6886
6887 .use-case-grid {
6888 display: grid;
6889 grid-template-columns: 1fr 1fr 1fr;
6890 gap: var(--gap-section);
6891 text-align: left;
6892 }
6893
6894 .use-case-grid-2col {
6895 grid-template-columns: 1fr 1fr;
6896 }
6897
6898 .use-case-title {
6899 font-family: var(--font-heading);
6900 font-size: var(--text-lead);
6901 margin-bottom: var(--gap-bound);
6902 }
6903
6904 .use-case-who {
6905 font-family: var(--font-mono);
6906 font-size: var(--text-fine);
6907 opacity: 0.6;
6908 margin-bottom: var(--gap-peer);
6909 }
6910
6911 .use-case-desc {
6912 font-size: var(--text-note);
6913 line-height: 1.5;
6914 margin-bottom: var(--gap-peer);
6915 }
6916
6917 .use-case-features {
6918 list-style: none;
6919 margin: 0 0 var(--gap-section);
6920 padding: 0;
6921 }
6922
6923 .use-case-features li {
6924 font-size: var(--text-note);
6925 padding-left: var(--gap-section);
6926 position: relative;
6927 margin-bottom: var(--gap-bound);
6928 }
6929
6930 .use-case-features li::before {
6931 content: "-";
6932 position: absolute;
6933 left: 0;
6934 opacity: 0.5;
6935 }
6936
6937 .use-case-tier {
6938 font-family: var(--font-mono);
6939 font-size: var(--text-fine);
6940 opacity: 0.5;
6941 }
6942
6943 /* ===========================================
6944 RESPONSIVE, 768px (tablet)
6945 =========================================== */
6946
6947 /* ==========================================================================
6948 Git Source Browser
6949 ========================================================================== */
6950
6951 .git-repo-header { margin-bottom: var(--gap-pane); }
6952 .git-repo-name { font-size: var(--text-head); margin: 0 0 var(--gap-bound); }
6953 .git-repo-name a { color: var(--content); text-decoration: none; }
6954 .git-repo-name a:hover { opacity: 0.6; }
6955 .git-repo-name .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
6956 .git-repo-desc { opacity: 0.6; margin: 0 0 var(--gap-section); font-size: var(--text-note); }
6957 .git-clone-url {
6958 font-family: var(--font-mono);
6959 font-size: var(--text-fine);
6960 padding: var(--gap-peer) var(--gap-group);
6961 background: var(--surface-overlay);
6962 border: 1px solid var(--border);
6963 border-radius: var(--radius-control);
6964 display: inline-block;
6965 user-select: all;
6966 margin-bottom: var(--gap-section);
6967 }
6968 .git-linked-project { margin-top: var(--gap-peer); font-size: var(--text-note); }
6969 .git-linked-project a { color: var(--content); }
6970
6971 /* Ref bar (branch/tag selector + nav links) */
6972 .git-ref-bar {
6973 display: flex;
6974 align-items: center;
6975 gap: var(--gap-section);
6976 margin-bottom: var(--gap-section);
6977 font-size: var(--text-note);
6978 }
6979 .git-ref-select {
6980 font-family: var(--font-mono);
6981 font-size: var(--text-fine);
6982 padding: var(--gap-bound) var(--gap-peer);
6983 background: var(--surface-overlay);
6984 border: 1px solid var(--border);
6985 border-radius: var(--radius-control);
6986 }
6987 .git-nav-links { display: flex; gap: var(--gap-section); }
6988 .git-nav-links a { text-decoration: none; color: var(--content); opacity: 0.6; }
6989 .git-nav-links a:hover,
6990 .git-nav-links a.is-selected { opacity: 1; }
6991
6992 /* Breadcrumb navigation */
6993 .git-breadcrumb {
6994 font-family: var(--font-mono);
6995 font-size: var(--text-note);
6996 margin-bottom: var(--gap-section);
6997 }
6998 .git-breadcrumb a { color: var(--content); text-decoration: none; }
6999 .git-breadcrumb a:hover { text-decoration: underline; }
7000 .git-breadcrumb .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
7001
7002 /* File tree table */
7003 .git-tree { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
7004 .git-tree th {
7005 text-align: left;
7006 padding: var(--gap-peer) var(--gap-group);
7007 border-bottom: 2px solid var(--border);
7008 font-family: var(--font-mono);
7009 font-size: var(--text-fine);
7010 opacity: 0.5;
7011 text-transform: uppercase;
7012 }
7013 .git-tree td { padding: var(--gap-peer) var(--gap-group); border-bottom: 1px solid var(--border); }
7014 .git-tree tr:last-child td { border-bottom: none; }
7015 .git-tree .icon { width: 1.5rem; text-align: center; opacity: 0.4; font-family: var(--font-mono); }
7016 .tree-icon-dir { font-weight: 700; color: var(--content); }
7017 .tree-icon-file { font-size: var(--text-note); }
7018 .git-tree .name a { text-decoration: none; color: var(--content); }
7019 .git-tree .name a:hover { text-decoration: underline; }
7020 .git-tree .size {
7021 text-align: right;
7022 opacity: 0.5;
7023 font-family: var(--font-mono);
7024 font-size: var(--text-fine);
7025 }
7026
7027 /* README rendering */
7028 .git-readme { margin-top: var(--gap-page); padding-top: var(--gap-pane); border-top: 1px solid var(--border); }
7029 .git-readme h2 { font-size: var(--text-body); opacity: 0.5; margin-bottom: var(--gap-section); }
7030 .git-readme-body { line-height: 1.6; }
7031 .git-readme-body h1,
7032 .git-readme-body h2,
7033 .git-readme-body h3 { margin-top: var(--gap-pane); margin-bottom: var(--gap-peer); }
7034 .git-readme-body pre {
7035 background: var(--surface-overlay);
7036 padding: var(--gap-section);
7037 border-radius: var(--radius-control);
7038 overflow-x: auto;
7039 font-family: var(--font-mono);
7040 font-size: var(--text-fine);
7041 }
7042 .git-readme-body code {
7043 font-family: var(--font-mono);
7044 font-size: var(--text-note);
7045 background: var(--surface-overlay);
7046 padding: var(--gap-bound);
7047 border-radius: var(--radius-fine);
7048 }
7049 .git-readme-body pre code { background: none; padding: 0; }
7050 .git-readme-body table { border-collapse: collapse; margin: var(--gap-section) 0; }
7051 .git-readme-body th,
7052 .git-readme-body td { border: 1px solid var(--border); padding: var(--gap-peer) var(--gap-group); }
7053
7054 /* File viewer */
7055 .git-file-header {
7056 display: flex;
7057 justify-content: space-between;
7058 align-items: center;
7059 padding: var(--gap-peer) var(--gap-group);
7060 background: var(--surface-overlay);
7061 border: 1px solid var(--border);
7062 border-bottom: none;
7063 border-radius: var(--radius-control) var(--radius-control) 0 0;
7064 font-size: var(--text-fine);
7065 }
7066 .git-file-meta {
7067 font-family: var(--font-mono);
7068 opacity: 0.6;
7069 }
7070 .git-file-actions a {
7071 font-family: var(--font-mono);
7072 text-decoration: none;
7073 color: var(--content);
7074 opacity: 0.6;
7075 font-size: var(--text-fine);
7076 }
7077 .git-file-actions a:hover { opacity: 1; }
7078 .git-file-content {
7079 border: 1px solid var(--border);
7080 border-radius: 0 0 var(--radius-control) var(--radius-control);
7081 overflow-x: auto;
7082 }
7083 .git-file-content table { border-collapse: collapse; width: 100%; }
7084 .git-file-content .line-number {
7085 width: 1px;
7086 white-space: nowrap;
7087 padding: 0 var(--gap-section);
7088 text-align: right;
7089 user-select: none;
7090 opacity: 0.3;
7091 font-family: var(--font-mono);
7092 font-size: var(--text-fine);
7093 border-right: 1px solid var(--border);
7094 vertical-align: top;
7095 }
7096 .git-file-content .line-code {
7097 padding: 0 var(--gap-section);
7098 white-space: pre;
7099 font-family: var(--font-mono);
7100 font-size: var(--text-fine);
7101 line-height: 1.5;
7102 }
7103 .git-file-content .line-code span {
7104 font-family: var(--font-mono);
7105 }
7106 .git-binary-notice {
7107 padding: var(--gap-page);
7108 text-align: center;
7109 opacity: 0.5;
7110 font-family: var(--font-mono);
7111 font-size: var(--text-note);
7112 border: 1px solid var(--border);
7113 border-radius: 0 0 var(--radius-control) var(--radius-control);
7114 }
7115
7116 /* Syntax highlighting (syntect class-based) */
7117 .git-file-content .source { color: var(--content); }
7118 .git-file-content .keyword,
7119 .git-file-content .storage { color: var(--syntax-keyword); }
7120 .git-file-content .string { color: var(--syntax-string); }
7121 .git-file-content .comment { color: var(--syntax-comment); font-style: italic; }
7122 .git-file-content .constant,
7123 .git-file-content .constant.numeric { color: var(--syntax-constant); }
7124 .git-file-content .entity.name { color: var(--syntax-entity); }
7125 .git-file-content .variable { color: var(--syntax-variable); }
7126 .git-file-content .support { color: var(--syntax-support); }
7127 .git-file-content .meta.preprocessor { color: var(--syntax-keyword); }
7128
7129 /* Commit list */
7130 .git-commit-list { list-style: none; }
7131 .git-commit { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7132 .git-commit:last-child { border-bottom: none; }
7133 .git-commit-message { font-size: var(--text-note); margin: 0 0 var(--gap-bound); }
7134 .git-commit-message .summary { font-weight: 600; }
7135 .git-commit-meta {
7136 font-family: var(--font-mono);
7137 font-size: var(--text-fine);
7138 opacity: 0.5;
7139 display: flex;
7140 gap: var(--gap-section);
7141 flex-wrap: wrap;
7142 }
7143 .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); }
7144 .git-commit-oid a { color: var(--content); text-decoration: none; }
7145 .git-commit-oid a:hover { text-decoration: underline; }
7146 .git-pagination {
7147 display: flex;
7148 justify-content: center;
7149 gap: var(--gap-section);
7150 margin-top: var(--gap-pane);
7151 padding-top: var(--gap-section);
7152 font-size: var(--text-note);
7153 }
7154 .git-pagination a { color: var(--content); text-decoration: none; }
7155 .git-pagination a:hover { text-decoration: underline; }
7156
7157 /* Release items on repo page */
7158 .git-release { margin-bottom: var(--gap-pane); padding: var(--gap-section); background: var(--surface-overlay); }
7159 .git-release-title { margin: 0 0 var(--gap-bound); font-size: var(--text-lead); }
7160 .git-release-title a { color: var(--content); text-decoration: none; }
7161 .git-release-meta { font-size: var(--text-note); opacity: 0.6; margin-bottom: var(--gap-peer); }
7162 .git-release-desc { font-size: var(--text-note); opacity: 0.8; margin-bottom: var(--gap-section); }
7163 .git-release-versions { font-size: var(--text-fine); }
7164 .git-release-version {
7165 display: flex;
7166 gap: var(--gap-section);
7167 align-items: center;
7168 padding: var(--gap-bound) 0;
7169 border-top: 1px solid var(--border);
7170 }
7171 .git-release-version .version-number { font-family: var(--font-mono); }
7172 .git-release-version .version-badge {
7173 font-size: var(--text-fine);
7174 padding: var(--gap-bound) var(--gap-peer);
7175 background: var(--content);
7176 color: var(--surface-page);
7177 border-radius: var(--radius-fine);
7178 }
7179 .git-release-version .version-meta { opacity: 0.5; }
7180
7181 /* Git commit detail + diff */
7182 .git-commit-detail { margin-bottom: var(--gap-pane); }
7183 .git-commit-full-message {
7184 font-family: var(--font-mono);
7185 font-size: var(--text-note);
7186 white-space: pre-wrap;
7187 line-height: 1.5;
7188 padding: var(--gap-section);
7189 background: var(--surface-overlay);
7190 border: 1px solid var(--border);
7191 margin-bottom: var(--gap-section);
7192 }
7193 .git-commit-detail-meta { font-size: var(--text-note); opacity: 0.7; line-height: 1.8; }
7194 .git-commit-detail-meta .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); word-break: break-all; }
7195 .git-commit-parents a { font-family: var(--font-mono); color: var(--content); text-decoration: none; }
7196 .git-commit-parents a:hover { text-decoration: underline; }
7197 /* Commit trailers, lifted out of the message body and shown as what they are */
7198 .git-commit-trailers {
7199 display: grid;
7200 grid-template-columns: max-content 1fr;
7201 gap: var(--gap-bound) var(--gap-section);
7202 font-size: var(--text-note);
7203 margin-bottom: var(--gap-section);
7204 }
7205 .git-commit-trailers dt { opacity: 0.6; }
7206 .git-commit-trailers dd { margin: 0; font-family: var(--font-mono); font-size: var(--text-fine); }
7207 /* Tags, with the annotation an annotated tag carries */
7208 .git-tag-list { list-style: none; }
7209 .git-tag { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7210 .git-tag:last-child { border-bottom: none; }
7211 .git-tag-name { font-size: var(--text-note); font-weight: 600; margin: 0 0 var(--gap-bound); }
7212 .git-tag-name a { color: var(--content); text-decoration: none; }
7213 .git-tag-name a:hover { text-decoration: underline; }
7214 .git-tag-lightweight { font-weight: 400; font-size: var(--text-fine); opacity: 0.5; }
7215 .git-tag-message {
7216 font-family: var(--font-mono);
7217 font-size: var(--text-note);
7218 white-space: pre-wrap;
7219 line-height: 1.5;
7220 padding: var(--gap-peer) var(--gap-section);
7221 border-left: 2px solid var(--border);
7222 margin-bottom: var(--gap-bound);
7223 }
7224 .git-tag-meta {
7225 font-family: var(--font-mono);
7226 font-size: var(--text-fine);
7227 opacity: 0.5;
7228 display: flex;
7229 gap: var(--gap-section);
7230 flex-wrap: wrap;
7231 }
7232 .git-tag-meta a { color: var(--content); text-decoration: none; }
7233 .git-tag-meta a:hover { text-decoration: underline; }
7234 /* Signature state. Neutral by default: only a bad signature gets a colour. */
7235 .git-signature { font-size: var(--text-fine); opacity: 0.7; }
7236 .git-signature a { color: var(--content); }
7237 .git-signature.is-verified { opacity: 1; }
7238 .git-signature.is-bad { color: var(--danger); opacity: 1; }
7239 /* refs/replace/*: object substitutions, listed but not applied while browsing */
7240 .git-replace-intro { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7241 .git-replace-intro p { margin: 0; }
7242 .git-replace-notice {
7243 font-size: var(--text-note);
7244 border-left: 2px solid var(--border);
7245 padding: var(--gap-peer) var(--gap-section);
7246 margin: 0 0 var(--gap-section);
7247 }
7248 .git-replace-table { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
7249 .git-replace-table th {
7250 text-align: left;
7251 font-weight: 400;
7252 opacity: 0.5;
7253 font-size: var(--text-fine);
7254 padding: var(--gap-bound) var(--gap-section) var(--gap-bound) 0;
7255 border-bottom: 1px solid var(--border);
7256 }
7257 .git-replace-table td { padding: var(--gap-peer) var(--gap-section) var(--gap-peer) 0; border-bottom: 1px solid var(--border); }
7258 .git-replace-table a { color: var(--content); text-decoration: none; }
7259 .git-replace-table a:hover { text-decoration: underline; }
7260 /* Git notes (refs/notes/*) shown against the object they annotate */
7261 .git-notes { margin-bottom: var(--gap-pane); }
7262 .git-note {
7263 border: 1px solid var(--border);
7264 border-left: 2px solid var(--content);
7265 margin-bottom: var(--gap-section);
7266 }
7267 .git-note-header {
7268 display: flex;
7269 align-items: baseline;
7270 justify-content: space-between;
7271 gap: var(--gap-group);
7272 padding: var(--gap-peer) var(--gap-section);
7273 border-bottom: 1px solid var(--border);
7274 font-size: var(--text-fine);
7275 }
7276 .git-note-namespace { font-family: var(--font-mono); }
7277 /* Sits beside the namespace rather than being spread across the header: the
7278 auto margin absorbs the free space `space-between` would otherwise put
7279 between the two, leaving the attribution on the right where it was. */
7280 .git-note-owned { margin-right: auto; opacity: 0.6; }
7281 .git-note-attribution { opacity: 0.6; }
7282 .git-note-namespaces {
7283 display: flex;
7284 flex-wrap: wrap;
7285 gap: var(--gap-group);
7286 padding: var(--gap-section) 0;
7287 border-bottom: 1px solid var(--border);
7288 margin-bottom: var(--gap-section);
7289 font-size: var(--text-note);
7290 }
7291 .git-note-namespaces a { color: var(--content); text-decoration: none; opacity: 0.6; }
7292 .git-note-namespaces a:hover { opacity: 1; }
7293 .git-note-namespaces a.is-selected { opacity: 1; text-decoration: underline; }
7294 .git-note-target-kind { opacity: 0.6; }
7295 .git-explore-intro { margin: 0 0 var(--gap-section); font-size: var(--text-note); opacity: 0.8; }
7296 .git-notes-fetch { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7297 .git-notes-fetch p { margin: 0 0 var(--gap-peer); }
7298 .git-note-feed-cap { font-size: var(--text-fine); opacity: 0.6; padding-top: var(--gap-section); }
7299 .git-note-search {
7300 display: flex;
7301 flex-wrap: wrap;
7302 align-items: center;
7303 gap: var(--gap-group);
7304 padding-bottom: var(--gap-section);
7305 font-size: var(--text-note);
7306 }
7307 .git-note-search input[type="search"] { flex: 1 1 16rem; }
7308 .git-note-search label { display: flex; align-items: center; gap: var(--gap-bound); opacity: 0.8; }
7309 .git-note-excerpt {
7310 margin: var(--gap-bound) 0 0;
7311 font-size: var(--text-note);
7312 opacity: 0.7;
7313 }
7314 .git-note-paging {
7315 display: flex;
7316 gap: var(--gap-group);
7317 align-items: center;
7318 padding-top: var(--gap-section);
7319 font-size: var(--text-note);
7320 }
7321 .git-note-paging span { font-size: var(--text-fine); opacity: 0.6; }
7322 .git-note-feed-link { padding-top: var(--gap-peer); font-size: var(--text-fine); }
7323 .git-note-badge {
7324 font-size: var(--text-fine);
7325 padding: var(--gap-bound) var(--gap-peer);
7326 border: 1px solid var(--border);
7327 border-radius: var(--radius-fine);
7328 color: var(--content);
7329 text-decoration: none;
7330 opacity: 0.7;
7331 }
7332 .git-note-badge:hover { opacity: 1; }
7333 .git-note-body { padding: var(--gap-section); font-size: var(--text-note); }
7334 .git-note-body > :first-child { margin-top: 0; }
7335 .git-note-body > :last-child { margin-bottom: 0; }
7336 /* Writing a note. Only rendered for a reader who can push. */
7337 .git-notes-edit {
7338 border-top: 1px solid var(--border);
7339 padding-top: var(--gap-section);
7340 margin-bottom: var(--gap-pane);
7341 }
7342 .git-notes-edit textarea { width: 100%; font-family: var(--font-mono); font-size: var(--text-note); }
7343 .git-note-form { margin-bottom: var(--gap-section); }
7344 .git-note-delete { margin-bottom: var(--gap-pane); }
7345 .git-note-merged {
7346 border-left: 2px solid var(--content);
7347 padding: var(--gap-peer) var(--gap-section);
7348 margin-bottom: var(--gap-section);
7349 font-size: var(--text-note);
7350 }
7351 .git-diff-stats {
7352 font-size: var(--text-note);
7353 padding: var(--gap-section) 0;
7354 margin-bottom: var(--gap-section);
7355 border-bottom: 1px solid var(--border);
7356 }
7357 .git-diff-stats .additions { color: var(--diff-add); }
7358 .git-diff-stats .deletions { color: var(--diff-del); }
7359 .git-diff-file { margin-bottom: var(--gap-pane); border: 1px solid var(--border); }
7360 .git-diff-file-header {
7361 display: flex;
7362 align-items: center;
7363 gap: var(--gap-peer);
7364 padding: var(--gap-peer) var(--gap-section);
7365 background: var(--surface-overlay);
7366 border-bottom: 1px solid var(--border);
7367 font-size: var(--text-note);
7368 flex-wrap: wrap;
7369 }
7370 .git-diff-file-header a { color: var(--content); text-decoration: none; }
7371 .git-diff-file-header a:hover { text-decoration: underline; }
7372 .git-diff-file-stats { margin-left: auto; font-size: var(--text-fine); }
7373 .git-diff-file-stats .additions { color: var(--diff-add); }
7374 .git-diff-file-stats .deletions { color: var(--diff-del); margin-left: var(--gap-bound); }
7375 .diff-status-added, .diff-status-deleted, .diff-status-modified, .diff-status-renamed {
7376 display: inline-block;
7377 width: 1.2rem;
7378 text-align: center;
7379 font-size: var(--text-fine);
7380 font-weight: 700;
7381 font-family: var(--font-mono);
7382 border-radius: var(--radius-fine);
7383 line-height: 1.4;
7384 }
7385 .diff-status-added { background: color-mix(in oklch, var(--success) 14%, var(--surface-page)); color: var(--success); }
7386 .diff-status-deleted { background: color-mix(in oklch, var(--danger) 14%, var(--surface-page)); color: var(--danger); }
7387 .diff-status-modified { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
7388 .diff-status-renamed { background: color-mix(in oklch, var(--info) 14%, var(--surface-page)); color: var(--info); }
7389 .git-diff-table {
7390 width: 100%;
7391 border-collapse: collapse;
7392 font-family: var(--font-mono);
7393 font-size: var(--text-fine);
7394 table-layout: fixed;
7395 }
7396 .git-diff-hunk-header td {
7397 padding: var(--gap-bound) var(--gap-peer);
7398 background: var(--highlight-faint);
7399 color: var(--content);
7400 opacity: 0.6;
7401 font-size: var(--text-fine);
7402 }
7403 .git-diff-lineno {
7404 width: 3.5rem;
7405 padding: 0 var(--gap-peer);
7406 text-align: right;
7407 color: var(--content);
7408 opacity: 0.35;
7409 user-select: none;
7410 vertical-align: top;
7411 }
7412 .git-diff-origin {
7413 width: 1rem;
7414 text-align: center;
7415 user-select: none;
7416 vertical-align: top;
7417 }
7418 .git-diff-content {
7419 padding: 0 var(--gap-peer);
7420 white-space: pre-wrap;
7421 word-break: break-all;
7422 }
7423 .git-diff-line-add { background: var(--diff-add-bg); }
7424 .git-diff-line-add .git-diff-origin { color: var(--diff-add); }
7425 .git-diff-line-del { background: var(--diff-del-bg); }
7426 .git-diff-line-del .git-diff-origin { color: var(--diff-del); }
7427 .git-diff-line-ctx { background: transparent; }
7428 .git-diff-truncated { padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.5; font-style: italic; }
7429
7430 /* Git file view line linking */
7431 .git-file-content .line-number a {
7432 color: inherit;
7433 text-decoration: none;
7434 display: block;
7435 }
7436 .git-file-content .line-number a:hover { opacity: 0.7; }
7437 .git-file-content tr:target td,
7438 .git-file-content tr.highlighted td { background: var(--highlight-faint); }
7439
7440 /* Git blame view */
7441 .git-blame-content { overflow-x: auto; border: 1px solid var(--border); }
7442 .git-blame-content table { border-collapse: collapse; width: 100%; font-size: var(--text-fine); }
7443 .git-blame-info {
7444 padding: 0 var(--gap-peer);
7445 font-family: var(--font-mono);
7446 white-space: nowrap;
7447 width: 4.5rem;
7448 }
7449 .git-blame-info a { color: var(--content); text-decoration: none; }
7450 .git-blame-info a:hover { text-decoration: underline; }
7451 .git-blame-author {
7452 padding: 0 var(--gap-bound);
7453 font-size: var(--text-fine);
7454 opacity: 0.5;
7455 white-space: nowrap;
7456 max-width: 8rem;
7457 overflow: hidden;
7458 text-overflow: ellipsis;
7459 }
7460 .git-blame-date {
7461 padding: 0 var(--gap-bound);
7462 font-size: var(--text-fine);
7463 opacity: 0.35;
7464 white-space: nowrap;
7465 width: 5.5rem;
7466 }
7467 .git-blame-content .line-number {
7468 padding: 0 var(--gap-peer);
7469 text-align: right;
7470 opacity: 0.35;
7471 user-select: none;
7472 width: 3rem;
7473 font-family: var(--font-mono);
7474 }
7475 .git-blame-content .line-code {
7476 padding: 0 var(--gap-peer);
7477 white-space: pre;
7478 font-family: var(--font-mono);
7479 }
7480 .git-blame-boundary td { border-top: 1px solid var(--border); }
7481
7482 /* Git user repos listing */
7483 .git-repos-list { list-style: none; }
7484 .git-repos-item { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7485 .git-repos-item:last-child { border-bottom: none; }
7486 .git-repos-item-header { display: flex; align-items: center; gap: var(--gap-peer); }
7487 .git-repos-item-name {
7488 font-size: var(--text-lead);
7489 font-weight: 600;
7490 color: var(--content);
7491 text-decoration: none;
7492 }
7493 .git-repos-item-name:hover { text-decoration: underline; }
7494 .git-repos-visibility {
7495 font-size: var(--text-fine);
7496 padding: var(--gap-bound) var(--gap-peer);
7497 border: 1px solid var(--border);
7498 border-radius: var(--radius-fine);
7499 opacity: 0.5;
7500 }
7501 .git-repos-item-desc { font-size: var(--text-note); opacity: 0.6; margin: var(--gap-bound) 0 0; }.git-repos-instructions p { margin-bottom: var(--gap-peer); }
7502 .git-repos-owner { opacity: 0.6; }
7503 .git-repos-item-name .sep { opacity: 0.4; }
7504 .git-file-log-label {
7505 font-family: var(--font-mono);
7506 font-size: var(--text-fine);
7507 opacity: 0.5;
7508 margin-right: var(--gap-bound);
7509 }
7510
7511 /* Site-wide footer */
7512 .site-footer {
7513 margin-top: var(--gap-page);
7514 padding: var(--gap-page) 0;
7515 text-align: center;
7516 font-family: var(--font-mono);
7517 font-size: var(--text-fine);
7518 opacity: 0.5;
7519 }
7520 .site-footer-links { margin-bottom: var(--gap-peer); }
7521 .site-footer-links a { margin: 0 var(--gap-section); color: inherit; text-decoration: none; }
7522 .site-footer-links a:hover { text-decoration: underline; }
7523
7524 @media (max-width: 839px) {
7525 /* Git browser mobile */
7526 .git-ref-bar { flex-wrap: wrap; gap: var(--gap-peer); }
7527 .git-repo-name { font-size: var(--text-subhead); }
7528 .git-clone-url { font-size: var(--text-fine); word-break: break-all; }
7529 .git-tree .icon { display: none; }
7530 .git-tree td, .git-tree th { padding: var(--gap-bound) var(--gap-peer); }
7531 .git-file-header { flex-direction: column; align-items: flex-start; gap: var(--gap-bound); }
7532 .git-file-content .line-number { padding: 0 var(--gap-peer); }
7533 .git-file-content .line-code { padding: 0 var(--gap-peer); font-size: var(--text-fine); }
7534 .git-commit-meta { flex-direction: column; gap: var(--gap-bound); }
7535 .git-release-version { flex-wrap: wrap; gap: var(--gap-peer); }
7536 .git-diff-file-header { font-size: var(--text-fine); }
7537 .git-diff-table { font-size: var(--text-fine); }
7538 .git-diff-lineno { width: 2.5rem; font-size: var(--text-fine); }
7539 .git-blame-author { display: none; }
7540 .git-blame-date { display: none; }
7541
7542 .container {
7543 padding: var(--gap-section);
7544 }
7545
7546 .padded-page {
7547 padding: var(--gap-section);
7548 }
7549
7550 .form-row {
7551 grid-template-columns: 1fr;
7552 }
7553
7554 .tab {
7555 padding: var(--gap-group) var(--gap-section);
7556 font-size: var(--text-note);
7557 }
7558
7559 .tab-content {
7560 padding: var(--gap-section);
7561 }
7562
7563 .data-table th,
7564 .data-table td {
7565 padding: var(--gap-peer) var(--gap-group);
7566 font-size: var(--text-note);
7567 }
7568
7569 .modal {
7570 max-width: 95%;
7571 }
7572
7573 .stats-grid {
7574 grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
7575 }
7576
7577 .content-section {
7578 padding: var(--gap-section);
7579 }
7580
7581 .action-buttons {
7582 flex-direction: column;
7583 }
7584
7585 .action-buttons button {
7586 width: 100%;
7587 }
7588
7589 .analytics-grid {
7590 grid-template-columns: 1fr;
7591 }
7592
7593 /* Hamburger menu */
7594 .nav-toggle-label {
7595 display: block;
7596 }
7597
7598 .header-search {
7599 display: none;
7600 flex: none;
7601 margin: 0;
7602 width: 100%;
7603 }
7604
7605 .nav-toggle-checkbox:checked ~ .header-search {
7606 display: block;
7607 position: absolute;
7608 top: 100%;
7609 left: 0;
7610 right: 0;
7611 padding: var(--gap-section) var(--gap-pane);
7612 background: var(--surface-page);
7613 border-bottom: 1px solid var(--border);
7614 z-index: var(--z-header);
7615 }
7616
7617 .site-header nav {
7618 display: none;
7619 position: absolute;
7620 top: 100%;
7621 left: 0;
7622 right: 0;
7623 background: var(--surface-page);
7624 border-bottom: 1px solid var(--border);
7625 padding: var(--gap-section) var(--gap-pane);
7626 z-index: var(--z-nav);
7627 }
7628
7629 .site-header {
7630 position: relative;
7631 }
7632
7633 .nav-toggle-checkbox:checked ~ nav {
7634 display: block;
7635 }
7636
7637 .site-header .nav-links {
7638 flex-direction: column;
7639 gap: var(--gap-section);
7640 }
7641
7642 /* Animate hamburger to X when open */
7643 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(1) {
7644 transform: rotate(45deg) translate(5px, 5px);
7645 }
7646
7647 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(2) {
7648 opacity: 0;
7649 }
7650
7651 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(3) {
7652 transform: rotate(-45deg) translate(5px, -5px);
7653 }
7654 }
7655
7656 /* Doc pages, mobile */
7657 @media (max-width: 599px) {
7658 .doc-title { font-size: var(--text-title); }
7659 .doc-body { font-size: var(--text-body); }
7660 .docs-index-title { font-size: var(--text-title); }
7661 }
7662
7663 /* ===========================================
7664 RESPONSIVE, 480px (phone)
7665 =========================================== */
7666
7667 @media (max-width: 599px) {
7668 .container {
7669 padding: var(--gap-section);
7670 }
7671
7672 .padded-page {
7673 padding: var(--gap-section);
7674 }
7675
7676 h1 {
7677 font-size: var(--text-title);
7678 }
7679
7680 .centered-page h1 {
7681 font-size: var(--text-hero);
7682 }
7683
7684 .login-container,
7685 .form-container {
7686 max-width: 100%;
7687 padding: var(--gap-section);
7688 }
7689
7690 .big-button {
7691 width: 100%;
7692 }
7693
7694 .stats-grid {
7695 grid-template-columns: repeat(2, 1fr);
7696 }
7697
7698 .tab {
7699 padding: var(--gap-peer) var(--gap-section);
7700 font-size: var(--text-note);
7701 }
7702
7703 .tab-content {
7704 padding: var(--gap-section);
7705 }
7706
7707 .content-section {
7708 padding: var(--gap-section);
7709 }
7710
7711 .section-header {
7712 flex-direction: column;
7713 align-items: flex-start;
7714 gap: var(--gap-section);
7715 }
7716
7717 /* Landing page (merged from 500px) */
7718 .landing-fork,
7719 .tier-selector,
7720 .feature-grid,
7721 .use-case-grid,
7722 .use-case-grid-2col {
7723 grid-template-columns: 1fr;
7724 }
7725
7726 .explore-links {
7727 flex-direction: column;
7728 align-items: center;
7729 gap: var(--gap-section);
7730 }
7731
7732 .pricing-input {
7733 font-size: var(--text-title);
7734 }
7735
7736 .pricing-currency {
7737 font-size: var(--text-title);
7738 }
7739
7740 .calc-verdict {
7741 font-size: var(--text-body);
7742 }
7743
7744 .calc-dials {
7745 grid-template-columns: 1fr;
7746 }
7747
7748 .secondary-links {
7749 flex-direction: column;
7750 align-items: center;
7751 gap: var(--gap-section);
7752 }
7753
7754 .landing-cta {
7755 flex-direction: column;
7756 gap: var(--gap-section);
7757 }
7758
7759 .toast-container {
7760 left: 0.75rem;
7761 right: 0.75rem;
7762 bottom: 0.75rem;
7763 }
7764
7765 .toast {
7766 max-width: 100%;
7767 }
7768 }
7769
7770 /* ===========================================
7771 DISCOVER PAGE
7772 =========================================== */
7773
7774 .discover-layout { display: grid; grid-template-columns: 200px 1fr; gap: var(--gap-pane); }
7775 .discover-layout.no-sidebar { grid-template-columns: 1fr; }
7776 .discover-layout.no-sidebar .discover-sidebar { display: none; }
7777 .discover-sidebar { font-size: var(--text-note); }
7778 .filter-section { margin-bottom: var(--gap-section); }
7779 .filter-title { font-family: var(--font-heading); font-weight: bold; font-size: var(--text-note); margin-bottom: var(--gap-peer); color: var(--content); }
7780 .filter-list { list-style: none; }
7781 /* The row carries selection (`.is-selected` recipe) and hover; the filter itself
7782 is a real <button> child so Enter/Space work without scripting. */
7783 .filter-item { display: flex; align-items: center; gap: var(--gap-bound); transition: opacity 0.1s ease; }
7784 .filter-item:hover { opacity: 0.6; }
7785 .filter-item.is-selected { font-weight: bold; }
7786 .filter-item .count { opacity: 0.4; font-size: var(--text-fine); }
7787 .tag-follow-btn { background: none; border: 1px solid var(--border); cursor: pointer; font-family: var(--font-mono); font-size: var(--text-fine); opacity: 0.55; padding: var(--gap-bound) var(--gap-peer); color: var(--content); }
7788 .tag-follow-btn:hover { opacity: 1; }
7789 .tag-follow-btn.is-selected { opacity: 0.85; border-color: var(--focus-ring); background: var(--highlight-faint); }
7790 .filter-checkbox { display: flex; align-items: center; gap: var(--gap-peer); padding: var(--gap-bound) 0; cursor: pointer; }
7791 .filter-checkbox input { width: auto; margin: 0; }
7792 .price-inputs { display: flex; gap: var(--gap-bound); align-items: center; margin-bottom: var(--gap-peer); }
7793 .price-inputs input { width: 60px; padding: var(--gap-bound); font-size: var(--text-fine); background: var(--surface-sunken); border: none; }
7794 .price-inputs span { opacity: 0.4; }
7795 .discover-main { min-width: 0; }
7796
7797 /* ---- Discover sidebar: tag spine ----------------------------------------
7798 Tags are the sidebar's structure, not one facet among several, so the spine
7799 reads as the primary surface and .filter-refine below it is subordinate.
7800 Everything here composes existing primitives: .filter-item carries the row
7801 and its .is-selected recipe, .search-suggestions carries the dropdown shell,
7802 .breadcrumb carries the crumb idiom. */
7803 .tag-spine { padding-bottom: var(--gap-section); border-bottom: 1px solid var(--border); }
7804
7805 /* Selected tags. Also the active-filter summary, so they use the canonical
7806 selected tint rather than a new colour. */
7807 .tag-chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--gap-bound); margin-bottom: var(--gap-section); }
7808 .tag-chip { display: flex; align-items: center; gap: var(--gap-bound); padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine); background: var(--highlight-faint); border: 1px solid var(--focus-ring); }
7809 .tag-chip-label { overflow-wrap: anywhere; }
7810 .tag-chip-remove { text-decoration: none; color: var(--content-secondary); font-size: var(--text-body); line-height: 1; padding: 0 var(--gap-bound); }
7811 .tag-chip-remove:hover { color: var(--content); }
7812 .tag-chip-clear { background: none; border: 1px dashed var(--border-strong); }
7813 .tag-chip-clear a { text-decoration: none; color: var(--content-secondary); font-size: var(--text-fine); }
7814 .tag-chip-clear a:hover { color: var(--content); }
7815
7816 /* Typeahead. Shares the .search-suggestions shell; it toggles with the
7817 `hidden` attribute rather than an inline display, so the shared rule's
7818 display:none has to be overridden when open. */
7819 .tag-combobox { position: relative; margin-bottom: var(--gap-section); }
7820 .tag-combobox-input { width: 100%; padding: var(--gap-bound); font-size: var(--text-fine); background: var(--surface-sunken); border: none; }
7821 .tag-suggest-list { list-style: none; }
7822 .tag-suggest-list:not([hidden]) { display: block; }
7823 .tag-suggest-item { gap: var(--gap-peer); }
7824
7825 /* Drill-down. A rung either selects (assignable leaf) or navigates (category);
7826 rows with both put the chevron after the label. */
7827 .tag-crumbs { list-style: none; display: flex; flex-wrap: wrap; font-family: var(--font-mono); font-size: var(--text-fine); color: var(--content-muted); margin-bottom: var(--gap-peer); }
7828 .tag-crumbs li + li::before { content: "/"; opacity: 0.4; margin: 0 var(--gap-bound); }
7829 .tag-crumbs a { color: var(--content); text-decoration: none; }
7830 .tag-crumbs a:hover { text-decoration: underline; }
7831 .tag-drill-select { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7832 .tag-drill-select input { width: auto; margin: 0; }
7833 .tag-drill-select span:first-of-type { flex: 1; }
7834 .tag-drill-into { display: flex; align-items: center; gap: var(--gap-peer); flex: 1; text-decoration: none; color: inherit; padding: var(--gap-bound) 0; }
7835 .tag-drill-into span:first-child { flex: 1; }
7836 .tag-drill-chevron { opacity: 0.4; }
7837
7838 /* ---- Discover sidebar: refine block -------------------------------------
7839 Subordinate to the spine: same controls, quieter. */
7840 .filter-refine { margin-top: var(--gap-section); }
7841 .filter-fieldset { border: none; padding: 0; margin: 0; min-width: 0; }
7842 .filter-check { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7843 .filter-check input { width: auto; margin: 0; }
7844 .filter-check span:first-of-type { flex: 1; }
7845
7846 /* A zero-count facet stays visible but inert, so the shape of the catalog
7847 stays legible as filters narrow. No existing rule covers a disabled input
7848 inside a label, so this is the checkbox counterpart of the button rule.
7849 `.is-empty` covers the drill rungs, which are links and carry no input at
7850 all; they were rendering identically to rungs with items. */
7851 .filter-item:has(input:disabled), .filter-item.is-empty { opacity: 0.5; }
7852 .filter-item:has(input:disabled) .filter-check { cursor: not-allowed; }
7853
7854 /* Price buckets are links carrying a range, not form state. */
7855 .price-buckets { margin-top: var(--gap-peer); }
7856 .price-bucket { flex: 1; display: flex; align-items: center; justify-content: space-between; gap: var(--gap-peer); text-decoration: none; color: inherit; padding: var(--gap-bound) 0; }
7857
7858 .mode-toggle { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
7859 .toggle-btn { background: var(--surface-sunken); border: none; padding: var(--gap-peer) var(--gap-section); font-size: var(--text-note); font-family: inherit; cursor: pointer; transition: background 0.1s ease, opacity 0.1s ease; }
7860 .toggle-btn:hover { opacity: 0.7; }
7861 .toggle-btn.is-selected { background: var(--primary-dark); color: var(--primary-light); }
7862 .table-controls { background: var(--surface-sunken); display: flex; align-items: center; gap: var(--gap-section); padding: var(--gap-peer) var(--gap-section); }
7863 .search-field { flex: 1 1 0; background: var(--surface-page); border: none; padding: var(--gap-peer) var(--gap-section); font-family: inherit; font-size: var(--text-note); color: var(--content); min-width: 0; }
7864 .search-field:focus { outline: 2px solid var(--action); }
7865 .search-field::placeholder { opacity: 0.5; }
7866 .table-meta { font-size: var(--text-fine); opacity: 0.6; white-space: nowrap; flex-shrink: 0; }
7867 .sort-select { background: var(--surface-page); border: none; padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine); font-family: inherit; cursor: pointer; width: auto; flex-shrink: 0; }
7868 .table-header { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: var(--gap-peer); padding: var(--gap-peer) var(--gap-section); background: var(--surface-overlay); font-size: var(--text-fine); opacity: 0.7; text-transform: uppercase; letter-spacing: 0.03em; }
7869 .table-header.projects-header { grid-template-columns: 1fr 100px 80px 70px; }
7870 /* Column headers describe the list layout only; hidden when the grid view is active. */
7871 .table-header.is-hidden { display: none; }
7872 .col-right { text-align: right; }
7873 .results-table { border: 1px solid var(--border); border-top: none; }
7874 .table-row { /* respec-ok: a five-column grid template. The generated `.table-row` sets a display
7875 but has no vocabulary for column tracks, so the tracks and the display that carries
7876 them are stated together here. */ display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: var(--gap-peer); padding: var(--gap-peer) var(--gap-section); align-items: center; text-decoration: none; color: var(--content); font-size: var(--text-note); border-bottom: 1px solid var(--border); transition: background 0.1s ease; }
7877 .table-row-item { grid-template-columns: 1fr 30px; padding: 0; gap: 0; }
7878 .table-row-item .table-row-link { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: var(--gap-peer); padding: var(--gap-peer) var(--gap-section); align-items: center; text-decoration: none; color: inherit; flex: 1; }
7879 .table-row-item .row-save { display: flex; align-items: center; justify-content: center; }
7880 .table-row.project-row { grid-template-columns: 1fr 100px 80px 70px; }
7881 .table-row:last-child { border-bottom: none; }
7882 .table-row:hover { background: var(--surface-sunken); text-decoration: none; }
7883 .row-type { font-size: var(--text-fine); background: var(--surface-sunken); padding: var(--gap-bound) var(--gap-peer); text-align: center; opacity: 0.7; }
7884 .row-info { min-width: 0; display: flex; flex-direction: column; gap: var(--gap-bound); }
7885 .row-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
7886 .row-creator { font-size: var(--text-fine); opacity: 0.5; }.row-category { font-size: var(--text-fine); opacity: 0.6; }
7887 .row-price { text-align: right; }
7888 .row-items { text-align: right; opacity: 0.6; font-size: var(--text-fine); }
7889 .row-date { text-align: right; opacity: 0.5; font-size: var(--text-fine); }.pagination { display: flex; gap: var(--gap-bound); }
7890 .pagination button { background: var(--surface-page); border: none; padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); cursor: pointer; }
7891 .pagination button:disabled { opacity: 0.3; cursor: not-allowed; }
7892 .pagination button.is-selected { background: var(--primary-dark); color: var(--primary-light); }
7893
7894 /* Issue tabs (open / closed) inside the git browser */
7895 .issue-tabs { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
7896 .issue-tab {
7897 padding: var(--gap-peer) var(--gap-section);
7898 border: 1px solid var(--border);
7899 background: var(--surface-overlay);
7900 font-family: var(--font-mono);
7901 font-size: var(--text-note);
7902 color: var(--content);
7903 text-decoration: none;
7904 opacity: 0.7;
7905 }
7906 .issue-tab:hover { opacity: 1; }
7907 .issue-tab.is-selected {
7908 opacity: 1;
7909 background: var(--highlight-faint);
7910 border-color: var(--focus-ring);
7911 }
7912 .page-info { opacity: 0.6; }
7913 .padded-page .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--gap-section); padding-bottom: var(--gap-section); border-bottom: 1px solid var(--border); }
7914 .padded-page .page-header h1 { font-size: var(--text-head); text-align: center; position: absolute; left: 50%; transform: translateX(-50%); }
7915 .padded-page .page-header nav { margin: 0; }
7916 .padded-page .page-header .nav-links { gap: var(--gap-pane); }
7917 .discover-filter-toggle {
7918 display: none;
7919 font-family: var(--font-mono);
7920 font-size: var(--text-note);
7921 padding: var(--gap-peer) var(--gap-section);
7922 background: var(--surface-overlay);
7923 border: 1px solid var(--border);
7924 cursor: pointer;
7925 margin-bottom: var(--gap-section);
7926 }
7927
7928 .discover-filter-toggle.active { background: var(--surface-sunken); border-color: var(--border-strong); }
7929
7930 .discover-filter-toggle .filter-count {
7931 background: var(--action);
7932 color: var(--primary-light);
7933 font-size: var(--text-fine);
7934 padding: var(--gap-bound) var(--gap-peer);
7935 border-radius: var(--radius-control);
7936 margin-left: var(--gap-bound);
7937 }
7938
7939 @media (max-width: 839px) {
7940 .discover-filter-toggle { display: inline-block; }
7941 .discover-layout { grid-template-columns: 1fr; }
7942 .discover-sidebar { display: none; }
7943 /* Open state lives on the layout, not the sidebar: the sidebar is replaced
7944 by an out-of-band swap on every filter change and would lose the class. */
7945 .discover-layout.filters-open .discover-sidebar { display: block; margin-bottom: var(--gap-section); }
7946 .table-header, .table-row { grid-template-columns: 40px 1fr 60px; }
7947 .table-row-item { grid-template-columns: 1fr 30px; }
7948 .table-row-item .table-row-link { grid-template-columns: 40px 1fr 60px; }
7949 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr 70px; }
7950 .row-date, .row-items, .row-category { display: none; }
7951 .table-header span:nth-child(3), .table-header span:nth-child(4), .table-header span:nth-child(5) { display: none; }
7952 .table-header.projects-header span:nth-child(3), .table-header.projects-header span:nth-child(4) { display: none; }
7953 }
7954
7955 @media (max-width: 599px) {
7956 .table-header, .table-row { grid-template-columns: 1fr 60px; }
7957 .table-row-item { grid-template-columns: 1fr 30px; }
7958 .table-row-item .table-row-link { grid-template-columns: 1fr 60px; }
7959 .row-type { display: none; }
7960 .table-header span:first-child { display: none; }
7961 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr; }
7962 .table-header.projects-header span:nth-child(2) { display: none; }
7963 .row-price { font-size: var(--text-fine); }
7964 .search-field { min-width: 0; width: 100%; }
7965 .table-controls { flex-wrap: wrap; }
7966 .mode-toggle { flex-wrap: wrap; }
7967 .table-footer { flex-direction: column; gap: var(--gap-peer); align-items: flex-start; }
7968 }
7969
7970 /* ── Feature Cards & Suggestion Input (shared by wizard + settings) ── */
7971
7972 .type-grid {
7973 display: grid;
7974 grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
7975 gap: var(--gap-section);
7976 margin-bottom: var(--gap-section);
7977 }
7978
7979 /* Canonical selectable-card primitive. One recipe; per-context size modifiers.
7980 Used by wizard type pickers, pricing-model pickers, content-choice pickers,
7981 monetization-mode pickers, anywhere a radio-like choice is rendered as a card. */
7982 .card--selectable {
7983 cursor: pointer;
7984 display: block;
7985 }
7986 .card--selectable input[type="radio"],
7987 .card--selectable input[type="checkbox"] {
7988 position: absolute;
7989 opacity: 0;
7990 pointer-events: none;
7991 }
7992
7993 .card--selectable-inner {
7994 display: flex;
7995 flex-direction: column;
7996 align-items: center;
7997 justify-content: center;
7998 padding: var(--gap-section);
7999 border: 2px solid var(--border);
8000 border-radius: var(--radius-square);
8001 background: var(--surface-page);
8002 text-align: center;
8003 transition: border-color 0.15s, background 0.15s;
8004 min-height: 60px;
8005 }
8006 /* Variant: column-aligned content (text-heavy pricing cards). */
8007 .card--selectable.is-text-heavy .card--selectable-inner {
8008 align-items: stretch;
8009 text-align: left;
8010 padding: var(--gap-section);
8011 }
8012
8013 .card--selectable input:checked + .card--selectable-inner,
8014 .card--selectable.is-selected .card--selectable-inner {
8015 border-color: var(--action);
8016 background: var(--highlight-faint);
8017 }
8018
8019 .card--selectable:hover .card--selectable-inner {
8020 background: var(--surface-sunken);
8021 }
8022
8023 /* Focus. The card is a <label> wrapping a visually hidden radio, so the focus
8024 lands on the input and the ring has to be drawn on the inner surface. The
8025 <a> form of the card takes focus itself. */
8026 .card--selectable:focus-visible,
8027 .card--selectable input:focus-visible + .card--selectable-inner {
8028 outline: 2px solid var(--focus-ring);
8029 outline-offset: 2px;
8030 }
8031
8032 .card--selectable-label {
8033 font-weight: bold;
8034 font-size: var(--text-note);
8035 }
8036
8037 .card--selectable-desc {
8038 font-size: var(--text-fine);
8039 color: var(--content-muted);
8040 margin-top: var(--gap-bound);
8041 }
8042
8043 /* Disabled selectable card, visually dimmed, ignores pointer events. */
8044 .card--selectable.is-disabled {
8045 opacity: 0.7;
8046 pointer-events: none;
8047 }
8048
8049 .suggestion-input {
8050 position: relative;
8051 }
8052
8053 .suggestion-dropdown {
8054 display: none;
8055 position: absolute;
8056 top: 100%;
8057 left: 0;
8058 right: 0;
8059 background: var(--surface-page);
8060 border: 1px solid var(--border);
8061 border-top: none;
8062 border-radius: 0 0 var(--radius-square) var(--radius-square);
8063 max-height: 200px;
8064 overflow-y: auto;
8065 z-index: var(--z-dropdown);
8066 box-shadow: var(--elevation-overlay);
8067 }
8068
8069 .suggestion-dropdown.open {
8070 display: block;
8071 }
8072
8073 .suggestion-item,
8074 .tag-suggest-item {
8075 padding: var(--gap-peer) var(--gap-section);
8076 cursor: pointer;
8077 font-size: var(--text-note);
8078 }
8079
8080 .suggestion-item:hover {
8081 background: var(--surface-overlay);
8082 }
8083
8084 .suggestion-create {
8085 font-style: italic;
8086 opacity: 0.8;
8087 border-top: 1px solid var(--border);
8088 }
8089
8090 /* ── Tips ── */
8091
8092 .tip-section {
8093 margin-top: var(--gap-section);
8094 text-align: center;
8095 }
8096
8097 .tip-toggle {
8098 font-family: var(--font-mono);
8099 font-size: var(--text-note);
8100 padding: var(--gap-peer) var(--gap-pane);
8101 background: none;
8102 border: 1px solid var(--content);
8103 color: var(--content);
8104 cursor: pointer;
8105 text-decoration: none;
8106 display: inline-block;
8107 }
8108
8109 .tip-toggle:hover {
8110 background: var(--content);
8111 color: var(--surface-page);
8112 }
8113
8114 .tip-form {
8115 margin-top: var(--gap-section);
8116 display: flex;
8117 flex-direction: column;
8118 gap: var(--gap-section);
8119 max-width: 300px;
8120 margin-left: auto;
8121 margin-right: auto;
8122 text-align: left;
8123 }
8124
8125 .tip-form.hidden {
8126 display: none;
8127 }
8128
8129 .tip-amount-row {
8130 display: flex;
8131 align-items: center;
8132 gap: var(--gap-bound);
8133 }
8134
8135 .tip-amount-row .pricing-currency {
8136 font-size: var(--text-subhead);
8137 opacity: 0.7;
8138 }
8139
8140 .tip-amount-input {
8141 width: 80px;
8142 font-size: var(--text-subhead);
8143 padding: var(--gap-peer);
8144 border: 1px solid var(--border);
8145 background: var(--surface-overlay);
8146 font-family: var(--font-mono);
8147 }
8148
8149 .tip-message-input {
8150 width: 100%;
8151 font-size: var(--text-note);
8152 padding: var(--gap-peer);
8153 border: 1px solid var(--border);
8154 background: var(--surface-overlay);
8155 font-family: var(--font-body);
8156 resize: vertical;
8157 }
8158
8159 .tip-submit {
8160 font-family: var(--font-mono);
8161 font-size: var(--text-note);
8162 padding: var(--gap-peer) var(--gap-section);
8163 background: var(--content);
8164 color: var(--surface-page);
8165 border: none;
8166 cursor: pointer;
8167 }
8168
8169 .tip-submit:hover {
8170 opacity: 0.85;
8171 }
8172
8173 /* ===========================================
8174 COLLECTION PICKER (shared dropdown)
8175 =========================================== */
8176
8177 .collection-picker-anchor { position: relative; }
8178 .collection-picker {
8179 position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
8180 background: var(--surface-page); border: 1px solid var(--border);
8181 box-shadow: var(--elevation-overlay); min-width: 220px;
8182 }
8183 .collection-picker-list { max-height: 200px; overflow-y: auto; padding: var(--gap-bound) 0; }
8184 .collection-picker-item {
8185 display: flex; align-items: center; gap: var(--gap-peer);
8186 padding: var(--gap-peer) var(--gap-section); cursor: pointer; font-size: var(--text-note);
8187 }
8188 .collection-picker-item:hover { background: var(--surface-sunken); }
8189 .collection-picker-loading,
8190 .collection-picker-create { border-top: 1px solid var(--border); padding: var(--gap-peer) var(--gap-section); }
8191 .collection-picker-create form { display: flex; gap: var(--gap-peer); }
8192 .collection-picker-create input { flex: 1; padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-note); min-width: 0; }
8193 .collection-picker-create button { font-size: var(--text-note); white-space: nowrap; }
8194
8195 /* Save button on discover cards */
8196 .row-save, .grid-card-save {
8197 background: none; border: none; cursor: pointer;
8198 font-size: var(--text-note); padding: var(--gap-bound) var(--gap-peer); opacity: 0.4;
8199 color: var(--content); transition: opacity 0.15s;
8200 }
8201 .row-save:hover, .grid-card-save:hover { opacity: 1; }
8202 .grid-card-save {
8203 position: absolute; top: 0.4rem; right: 0.4rem; z-index: var(--z-raised);
8204 background: var(--surface-page); border-radius: var(--radius-control); padding: var(--gap-bound) var(--gap-peer);
8205 box-shadow: var(--elevation-overlay); opacity: 0;
8206 }
8207 .grid-card:hover .grid-card-save { opacity: 0.7; }
8208 .grid-card-save:hover { opacity: 1; }
8209
8210 /* Item page save button saved state */
8211 button.saved { border-color: var(--action); color: var(--action); }
8212
8213 /* ===========================================
8214 SEARCH SUGGESTIONS
8215 =========================================== */
8216
8217 .search-wrapper { position: relative; flex: 1; min-width: 0; }
8218 .search-suggestions,
8219 .tag-suggest-list {
8220 display: none; position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
8221 background: var(--surface-page); border: 1px solid var(--border); border-top: none;
8222 box-shadow: var(--elevation-overlay); max-height: 280px; overflow-y: auto;
8223 }
8224 .suggestion-item {
8225 display: flex; justify-content: space-between; align-items: center;
8226 padding: var(--gap-peer) var(--gap-section); text-decoration: none; color: var(--content);
8227 font-size: var(--text-note); cursor: pointer;
8228 }
8229 .suggestion-item:hover, .suggestion-item.highlighted,
8230 .tag-suggest-item:hover, .tag-suggest-item.highlighted { background: var(--surface-sunken); }
8231 .suggestion-category { font-size: var(--text-fine); opacity: 0.5; text-transform: uppercase; letter-spacing: 0.05em; }
8232
8233 /* ===========================================
8234 DOC UI EXAMPLES (embedded live UI in docs)
8235 =========================================== */
8236
8237 .doc-ui {
8238 margin: var(--gap-pane) 0; border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden;
8239 }
8240 .doc-ui-frame {
8241 padding: var(--gap-section); background: var(--surface-overlay); pointer-events: none; user-select: none;
8242 }
8243 .doc-ui figcaption {
8244 padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.6;
8245 border-top: 1px solid var(--border); background: var(--surface-overlay);
8246 font-family: var(--font-mono);
8247 }
8248 .doc-ui-missing {
8249 padding: var(--gap-section); opacity: 0.4; font-style: italic;
8250 }
8251
8252 /* Library "New" badge for items with undownloaded versions. Not a status, so
8253 it takes no tone; it is the plain badge with the accent hue and a little
8254 more weight, since "New" is the one badge meant to catch the eye. */
8255 .badge-new {
8256 color: var(--action); font-weight: bold;
8257 margin-left: var(--gap-peer); vertical-align: middle;
8258 }
8259
8260 /* ===========================================
8261 BENEFIT LIST (bulleted feature lists, e.g. Stripe Connect prompt)
8262 =========================================== */
8263 .benefit-list {
8264 list-style: none;
8265 padding: 0;
8266 margin: 0 auto var(--gap-pane);
8267 max-width: 280px;
8268 text-align: left;
8269 display: flex;
8270 flex-direction: column;
8271 gap: var(--gap-peer);
8272 }
8273 .benefit-list li {
8274 position: relative;
8275 padding-left: var(--gap-section);
8276 font-size: var(--text-note);
8277 }
8278 .benefit-list li::before {
8279 content: "";
8280 position: absolute;
8281 left: 0.15rem;
8282 top: 0.55rem;
8283 width: 6px;
8284 height: 6px;
8285 border-radius: var(--radius-round);
8286 background: var(--success);
8287 }
8288
8289 /* ===========================================
8290 CHECKLIST (setup / onboarding step lists)
8291 =========================================== */
8292 .checklist {
8293 margin-bottom: var(--gap-pane);
8294 padding: var(--gap-section) var(--gap-pane);
8295 background: var(--surface-overlay);
8296 border: 1px solid var(--border);
8297 }
8298 .checklist-header {
8299 display: flex;
8300 justify-content: space-between;
8301 align-items: center;
8302 margin-bottom: var(--gap-section);
8303 }
8304 .checklist-title {
8305 margin: 0;
8306 font-family: var(--font-heading);
8307 font-weight: bold;
8308 }
8309 .checklist-mark {
8310 width: 1.1rem;
8311 height: 1.1rem;
8312 border-radius: var(--radius-round);
8313 border: 2px solid var(--border);
8314 flex-shrink: 0;
8315 box-sizing: border-box;
8316 }
8317 .checklist-mark--done {
8318 border-color: var(--success);
8319 background: var(--success);
8320 }
8321 .checklist-label {
8322 flex: 1;
8323 font-size: var(--text-note);
8324 }
8325 .checklist-label--done {
8326 opacity: 0.5;
8327 text-decoration: line-through;
8328 }
8329
8330 /* ===========================================
8331 PAYMENTS TAB (replaces inline styles in user_payments.html)
8332 =========================================== */
8333 /* Canonical "stack row", title-on-left + actions-on-right header bar.
8334 One primitive; per-context modifiers handle gap, wrap, alignment, margins.
8335 Aliases below cover existing per-tab class names (kept as JS hooks). */
8336 .stack-row,
8337 .content-header,
8338 .user-media-header,
8339 .transactions-header,
8340 .analytics-range-bar,
8341 .cart-multi-bar {
8342 display: flex;
8343 justify-content: space-between;
8344 align-items: center;
8345 flex-wrap: wrap;
8346 gap: var(--gap-section);
8347 }
8348 .stack-row--tight { gap: var(--gap-section); flex-wrap: nowrap; }
8349 .stack-row--top { align-items: flex-start; }
8350 .stack-row--bordered,
8351 .transactions-header {
8352 margin-bottom: var(--gap-section);
8353 padding-bottom: var(--gap-section);
8354 border-bottom: 1px solid var(--border);
8355 }
8356 .content-header,
8357 .user-media-header,
8358 .analytics-range-bar { margin-bottom: var(--gap-section); }
8359 .cart-multi-bar {
8360 margin-bottom: var(--gap-pane);
8361 padding: var(--gap-section);
8362 background: var(--surface-sunken);
8363 border: 1px solid var(--border);
8364 }
8365
8366 /* Canonical list row (flex, gap, padding, bottom border). Aliases below cover
8367 the per-tab variants. They exist as JS hooks but share the same look. */
8368 .list-row,
8369 .psection-row,
8370 .section-mgmt-row,
8371 .bundle-picker-row,
8372 .checklist-row {
8373 display: flex;
8374 align-items: center;
8375 gap: var(--gap-section);
8376 padding: var(--gap-group) 0;
8377 border-bottom: 1px solid var(--border);
8378 }
8379 .list-row:last-child,
8380 .psection-row:last-child,
8381 .section-mgmt-row:last-child,
8382 .bundle-picker-row:last-child,
8383 .checklist-row:last-child { border-bottom: none; }
8384 .warn-glyph {
8385 font-size: var(--text-subhead);
8386 color: var(--warning);
8387 }
8388
8389 .card-h {
8390 margin: 0 0 var(--gap-section) 0;
8391 font-size: var(--text-body);
8392 }
8393
8394 .amount-big {
8395 font-size: var(--text-subhead);
8396 font-weight: bold;
8397 }
8398
8399 .account-details {
8400 margin-top: var(--gap-section);
8401 padding-top: var(--gap-section);
8402 border-top: 1px solid var(--border);
8403 }
8404 .account-details summary { cursor: pointer; }
8405 .account-details-id {
8406 font-size: var(--text-fine);
8407 opacity: 0.5;
8408 font-family: var(--font-mono);
8409 margin-top: var(--gap-bound);
8410 }
8411
8412 .stripe-actions {
8413 display: flex;
8414 gap: var(--gap-section);
8415 flex-wrap: wrap;
8416 }
8417 .stripe-actions-disconnect { margin-left: auto; }
8418
8419 .payout-stats-grid {
8420 display: grid;
8421 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
8422 gap: var(--gap-section);
8423 margin-top: var(--gap-section);
8424 padding-top: var(--gap-section);
8425 border-top: 1px solid var(--border);
8426 }
8427
8428 .payouts-disabled-notice {
8429 margin-top: var(--gap-section);
8430 padding: var(--gap-section);
8431 background: var(--warning);
8432 color: var(--primary-light);
8433 font-size: var(--text-note);
8434 }
8435
8436 .fee-breakdown {
8437 display: grid;
8438 grid-template-columns: 1fr 1fr;
8439 gap: var(--gap-peer);
8440 font-size: var(--text-note);
8441 }
8442 .fee-row {
8443 display: flex;
8444 justify-content: space-between;
8445 padding: var(--gap-peer) 0;
8446 border-bottom: 1px solid var(--border);
8447 }
8448
8449 .checkbox-row {
8450 display: flex;
8451 align-items: flex-start;
8452 gap: var(--gap-peer);
8453 cursor: pointer;
8454 font-size: var(--text-note);
8455 }
8456 .check-inline {
8457 width: auto;
8458 margin-top: var(--gap-bound);
8459 }
8460
8461 .stripe-connect-prompt { text-align: center; padding: var(--gap-pane); }
8462 .stripe-logo { opacity: 0.8; }
8463 .stripe-connect-btn {
8464 background: var(--stripe);
8465 padding: var(--gap-section) var(--gap-page);
8466 }
8467
8468 .export-row {
8469 display: flex;
8470 justify-content: flex-end;
8471 margin-bottom: var(--gap-section);
8472 }
8473
8474 .splits-grid {
8475 display: grid;
8476 grid-template-columns: 1fr 1fr;
8477 gap: var(--gap-section);
8478 margin-bottom: var(--gap-section);
8479 }
8480
8481 .tip-message {
8482 max-width: 300px;
8483 overflow: hidden;
8484 text-overflow: ellipsis;
8485 }
8486
8487 .transactions-title { font-size: var(--text-subhead); }
8488
8489 /* ===========================================
8490 CREATOR TAB (replaces inline styles in user_creator.html)
8491 =========================================== */
8492 .creator-tab-section {
8493 padding: var(--gap-page);
8494 }
8495 .creator-tab-section--centered { text-align: center; }
8496 .creator-tab-section--no-top { padding-top: 0; }
8497
8498 .creator-h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
8499 .creator-h2--sm { font-size: var(--text-subhead); }
8500
8501 .creator-section-label {
8502 margin-left: var(--gap-page);
8503 margin-right: var(--gap-page);
8504 }
8505 .creator-form-section { padding: 0 var(--gap-page); }
8506
8507 .creator-plan-box {
8508 background: var(--surface-overlay);
8509 padding: var(--gap-section) var(--gap-pane);
8510 }
8511 .creator-plan-head {
8512 display: flex;
8513 align-items: center;
8514 gap: var(--gap-section);
8515 margin-bottom: var(--gap-peer);
8516 flex-wrap: wrap;
8517 }
8518 .creator-plan-name {
8519 font-family: var(--font-heading);
8520 font-weight: bold;
8521 font-size: var(--text-lead);
8522 }
8523
8524 /* Founder pricing, two states, and neither is a status tone: locked-for-life
8525 is emphasis and pending-lock-in is an aside. They were a --content fill and
8526 a --surface-sunken fill back when every badge was filled; as coloured text
8527 the same distinction is full-strength ink against muted. */
8528 .badge--founder-locked {
8529 color: var(--content);
8530 font-weight: bold;
8531 }
8532 .badge--founder-pending {
8533 color: var(--content-muted);
8534 }
8535
8536 .founder-callout {
8537 background: var(--surface-overlay);
8538 border-left: 3px solid var(--content);
8539 padding: var(--gap-section);
8540 }
8541
8542 .creator-tier-grid {
8543 display: grid;
8544 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
8545 gap: var(--gap-section);
8546 }
8547 .creator-tier-card {
8548 background: var(--surface-overlay);
8549 padding: var(--gap-section);
8550 text-align: center;
8551 }
8552 .creator-tier-name {
8553 font-family: var(--font-heading);
8554 font-weight: bold;
8555 font-size: var(--text-body);
8556 margin-bottom: var(--gap-bound);
8557 }
8558 .creator-tier-link {
8559 color: inherit;
8560 text-decoration: none;
8561 border-bottom: 1px solid var(--border);
8562 }
8563 .creator-tier-sub {
8564 font-size: var(--text-fine);
8565 opacity: 0.7;
8566 }
8567 .creator-tier-storage { font-size: var(--text-fine); }
8568 .creator-tier-buttons {
8569 display: flex;
8570 flex-direction: column;
8571 gap: var(--gap-peer);
8572 }
8573 .creator-tier-btn {
8574 font-size: var(--text-note);
8575 width: 100%;
8576 }
8577
8578 .strike-dim {
8579 opacity: 0.5;
8580 text-decoration: line-through;
8581 }
8582
8583 .storage-box {
8584 background: var(--surface-overlay);
8585 padding: var(--gap-section) var(--gap-pane);
8586 }
8587 .storage-row {
8588 display: flex;
8589 justify-content: space-between;
8590 margin-bottom: var(--gap-peer);
8591 font-size: var(--text-note);
8592 }
8593 .storage-breakdown {
8594 display: grid;
8595 grid-template-columns: 1fr 1fr;
8596 gap: var(--gap-bound) var(--gap-pane);
8597 margin-top: var(--gap-section);
8598 font-size: var(--text-note);
8599 }
8600
8601 .broadcast-head {
8602 display: flex;
8603 justify-content: space-between;
8604 align-items: flex-start;
8605 }
8606 .broadcast-desc { flex: 1; }
8607 .broadcast-export { margin-left: var(--gap-section); }
8608
8609 .creator-divider {
8610 margin: 0 var(--gap-page);
8611 border-color: var(--border);
8612 }
8613
8614 .creator-intro { line-height: 1.6; }
8615 .creator-pitch-box {
8616 background: var(--surface-overlay);
8617 padding: var(--gap-pane);
8618 }
8619
8620 .mono-sm {
8621 font-family: var(--font-mono);
8622 font-size: var(--text-note);
8623 }
8624
8625 /* ===========================================
8626 PROJECT CONTENT TAB (replaces inline styles in project_content.html)
8627 =========================================== */
8628
8629 .empty-state--lg { padding: var(--gap-page) var(--gap-section); }
8630
8631 .content-filters {
8632 display: flex;
8633 gap: var(--gap-section);
8634 margin-bottom: var(--gap-section);
8635 align-items: center;
8636 flex-wrap: wrap;
8637 }
8638 .content-filter-search {
8639 flex: 1;
8640 min-width: 180px;
8641 padding: var(--gap-peer) var(--gap-group);
8642 font-size: var(--text-note);
8643 }
8644 .content-filter-select {
8645 padding: var(--gap-peer);
8646 font-size: var(--text-note);
8647 }
8648
8649 .bulk-action-bar {
8650 padding: var(--gap-section);
8651 margin-bottom: var(--gap-section);
8652 background: var(--surface-raised);
8653 border: 1px solid var(--border);
8654 border-radius: var(--radius-square);
8655 display: flex;
8656 align-items: center;
8657 gap: var(--gap-peer);
8658 flex-wrap: wrap;
8659 opacity: 0.4;
8660 }
8661 .bulk-action-bar--active { opacity: 1; }
8662 .bulk-count { font-weight: bold; min-width: 5rem; }
8663 .bulk-deselect { margin-left: auto; }
8664 .danger-text { color: var(--danger); }
8665
8666 .bulk-form {
8667 margin-bottom: var(--gap-section);
8668 padding: var(--gap-section);
8669 background: var(--surface-sunken);
8670 border: 1px solid var(--border);
8671 border-radius: var(--radius-square);
8672 }
8673 .bulk-form-row {
8674 display: flex;
8675 gap: var(--gap-section);
8676 align-items: end;
8677 }
8678 .bulk-form-label {
8679 font-size: var(--text-note);
8680 display: block;
8681 margin-bottom: var(--gap-bound);
8682 }
8683
8684 /* Sortable table column header
8685
8686 The cursor and the caret are NOT here. `.table-heading[data-sortable]` and
8687 `.table-heading[aria-sort="..."]::after` are generated into static/layout.css
8688 by makeover-webview out of `Column::sortable` and `Column::sorted`. This file
8689 carried two hand-written copies of the same fact -- `.sortable-th` here and
8690 `.data-table th.sortable` up at the data-table block -- and a third channel in
8691 tab-project-content.js, which wrote the glyph as text and toggled
8692 `.sort-arrow--active`. Three vocabularies, one state, and the generated pair
8693 matched nothing because no heading carried `.table-heading`.
8694
8695 The focus ring stays: it is this app's focus treatment, not a restatement of
8696 the caret, and makeover emits no focus rule for a heading. */
8697 .table-heading[data-sortable] {
8698 user-select: none;
8699 transition: background 0.2s ease;
8700 }
8701
8702 .table-heading[data-sortable]:hover {
8703 background: var(--border);
8704 }
8705
8706 .table-heading[data-sortable]:focus-visible {
8707 outline: 2px solid var(--focus-ring);
8708 outline-offset: 2px;
8709 }
8710
8711 /* Data-table column-width utilities (numeric width hints,
8712 kept as classes to avoid inline `style="width: NN%"`) */
8713 .col-3 { width: 3%; }
8714 .col-5 { width: 5%; }
8715 .col-8 { width: 8%; }
8716 .col-10 { width: 10%; }
8717 .col-12 { width: 12%; }
8718 .col-15 { width: 15%; }
8719 .col-20 { width: 20%; }
8720 .col-32 { width: 32%; }
8721 .col-45 { width: 45%; }
8722
8723 /* Pixel-width utilities for inputs and narrow fixed-width controls.
8724 Use on form inputs that can't be sized by their parent column. */
8725 .w-80 { width: 80px; }
8726 .w-100 { width: 100px; }
8727 .w-120 { width: 120px; }
8728 .w-180 { width: 180px; }
8729 .w-220 { width: 220px; }
8730 .w-260 { width: 260px; }
8731 .w-full { width: 100%; }
8732 .maxw-320 { max-width: 320px; }
8733
8734 /* Canonical input shape modifiers. One base form-input look (defined globally
8735 at line ~365 input[type], textarea, select) plus these size + feature variants.
8736 Apply alongside the per-tab class which acts as a JS hook. */
8737 .input--xs {
8738 padding: var(--gap-bound) var(--gap-peer);
8739 font-size: var(--text-note);
8740 }
8741 .input--sm {
8742 padding: var(--gap-bound) var(--gap-peer);
8743 font-size: var(--text-note);
8744 }
8745 .input--mono { font-family: var(--font-mono); }
8746 .input--upper { text-transform: uppercase; }
8747 .input--numeric { text-align: right; }
8748
8749 .order-arrow-btn {
8750 padding: var(--gap-bound);
8751 font-size: var(--text-fine);
8752 line-height: 1;
8753 }
8754
8755 .bundle-toggle {
8756 background: none;
8757 border: none;
8758 cursor: pointer;
8759 padding: 0 var(--gap-bound) 0 0;
8760 font-size: var(--text-fine);
8761 color: var(--content-muted);
8762 }
8763 .bundle-child-cell { padding-left: var(--gap-page); }
8764 .bundle-child-title { opacity: 0.85; }
8765 .bundle-child-arrow {
8766 color: var(--content-muted);
8767 margin-right: var(--gap-bound);
8768 }
8769
8770 .item-actions {
8771 display: flex;
8772 gap: var(--gap-peer);
8773 }
8774
8775 .badge--inline-tiny {
8776 font-size: var(--text-fine);
8777 margin-left: var(--gap-bound);
8778 opacity: 0.7;
8779 }
8780
8781 .deleted-items-details { margin-top: var(--gap-page); }
8782 .deleted-items-summary {
8783 cursor: pointer;
8784 font-size: var(--text-body);
8785 opacity: 0.7;
8786 }
8787
8788 .section-divider {
8789 margin: var(--gap-page) 0;
8790 border: none;
8791 border-top: 1px solid var(--border);
8792 }
8793
8794 .inline-rename-input {
8795 font-weight: bold;
8796 width: 100%;
8797 padding: var(--gap-bound) var(--gap-peer);
8798 font-size: inherit;
8799 }
8800
8801 /* ===========================================
8802 ITEM DETAILS TAB (replaces inline styles in item_details.html)
8803 =========================================== */
8804
8805 /* Compact button utility (small icon-side actions) */
8806 .btn-compact { padding: var(--gap-peer) var(--gap-section); }
8807
8808 .ml-2 { margin-left: var(--gap-peer); }
8809 .cursor-pointer { cursor: pointer; }
8810 .inline { display: inline; }
8811
8812 .tag-search-wrap { position: relative; }
8813 .tag-suggestions {
8814 position: absolute;
8815 z-index: var(--z-dropdown);
8816 background: var(--surface-sunken);
8817 border: 1px solid var(--border);
8818 width: 100%;
8819 max-height: 200px;
8820 overflow-y: auto;
8821 }
8822
8823 .advanced-details { margin-top: var(--gap-pane); }
8824 .advanced-summary {
8825 cursor: pointer;
8826 font-size: var(--text-body);
8827 opacity: 0.8;
8828 }
8829
8830 /* Bundle table inside item-details */
8831 .bundle-table {
8832 width: 100%;
8833 border-collapse: collapse;
8834 }
8835 .bundle-table-head {
8836 text-align: left;
8837 font-size: var(--text-fine);
8838 opacity: 0.7;
8839 text-transform: uppercase;
8840 letter-spacing: 0.03em;
8841 }
8842 .bundle-row { border-bottom: 1px solid var(--border); }
8843 .bundle-cell { padding: var(--gap-peer); }
8844 .bundle-cell--first { padding-left: 0; }
8845 .bundle-cell--actions { width: 60px; }
8846 .bundle-cell--desc { font-size: var(--text-note); opacity: 0.8; }
8847 .bundle-cell--file { font-size: var(--text-note); }
8848
8849 .bundle-remove-btn {
8850 padding: var(--gap-bound) var(--gap-peer);
8851 font-size: var(--text-fine);
8852 }
8853
8854 .bundle-add-row {
8855 margin-top: var(--gap-section);
8856 display: flex;
8857 gap: var(--gap-section);
8858 align-items: center;
8859 }
8860
8861 .bundle-existing-details { margin-top: var(--gap-section); }
8862 .bundle-existing-summary {
8863 cursor: pointer;
8864 font-size: var(--text-note);
8865 opacity: 0.7;
8866 }
8867 .bundle-existing-row {
8868 margin-top: var(--gap-peer);
8869 display: flex;
8870 gap: var(--gap-peer);
8871 align-items: center;
8872 }
8873 .bundle-existing-select {
8874 flex: 1;
8875 padding: var(--gap-peer);
8876 }
8877
8878 .sections-intro {
8879 font-size: var(--text-note);
8880 opacity: 0.7;
8881 margin: var(--gap-section) 0 var(--gap-section);
8882 }
8883
8884 .section-mgmt-title {
8885 flex: 1;
8886 font-weight: bold;
8887 }
8888 .section-edit-btn,
8889 .section-del-btn {
8890 padding: var(--gap-bound) var(--gap-group);
8891 font-size: var(--text-fine);
8892 }
8893
8894 .section-add-details { margin-top: var(--gap-section); }
8895 .section-add-summary {
8896 cursor: pointer;
8897 font-size: var(--text-body);
8898 }
8899
8900 .insert-image-btn {
8901 margin-top: var(--gap-peer);
8902 font-size: var(--text-note);
8903 }
8904
8905 .section-edit-modal {
8906 margin-top: var(--gap-section);
8907 padding: var(--gap-section);
8908 background: var(--surface-sunken);
8909 }
8910 .section-edit-actions {
8911 display: flex;
8912 gap: var(--gap-peer);
8913 }
8914
8915 .publish-note {
8916 margin: var(--gap-section) 0;
8917 opacity: 0.7;
8918 text-align: left;
8919 }
8920
8921 /* ===========================================
8922 CONTENT WIZARD STEP (replaces inline styles in wizards/steps/item/content.html)
8923 =========================================== */
8924
8925 /* (Wizard upload-status + slim progress moved into canonical .upload-status*
8926 / .progress-bar-container--slim primitives near top of file.) */
8927
8928 /* Bundle items picker, server-rendered rows and JS-appended rows share these. */
8929 .bundle-picker-row-title { flex: 1; }
8930 .bundle-picker-row-type { font-size: var(--text-fine); opacity: 0.6; }
8931 .bundle-picker-row-unlisted {
8932 font-size: var(--text-fine);
8933 display: flex;
8934 align-items: center;
8935 gap: var(--gap-bound);
8936 }
8937
8938 /* Batch upload queue table. */
8939 .batch-queue-table {
8940 width: 100%;
8941 border-collapse: collapse;
8942 }
8943 .batch-queue-table thead tr {
8944 border-bottom: 2px solid var(--border);
8945 text-align: left;
8946 }
8947 .batch-queue-table th,
8948 .batch-queue-table td {
8949 padding: var(--gap-peer);
8950 font-size: var(--text-note);
8951 }
8952 .batch-queue-table th:first-child,
8953 .batch-queue-table td:first-child { padding-left: 0; }
8954 .batch-queue-table th:last-child,
8955 .batch-queue-table td:last-child { padding-right: 0; text-align: right; }
8956 .batch-queue-table tbody tr { border-bottom: 1px solid var(--border); }
8957
8958 .batch-queue-name {
8959 max-width: 150px;
8960 overflow: hidden;
8961 text-overflow: ellipsis;
8962 white-space: nowrap;
8963 }
8964
8965 /* ===========================================
8966 ACCOUNT TAB (replaces inline styles in partials/tabs/user_account.html)
8967 =========================================== */
8968
8969 /* Canonical solid-tint callout (one of these in the codebase).
8970 Distinct from .alert (left-border) and .banner (full-bleed page notice).
8971 Variants:
8972 - .callout--danger (light bg, danger border, moderation, errors)
8973 - .callout--warning (light bg, warning border, DNS, email-not-verified)
8974 - .callout--solid-warning (full saturation warning bg, dark text, partial checkout) */
8975 .callout {
8976 padding: var(--gap-section);
8977 margin-bottom: var(--gap-section);
8978 font-size: var(--text-note);
8979 border: 1px solid;
8980 }
8981 .callout--danger {
8982 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
8983 border-color: var(--danger);
8984 }
8985 .callout--warning {
8986 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
8987 border-color: var(--warning);
8988 }
8989 .callout--solid-warning {
8990 background: var(--warning);
8991 color: var(--primary-dark);
8992 border-color: transparent;
8993 padding: var(--gap-section);
8994 }
8995 .callout-title { text-transform: capitalize; }
8996 .callout-meta {
8997 opacity: 0.6;
8998 margin-left: var(--gap-peer);
8999 }
9000 .callout-body { margin: var(--gap-peer) 0 0 0; }
9001 .callout-hint {
9002 font-size: var(--text-note);
9003 margin-top: var(--gap-peer);
9004 }
9005
9006 .account-mod-history { margin-top: var(--gap-section); }
9007 .account-mod-history summary {
9008 cursor: pointer;
9009 font-size: var(--text-note);
9010 opacity: 0.7;
9011 }
9012 .account-mod-history-list { margin-top: var(--gap-peer); }
9013 .account-mod-history-item {
9014 padding: var(--gap-peer) 0;
9015 border-bottom: 1px solid var(--border);
9016 font-size: var(--text-note);
9017 }
9018 .account-mod-history-reason {
9019 margin: var(--gap-bound) 0 0 0;
9020 opacity: 0.8;
9021 }
9022
9023 .account-tip-card-title {
9024 font-weight: bold;
9025 margin-bottom: var(--gap-peer);
9026 }
9027 .account-tip-list {
9028 display: flex;
9029 flex-direction: column;
9030 gap: var(--gap-bound);
9031 opacity: 0.8;
9032 }
9033
9034 /* Resend-verification button needs left spacing from preceding strong. */
9035 .account-resend-btn { margin-left: var(--gap-peer); }
9036
9037 /* Cluster of small inline forms (manage-billing / cancel / resume). */
9038 .inline-form { display: inline; }
9039
9040 /* Notification preferences, bare fieldset + monospace legend. */
9041 .pref-fieldset {
9042 border: none;
9043 padding: 0;
9044 margin: 0 0 var(--gap-section) 0;
9045 }
9046 .pref-fieldset--last { margin-bottom: var(--gap-peer); }
9047 .pref-legend {
9048 font-family: var(--font-mono);
9049 font-size: var(--text-note);
9050 font-weight: bold;
9051 margin-bottom: var(--gap-peer);
9052 }
9053
9054 /* The cannot-opt-out list, rendered from email::OperationalKind. Reads as
9055 reference material rather than as more settings: no controls, and the
9056 justification sits under its label rather than beside it. */
9057 .operational-mail {
9058 margin: var(--gap-peer) 0 0 0;
9059 }
9060 .operational-mail dt {
9061 font-weight: bold;
9062 font-size: var(--text-note);
9063 margin-top: var(--gap-peer);
9064 }
9065 .operational-mail dd {
9066 margin: 0;
9067 opacity: 0.8;
9068 font-size: var(--text-note);
9069 }
9070
9071 /* ===========================================
9072 PROJECT CODE TAB (replaces inline styles in partials/tabs/project_code.html)
9073 =========================================== */
9074
9075 .proj-code-docs-link { margin-left: var(--gap-section); }
9076
9077 .proj-code-linked-list { margin-bottom: var(--gap-pane); }
9078 .proj-code-linked-list > label { margin-bottom: var(--gap-peer); display: block; }
9079
9080 .proj-code-repo {
9081 margin-bottom: var(--gap-section);
9082 padding: var(--gap-section);
9083 background: var(--surface-overlay);
9084 }
9085 .proj-code-repo-row {
9086 display: flex;
9087 align-items: center;
9088 gap: var(--gap-section);
9089 }
9090 .proj-code-repo-link { flex: 1; }
9091 .proj-code-repo .danger { padding: var(--gap-bound) var(--gap-section); font-size: var(--text-note); }
9092
9093 .proj-code-collab-block {
9094 margin-top: var(--gap-peer);
9095 padding-top: var(--gap-peer);
9096 border-top: 1px solid var(--border);
9097 }
9098 .proj-code-collab-label { font-size: var(--text-note); margin-bottom: var(--gap-bound); display: block; }
9099 .proj-code-collab-row {
9100 display: flex;
9101 align-items: center;
9102 gap: var(--gap-peer);
9103 margin-bottom: var(--gap-bound);
9104 font-size: var(--text-note);
9105 }
9106 .proj-code-collab-name { flex: 1; }
9107 .proj-code-collab-readonly { opacity: 0.5; }
9108
9109 .proj-code-add-collab-row {
9110 display: flex;
9111 gap: var(--gap-peer);
9112 align-items: center;
9113 }
9114 .proj-code-add-collab-row .collab-username-input {
9115 flex: 1;
9116 font-size: var(--text-note);
9117 padding: var(--gap-bound) var(--gap-peer);
9118 }
9119 .proj-code-add-collab-row .collab-add-btn { white-space: nowrap; }
9120
9121 .proj-code-link-row {
9122 display: flex;
9123 gap: var(--gap-peer);
9124 align-items: center;
9125 }
9126 .proj-code-link-row #link-repo-select { flex: 1; }
9127 .proj-code-link-row #link-repo-btn { white-space: nowrap; }
9128
9129 .proj-code-create-section {
9130 margin-top: var(--gap-pane);
9131 padding-top: var(--gap-section);
9132 border-top: 1px solid var(--border);
9133 }
9134 .proj-code-create-section #create-repo-name { flex: 1; }
9135 .proj-code-create-section #create-repo-btn { white-space: nowrap; }
9136 .proj-code-create-status { margin-top: var(--gap-bound); }
9137 .proj-code-create-status.is-error { color: var(--danger); }
9138
9139 .proj-code-disabled {
9140 padding: var(--gap-page);
9141 text-align: center;
9142 opacity: 0.7;
9143 }
9144
9145 /* ===========================================
9146 ADMIN REPORT ENTRIES (replaces inline styles in partials/admin_report_entries.html)
9147 =========================================== */
9148
9149 .admin-report-table {
9150 width: 100%;
9151 border-collapse: collapse;
9152 font-size: var(--text-note);
9153 }
9154 .admin-report-table thead tr {
9155 border-bottom: 2px solid var(--border);
9156 text-align: left;
9157 }
9158 .admin-report-table tbody tr {
9159 border-bottom: 1px solid var(--border);
9160 }
9161 .admin-report-table th,
9162 .admin-report-table td {
9163 padding: var(--gap-peer);
9164 }
9165 .admin-report-table td.admin-report-reason {
9166 max-width: 200px;
9167 overflow: hidden;
9168 text-overflow: ellipsis;
9169 white-space: nowrap;
9170 }
9171 .admin-report-table td.admin-report-date {
9172 white-space: nowrap;
9173 }
9174
9175 .admin-report-target-type {
9176 opacity: 0.5;
9177 font-size: var(--text-fine);
9178 }
9179
9180 .admin-report-status-open {
9181 color: var(--action);
9182 font-weight: 600;
9183 }
9184 .admin-report-status-closed { opacity: 0.6; }
9185
9186 .admin-report-resolve-form {
9187 display: inline-flex;
9188 gap: var(--gap-bound);
9189 align-items: center;
9190 }
9191 .admin-report-resolve-form input[type="text"] {
9192 width: 120px;
9193 padding: var(--gap-bound) var(--gap-peer);
9194 font-size: var(--text-fine);
9195 }
9196 .admin-report-dismiss-form { display: inline; }
9197
9198 .admin-report-admin-notes {
9199 font-size: var(--text-fine);
9200 opacity: 0.6;
9201 }
9202
9203 /* ===========================================
9204 PROJECT PROMOTIONS TAB (replaces inline styles in partials/tabs/project_promotions.html)
9205 =========================================== */
9206
9207 .proj-promo-form {
9208 margin-bottom: var(--gap-page);
9209 }
9210
9211 .proj-promo-grid {
9212 display: grid;
9213 grid-template-columns: 1fr 1fr;
9214 gap: var(--gap-section) var(--gap-pane);
9215 max-width: 500px;
9216 }
9217
9218 .proj-promo-grid--bordered {
9219 margin-top: var(--gap-section);
9220 padding-top: var(--gap-section);
9221 border-top: 1px solid var(--border);
9222 }
9223
9224 .proj-promo-trial-fields {
9225 display: none;
9226 max-width: 500px;
9227 margin-top: var(--gap-section);
9228 }
9229
9230 .proj-promo-trial-fields.is-visible {
9231 display: block;
9232 }
9233
9234 .proj-promo-grid.is-hidden,
9235 .proj-promo-trial-fields.is-hidden {
9236 display: none;
9237 }
9238
9239 .proj-promo-form .form-group {
9240 margin-bottom: 0;
9241 }
9242
9243 .proj-promo-form .form-group label {
9244 font-size: var(--text-note);
9245 }
9246
9247 .proj-promo-trial-group {
9248 max-width: 240px;
9249 }
9250
9251 .proj-promo-scope {
9252 grid-column: 1 / -1;
9253 }
9254
9255 .proj-promo-actions {
9256 margin-top: var(--gap-section);
9257 }
9258
9259 .proj-promo-hint {
9260 margin-top: var(--gap-peer);
9261 }
9262
9263 /* ===========================================
9264 PROJECT OVERVIEW TAB (replaces inline styles in partials/tabs/project_overview.html)
9265 =========================================== */
9266
9267 .proj-overview-setup {
9268 margin-bottom: var(--gap-section);
9269 padding: var(--gap-section);
9270 background: var(--surface-overlay);
9271 border: 1px solid var(--border);
9272 }
9273 .proj-overview-setup-title {
9274 margin: 0 0 var(--gap-section) 0;
9275 font-family: var(--font-heading);
9276 font-weight: bold;
9277 }
9278 .proj-overview-setup-row {
9279 display: flex;
9280 align-items: center;
9281 gap: var(--gap-section);
9282 padding: var(--gap-peer) 0;
9283 border-bottom: 1px solid var(--border);
9284 }
9285 .proj-overview-setup-row:last-child { border-bottom: none; }
9286 .proj-overview-setup-check {
9287 color: var(--success);
9288 font-family: var(--font-mono);
9289 font-size: var(--text-fine);
9290 text-transform: uppercase;
9291 letter-spacing: 0.04em;
9292 flex-shrink: 0;
9293 }
9294 .proj-overview-setup-bullet {
9295 width: 1.1rem;
9296 height: 1.1rem;
9297 border: 2px solid var(--border);
9298 border-radius: var(--radius-round);
9299 flex-shrink: 0;
9300 }
9301 .proj-overview-setup-label {
9302 flex: 1;
9303 font-size: var(--text-note);
9304 }
9305 .proj-overview-setup-label--done {
9306 opacity: 0.5;
9307 text-decoration: line-through;
9308 }
9309 .proj-overview-setup-btn {
9310 font-size: var(--text-fine);
9311 padding: var(--gap-bound) var(--gap-group);
9312 }
9313
9314 .proj-overview-tools { margin-top: var(--gap-page); }
9315 .proj-overview-tools-summary {
9316 cursor: pointer;
9317 font-size: var(--text-body);
9318 font-family: var(--font-heading);
9319 font-weight: bold;
9320 }
9321 .proj-overview-tools-grid {
9322 display: grid;
9323 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
9324 gap: var(--gap-section);
9325 margin-top: var(--gap-section);
9326 }
9327 .proj-overview-tool {
9328 background: var(--surface-sunken);
9329 padding: var(--gap-section);
9330 }
9331 .proj-overview-tool-name {
9332 font-weight: bold;
9333 font-size: var(--text-body);
9334 margin-bottom: var(--gap-bound);
9335 }
9336 .proj-overview-tool-desc {
9337 font-size: var(--text-note);
9338 opacity: 0.7;
9339 margin: 0;
9340 }
9341
9342 /* ===========================================
9343 PROJECT SYNCKIT TAB (replaces inline styles in partials/tabs/project_synckit.html)
9344 =========================================== */
9345
9346 .proj-synckit-intro { margin-bottom: var(--gap-section); }
9347 .proj-synckit-intro-secondary { margin-bottom: var(--gap-pane); }
9348 .proj-synckit-create { margin-bottom: var(--gap-pane); }
9349 .proj-synckit-create-body {
9350 padding: var(--gap-section); background: var(--surface-overlay); border: 1px solid var(--border);
9351 }
9352 .proj-synckit-create-form {
9353 display: flex; flex-direction: row; gap: var(--gap-section); align-items: flex-end;
9354 }
9355 .proj-synckit-label {
9356 display: block; font-size: var(--text-note); margin-bottom: var(--gap-bound);
9357 }
9358 .proj-synckit-code { font-size: var(--text-fine); }
9359 .proj-synckit-code--wrap { font-size: var(--text-fine); word-break: break-all; }
9360 .proj-synckit-code--selectable { font-size: var(--text-note); word-break: break-all; user-select: all; }
9361 .proj-synckit-btn-tight {
9362 margin-left: var(--gap-peer); padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9363 }
9364 .proj-synckit-btn-row {
9365 padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9366 }
9367 .proj-synckit-btn-row + .proj-synckit-btn-row { margin-left: var(--gap-bound); }
9368 .proj-synckit-hint-inline {
9369 font-size: var(--text-fine); margin-left: var(--gap-bound);
9370 }
9371 .proj-synckit-hint-warn {
9372 font-size: var(--text-fine); color: var(--danger);
9373 }
9374 .proj-synckit-status-active { color: var(--success); }
9375 .proj-synckit-status-inactive { color: var(--danger); }
9376 .proj-synckit-new-key {
9377 padding: var(--gap-section); background: var(--surface-overlay);
9378 border: 1px solid var(--border); margin-top: var(--gap-peer);
9379 }
9380 .proj-synckit-new-key-heading {
9381 margin: 0 0 var(--gap-peer) 0; font-weight: 500; color: var(--danger);
9382 }
9383 .proj-synckit-create-status--error { color: var(--danger); margin-top: var(--gap-peer); }
9384 .proj-synckit-create-status--reset { margin-top: var(--gap-peer); }
9385
9386 /* ===========================================
9387 ITEM PAGE (replaces inline styles in pages/item.html)
9388 =========================================== */
9389
9390 .item-page .breadcrumb {
9391 display: flex;
9392 justify-content: space-between;
9393 align-items: center;
9394 }
9395
9396 .item-page .breadcrumb-edit-link {
9397 font-size: var(--text-note);
9398 color: var(--content);
9399 }
9400
9401 .item-page .video-player {
9402 width: 100%;
9403 max-height: 600px;
9404 background: var(--primary-dark);
9405 }
9406
9407 .item-page .item-cover-img {
9408 width: 100%;
9409 aspect-ratio: 1 / 1;
9410 object-fit: cover;
9411 display: block;
9412 }
9413
9414
9415 .item-page .bundle-notice-inline {
9416 background: var(--surface-sunken);
9417 padding: var(--gap-section);
9418 }
9419
9420 .item-page .bundle-notice-title {
9421 font-weight: bold;
9422 margin-bottom: var(--gap-peer);
9423 }
9424
9425 .item-page .bundle-notice-desc {
9426 opacity: 0.8;
9427 font-size: var(--text-note);
9428 }
9429
9430 .item-page .bundle-notice-entry {
9431 margin-top: var(--gap-section);
9432 }
9433
9434 .item-page .bundle-notice-entry a {
9435 font-weight: bold;
9436 }
9437
9438 .item-page .bundle-notice-price {
9439 opacity: 0.7;
9440 }
9441
9442 .item-page .bundle-notice-empty {
9443 margin-top: var(--gap-peer);
9444 opacity: 0.7;
9445 font-size: var(--text-note);
9446 }
9447
9448 .item-page .promo-details {
9449 margin-bottom: var(--gap-section);
9450 font-size: var(--text-note);
9451 }
9452
9453 .item-page .promo-summary {
9454 cursor: pointer;
9455 opacity: 0.7;
9456 }
9457
9458 .item-page .promo-form {
9459 display: flex;
9460 gap: var(--gap-peer);
9461 align-items: center;
9462 margin-top: var(--gap-peer);
9463 }
9464
9465 .item-page .promo-input {
9466 flex: 1;
9467 padding: var(--gap-peer) var(--gap-group);
9468 font-size: var(--text-note);
9469 }
9470
9471 .item-page .action-row {
9472 margin-top: var(--gap-section);
9473 }
9474
9475 .item-page .action-row-tight {
9476 margin-top: var(--gap-peer);
9477 }
9478
9479 .item-page .full-width-btn {
9480 width: 100%;
9481 font-size: var(--text-note);
9482 }
9483
9484 .item-page .license-preset-name {
9485 margin-bottom: var(--gap-section);
9486 }
9487
9488 .item-page .license-summary {
9489 cursor: pointer;
9490 opacity: 0.7;
9491 font-size: var(--text-note);
9492 }
9493
9494 .item-page .license-text {
9495 white-space: pre-wrap;
9496 font-family: var(--font-body);
9497 font-size: var(--text-note);
9498 margin-top: var(--gap-section);
9499 padding: var(--gap-section);
9500 background: var(--surface-sunken);
9501 }
9502
9503 .item-page .license-download {
9504 margin-top: var(--gap-section);
9505 }
9506
9507 .item-page .license-download a {
9508 font-size: var(--text-note);
9509 color: var(--content);
9510 }
9511
9512 .item-page .bundle-list-entry {
9513 margin-top: var(--gap-peer);
9514 }
9515
9516 .item-page .footer-links {
9517 margin-top: var(--gap-peer);
9518 }
9519
9520 /* ===========================================
9521 JOIN COMPLETE STEP (replaces inline styles in wizards/steps/join/complete.html)
9522 =========================================== */
9523 .join-complete-intro { margin-bottom: var(--gap-pane); }
9524 .join-complete-choices {
9525 display: grid; grid-template-columns: 1fr 1fr;
9526 gap: var(--gap-section); max-width: 500px;
9527 }
9528 .join-complete-choice { text-decoration: none; color: inherit; display: block; }
9529 .join-complete-card {
9530 background: var(--surface-sunken); padding: var(--gap-section); text-align: center;
9531 border: 2px solid transparent; transition: border-color 0.15s;
9532 }
9533 .join-complete-choice:hover .join-complete-card,
9534 .join-complete-card--active { border-color: var(--content); }
9535 .join-complete-card-title {
9536 font-family: var(--font-heading); font-weight: bold;
9537 font-size: var(--text-lead); margin-bottom: var(--gap-peer);
9538 }
9539 .join-complete-card-desc {
9540 font-size: var(--text-note); opacity: 0.7; margin: 0;
9541 }
9542 .join-complete-footnote {
9543 font-size: var(--text-note); opacity: 0.5;
9544 margin-top: var(--gap-section); text-align: center;
9545 }
9546
9547 /* ===========================================
9548 PROMO CODES LIST (replaces inline styles in partials/promo_codes_list.html)
9549 =========================================== */
9550 .promo-codes-row--expired { opacity: 0.5; }
9551 .promo-codes-row--pending { opacity: 0.7; }
9552 .promo-codes-schedule { font-size: var(--text-note); }
9553 .promo-codes-schedule-pending { color: var(--warning); }
9554 .promo-codes-schedule-expired { color: var(--danger); }
9555 .promo-codes-schedule-none { opacity: 0.5; }
9556 .promo-codes-actions { white-space: nowrap; }
9557 .promo-codes-edit-btn { font-size: var(--text-fine); }
9558 .promo-codes-edit-row { background: var(--surface-sunken); }
9559 .promo-codes-edit-cell { padding: var(--gap-section); }
9560 .promo-codes-edit-form {
9561 display: flex; gap: var(--gap-section); align-items: end; flex-wrap: wrap;
9562 }
9563 .promo-codes-edit-label {
9564 font-size: var(--text-fine); display: block; margin-bottom: var(--gap-bound);
9565 }
9566 .promo-codes-edit-save { font-size: var(--text-note); }
9567 .promo-codes-bulk-row { margin-top: var(--gap-section); }
9568 .promo-codes-bulk-btn { font-size: var(--text-note); opacity: 0.7; }
9569
9570 /* ===========================================
9571 USER MEDIA TAB (replaces inline styles in partials/tabs/user_media.html)
9572 =========================================== */
9573
9574 .user-media-intro {
9575 margin-bottom: var(--gap-section);
9576 }
9577
9578 .user-media-upload {
9579 border: 2px dashed var(--border);
9580 padding: var(--gap-pane);
9581 margin-bottom: var(--gap-pane);
9582 text-align: center;
9583 background: var(--surface-overlay);
9584 }
9585
9586 .user-media-upload--dragover {
9587 border-color: var(--action);
9588 }
9589
9590 .user-media-upload-row {
9591 margin-bottom: var(--gap-section);
9592 }
9593
9594 .user-media-upload-label {
9595 display: inline-block;
9596 font-size: var(--text-note);
9597 margin-right: var(--gap-section);
9598 }
9599
9600 .user-media-upload-folder-input {
9601 padding: var(--gap-bound);
9602 font-size: var(--text-note);
9603 width: 140px;
9604 margin-left: var(--gap-bound);
9605 }
9606
9607 .user-media-upload-progress {
9608 margin-top: var(--gap-section);
9609 }
9610
9611 .user-media-upload-status {
9612 font-size: var(--text-note);
9613 margin-bottom: var(--gap-bound);
9614 }
9615
9616 .user-media-upload-status--ok {
9617 color: var(--success);
9618 }
9619
9620 .user-media-upload-status--err {
9621 color: var(--danger);
9622 }
9623
9624 .user-media-folders {
9625 margin-bottom: var(--gap-section);
9626 display: flex;
9627 gap: var(--gap-peer);
9628 flex-wrap: wrap;
9629 align-items: center;
9630 }
9631
9632 .user-media-folders-label {
9633 font-size: var(--text-note);
9634 opacity: 0.7;
9635 }
9636
9637 .user-media-folders-btn {
9638 font-size: var(--text-fine);
9639 }
9640
9641 .user-media-grid {
9642 display: grid;
9643 grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
9644 gap: var(--gap-section);
9645 }
9646
9647 .user-media-card {
9648 background: var(--surface-overlay);
9649 border: 1px solid var(--border);
9650 padding: var(--gap-section);
9651 position: relative;
9652 }
9653
9654 .user-media-card-thumb {
9655 width: 100%;
9656 height: 120px;
9657 overflow: hidden;
9658 margin-bottom: var(--gap-peer);
9659 display: flex;
9660 align-items: center;
9661 justify-content: center;
9662 background: var(--surface-sunken);
9663 }
9664
9665 .user-media-card-thumb--video {
9666 font-size: var(--text-title);
9667 opacity: 0.4;
9668 }
9669
9670 .user-media-card-img {
9671 max-width: 100%;
9672 max-height: 120px;
9673 object-fit: contain;
9674 }
9675
9676 .user-media-card-name {
9677 font-size: var(--text-fine);
9678 word-break: break-all;
9679 margin-bottom: var(--gap-bound);
9680 }
9681
9682 .user-media-card-meta {
9683 font-size: var(--text-fine);
9684 opacity: 0.6;
9685 margin-bottom: var(--gap-peer);
9686 }
9687
9688 .user-media-card-actions {
9689 display: flex;
9690 gap: var(--gap-bound);
9691 }
9692
9693 .user-media-card-btn {
9694 font-size: var(--text-fine);
9695 padding: var(--gap-bound) var(--gap-peer);
9696 }
9697
9698 /* ===========================================
9699 ITEM VERSION UPLOAD (replaces inline styles in partials/item_version_upload.html)
9700 =========================================== */
9701
9702 .item-version-upload-cell-sm {
9703 font-size: var(--text-note);
9704 }
9705
9706 .item-version-upload-no-file {
9707 opacity: 0.5;
9708 }
9709
9710 .item-version-upload-btn {
9711 padding: var(--gap-peer) var(--gap-section);
9712 font-size: var(--text-note);
9713 }
9714
9715 .item-version-upload-add-btn {
9716 padding: var(--gap-peer) var(--gap-section);
9717 }
9718
9719 .item-version-upload-grid {
9720 display: grid;
9721 grid-template-columns: 1fr 1fr;
9722 gap: var(--gap-section);
9723 }
9724
9725 .item-version-upload-hint {
9726 margin-bottom: var(--gap-peer);
9727 }
9728
9729 .item-version-upload-file-table {
9730 width: 100%;
9731 border-collapse: collapse;
9732 }
9733
9734 .item-version-upload-file-table thead tr {
9735 text-align: left;
9736 font-size: var(--text-fine);
9737 opacity: 0.7;
9738 text-transform: uppercase;
9739 letter-spacing: 0.03em;
9740 }
9741
9742 .item-version-upload-file-table th:first-child {
9743 padding: var(--gap-peer) var(--gap-peer) var(--gap-peer) 0;
9744 }
9745
9746 .item-version-upload-file-table th + th {
9747 padding: var(--gap-peer);
9748 }
9749
9750 .item-version-upload-file-table th:last-child {
9751 width: 40px;
9752 }
9753
9754 .item-version-upload-add-row {
9755 margin-top: var(--gap-peer);
9756 }
9757
9758 .item-version-upload-queue {
9759 margin-bottom: var(--gap-section);
9760 }
9761
9762 .item-version-upload-speed {
9763 font-size: var(--text-fine);
9764 opacity: 0.6;
9765 margin-top: var(--gap-bound);
9766 }
9767
9768 /* ===========================================
9769 PROJECT PAGE (replaces inline styles in pages/project.html)
9770 =========================================== */
9771
9772 .project-page .store-cover {
9773 width: 120px;
9774 height: 120px;
9775 border-radius: var(--radius-panel);
9776 object-fit: cover;
9777 margin-bottom: var(--gap-pane);
9778 }
9779 .project-page .follow-btn.is-selected {
9780 opacity: 0.7;
9781 }
9782 .project-page .follower-count-muted {
9783 font-size: var(--text-note);
9784 opacity: 0.7;
9785 padding: var(--gap-peer) 0;
9786 }
9787 .project-page .promo-details {
9788 font-size: var(--text-note);
9789 margin-bottom: var(--gap-peer);
9790 }
9791 .project-page .promo-details summary {
9792 cursor: pointer;
9793 opacity: 0.7;
9794 }
9795 .project-page .promo-input {
9796 width: 100%;
9797 margin-top: var(--gap-peer);
9798 padding: var(--gap-bound) var(--gap-peer);
9799 font-size: var(--text-note);
9800 text-transform: uppercase;
9801 }
9802 .project-page .store-footer-links {
9803 margin-top: var(--gap-peer);
9804 }
9805
9806 /* ===========================================
9807 INSERTION LIST (replaces inline styles in partials/insertion_list.html)
9808 =========================================== */
9809
9810 .insertion-list-heading {
9811 font-family: var(--font-mono);
9812 font-size: var(--text-body);
9813 margin-bottom: var(--gap-section);
9814 }
9815 .insertion-list-toolbar {
9816 margin-bottom: var(--gap-section);
9817 }
9818 .insertion-list-upload {
9819 font-family: var(--font-mono);
9820 }
9821 .insertion-list-table {
9822 width: 100%;
9823 font-family: var(--font-body);
9824 }
9825 .insertion-list-table th {
9826 text-align: left;
9827 font-family: var(--font-mono);
9828 font-size: var(--text-note);
9829 }
9830 .insertion-list-table th.insertion-list-th-actions {
9831 text-align: right;
9832 }
9833 .insertion-list-table .insertion-list-actions {
9834 text-align: right;
9835 }
9836 .insertion-list-table .insertion-list-action-btn {
9837 font-family: var(--font-mono);
9838 }
9839
9840 /* ===========================================
9841 PROJECT MEMBERS TAB (replaces inline styles in partials/tabs/project_members.html)
9842 =========================================== */
9843
9844 .proj-members-summary {
9845 background: var(--surface-sunken);
9846 padding: var(--gap-section);
9847 margin-bottom: var(--gap-section);
9848 }
9849
9850 .proj-members-summary-row {
9851 display: flex;
9852 justify-content: space-between;
9853 align-items: center;
9854 }
9855
9856 .proj-members-stat {
9857 font-size: var(--text-head);
9858 font-weight: bold;
9859 }
9860
9861 .proj-members-add {
9862 margin-bottom: var(--gap-page);
9863 }
9864
9865 .proj-members-add-grid {
9866 display: grid;
9867 grid-template-columns: 1fr auto auto;
9868 gap: var(--gap-section);
9869 align-items: end;
9870 }
9871
9872 .proj-members-field-label {
9873 font-size: var(--text-note);
9874 display: block;
9875 margin-bottom: var(--gap-bound);
9876 }
9877
9878 .proj-members-add-result {
9879 margin-top: var(--gap-peer);
9880 }
9881
9882 .proj-members-name-link {
9883 color: inherit;
9884 }
9885
9886 .proj-members-split-cell {
9887 font-weight: bold;
9888 }
9889
9890 /* ===========================================
9891 PLACEMENT LIST (replaces inline styles in partials/placement_list.html)
9892 =========================================== */
9893
9894 .placement-list-heading {
9895 font-family: var(--font-mono);
9896 font-size: var(--text-body);
9897 margin-bottom: var(--gap-section);
9898 }
9899 .placement-list-table {
9900 width: 100%;
9901 font-family: var(--font-body);
9902 margin-bottom: var(--gap-section);
9903 }
9904 .placement-list-table th {
9905 text-align: left;
9906 font-family: var(--font-mono);
9907 font-size: var(--text-note);
9908 }
9909 .placement-list-table th.placement-list-th-actions {
9910 text-align: right;
9911 }
9912 .placement-list-table .placement-list-actions {
9913 text-align: right;
9914 }
9915 .placement-list-table .placement-list-remove {
9916 font-family: var(--font-mono);
9917 }
9918 .placement-list-form {
9919 display: flex;
9920 gap: var(--gap-peer);
9921 align-items: flex-end;
9922 flex-wrap: wrap;
9923 }
9924 .placement-list-label {
9925 display: block;
9926 font-family: var(--font-mono);
9927 font-size: var(--text-fine);
9928 margin-bottom: var(--gap-bound);
9929 }
9930 .placement-list-add {
9931 font-family: var(--font-mono);
9932 }
9933
9934 /* ===========================================
9935 USER SYNCKIT TAB (replaces inline styles in partials/tabs/user_synckit.html)
9936 =========================================== */
9937
9938 .user-synckit-create { margin-bottom: var(--gap-pane); }
9939 .user-synckit-create-body {
9940 padding: var(--gap-section);
9941 background: var(--surface-overlay);
9942 border: 1px solid var(--border);
9943 }
9944 .user-synckit-create-form {
9945 display: flex;
9946 flex-direction: row;
9947 gap: var(--gap-section);
9948 align-items: flex-end;
9949 }
9950 .user-synckit-create-form label {
9951 display: block;
9952 font-size: var(--text-note);
9953 margin-bottom: var(--gap-bound);
9954 }
9955 .user-synckit-create-form input[type="text"],
9956 .user-synckit-create-form select {
9957 padding: var(--gap-peer);
9958 font-size: var(--text-note);
9959 }
9960 .user-synckit-create-form input[type="text"] { width: 220px; }
9961 .user-synckit-create-form select { width: 180px; }
9962
9963 .user-synckit-code { font-size: var(--text-fine); }
9964 .user-synckit-hint {
9965 font-size: var(--text-fine);
9966 margin-left: var(--gap-bound);
9967 }
9968
9969 .user-synckit-inline-btn {
9970 margin-left: var(--gap-peer);
9971 padding: var(--gap-bound) var(--gap-peer);
9972 font-size: var(--text-fine);
9973 }
9974 .user-synckit-row-btn {
9975 padding: var(--gap-bound) var(--gap-peer);
9976 font-size: var(--text-fine);
9977 }
9978 .user-synckit-row-btn + .user-synckit-row-btn {
9979 margin-left: var(--gap-bound);
9980 }
9981
9982 .user-synckit-status-active { color: var(--success); }
9983 .user-synckit-status-inactive { color: var(--danger); }
9984
9985 .user-synckit-edit-btn {
9986 padding: var(--gap-bound) var(--gap-peer);
9987 font-size: var(--text-fine);
9988 }
9989
9990 .user-synckit-create-error {
9991 color: var(--danger);
9992 margin-top: var(--gap-peer);
9993 }
9994
9995 /* ===========================================
9996 TOTP (replaces inline styles in partials/totp_setup.html and totp_status.html)
9997 =========================================== */
9998
9999 /* Section wrapper between TOTP subsections (QR + backup codes + confirm,
10000 or regenerate + disable). */
10001 .totp-section { margin-bottom: var(--gap-pane); }
10002
10003 /* Confirm-setup section is separated from the backup codes by a top border. */
10004 .totp-confirm {
10005 border-top: 1px solid var(--border);
10006 padding-top: var(--gap-section);
10007 }
10008
10009 /* QR code container, centered above the manual-entry details. */
10010 .totp-qr {
10011 text-align: center;
10012 margin-bottom: var(--gap-section);
10013 }
10014 .totp-qr img {
10015 max-width: 200px;
10016 image-rendering: pixelated;
10017 }
10018
10019 /* Collapsible "Manual entry key" group. */
10020 .totp-manual { margin-bottom: var(--gap-section); }
10021 .totp-manual summary {
10022 cursor: pointer;
10023 opacity: 0.7;
10024 font-size: var(--text-note);
10025 }
10026 .totp-manual-secret {
10027 display: block;
10028 margin-top: var(--gap-peer);
10029 word-break: break-all;
10030 font-size: var(--text-note);
10031 padding: var(--gap-peer);
10032 background: var(--surface-sunken);
10033 }
10034
10035 /* Backup codes grid (rendered list of one-time codes). */
10036 .backup-codes-grid {
10037 display: grid;
10038 grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
10039 gap: var(--gap-peer);
10040 }
10041 .backup-codes-grid code {
10042 padding: var(--gap-peer);
10043 background: var(--surface-sunken);
10044 text-align: center;
10045 font-size: var(--text-note);
10046 }
10047
10048 /* Status row for the "Enabled" badge above the management forms. */
10049 .totp-status-row {
10050 display: flex;
10051 align-items: center;
10052 gap: var(--gap-peer);
10053 margin-bottom: var(--gap-section);
10054 }
10055
10056 /* Container where regenerated backup codes are swapped in via HTMX. */
10057 .totp-regen-output { margin-top: var(--gap-section); }
10058
10059 /* ===========================================
10060 DASHBOARD-USER SHELL (replaces inline styles in dashboards/dashboard-user.html)
10061 =========================================== */
10062
10063 .sandbox-banner {
10064 background: var(--action);
10065 color: var(--primary-light);
10066 padding: var(--gap-section) var(--gap-pane);
10067 text-align: center;
10068 font-family: var(--font-mono);
10069 font-size: var(--text-note);
10070 }
10071
10072 .sandbox-banner-link {
10073 color: var(--primary-light);
10074 text-decoration: underline;
10075 margin-left: var(--gap-peer);
10076 }
10077 .account-status-actions {
10078 display: flex;
10079 gap: var(--gap-section);
10080 flex-wrap: wrap;
10081 margin-top: var(--gap-section);
10082 }
10083
10084 .suspension-wrap {
10085 margin-bottom: var(--gap-section);
10086 }
10087
10088 .password-warning-banner {
10089 margin-bottom: var(--gap-section);
10090 padding: var(--gap-section);
10091 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
10092 border: 1px solid var(--warning);
10093 font-size: var(--text-body);
10094 }
10095
10096 .dash-user-header-links {
10097 margin-bottom: var(--gap-peer);
10098 display: flex;
10099 gap: var(--gap-pane);
10100 }
10101
10102 .dash-user-header-link {
10103 font-size: var(--text-note);
10104 opacity: 0.7;
10105 }
10106
10107 .checklist-recovery {
10108 padding: var(--gap-section) 0;
10109 text-align: right;
10110 }
10111
10112 .checklist-recovery-link {
10113 font-size: var(--text-note);
10114 opacity: 0.7;
10115 }
10116
10117 .tab-spinner {
10118 margin-left: var(--gap-section);
10119 }
10120
10121 /* ===========================================
10122 USER PROFILE PAGE (replaces inline styles in pages/user.html)
10123 =========================================== */
10124
10125 .user-page .creator-paused-notice {
10126 margin-bottom: var(--gap-pane);
10127 padding: var(--gap-section);
10128 background: var(--surface-sunken);
10129 border: 1px solid var(--border);
10130 text-align: center;
10131 font-size: var(--text-body);
10132 }
10133
10134 .user-page .profile-action-row {
10135 margin-top: var(--gap-section);
10136 }
10137
10138 .user-page .profile-edit-btn {
10139 display: inline-block;
10140 padding: var(--gap-peer) var(--gap-section);
10141 text-decoration: none;
10142 font-size: var(--text-note);
10143 }
10144
10145 .user-page .follow-btn.is-selected {
10146 opacity: 0.7;
10147 }
10148
10149 .user-page .follower-count {
10150 margin-top: var(--gap-section);
10151 font-size: var(--text-note);
10152 opacity: 0.7;
10153 }
10154
10155 .user-page .profile-share-row {
10156 margin-top: var(--gap-section);
10157 display: flex;
10158 gap: var(--gap-section);
10159 justify-content: center;
10160 font-size: var(--text-note);
10161 }
10162
10163 .user-page .profile-share-link {
10164 opacity: 0.7;
10165 }
10166
10167 .user-page .collections-section {
10168 margin-bottom: var(--gap-page);
10169 }
10170
10171 /* ===========================================
10172 SUSPENSION BANNER (replaces inline styles in partials/suspension_banner.html)
10173 =========================================== */
10174
10175 .suspension-banner {
10176 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
10177 border: 1px solid var(--danger);
10178 padding: var(--gap-pane);
10179 margin-bottom: var(--gap-pane);
10180 }
10181
10182 .suspension-banner-title {
10183 margin: 0 0 var(--gap-peer) 0;
10184 color: var(--danger);
10185 }
10186
10187 .suspension-banner-reason {
10188 font-size: var(--text-note);
10189 margin: 0 0 var(--gap-section) 0;
10190 }
10191
10192 .suspension-banner-denial {
10193 background: var(--surface-overlay);
10194 padding: var(--gap-section);
10195 margin-bottom: var(--gap-section);
10196 font-size: var(--text-note);
10197 }
10198
10199 .suspension-banner-denial-response {
10200 margin: var(--gap-bound) 0 0 0;
10201 }
10202
10203 .suspension-banner-denial-hint {
10204 margin: var(--gap-peer) 0 0 0;
10205 opacity: 0.7;
10206 }
10207
10208 .suspension-banner-pending {
10209 font-size: var(--text-note);
10210 opacity: 0.7;
10211 margin: 0;
10212 }
10213
10214 .suspension-banner-appeal {
10215 margin-top: var(--gap-peer);
10216 }
10217
10218 .suspension-banner-appeal-summary {
10219 cursor: pointer;
10220 font-size: var(--text-note);
10221 }
10222
10223 .suspension-banner-appeal-form {
10224 margin-top: var(--gap-section);
10225 }
10226
10227 .suspension-banner-appeal-text {
10228 width: 100%;
10229 margin-bottom: var(--gap-peer);
10230 font-size: var(--text-note);
10231 }
10232
10233 .suspension-banner-appeal-submit {
10234 font-size: var(--text-note);
10235 }
10236
10237 .suspension-banner-export {
10238 margin-top: var(--gap-section);
10239 }
10240
10241 .suspension-banner-export-link {
10242 font-size: var(--text-note);
10243 }
10244
10245 /* ===========================================
10246 REPORT MODAL (replaces inline styles in partials/report_modal.html)
10247 =========================================== */
10248
10249 .report-modal-content {
10250 background: var(--surface-page);
10251 padding: var(--gap-page);
10252 width: 90%;
10253 max-width: 480px;
10254 max-height: 90vh;
10255 overflow-y: auto;
10256 }
10257
10258 .report-modal-title {
10259 font-size: var(--text-subhead);
10260 margin: 0;
10261 }
10262
10263 .report-modal-fieldset {
10264 border: none;
10265 padding: 0;
10266 margin: 0 0 var(--gap-section) 0;
10267 }
10268
10269 .report-modal-legend {
10270 font-size: var(--text-note);
10271 font-weight: 600;
10272 margin-bottom: var(--gap-peer);
10273 }
10274
10275 .report-modal-option {
10276 display: block;
10277 margin-bottom: var(--gap-peer);
10278 cursor: pointer;
10279 }
10280
10281 .report-modal-details {
10282 margin-bottom: var(--gap-section);
10283 }
10284
10285 .report-modal-details-label {
10286 display: block;
10287 font-size: var(--text-note);
10288 margin-bottom: var(--gap-bound);
10289 }
10290
10291 .report-modal-textarea {
10292 width: 100%;
10293 font-size: var(--text-note);
10294 }
10295
10296 .report-modal-actions {
10297 display: flex;
10298 gap: var(--gap-peer);
10299 justify-content: flex-end;
10300 }
10301
10302 /* ===========================================
10303 LIBRARY COLLECTIONS TAB (partials/tabs/library_collections.html)
10304 =========================================== */
10305
10306 .badge.is-faded { opacity: 0.5; }
10307 .lib-coll-new { margin-top: var(--gap-pane); }
10308 .lib-coll-new-summary { cursor: pointer; font-weight: bold; }
10309 .lib-coll-new-form { margin-top: var(--gap-section); }
10310 .lib-coll-wishlist-heading {
10311 font-size: var(--text-lead);
10312 margin-top: var(--gap-page);
10313 margin-bottom: var(--gap-section);
10314 opacity: 0.8;
10315 }
10316 .lib-coll-wishlist-wrap {
10317 overflow-x: auto;
10318 -webkit-overflow-scrolling: touch;
10319 }
10320 .lib-coll-wishlist-table { min-width: 500px; }
10321 .lib-coll-row-actions { white-space: nowrap; }
10322 .lib-coll-row-actions .lib-coll-cart-btn { margin-right: var(--gap-bound); }
10323
10324 /* ===========================================
10325 USER ANALYTICS TAB (partials/tabs/user_analytics.html)
10326 =========================================== */
10327
10328 .analytics-export-link { margin-left: var(--gap-section); }
10329 .analytics-range-heading {
10330 font-size: var(--text-lead);
10331 margin: 0;
10332 }
10333 .analytics-section--comparison { margin-bottom: var(--gap-page); }
10334 .analytics-section--comparison h2 { margin-bottom: var(--gap-section); }
10335 .analytics-table-wrap {
10336 overflow-x: auto;
10337 -webkit-overflow-scrolling: touch;
10338 }
10339 .analytics-table { min-width: 500px; }
10340 .analytics-revenue-cell {
10341 display: flex;
10342 align-items: center;
10343 gap: var(--gap-peer);
10344 }
10345 .analytics-revenue-bar {
10346 width: var(--fill, 0%);
10347 height: 10px;
10348 min-width: 2px;
10349 max-width: 80px;
10350 background: var(--action);
10351 }
10352
10353 /* ===========================================
10354 ITEM EMBED TAB (templates/partials/tabs/item_embed.html)
10355 =========================================== */
10356 .embed-intro {
10357 opacity: 0.7;
10358 font-size: var(--text-note);
10359 margin-bottom: var(--gap-pane);
10360 }
10361 .embed-option {
10362 margin-bottom: var(--gap-pane);
10363 padding: var(--gap-section);
10364 border: 1px solid var(--border);
10365 border-radius: var(--radius-panel);
10366 }
10367 .embed-option h4 {
10368 margin-bottom: var(--gap-bound);
10369 font-size: var(--text-body);
10370 }
10371 .embed-option-desc {
10372 font-size: var(--text-note);
10373 opacity: 0.7;
10374 margin-bottom: var(--gap-peer);
10375 }
10376 .embed-preview {
10377 margin: var(--gap-section) 0;
10378 overflow: hidden;
10379 }
10380 .embed-iframe {
10381 border: none;
10382 border-radius: var(--radius-control);
10383 }
10384 .embed-layout-toggle {
10385 font-size: var(--text-note);
10386 margin-bottom: var(--gap-peer);
10387 display: flex;
10388 gap: var(--gap-section);
10389 }
10390 .embed-layout-toggle label {
10391 cursor: pointer;
10392 display: flex;
10393 align-items: center;
10394 gap: var(--gap-bound);
10395 }
10396 .embed-code-row {
10397 display: flex;
10398 align-items: center;
10399 gap: var(--gap-peer);
10400 }
10401 .embed-snippet {
10402 flex: 1;
10403 padding: var(--gap-peer) var(--gap-section);
10404 background: var(--surface-overlay);
10405 border-radius: var(--radius-control);
10406 font-size: var(--text-fine);
10407 overflow-x: auto;
10408 white-space: nowrap;
10409 font-family: var(--font-mono);
10410 border: 1px solid var(--border);
10411 }
10412 .copy-btn {
10413 padding: 6px 12px;
10414 font-size: var(--text-fine);
10415 background: var(--action);
10416 color: var(--primary-light);
10417 border: none;
10418 border-radius: var(--radius-control);
10419 cursor: pointer;
10420 white-space: nowrap;
10421 }
10422 .copy-btn:hover {
10423 opacity: 0.9;
10424 }
10425
10426 /* ===========================================
10427 ADMIN NAV (templates/partials/admin_nav.html)
10428 =========================================== */
10429 .admin-nav {
10430 display: flex;
10431 gap: var(--gap-peer);
10432 margin-bottom: var(--gap-pane);
10433 flex-wrap: wrap;
10434 font-family: var(--font-mono);
10435 font-size: var(--text-note);
10436 }
10437 .admin-nav a {
10438 padding: var(--gap-bound) var(--gap-section);
10439 text-decoration: none;
10440 }
10441
10442 /* Admin nav items and filter-bar toggles are .btn-primary / .btn-secondary,
10443 toggled by the templates to mark the current page or active filter. Colour
10444 and depth come from the button recipe; only the tighter admin metrics and
10445 the active weight are stated here. */
10446 .admin-nav a.btn-primary,
10447 .filter-bar button.btn-primary {
10448 font-weight: 700;
10449 }
10450 .admin-nav a.btn-secondary:hover,
10451 .filter-bar button.btn-secondary:hover {
10452 color: var(--content);
10453 }
10454
10455 /* ===========================================
10456 VIDEO PLAYER PAGE (templates/pages/video_player.html)
10457 =========================================== */
10458 .video-cover-img {
10459 width: 100%;
10460 display: block;
10461 background: var(--primary-dark);
10462 }
10463 .video-cover-placeholder {
10464 width: 100%;
10465 aspect-ratio: 16 / 9;
10466 background: var(--primary-dark);
10467 display: flex;
10468 align-items: center;
10469 justify-content: center;
10470 color: var(--content-muted);
10471 }
10472 .video-store-cta {
10473 margin: var(--gap-page) 0;
10474 padding: var(--gap-pane);
10475 background: var(--surface-sunken);
10476 text-align: center;
10477 }
10478 .video-cta-lead {
10479 font-size: var(--text-lead);
10480 margin-bottom: var(--gap-section);
10481 }
10482 .video-cta-price {
10483 font-size: var(--text-head);
10484 margin-bottom: var(--gap-section);
10485 }
10486 .video-cta-fineprint {
10487 font-size: var(--text-note);
10488 opacity: 0.7;
10489 margin-top: var(--gap-section);
10490 }
10491 .video-library-status {
10492 font-family: var(--font-mono);
10493 color: var(--content-muted);
10494 }
10495
10496 /* ===========================================
10497 LIBRARY LOCKED PAGE (templates/pages/library_locked.html)
10498 =========================================== */
10499 .library-locked-byline {
10500 opacity: 0.7;
10501 }
10502 .library-locked-cover {
10503 max-width: 320px;
10504 margin: var(--gap-section) 0;
10505 /* Reserve the space before the image loads (audit Run 17 Frontend). */
10506 aspect-ratio: 1 / 1;
10507 object-fit: cover;
10508 }
10509 .library-locked-purchase {
10510 margin-top: var(--gap-page);
10511 }
10512 .library-locked-lead {
10513 opacity: 0.85;
10514 }
10515 .library-locked-actions {
10516 margin-top: var(--gap-pane);
10517 display: flex;
10518 gap: var(--gap-section);
10519 flex-wrap: wrap;
10520 }
10521 .library-locked-bundles {
10522 margin-top: var(--gap-pane);
10523 }
10524 .library-locked-bundles-title {
10525 font-weight: bold;
10526 }
10527 .library-locked-bundle {
10528 margin-top: var(--gap-peer);
10529 }
10530 .library-locked-bundle-price {
10531 opacity: 0.7;
10532 }
10533
10534 /* ===========================================
10535 BLOG (replaces inline styles in project_blog.html
10536 and dashboard-blog-editor.html)
10537 =========================================== */
10538
10539 .blog-empty-state { padding: var(--gap-page) 0; }
10540 .blog-empty-hint {
10541 opacity: 0.7;
10542 font-size: var(--text-note);
10543 margin-top: var(--gap-peer);
10544 }
10545 .blog-post-title-link { font-weight: bold; }
10546 .blog-post-actions {
10547 display: flex;
10548 gap: var(--gap-peer);
10549 }
10550
10551 /* Blog editor dashboard (templates/dashboards/dashboard-blog-editor.html). */
10552 .blog-editor h1 {
10553 font-size: var(--text-title);
10554 margin-bottom: var(--gap-section);
10555 text-align: left;
10556 }
10557 .blog-editor .editor-form { max-width: 800px; }
10558 .blog-editor .editor-form .form-group { margin-bottom: var(--gap-section); }
10559 .blog-editor .editor-form textarea {
10560 font-family: var(--font-mono);
10561 font-size: var(--text-note);
10562 min-height: 400px;
10563 resize: vertical;
10564 }
10565 .blog-editor-slug-spinner { font-size: var(--text-note); }
10566 .blog-editor-media-btn {
10567 margin-top: var(--gap-peer);
10568 font-size: var(--text-note);
10569 }
10570 .blog-editor-actions {
10571 display: flex;
10572 gap: var(--gap-section);
10573 align-items: center;
10574 }
10575 .blog-editor-status { margin-top: var(--gap-peer); }
10576
10577 /* ===========================================
10578 AUDIO PLAYER (replaces inline styles in pages/audio_player.html)
10579 =========================================== */
10580
10581 .audio-store-cta {
10582 margin: var(--gap-page) 0;
10583 padding: var(--gap-pane);
10584 background: var(--surface-sunken);
10585 text-align: center;
10586 }
10587 .audio-store-cta .audio-access-msg {
10588 font-size: var(--text-lead);
10589 margin-bottom: var(--gap-section);
10590 }
10591 .audio-store-cta .audio-price {
10592 font-size: var(--text-head);
10593 margin-bottom: var(--gap-section);
10594 }
10595 .audio-store-cta .audio-fee-note {
10596 font-size: var(--text-note);
10597 opacity: 0.7;
10598 margin-top: var(--gap-section);
10599 }
10600 .audio-library-status {
10601 font-family: var(--font-mono);
10602 color: var(--content-muted);
10603 }
10604
10605 /* ===========================================
10606 SECTIONS WIZARD STEP (templates/wizards/steps/item/sections.html)
10607 Parallels .psection-row used by item_details + project-sections.js,
10608 but isolated so wizard markup can evolve independently.
10609 =========================================== */
10610
10611 .section-row {
10612 display: flex;
10613 align-items: center;
10614 gap: var(--gap-section);
10615 padding: var(--gap-group) 0;
10616 border-bottom: 1px solid var(--border);
10617 }
10618 .section-row-title { flex: 1; font-weight: bold; }
10619 .section-row-length { font-size: var(--text-fine); opacity: 0.6; }
10620 .section-row .section-delete-btn {
10621 padding: var(--gap-bound) var(--gap-group);
10622 font-size: var(--text-fine);
10623 }
10624
10625 .section-add-body { margin-top: var(--gap-section); }
10626
10627 .wizard-actions { margin-top: var(--gap-pane); }
10628
10629 /* ===========================================
10630 PROJECT SUBSCRIPTIONS TAB (templates/partials/tabs/project_subscriptions.html)
10631 =========================================== */
10632
10633 .subs-connect-info {
10634 margin-bottom: var(--gap-pane);
10635 padding: var(--gap-section);
10636 }
10637 .subs-new-tier { margin-bottom: var(--gap-page); }
10638
10639 .tier-row-desc {
10640 font-size: var(--text-note);
10641 opacity: 0.7;
10642 margin-top: var(--gap-bound);
10643 }
10644 .tier-action-btn {
10645 font-size: var(--text-note);
10646 padding: var(--gap-bound) var(--gap-group);
10647 }
10648
10649 .subs-members-bar {
10650 margin-top: var(--gap-page);
10651 display: flex;
10652 justify-content: space-between;
10653 align-items: center;
10654 }
10655
10656 /* ===========================================
10657 LIBRARY CONSUMPTION PAGES
10658 ===========================================
10659 Shared styles for the library-side consumption surfaces:
10660 text_reader.html, library_text.html (.article-page),
10661 library_downloads.html (.library-page),
10662 library_audio.html / library_video.html (.media-container + helpers).
10663 The .article-page and .library-page sections higher up in this file
10664 are activated by body classes set in the templates. */
10665
10666 /* Faded excerpt text on the public store-page text reader. */
10667 .article-page .article-body--excerpt { opacity: 0.85; }
10668
10669 /* Store-page call-to-action block (text_reader.html). */
10670 .article-page .store-cta {
10671 margin: var(--gap-page) 0;
10672 padding: var(--gap-pane);
10673 background: var(--surface-sunken);
10674 text-align: center;
10675 }
10676 .article-page .store-cta-lead {
10677 font-size: var(--text-lead);
10678 margin-bottom: var(--gap-section);
10679 }
10680 .article-page .store-cta-price {
10681 font-size: var(--text-head);
10682 margin-bottom: var(--gap-section);
10683 }
10684 .article-page .store-cta-note {
10685 font-size: var(--text-note);
10686 opacity: 0.7;
10687 margin-top: var(--gap-section);
10688 }
10689 .article-page .store-cta .library-status {
10690 font-family: var(--font-mono);
10691 color: var(--content-muted);
10692 }
10693
10694 /* Library back-link breadcrumb shared by the media library pages. */
10695 .media-container .library-back {
10696 font-size: var(--text-note);
10697 opacity: 0.7;
10698 margin: var(--gap-section) 0;
10699 }
10700 .media-container .library-back a { color: var(--content); }
10701
10702 /* Source-files download list under audio/video library pages.
10703 The .library-page version exists higher up; this block scopes the
10704 same look under .media-container for audio/video. */
10705 .media-container .library-downloads {
10706 background: var(--surface-overlay);
10707 padding: var(--gap-pane);
10708 margin-top: var(--gap-page);
10709 }
10710 .media-container .library-downloads h3 {
10711 font-size: var(--text-lead);
10712 margin-bottom: var(--gap-section);
10713 }
10714 .media-container .download-row {
10715 display: flex;
10716 align-items: center;
10717 gap: var(--gap-section);
10718 padding: var(--gap-peer) 0;
10719 border-bottom: 1px solid var(--border);
10720 }
10721 .media-container .download-row:last-child { border-bottom: none; }
10722 .media-container .download-version {
10723 min-width: 4em;
10724 font-family: var(--font-mono);
10725 font-size: var(--text-note);
10726 }
10727 .media-container .download-label { flex: 1; }
10728 .media-container .download-label--empty { flex: 1; opacity: 0.5; }
10729 .media-container .download-size {
10730 font-size: var(--text-note);
10731 opacity: 0.6;
10732 min-width: 5em;
10733 text-align: right;
10734 }
10735
10736 /* Compact download button used in both library_downloads.html and
10737 media library pages. */
10738 .btn-download-compact {
10739 padding: var(--gap-peer) var(--gap-section);
10740 font-size: var(--text-note);
10741 }
10742
10743 /* Faded "no label" placeholder on library_downloads.html download rows. */
10744 .library-page .download-label--empty { opacity: 0.5; }
10745
10746 /* Library-downloads description <details> summary + body. */
10747 .library-page .lib-summary {
10748 cursor: pointer;
10749 font-family: var(--font-heading);
10750 font-size: var(--text-subhead);
10751 }
10752 .library-page .lib-summary-body { margin-top: var(--gap-section); }
10753
10754 /* License section on library_downloads.html. */
10755 .library-page .lib-license-name { margin-bottom: var(--gap-section); }
10756 .library-page .lib-license-download {
10757 font-size: var(--text-note);
10758 color: var(--content);
10759 }
10760
10761 /* Footer below the item-footer (was inline on library_downloads.html). */
10762 .library-page .item-footer-sub { margin-top: var(--gap-peer); }
10763
10764 /* library_text.html footer: a terse in-page footer for the library's own
10765 text view, unrelated to base.html's site-footer. */
10766 .article-page .lib-text-footer {
10767 text-align: center;
10768 padding: var(--gap-page);
10769 font-size: var(--text-note);
10770 opacity: 0.6;
10771 border-top: 1px solid var(--border);
10772 }
10773 .article-page .lib-text-footer a { color: var(--content); }
10774
10775 /* Media player play/pause icon initial state. The JS in media-player.js
10776 toggles via element.style.display directly, so we must NOT use
10777 !important here, element.style would still beat a normal-priority
10778 declaration, but !important in the stylesheet would defeat that. */
10779 .media-icon-hidden { display: none; }
10780
10781 /* Video element fills its .video-display parent. .video-display already
10782 provides a black background. */
10783 .video-display video { width: 100%; }
10784
10785 /* Cover image inside .cover-art on library_audio.html, the parent
10786 .cover-art already sets aspect ratio and centering; the inline
10787 width/aspect-ratio/object-fit was redundant with .cover-art img. */
10788
10789 /* ===========================================
10790 USER DASHBOARD TABS (support / sessions / buyer contacts)
10791 =========================================== */
10792
10793 /* Support tab (templates/partials/tabs/user_support.html). */
10794 .support-tab .support-intro-meta {
10795 margin-bottom: var(--gap-pane);
10796 font-size: var(--text-note);
10797 opacity: 0.8;
10798 }
10799 .support-tab .support-form { max-width: 600px; }
10800 .support-tab .response-times {
10801 margin-top: var(--gap-page);
10802 font-size: var(--text-note);
10803 opacity: 0.7;
10804 }
10805 .support-tab .response-times ul {
10806 margin: var(--gap-bound) 0 0 var(--gap-pane);
10807 }
10808
10809 /* Sessions tab (templates/partials/tabs/user_sessions.html). */
10810 .sessions-tab .sessions-table { width: 100%; }
10811 .sessions-tab .device-cell {
10812 max-width: 300px;
10813 overflow: hidden;
10814 text-overflow: ellipsis;
10815 white-space: nowrap;
10816 }
10817
10818 /* Buyer contacts tab (templates/partials/tabs/buyer_contacts.html). */
10819 .buyer-contacts .contacts-table { min-width: 500px; }
10820
10821 /* Creators page extras (templates/pages/creators.html). */
10822 .creators-page .pricing-fineprint {
10823 font-size: var(--text-note);
10824 opacity: 0.8;
10825 margin-top: var(--gap-peer);
10826 }
10827 .creators-page .trial-callout {
10828 font-size: var(--text-note);
10829 margin-top: var(--gap-pane);
10830 }
10831
10832 /* ===========================================
10833 LIBRARY TABS
10834 Shared classes for the library-dashboard tab partials:
10835 library_wishlists, library_contacts, library_purchases,
10836 library_subscriptions. Migrated from inline styles.
10837 =========================================== */
10838
10839 /* Tables in library tabs need a minimum width so columns stay readable
10840 while the outer container scrolls horizontally on narrow viewports. */
10841
10842 /* Empty-state hint paragraph (smaller, slightly faded). */
10843 .library-tab-empty-hint {
10844 font-size: var(--text-note);
10845 opacity: 0.7;
10846 margin-bottom: var(--gap-section);
10847 }
10848
10849 /* Compact row-action button (wishlist Add to Cart / Remove, etc.). */
10850
10851 .library-filter-wrap { margin-bottom: var(--gap-section); }
10852
10853 /* Purchases table "Open" action link + context menu trigger group. */
10854 .library-row-actions {
10855 display: flex;
10856 align-items: center;
10857 gap: var(--gap-peer);
10858 }
10859 .library-row-open {
10860 font-size: var(--text-fine);
10861 padding: var(--gap-bound) var(--gap-section);
10862 text-decoration: none;
10863 white-space: nowrap;
10864 }
10865
10866 /* Section sub-heading inside purchases tab (e.g. "Subscriptions"). */
10867 .library-tab-subheading {
10868 font-size: var(--text-lead);
10869 margin-top: var(--gap-page);
10870 margin-bottom: var(--gap-section);
10871 opacity: 0.8;
10872 }
10873
10874 /* Inline-block button styled as a link target on empty-state CTAs. */
10875 .library-tab-cta {
10876 display: inline-block;
10877 text-decoration: none;
10878 }
10879
10880 /* Context menu (dropdown) on each purchase row. */
10881 .context-menu-wrapper { position: relative; }
10882 .context-menu-btn {
10883 background: none;
10884 border: none;
10885 cursor: pointer;
10886 padding: var(--gap-peer);
10887 font-size: var(--text-subhead);
10888 line-height: 1;
10889 color: var(--content-muted);
10890 border-radius: var(--radius-control);
10891 }
10892 .context-menu-btn:hover {
10893 background: var(--surface-sunken);
10894 color: var(--content);
10895 }
10896 .context-menu {
10897 display: none;
10898 position: absolute;
10899 right: 0;
10900 top: 100%;
10901 background: var(--surface-overlay);
10902 border: 1px solid var(--border);
10903 box-shadow: var(--elevation-overlay);
10904 min-width: 160px;
10905 z-index: var(--z-context);
10906 }
10907 .context-menu.open { display: block; }
10908 .context-menu-item {
10909 display: block;
10910 width: 100%;
10911 padding: var(--gap-section);
10912 text-align: left;
10913 background: none;
10914 border: none;
10915 font-family: var(--font-body);
10916 font-size: var(--text-note);
10917 color: var(--content);
10918 cursor: pointer;
10919 text-decoration: none;
10920 }
10921 .context-menu-item:hover { background: var(--surface-sunken); }
10922 .context-menu-item.danger { color: var(--danger); }
10923
10924 /* Inline license-key code chip shown beneath a purchase date. */
10925 .license-key-inline { margin-top: var(--gap-bound); }
10926 .key-code-small {
10927 font-size: var(--text-fine);
10928 padding: var(--gap-bound) var(--gap-peer);
10929 background: var(--surface-sunken);
10930 border-radius: var(--radius-control);
10931 cursor: pointer;
10932 user-select: all;
10933 }
10934 .key-code-small:hover { background: var(--border); }
10935
10936 /* Library row title link reads as plain text until hovered. */
10937 .library-title-link:hover { text-decoration: underline; }
10938 /* ===========================================
10939 ADMIN ENTRIES (replaces inline styles in partials/admin_user_entries.html,
10940 partials/admin_waitlist_entries.html,
10941 partials/admin_appeal_entries.html)
10942 =========================================== */
10943
10944 /* (`.badge-active` / `.badge-trusted` removed in wave 2 tier B2. They were a
10945 second, ad-hoc spelling of what data-tone="success" already says, and the
10946 admin entries take the tone now.) */
10947
10948 /* "Trust" toggle button sits next to a Trusted badge. */
10949 .admin-entries-trust-btn {
10950 margin-left: var(--gap-bound);
10951 }
10952
10953 /* Waitlist pitch cell, narrow body text. */
10954 .admin-entries-pitch {
10955 font-size: var(--text-note);
10956 max-width: 300px;
10957 }
10958 .admin-entries-pitch-empty {
10959 opacity: 0.5;
10960 font-style: italic;
10961 }
10962 .admin-entries-pitch-inviter {
10963 margin-top: var(--gap-bound);
10964 }
10965
10966 /* Appeal text columns. */
10967 .admin-entries-reason {
10968 font-size: var(--text-note);
10969 max-width: 200px;
10970 }
10971 .admin-entries-appeal {
10972 font-size: var(--text-note);
10973 max-width: 300px;
10974 }
10975
10976 /* Decide form textarea + stacked action buttons. */
10977 .admin-entries-decide-textarea {
10978 width: 200px;
10979 }
10980 .admin-entries-decide-actions {
10981 display: flex;
10982 flex-direction: column;
10983 gap: var(--gap-bound);
10984 }
10985
10986 /* Pagination row at the bottom of admin entry tables. */
10987 .admin-entries-pagination {
10988 display: flex;
10989 justify-content: center;
10990 align-items: center;
10991 gap: var(--gap-section);
10992 padding: var(--gap-section) 0;
10993 }
10994
10995 /* ===========================================
10996 ITEM TABS (item_settings, item_sales, item_overview partials)
10997 =========================================== */
10998
10999 /* Schedule form is hidden until "Schedule" button is clicked. JS toggles
11000 the `.hidden` utility class. */
11001 .item-settings-schedule-form {
11002 margin-top: var(--gap-section);
11003 }
11004
11005 .item-sales-tab-header {
11006 display: flex;
11007 justify-content: space-between;
11008 align-items: center;
11009 margin-bottom: var(--gap-section);
11010 }
11011 .item-sales-tab-header h2 { margin: 0; }
11012 .item-overview-embed {
11013 margin-top: var(--gap-pane);
11014 }
11015 .item-overview-embed > summary {
11016 cursor: pointer;
11017 font-weight: bold;
11018 font-size: var(--text-lead);
11019 }
11020
11021 /* ===========================================
11022 ITEM TEXT EDITOR (partials/item_text_editor.html)
11023 =========================================== */
11024
11025 .text-editor-insert-btn {
11026 margin-top: var(--gap-peer);
11027 font-size: var(--text-note);
11028 }
11029
11030 .text-editor-placeholder {
11031 opacity: 0.6;
11032 }
11033
11034 .text-editor-actions {
11035 margin-top: var(--gap-section);
11036 }
11037
11038 /* ===========================================
11039 PROJECT ANALYTICS TAB (partials/tabs/project_analytics.html)
11040 =========================================== */
11041
11042 .tab-docs-extra {
11043 margin-left: var(--gap-section);
11044 }
11045
11046 .analytics-tab-header {
11047 display: flex;
11048 justify-content: space-between;
11049 align-items: center;
11050 margin-bottom: var(--gap-section);
11051 }
11052 .analytics-tab-header h2 {
11053 font-size: var(--text-lead);
11054 margin: 0;
11055 }
11056
11057 .analytics-empty-block {
11058 text-align: center;
11059 padding: var(--gap-page) var(--gap-section);
11060 opacity: 0.6;
11061 }
11062
11063 /* ===========================================
11064 RECEIPT PAGE INLINE FRAGMENTS (pages/receipt.html)
11065 =========================================== */
11066
11067 .receipt-page .receipt-brand {
11068 opacity: 0.6;
11069 font-size: var(--text-note);
11070 }
11071 .receipt-page .receipt-meta {
11072 text-align: right;
11073 }
11074 .receipt-page .receipt-meta-line {
11075 font-family: var(--font-mono);
11076 font-size: var(--text-note);
11077 opacity: 0.6;
11078 }
11079 .receipt-page .receipt-meta-sub {
11080 font-family: var(--font-mono);
11081 font-size: var(--text-fine);
11082 opacity: 0.4;
11083 margin-top: var(--gap-bound);
11084 }
11085 .receipt-page .print-btn {
11086 margin-top: var(--gap-section);
11087 font-size: var(--text-note);
11088 }
11089
11090 /* ===========================================
11091 IMPORT PAGE INLINE FRAGMENTS (dashboards/dashboard-import.html)
11092 =========================================== */
11093
11094 .import-page .import-preview-label,
11095 .import-page .import-mapping-label,
11096 .import-page .import-progress-label {
11097 font-family: var(--font-mono);
11098 font-size: var(--text-note);
11099 margin-bottom: var(--gap-peer);
11100 }
11101 .import-page .import-mapping-label {
11102 margin-bottom: var(--gap-section);
11103 }
11104 .import-page .import-progress-label {
11105 font-size: var(--text-note);
11106 }
11107 .import-page .import-start-btn {
11108 margin-top: var(--gap-section);
11109 }
11110 .import-page .import-history-heading {
11111 font-size: var(--text-subhead);
11112 margin-bottom: var(--gap-section);
11113 }
11114
11115 /* ===========================================
11116 SLUG STATUS PARTIAL (partials/slug_status.html)
11117 =========================================== */
11118
11119 .slug-status {
11120 font-size: var(--text-note);
11121 }
11122 .slug-status.available { color: var(--success); }
11123 .slug-status.taken { color: var(--danger); }
11124
11125 .slug-suggestions {
11126 font-size: var(--text-note);
11127 display: block;
11128 margin-top: var(--gap-bound);
11129 }
11130 .slug-suggestion-link {
11131 margin-right: var(--gap-peer);
11132 }
11133
11134 /* ===========================================
11135 STRIPE DISCLAIMER PAGE (pages/stripe_disclaimer.html)
11136 =========================================== */
11137
11138 .stripe-disclaimer-page .container {
11139 max-width: 700px;
11140 margin: var(--gap-pane) auto;
11141 padding: 0 var(--gap-page);
11142 }
11143 .stripe-disclaimer-page .stripe-disclaimer-title {
11144 text-align: center;
11145 }
11146 .stripe-disclaimer-page .tagline {
11147 font-size: var(--text-note);
11148 opacity: 0.7;
11149 margin-bottom: var(--gap-page);
11150 text-align: center;
11151 }
11152 .stripe-disclaimer-page h2 {
11153 font-size: var(--text-head);
11154 margin-bottom: var(--gap-pane);
11155 }
11156 .stripe-disclaimer-page .stripe-disclaimer-lead {
11157 margin-bottom: var(--gap-pane);
11158 opacity: 0.8;
11159 }
11160 .stripe-disclaimer-page .section { margin-bottom: var(--gap-page); }
11161 .stripe-disclaimer-page .section h3 {
11162 font-size: var(--text-lead);
11163 margin-bottom: var(--gap-section);
11164 font-family: var(--font-mono);
11165 }
11166 .stripe-disclaimer-page .section ul {
11167 margin: 0;
11168 padding-left: var(--gap-section);
11169 }
11170 .stripe-disclaimer-page .section li {
11171 margin-bottom: var(--gap-peer);
11172 line-height: 1.5;
11173 }
11174 .stripe-disclaimer-page .stripe-disclaimer-fee-lead {
11175 margin-bottom: var(--gap-section);
11176 }
11177 .stripe-disclaimer-page .fee-example {
11178 background: var(--surface-page);
11179 padding: var(--gap-section);
11180 margin: var(--gap-section) 0;
11181 font-family: var(--font-mono);
11182 font-size: var(--text-note);
11183 }
11184 .stripe-disclaimer-page .fee-row {
11185 display: flex;
11186 justify-content: space-between;
11187 padding: var(--gap-bound) 0;
11188 }
11189 .stripe-disclaimer-page .fee-row.highlight {
11190 font-weight: bold;
11191 border-top: 1px solid var(--border);
11192 margin-top: var(--gap-peer);
11193 padding-top: var(--gap-peer);
11194 }
11195 .stripe-disclaimer-page .fee-row .zero { color: var(--content); }
11196 .stripe-disclaimer-page .stripe-disclaimer-note {
11197 font-size: var(--text-note);
11198 opacity: 0.8;
11199 }
11200 .stripe-disclaimer-page .acceptance {
11201 background: var(--surface-page);
11202 padding: var(--gap-section);
11203 margin: var(--gap-pane) 0;
11204 }
11205 .stripe-disclaimer-page .checkbox-group {
11206 display: flex;
11207 align-items: flex-start;
11208 gap: var(--gap-section);
11209 }
11210 .stripe-disclaimer-page .checkbox-group input[type="checkbox"] {
11211 margin-top: var(--gap-bound);
11212 flex-shrink: 0;
11213 }
11214 .stripe-disclaimer-page .checkbox-group label {
11215 line-height: 1.5;
11216 cursor: pointer;
11217 }
11218 .stripe-disclaimer-page .button-row {
11219 display: flex;
11220 gap: var(--gap-section);
11221 margin-top: var(--gap-pane);
11222 }
11223 .stripe-disclaimer-page .button-row button { flex: 1; }
11224 .stripe-disclaimer-page button:disabled {
11225 opacity: 0.5;
11226 cursor: not-allowed;
11227 }
11228 .stripe-disclaimer-page .note {
11229 font-size: var(--text-note);
11230 opacity: 0.6;
11231 margin-top: var(--gap-section);
11232 text-align: center;
11233 }
11234
11235 /* ===========================================
11236 LONG-TAIL CLEANUP, shared utilities promoted from the final inline-style sweep.
11237 Each rule replaces ≥2 inline-style instances across templates.
11238 =========================================== */
11239
11240 /* Tab-spinner loading indicator (used by 3+ dashboards). */
11241 .tab-spinner-indicator { margin-left: var(--gap-section); }
11242
11243 /* Inline-block secondary action link styled as a button. */
11244 .btn-link {
11245 display: inline-block;
11246 padding: var(--gap-peer) var(--gap-section);
11247 text-decoration: none;
11248 }
11249 .btn-link--sm { padding: var(--gap-peer) var(--gap-section); }
11250
11251 /* Username-availability spinner (wizards + standalone account form). */
11252 .username-spinner { font-size: var(--text-note); }
11253
11254 /* Centered foot-link beneath wizard cards. */
11255 .foot-link--centered { margin-top: var(--gap-section); text-align: center; }
11256
11257 /* Health-page status dots. */
11258 .health-indicator-dot { width: 8px; height: 8px; margin-right: var(--gap-peer); }
11259 .health-error-text { color: var(--danger); }
11260
11261 /* Inline error fragments (login_error, error_fragment) toggle this on. */
11262 .error-message.is-active { display: block; }
11263
11264 /* Cart count badge in nav. */
11265 .cart-badge-count { font-size: var(--text-fine); }
11266
11267 /* Empty-row inside a data table. */
11268 .table-empty-row { text-align: center; opacity: 0.6; }
11269
11270 /* Discover grid: card wrapper + body anchor. */
11271 .discover-grid-card { position: relative; }
11272 .discover-card-link {
11273 text-decoration: none;
11274 color: inherit;
11275 display: flex;
11276 flex-direction: column;
11277 flex: 1;
11278 }
11279 .discover-filter-checkbox {
11280 display: flex;
11281 align-items: center;
11282 gap: var(--gap-peer);
11283 cursor: pointer;
11284 font-size: var(--text-note);
11285 }
11286
11287 /* Tag suggestion list. */
11288 .tag-suggest-label {
11289 font-size: var(--text-fine);
11290 opacity: 0.7;
11291 margin-bottom: var(--gap-peer);
11292 overflow-wrap: anywhere;
11293 }
11294 .tag-suggest-pill {
11295 background: var(--surface-sunken);
11296 border: 1px solid var(--border);
11297 padding: var(--gap-bound) var(--gap-peer);
11298 font-size: var(--text-fine);
11299 cursor: pointer;
11300 margin: var(--gap-bound);
11301 }
11302
11303 /* Dashboard-export header layout + Export-all CTA. */
11304 .dashboard-export-header {
11305 display: flex;
11306 justify-content: space-between;
11307 align-items: flex-start;
11308 }
11309 .export-all-btn {
11310 white-space: nowrap;
11311 margin-top: var(--gap-peer);
11312 }
11313
11314 /* Tiny inline action button (admin-metrics, dashboard-item share). */
11315 .dashboard-item-share-btn {
11316 font-size: var(--text-fine);
11317 padding: var(--gap-bound) var(--gap-peer);
11318 opacity: 0.6;
11319 }
11320
11321 /* Monospaced page sub-headers. */
11322 .section-mono-meta {
11323 font-family: var(--font-mono);
11324 font-size: var(--text-note);
11325 opacity: 0.6;
11326 margin-bottom: var(--gap-pane);
11327 }
11328 /* Email-result single-card page. */
11329 .email-result-wrap {
11330 max-width: 600px;
11331 margin: 50px auto;
11332 padding: 20px;
11333 }
11334
11335 /* Admin lottery form (admin-waitlist). */
11336 .lottery-form {
11337 display: flex;
11338 gap: var(--gap-section);
11339 align-items: flex-end;
11340 }
11341
11342 /* Login screen: "or" divider + passkey button + error slot. */
11343 .login-divider {
11344 text-align: center;
11345 margin: var(--gap-pane) 0 var(--gap-section);
11346 opacity: 0.5;
11347 font-size: var(--text-note);
11348 }
11349 .login-passkey-btn { width: 100%; }
11350 .login-passkey-error { margin-top: var(--gap-peer); }
11351
11352 /* Upload-progress sibling text (item_audio_upload). */
11353 .upload-speed-text {
11354 font-size: var(--text-fine);
11355 opacity: 0.6;
11356 margin-top: var(--gap-bound);
11357 }
11358
11359 /* Faded "Following" follow-button state. */
11360 .follow-btn.is-selected { opacity: 0.7; }
11361
11362 /* Project blog footer. */
11363 .project-blog-footer {
11364 margin-top: var(--gap-page);
11365 padding-top: var(--gap-page);
11366 border-top: 1px solid var(--border);
11367 opacity: 0.6;
11368 }
11369 .project-blog-footer a { color: var(--content); }
11370
11371 /* Compact alert / paragraph margin variants used 2+ times. */
11372
11373 /* Paywall trust line. */
11374 .paywall-trust {
11375 font-size: var(--text-note);
11376 opacity: 0.7;
11377 margin-top: var(--gap-section);
11378 line-height: 1.4;
11379 }
11380
11381 /* Confirm-delete: button background already covered by .danger class.
11382 The wizard step uses `.primary` and inlines the danger background; we just
11383 change the class instead. No CSS rule needed here. */
11384
11385 /* Paywall-style centered narrow form-group used inside project_paywall. */
11386 .form-group--centered-narrow {
11387 max-width: 240px;
11388 margin: var(--gap-section) auto;
11389 }
11390
11391 /* Inline-form margin-left for adjacent pills (collection.html private badge). */
11392 .badge--inline-l { margin-left: var(--gap-peer); }
11393
11394 /* ===== Team page ===== */
11395
11396 .team-page { max-width: 56rem; margin: 0 auto; }
11397 .team-intro {
11398 font-size: var(--text-body);
11399 line-height: 1.55;
11400 margin: 0 0 var(--gap-page);
11401 opacity: 0.85;
11402 }
11403 .team-section { margin-top: var(--gap-page); }
11404 .team-section + .team-section { margin-top: var(--gap-page); }
11405 .team-card-experience {
11406 margin: var(--gap-section) 0 var(--gap-peer);
11407 line-height: 1.5;
11408 }
11409 .team-card-links {
11410 margin: var(--gap-peer) 0;
11411 font-size: var(--text-body);
11412 }
11413 .team-card-link { font-weight: 500; }
11414 .team-card-bio {
11415 margin-top: var(--gap-section);
11416 border-top: 1px solid var(--border);
11417 padding-top: var(--gap-section);
11418 }
11419 .team-card-bio > summary {
11420 cursor: pointer;
11421 font-weight: 500;
11422 font-size: var(--text-body);
11423 list-style: none;
11424 }
11425 .team-card-bio > summary::-webkit-details-marker { display: none; }
11426 .team-card-bio > summary::before {
11427 content: "+ ";
11428 display: inline-block;
11429 width: 1.2em;
11430 opacity: 0.6;
11431 }
11432 .team-card-bio[open] > summary::before { content: "− "; }
11433 .team-card-bio > p {
11434 margin-top: var(--gap-section);
11435 line-height: 1.6;
11436 }
11437
11438 /* ── SyncKit billing panel ──
11439 Per-app accordion shown inside the SyncKit dashboard tab (both user and
11440 project levels). The .synckit-billing root carries pricing constants as
11441 data-attrs that static/synckit-billing.js reads to mirror monthly_price_cents.
11442 */
11443 .synckit-billing {
11444 background: var(--surface-raised);
11445 border: 1px solid var(--border);
11446 border-radius: var(--radius-control);
11447 padding: var(--gap-peer) var(--gap-section);
11448 min-width: 220px;
11449 }
11450 .synckit-billing-summary {
11451 cursor: pointer;
11452 display: flex;
11453 align-items: center;
11454 gap: var(--gap-section);
11455 font-size: var(--text-note);
11456 }
11457 .synckit-billing-toggle { margin-left: auto; color: var(--content-muted); }
11458 .synckit-billing-status {
11459 display: inline-block;
11460 padding: 2px var(--gap-peer);
11461 border-radius: var(--radius-fine);
11462 font-size: var(--text-fine);
11463 font-weight: 600;
11464 }
11465 .synckit-billing-status--draft { background: var(--surface-sunken); }
11466 .synckit-billing-status--active { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); color: var(--success); }
11467 .synckit-billing-status--past-due { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
11468 .synckit-billing-status--canceled { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); color: var(--danger); }
11469 .synckit-billing-price { font-weight: 600; }
11470 .synckit-billing-body {
11471 padding-top: var(--gap-section);
11472 display: flex;
11473 flex-direction: column;
11474 gap: var(--gap-section);
11475 }
11476 .synckit-billing-section { display: flex; flex-direction: column; gap: var(--gap-peer); }
11477 .synckit-billing-subheading { font-size: var(--text-note); margin: 0; font-weight: 600; }
11478 .synckit-knob-row {
11479 display: flex;
11480 align-items: center;
11481 gap: var(--gap-peer);
11482 flex-wrap: wrap;
11483 }
11484 .synckit-knob-row label { min-width: 110px; }
11485 .synckit-knob-row input[type="range"] { flex: 1 1 160px; min-width: 120px; }
11486 .synckit-radio { display: inline-flex; align-items: center; gap: var(--gap-bound); min-width: 0; }
11487 .synckit-billing-summary-line {
11488 display: flex;
11489 align-items: baseline;
11490 gap: var(--gap-peer);
11491 padding-top: var(--gap-peer);
11492 border-top: 1px solid var(--border);
11493 }
11494 .synckit-price-preview { font-size: var(--text-lead); font-weight: 600; }
11495 .synckit-billing-actions { display: flex; gap: var(--gap-peer); }
11496 .synckit-billing-manage { flex-direction: row; gap: var(--gap-peer); }
11497 .synckit-billing-status-msg { font-size: var(--text-note); min-height: 1.25em; }
11498 .synckit-billing-status-msg--info { color: var(--content-muted); }
11499 .synckit-billing-status-msg--error { color: var(--danger); }
11500 .synckit-billing-period { margin: 0; }
11501 .synckit-gauge { display: flex; flex-direction: column; gap: 2px; }
11502 .synckit-gauge-label {
11503 display: flex;
11504 justify-content: space-between;
11505 font-size: var(--text-fine);
11506 }
11507 .synckit-gauge-value { color: var(--content-muted); }
11508
11509 /* ============================================================================
11510 Carousel: the widget tier's first consumer.
11511
11512 The markup is no longer written in a template. It comes from a description --
11513 `crate::quasi::widgets::carousel` says "an ordered set of pictures, called a
11514 carousel" -- and quasi-webview turns that into a region carrying
11515 `data-widget="carousel"` with one `.picture` or `.picture-img` per frame.
11516
11517 So these rules key on the widget name rather than on a hand-written class.
11518 That is what "a renderer that recognises the name draws it its own way"
11519 means on this host: the name is the hook, and the stylesheet plus the island
11520 are the browser's idea of a carousel.
11521
11522 THE FALLBACK RUNS THE OTHER WAY NOW. This used to render frame one and hide
11523 the rest, because the controls that reach them are the scripted part -- so
11524 with JS off, two of three screenshots were unreachable. Now the unenhanced
11525 page is the whole gallery, stacked in order.
11526
11527 THE CHROME IS DESCRIBED TOO, as of makeover-layout 0.23.0. The description
11528 says `Showing::One` -- an ordered set of pictures with one showing -- and
11529 quasi-webview derives prev/position/next from that, once, for every widget
11530 there will ever be. Nothing here or in any renderer matches on the name
11531 "carousel" to get it. The island stopped injecting chrome and now only binds
11532 what is already on the page; the dot strip and the overlaid arrows are gone.
11533
11534 The controls are still not shipped live to a reader with no script: makeover
11535 keeps `.showing` hidden until something sets [data-ready], so the markup no
11536 longer has to be absent to avoid lying.
11537 ========================================================================= */
11538
11539 /* Public gallery carousel sections (item + project pages). */
11540 .item-gallery,
11541 .project-gallery {
11542 margin: var(--gap-pane) 0;
11543 }
11544
11545 /* <mnw-carousel> wraps the described region rather than being it: a custom
11546 element is what the browser upgrades automatically after an htmx swap, and
11547 the description emits a plain div because it does not know this host's tag
11548 prefix. Default display for an unknown element is inline, so say otherwise. */
11549 mnw-carousel {
11550 display: block;
11551 }
11552
11553 /* The default is ONE frame, which is what a visitor with JavaScript sees, so
11554 the page is settled from first paint and nothing rearranges on load. Showing
11555 all three and collapsing them was a 141px -> 58px jump on every load that
11556 everyone with JS paid; `no-js.css`, loaded from a <noscript>, is what opens
11557 the stack out for a visitor without scripting.
11558
11559 This is the one half of the carousel that stays local, and it is host policy
11560 rather than a gap. makeover's generated sheet collapses the stack on
11561 [data-ready], which is the honest default for a renderer that cannot know
11562 whether this page has script: it ships every child and takes them away once
11563 something binds them. Trading a load-time jump for that is a decision about
11564 this site's landing page, and <noscript> is a mechanism a generated
11565 stylesheet has no way to reach.
11566
11567 No padding-bottom and no position: relative any more. Both were for chrome
11568 that is gone -- the strip that had to be reserved and the arrows that were
11569 absolutely positioned over the frame. */
11570 [data-widget="carousel"] > .showing-frame:not(.current) {
11571 /* respec-ok: the generated rule is the same claim gated on [data-ready], and
11572 what it cannot express is <noscript>. A renderer that cannot know whether a
11573 page has script has to ship every child and take them away once something
11574 binds them; this page collapses from first paint instead and lets no-js.css
11575 open it back, which is a trade about this landing page rather than about
11576 the vocabulary. */
11577 display: none;
11578 }
11579
11580 [data-widget="carousel"] .picture {
11581 margin: 0;
11582 }
11583
11584 /* Only the corner. `display`, `width`, `height` and the raised fill and bevel
11585 all come from makeover's own `.picture-img` rule, which layout.css puts on
11586 every page through crate::shell::head. Restating any of them here is an
11587 unlayered declaration quietly beating the design system, which is what the
11588 drift check in build.rs exists to catch -- and did, on the first attempt at
11589 this file. */
11590 [data-widget="carousel"] .picture-img {
11591 border-radius: var(--radius-control);
11592 }
11593
11594 /* No colour: makeover's `.picture-caption` already sets it to
11595 --content-muted, and this said the same thing one layer louder. What is
11596 local is the centring and the mono face, which are this widget's look and
11597 not a claim about what a caption is. */
11598 [data-widget="carousel"] .picture-caption {
11599 margin-top: var(--gap-group);
11600 font-family: var(--font-mono);
11601 font-size: var(--text-note);
11602 text-align: center;
11603 }
11604
11605 /* Landing showcase only: close the top edge.
11606
11607 The three landing frames are screenshots of this site, so the parchment they
11608 were shot on is the parchment they sit on, and a 1px border between two
11609 identical grounds is not an edge anyone sees.
11610
11611 THE ELEVATION IS GONE, and that is the point of the port. This carried
11612 `box-shadow: 0 4px 10px var(--elevation), 0 14px 36px var(--elevation)`,
11613 which is the defect `c0b63ea9` names: in-flow depth is a bevel and elevation
11614 is for what sits *over* the page. It survived this long because it was
11615 invisible -- an unlayered declaration on the same class makeover sets, so it
11616 beat the design system's bevel silently. The drift check in build.rs found it
11617 the moment the frame became a described `.picture-img`.
11618
11619 So the frame is raised by makeover's bevel now, from `Depth::Raised`, and
11620 what is left here is the one thing that was never about depth: a stronger
11621 border colour, because a shadow falling downward does nothing for the top
11622 edge and the top edge is where a parchment screenshot dissolves into a
11623 parchment page. */
11624 .landing-showcase .picture-img {
11625 border-color: var(--border-strong);
11626 }
11627
11628 /* ---------------------------------------------------------------------------
11629 Enhanced. Everything below needs the island, and the island sets
11630 [data-ready] once it has upgraded and bound the controls.
11631 --------------------------------------------------------------------------- */
11632
11633 [data-widget="carousel"][data-ready]:focus-visible {
11634 outline: 2px solid var(--focus-ring);
11635 outline-offset: 3px;
11636 }
11637
11638 /* The control row is makeover's `.showing`, rendered by the description rather
11639 than injected here, and it needs no rules: it is two `.button`s and a muted
11640 readout, all three of which the generated sheet already styles. What used to
11641 sit here was `.carousel-nav`, `.carousel-prev`, `.carousel-next`,
11642 `.carousel-dots` and `.carousel-dot` -- 60 lines for chrome this host drew by
11643 hand because nothing could describe it.
11644
11645 The arrows were absolutely positioned over the frame, which Max called
11646 cluttered and which a terminal cannot honestly do at all; the dots had no
11647 form past a handful of frames, and two of the three galleries here are
11648 creator uploads of arbitrary length. Both halves of that were the same half. */
11649
11650
11651 /* ---------------------------------------------------------------------------
11652 Custom page editor (dashboard/custom_page_editor.html)
11653 Moved out of an inline <style> block per the extract-shared-CSS rule.
11654 --------------------------------------------------------------------------- */
11655 .cp-editor { max-width: 1200px; margin: 0 auto; padding: var(--gap-section); }
11656 .cp-editor-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--gap-section); flex-wrap: wrap; }
11657 .cp-editor-actions { display: flex; gap: var(--gap-section); }
11658 .cp-intro { color: var(--content-muted); margin: var(--gap-bound) 0 var(--gap-section); }
11659 /* Form beside preview. A code textarea below about 380px is not editable, so
11660 that number is the pane's, not the viewport's, and the pair stacks itself. */
11661 .cp-editor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: var(--gap-section); align-items: start; }
11662 .cp-editor-form { display: flex; flex-direction: column; gap: var(--gap-peer); }
11663 .cp-code { width: 100%; min-height: 220px; font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--text-note); line-height: 1.4; resize: vertical; }
11664 .cp-editor-buttons { display: flex; align-items: center; gap: var(--gap-section); margin-top: var(--gap-peer); }
11665 .cp-preview-pane { border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden; }
11666 .cp-preview-bar { font-size: var(--text-fine); padding: var(--gap-bound) var(--gap-group); background: var(--surface-sunken); border-bottom: 1px solid var(--border); }
11667 .cp-preview { width: 100%; height: 520px; border: 0; background: var(--primary-light); display: block; }
11668 .cp-blocked { margin-top: var(--gap-section); }
11669 .cp-blocked-title { font-weight: 600; margin-bottom: var(--gap-peer); }
11670 .cp-blocked-empty { color: var(--content-muted); }
11671 .cp-blocked-list { list-style: none; padding: 0; margin: 0; }
11672 .cp-blocked-list li { padding: var(--gap-peer) 0; border-bottom: 1px solid var(--border); font-size: var(--text-note); display: flex; gap: var(--gap-peer); flex-wrap: wrap; align-items: baseline; }
11673 .cp-blocked-kind { font-weight: 600; }
11674 .cp-blocked-loc { color: var(--content-muted); }
11675 .cp-blocked-val { background: var(--surface-sunken); padding: 0 var(--gap-bound); }
11676 .cp-status.cp-ok { color: var(--success); }
11677 .cp-status.cp-err { color: var(--warning); }
11678 .cp-reset { margin-top: var(--gap-pane); }
11679 .cp-locked-banner { margin: var(--gap-peer) 0 var(--gap-section); padding: var(--gap-section); border: 1px solid var(--warning); border-radius: var(--radius-control); background: color-mix(in oklch, var(--warning) 12%, var(--surface-page)); }
11680 /* Layout-shift fix (audit Run 17 Frontend): reserve space for a cover image
11681 whose class didn't already set an aspect-ratio / fixed size. */
11682 .proj-image-preview img { width: 120px; height: 120px; object-fit: cover; }
11683
11684 /* ---- Discover search: how well a result matched -------------------------
11685 Two surfaces, doing two different jobs (db/discover.rs, migration 179).
11686
11687 .results-tier-break is the single heading, and it sits exactly where the
11688 claim it makes turns true: above it every row holds at least one word the
11689 searcher typed, below it none of them do. .row-match-note is the per-row
11690 half, "Matched 3 of 5 words", because coverage varies row to row and one
11691 page can hold a 4-of-5 beside a 1-of-5.
11692
11693 Both are deliberately quiet. They explain a result the reader is already
11694 looking at; competing with the title would make search feel like it is
11695 apologising. Muted colour and small type, no accent, no border of their own
11696 beyond the rule that separates the tiers. */
11697 .results-tier-break {
11698 display: flex;
11699 align-items: baseline;
11700 gap: var(--gap-peer);
11701 flex-wrap: wrap;
11702 padding: var(--gap-section) var(--gap-section) var(--gap-peer);
11703 border-top: 1px solid var(--border);
11704 background: var(--surface-page);
11705 }
11706
11707 /* .results-table sets border-top: none and lets .table-row draw the rules, so
11708 the break supplies its own top border. Mid-list that sits against the
11709 preceding row's border-bottom and reads as one heavier 2px rule, which is
11710 wanted here: it is a section change, not another row. At the top of an
11711 all-fuzzy page there is no preceding row, and the same border supplies the
11712 table's missing top edge. */
11713 .results-tier-label {
11714 font-family: var(--font-heading);
11715 font-weight: bold;
11716 font-size: var(--text-fine);
11717 text-transform: uppercase;
11718 letter-spacing: 0.03em;
11719 color: var(--content);
11720 }
11721
11722 .results-tier-hint {
11723 font-size: var(--text-fine);
11724 color: var(--content-muted);
11725 }
11726
11727 /* The grid is `repeat(auto-fill, minmax(280px, 1fr))`, so without this the
11728 heading takes one card's slot and the tiers interleave visually. */
11729 .results-container.results-grid .results-tier-break {
11730 grid-column: 1 / -1;
11731 padding-left: 0;
11732 padding-right: 0;
11733 background: none;
11734 }
11735
11736 /* In list view this rides inside .row-info, under the creator line, not as
11737 another child of .table-row-link: that is a fixed five-column grid and a
11738 sixth child would invent a column and shift every row that carried one. */
11739 .row-match-note {
11740 font-family: var(--font-mono);
11741 font-size: var(--text-fine);
11742 color: var(--content-muted);
11743 white-space: nowrap;
11744 overflow: hidden;
11745 text-overflow: ellipsis;
11746 }
11747
11748 /* Grid cards have room to breathe; give the note its own line under the meta. */
11749 .grid-card .row-match-note {
11750 display: block;
11751 margin-top: var(--gap-bound);
11752 }
11753
11754 /* The narrow list layouts drop columns rather than wrap, and .row-info is the
11755 column that survives. Keeping the note there would crowd the title on a
11756 phone, where the tier heading above still carries the important half. */
11757 @media (max-width: 599px) {
11758 .table-row .row-match-note { /* respec-ok: hiding a cell at a breakpoint. The
11759 generated sheet describes one `.table-row`; which of its children survive a
11760 narrow viewport is this page's call, not the design system's. */ display: none; }
11761 }
11762
11763 }
11764