Skip to main content

max / makenotwork

325.5 KB · 11981 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 /* No `@font-face` here at all any more, and no font token either. All three
94 faces are fetched by the generated `typography.css`: Quasi Mono and Quasi
95 Body from makeover's house layer, and Young Serif from MNW's own override,
96 declared in build.rs. What left first: four blocks for IBM Plex Mono and
97 Lato, regular and bold, 101 KB of woff2 covering two weights, replaced by
98 81 KB carrying the whole 200-800 axis. What left second: the Young Serif
99 block that used to sit here, and the `--font-display` beside it.
100
101 Young Serif is still the brand tier and still MNW's alone. That has not
102 changed and is not what moved. What changed is that the exemption is a
103 mechanism now rather than a gap in the vocabulary: the face is declared
104 once, as an override of the `display` slot, and reads as one. */
105
106 /* Everything below is in the `components` cascade layer.
107
108 _head_assets.html declares the order once, `@layer makeover, base,
109 components, responsive`, and until now three of those four names were
110 empty: this file adopted none of them, so 11,788 unlayered lines outranked
111 every named layer by construction and the generated sheets lost every
112 contest they entered.
113
114 Adopting `components` changes no rendering today. `components` already sits
115 after `makeover` in the declared order, so this file wins the same contests
116 it was winning before; what changes is that it wins them by a stated rule
117 rather than by being unlayered. wizard.css and media-player.css adopt the
118 same layer, and they still beat this file on a tie because a layer resolves
119 internally by source order and they load later.
120
121 Two things stay OUT of the layer, deliberately. The @font-face rules above
122 are not style rules and take no part in the cascade. And the theme block
123 theming.rs injects into <head> is unlayered, which is load-bearing: an
124 unlayered rule outranks every layer, so a creator's chosen theme keeps
125 overriding this file's :root without needing to know the order. */
126 @layer components {
127 :root {
128 /* Typography: nothing. All three tokens come from typography.css, which
129 loads before this file.
130
131 `--font-mono` and `--font-sans` are the house slots and `--font-display`
132 is MNW's own, an override declared in build.rs. Do not redefine any of the
133 three here -- that is the divergence the unification deleted, and this
134 file is the one that would win. A product face belongs in the override,
135 where the whole tree can read it. */
136
137 /* --------------------------------------------------------------------
138 INTENT LAYER, the shared theme vocabulary (themeable)
139
140 These are the intent tokens emitted by theme_common::intent_css_vars():
141 colors declared by ROLE (surface/content/action/status/line/category),
142 plus the derived interactive states (hover/active/selection/row-stripe/
143 contrast) the crate computes once for every product. The values below
144 are the platform default (shared/themes/makenotwork.toml resolved) so
145 non-creator pages render without injection; creator profile/project/item
146 pages inject a <style> that supersedes this block with the chosen theme.
147 Generated. Keep in sync with `cargo run --example dump_intents makenotwork`.
148 -------------------------------------------------------------------- */
149 --surface-page: #ddd6c9;
150 --surface-raised: #efe9e0;
151 --surface-sunken: #cdc5b5;
152 --surface-overlay: #efe9e0;
153 /* The content surface cut into a raised one, so a list reads as content in a
154 container rather than as bands on a panel. Derived by makeover from
155 surface-raised, away from resolved content, so it goes lighter on a light
156 theme and darker on a dark one. */
157 --surface-well: #fffef5;
158 /* The two edges of a bevel. makeover derives both from the theme's raised
159 surface, so they arrive with the rest of the intent set through
160 intent_css_vars() and every creator theme gains them automatically. */
161 --bevel-light: #fffef5;
162 --bevel-dark: #b5afa6;
163 --content: #3d3530;
164 --content-secondary: #5c554f;
165 --content-muted: #8a8480;
166 --content-on-action: #ffffff;
167 --action: #6c5ce7;
168 --action-hover: #7a6cf8;
169 --danger: #c0392b;
170 --success: #27ae60;
171 --warning: #f59e0b;
172 --info: #17a2b8;
173 --border: #b3ab97;
174 --border-strong: #a39c88;
175 --focus-ring: #6c5ce7;
176 --hover-surface: #cdc5b5;
177 --category-one: #c0392b;
178 --category-two: #27ae60;
179 --category-three: #6c5ce7;
180 --category-four: #f59e0b;
181 --category-five: #a29bfe;
182 --category-six: #17a2b8;
183 --overlay: rgba(3, 1, 0, 0.5);
184 /* What a surface floating over the page casts onto it. Same tone as the
185 scrim above and pinned near-black on every theme, because a shadow is
186 absence of light rather than a tint. layout.css composes the geometry
187 into --elevation-overlay; a consumer that needs a different geometry
188 builds its own from this token rather than from a literal. */
189 --elevation: rgba(3, 1, 0, 0.18);
190
191 /* --------------------------------------------------------------------
192 APP-LOCAL CONSTANTS, not theme colors, not derivable from intents.
193
194 Component CSS references the intent tokens above directly (no brand
195 aliases). Status backgrounds derive from the intents inline via
196 color-mix(); these few stay fixed:
197 - pure black/white contrast anchors
198 - Stripe's brand color (an external constant)
199 - diff add/del, a hard readability convention that must not re-theme
200 -------------------------------------------------------------------- */
201 --primary-dark: #000000;
202 --primary-light: #ffffff;
203 --stripe: #635BFF;
204
205 /* The faint accent tint every selection and highlight recipe fills with.
206 Derived from --action, so it re-tints with the creator's theme. Named
207 because the charter names it and because a dozen rules read it. */
208 --highlight-faint: color-mix(in oklch, var(--action) 8%, transparent);
209
210 /* Diff / code review, fixed green-add / red-del convention */
211 --diff-add: #2d7d2d;
212 --diff-add-bg: rgba(45, 125, 45, 0.08);
213 --diff-del: #a83232;
214 --diff-del-bg: rgba(168, 50, 50, 0.08);
215
216 /* Radius lives in geometry.css now, generated from makeover-geometry:
217 --radius-square / -fine / -control / -panel / -round. Containers are
218 square and controls keep a corner, so the rounding survives only where a
219 thing is meant to be pressed. */
220
221 /* Syntax highlighting for the git source browser: base16 Tomorrow, used
222 whole rather than assembled colour by colour. It stays fixed across all
223 31 themes on purpose, the same argument as the diff add/del pair above:
224 a reader recognises a highlighting palette, and re-tinting it per theme
225 costs that recognition to gain nothing. Named so it is legible as one
226 palette instead of seven hex literals scattered through the git rules. */
227 --syntax-keyword: #8959a8;
228 --syntax-string: #718c00;
229 --syntax-comment: #8e908c;
230 --syntax-constant: #f5871f;
231 --syntax-entity: #4271ae;
232 --syntax-variable: #c82829;
233 --syntax-support: #3e999f;
234
235 /* Stacking order, named for what floats over what. Local rather than
236 generated: this is a claim about MNW's own furniture, not an invariant
237 like spacing. The numbers are spaced so a value can be slotted between
238 two of them without renumbering, and nothing outside this block should
239 write a z-index literal. Anything not listed here does not float. */
240 --z-raised: 1; /* lifted within the flow: a saved badge on a card */
241 --z-nav: 10; /* the nav panel that drops out of the mobile header */
242 --z-header: 11; /* the header's own toggle and search panel, both of
243 which have to stay above the panel they open */
244 --z-dropdown: 20; /* menus and suggestion lists opened from a control */
245 --z-picker: 30; /* pickers that open over a dropdown */
246 --z-context: 40; /* right-click / overflow context menus */
247 --z-overlay: 50; /* full-page scrims */
248 --z-modal: 60; /* dialogs, above their scrim */
249 --z-toast: 70; /* transient messages, above a dialog */
250 --z-banner: 80; /* page-level interruptions, above everything */
251 --z-skip-link: 90; /* the keyboard escape hatch outranks all of it */
252
253 /* Depth, and there are only two kinds of it.
254
255 A surface IN the page takes a bevel: an edge, not a distance. That is
256 --shadow-raised/-card/-inset, aliasing the pair layout.css derives from
257 each theme's own raised surface. --shadow-raised and --shadow-card
258 collapse onto one value because an edge has no heavier and lighter
259 version to pick between.
260
261 A surface floating OVER the page takes --elevation-overlay, also from
262 layout.css, derived from the theme's page colour. Menus, toasts,
263 popovers, dropdowns and pickers float. A card, a plate and a framed
264 image do not; they are .raised.
265
266 There used to be a third kind, --shadow-1/-2/-3, a blurred scale of
267 rgba-black literals picked against parchment and then applied to all 31
268 themes a creator can select. It was the same defect the bevel trio was
269 rewritten to undo, one scale later, and the elevation intent is what
270 replaced it. No literal survives here now, so every consumer re-themes
271 without being opened. Geometry stays consumer-side: a site that needs a
272 different blur composes it from var(--elevation), never from a literal. */
273 --shadow-raised: var(--bevel-raised);
274 --shadow-card: var(--bevel-raised);
275 --shadow-inset: var(--bevel-inset);
276 }
277
278 /* Reset */
279 * {
280 margin: 0;
281 padding: 0;
282 box-sizing: border-box;
283 }
284
285 /* Base styles */
286 body {
287 margin: 0;
288 background-color: var(--surface-page);
289 color: var(--content);
290 font-family: var(--font-sans);
291 line-height: 1.6;
292 }
293
294 /* Padded page layout for most content pages */
295 .padded-page {
296 padding: var(--gap-pane);
297 }
298
299 /* Centered page layout for landing, login, signup */
300 .centered-page {
301 display: flex;
302 flex-direction: column;
303 justify-content: center;
304 align-items: center;
305 min-height: 100vh;
306 gap: var(--gap-page);
307 }
308
309 .centered-page h1 {
310 font-size: var(--text-hero);
311 }
312
313 .message-container {
314 max-width: 500px;
315 text-align: center;
316 }
317
318 .centered-wrapper {
319 justify-content: center;
320 text-align: center;
321 }
322
323 h1 {
324 font-family: var(--font-display);
325 font-weight: normal;
326 color: var(--content);
327 text-align: center;
328 font-size: var(--text-display);
329 }
330
331 h2,
332 h3 {
333 font-family: var(--font-mono);
334 font-weight: normal;
335 color: var(--content);
336 }
337
338 /* Heading classes (charter: docs/design-system.md, every h1/h2 in a
339 template must carry one of these so the role is explicit).
340
341 .brand-h1: the "Makenot.work" wordmark on auth/wizard pages.
342 .page-title: page-level h1 (Young Serif, centered).
343 .subtitle-h2: page subtitle h2 used under .brand-h1 in auth/wizards.
344 .subsection-title: h2 inside dashboards, tabs, and prose partials
345 (mono, no border, the default-h2 role made explicit).
346 .section-header: h2 prose sub-section heading with bottom border
347 (existing rule, see SECTIONS block). */
348 .brand-h1,
349 .page-title {
350 font-family: var(--font-display);
351 font-weight: normal;
352 color: var(--content);
353 text-align: center;
354 font-size: var(--text-display);
355 }
356
357 .subtitle-h2 {
358 font-family: var(--font-mono);
359 font-weight: normal;
360 color: var(--content);
361 text-align: center;
362 font-size: var(--text-head);
363 }
364
365 .subsection-title {
366 font-family: var(--font-mono);
367 font-weight: normal;
368 color: var(--content);
369 }
370
371 p {
372 font-family: var(--font-sans);
373 color: var(--content);
374 }
375
376 a {
377 color: var(--content);
378 text-decoration: none;
379 }
380
381 a:hover {
382 text-decoration: underline;
383 }
384
385 /* The signature dot */
386 .dot {
387 color: var(--action);
388 }
389
390 /* Container, default 1200px max-width. Modifier classes for narrower pages,
391 or comma-grouped page-scoped overrides below for body-class-based control. */
392 .container {
393 max-width: 1200px;
394 margin: 0 auto;
395 padding: var(--gap-page);
396 }
397
398 /* Per-page container width overrides (selected via body class). One rule
399 per width tier; new pages should add their body class here rather than
400 defining a new .foo-page .container rule. */
401 .delete-account-page .container,
402 .receipt-page .container,
403 .user-page .container { max-width: 600px; margin: 0 auto; }
404
405 .import-page .container,
406 .export-page .container { max-width: 800px; margin: 0 auto; }
407
408 .fan-plus-page .container,
409 .feed-page .container,
410 .creators-page .container { max-width: 900px; margin: 0 auto; }
411
412 /* READING COLUMN, the 680px measure every long-form surface uses. Narrower
413 than .container and with tighter horizontal padding, so it is its own
414 primitive rather than a .container width tier.
415
416 These four wrappers were four identical rules until 2026-08-16. They are
417 grouped rather than replaced by one class because the charter treats a new
418 class as a smell, and because grouping keeps each selector's own
419 specificity intact. A fifth long-form surface joins this list; it does not
420 copy the recipe.
421
422 .media-container's rule used to live in media-player.css. It is here now so
423 the measure has one definition; that sheet keeps its descendant rules. */
424 .article-page .article-container,
425 .doc-container,
426 .docs-index,
427 .media-container {
428 max-width: 680px;
429 margin: 0 auto;
430 padding: var(--gap-page) var(--gap-pane) var(--gap-page);
431 }
432
433 /* ===========================================
434 BUTTONS
435 =========================================== */
436
437 button {
438 color: var(--content);
439 background: var(--surface-overlay);
440 padding: 6px 12px;
441 border: 1px solid var(--content);
442 font-family: inherit;
443 cursor: pointer;
444 box-shadow: var(--shadow-raised);
445 transition: box-shadow 0.1s ease, background 0.2s ease;
446 }
447
448 button:hover {
449 background: var(--hover-surface);
450 }
451
452 button:active {
453 box-shadow: var(--shadow-inset);
454 }
455
456 /* A described act carries the generated `.button` class, and the element rule
457 above sets the same surface, bevel and tone the generated class does. This
458 file is in @layer components, which the order statement puts after
459 `makeover`, so the element rule wins whatever the specificity. Without the
460 handoff below a described act renders in --surface-overlay with
461 --shadow-raised and, worse, in --content rather than its tone: a
462 destructive act looks identical to an ordinary one. Same mechanism and same
463 remedy as `.tab.chosen` further down; read the comment there.
464
465 The split is deliberate. The app keeps padding, border and font from the
466 element rule, makeover keeps surface, bevel and tone. That is the
467 arrangement that already makes `.field` work, where the app's input rule
468 happens to set only geometry and typography.
469
470 Tone is handed back on `[data-tone]` rather than on `.button`, because an
471 untoned button has no colour in the layer to roll back to and would land on
472 the UA default instead of --content. */
473 .button,
474 .chip,
475 .segment,
476 .toggle {
477 /* respec-ok: a layer handoff, not a value. */
478 background: revert-layer;
479 box-shadow: revert-layer;
480 }
481
482 .button:hover,
483 .chip:hover,
484 .segment:hover,
485 .toggle:hover {
486 /* respec-ok: a layer handoff, not a value. */
487 background: revert-layer;
488 }
489
490 .button:active,
491 .chip:active,
492 .segment:active,
493 .toggle:active {
494 /* respec-ok: a layer handoff, not a value. */
495 background: revert-layer;
496 box-shadow: revert-layer;
497 }
498
499 .button[data-tone] {
500 /* respec-ok: a layer handoff, not a value. */
501 color: revert-layer;
502 }
503
504 /* A described link, same handoff on the `a` and `button` element rules.
505 `a { color: var(--content); text-decoration: none }` renders a described
506 link as body text -- the reader cannot tell it is a link at all, which is
507 the same defect as the lost tone above and worse for being silent.
508
509 `button.link` is the second arm: a link that posts is a button in the
510 markup, and makeover already flattens it (`background: none`, no border, no
511 padding, `font: inherit`). The element rule's chrome beat all four, so it
512 rendered as an ordinary raised button. Only the properties makeover sets on
513 this class are handed back; `font` is handed back on the button arm alone,
514 because an `a.link` has no lower-layer font to roll back to and would land
515 on the UA default face. */
516 .link {
517 /* respec-ok: a layer handoff, not a value. */
518 color: revert-layer;
519 text-decoration: revert-layer;
520 }
521
522 button.link {
523 /* respec-ok: a layer handoff, not a value. */
524 background: revert-layer;
525 border: revert-layer;
526 padding: revert-layer;
527 font: revert-layer;
528 box-shadow: revert-layer;
529 cursor: revert-layer;
530 }
531
532 /* A described field on a `select` or a `textarea`. The input rule further down
533 sets padding and font only, which is why the shipped `.field` precedents
534 survived; these two also set `background` and `box-shadow`, so a described
535 field on either renders in --surface-sunken rather than the well. The focus
536 ring is handed back with them: makeover insets it (`calc(-1 * 2px)`) so it
537 does not collide with the bevel, and the app's outer 2px was winning. */
538 .field {
539 /* respec-ok: a layer handoff, not a value. */
540 background: revert-layer;
541 box-shadow: revert-layer;
542 }
543
544 .field:focus-visible {
545 /* respec-ok: a layer handoff, not a value. */
546 outline-offset: revert-layer;
547 }
548
549 .btn-primary {
550 background: var(--primary-dark);
551 color: var(--primary-light);
552 border: none;
553 padding: var(--gap-section) var(--gap-pane);
554 font-family: inherit;
555 font-size: var(--text-body);
556 cursor: pointer;
557 box-shadow: var(--shadow-raised);
558 transition: box-shadow 0.1s ease, background 0.2s ease;
559 display: inline-block;
560 text-decoration: none;
561 }
562
563 /* A solid-fill button shifts its own fill rather than taking --hover-surface.
564 The charter's hover rule is "one step lighter than the resting fill", and on
565 a neutral control that step is --hover-surface; on this one it is not, since
566 the label is --primary-light and a parchment fill under it is unreadable. */
567 .btn-primary:hover {
568 background: color-mix(in oklch, var(--primary-dark) 85%, var(--primary-light));
569 text-decoration: none;
570 }
571
572 .btn-primary:active {
573 box-shadow: var(--shadow-inset);
574 }
575
576 .btn-secondary {
577 background: var(--surface-sunken);
578 color: var(--content);
579 border: none;
580 padding: var(--gap-section) var(--gap-pane);
581 font-family: var(--font-mono);
582 font-size: var(--text-body);
583 cursor: pointer;
584 box-shadow: var(--shadow-raised);
585 transition: box-shadow 0.1s ease, background 0.2s ease;
586 display: inline-block;
587 text-decoration: none;
588 }
589
590 .btn-secondary:hover {
591 background: var(--hover-surface);
592 text-decoration: none;
593 }
594
595 .btn-secondary:active {
596 box-shadow: var(--shadow-inset);
597 }
598
599 .btn-danger {
600 background: var(--danger);
601 color: var(--primary-light);
602 border: none;
603 padding: var(--gap-section) var(--gap-pane);
604 font-family: var(--font-mono);
605 font-size: var(--text-body);
606 cursor: pointer;
607 box-shadow: var(--shadow-raised);
608 transition: box-shadow 0.1s ease, background 0.2s ease;
609 display: inline-block;
610 text-decoration: none;
611 }
612
613 /* Solid fill, so the same exception as .btn-primary applies. */
614 .btn-danger:hover {
615 background: color-mix(in oklch, var(--danger) 85%, var(--primary-light));
616 text-decoration: none;
617 }
618
619 .btn-danger:active {
620 box-shadow: var(--shadow-inset);
621 }
622
623 /* Disabled state for all button variants (charter rule).
624 Applies whether disabled via attribute or aria-disabled. */
625 button:disabled,
626 button[aria-disabled="true"],
627 .btn-primary:disabled,
628 .btn-secondary:disabled,
629 .btn-danger:disabled {
630 opacity: 0.5;
631 cursor: not-allowed;
632 box-shadow: none;
633 transform: none;
634 }
635
636 /* Button size + style modifiers (charter: docs/design-system.md).
637 Use these on top of .btn-primary / -secondary / -danger.
638
639 .btn--large: large CTA padding bump.
640 .btn--icon: square icon-only / micro button (small padding,
641 tight line-height).
642 .btn--link: visually a link, semantically a button (no bg/
643 border, opacity-fade hover, mono).
644
645 Some surfaces have tuned button variants that are NOT compositions
646 of these modifiers and keep their own classes:
647 .big-button: Young Serif 200×60 anchor on splash pages.
648 .order-btn: list reorder up/down (with active flash).
649 .play-button: circular media-player play control.
650 .speed-button: segment in media-player speed group.
651 .shortcuts-help-btn: 1.5rem square help glyph in toolbars.
652 .toast-retry-btn: inline bordered button inside a toast,
653 uses currentColor to inherit toast tone.
654 These are documented variants, not deprecation targets. */
655 .btn--large {
656 padding: var(--gap-section) var(--gap-page);
657 font-size: var(--text-lead);
658 }
659
660 .btn--icon {
661 padding: var(--gap-bound) var(--gap-peer);
662 font-size: var(--text-note);
663 line-height: 1;
664 min-width: 1.75rem;
665 }
666
667 .btn--link {
668 background: none;
669 color: var(--content);
670 border: none;
671 padding: 0;
672 font-family: var(--font-mono);
673 font-size: var(--text-body);
674 cursor: pointer;
675 /* Link semantics, flat, no depth. Overrides the shadow that would
676 otherwise cascade from `button` / `form button` when a .btn--link
677 happens to be a <button> inside a <form> (e.g. logout). */
678 box-shadow: none;
679 transition: opacity 0.2s ease;
680 }
681
682 .btn--link:hover {
683 opacity: 0.6;
684 box-shadow: none;
685 }
686
687 .btn--link:active {
688 box-shadow: var(--shadow-inset);
689 }
690
691 /* Menu / disclosure openers, buttons whose job is to reveal a menu,
692 filter panel, expander, or popover. They sit flat rather than raised.
693 Add new opener selectors here as they appear. */
694 .context-menu-btn,
695 .discover-filter-toggle,
696 .tip-toggle,
697 .bundle-toggle {
698 box-shadow: none;
699 }
700
701 .context-menu-btn:hover,
702 .discover-filter-toggle:hover,
703 .tip-toggle:hover,
704 .bundle-toggle:hover {
705 box-shadow: none;
706 }
707
708 .context-menu-btn:active,
709 .discover-filter-toggle:active,
710 .tip-toggle:active,
711 .bundle-toggle:active {
712 box-shadow: var(--shadow-inset);
713 }
714
715 /* ===========================================
716 FORMS
717 =========================================== */
718
719 form {
720 display: flex;
721 flex-direction: column;
722 gap: var(--gap-section);
723 width: 100%;
724 }
725
726 .form-container {
727 max-width: 400px;
728 background: var(--surface-overlay);
729 padding: var(--gap-page);
730 }
731
732 label {
733 font-family: var(--font-mono);
734 color: var(--content);
735 font-size: var(--text-note);
736 }
737
738 input[type="text"],
739 input[type="email"],
740 input[type="password"],
741 input[type="number"],
742 input[type="date"],
743 textarea,
744 select {
745 width: 100%;
746 background: var(--surface-sunken);
747 border: 1px solid var(--border);
748 padding: var(--gap-section);
749 font-family: var(--font-mono);
750 font-size: var(--text-note);
751 color: var(--content);
752 box-shadow: var(--shadow-inset);
753 }
754
755 input::placeholder,
756 textarea::placeholder {
757 opacity: 0.5;
758 }
759
760 input[type="text"]:focus,
761 input[type="email"]:focus,
762 input[type="password"]:focus,
763 input[type="number"]:focus,
764 textarea:focus,
765 select:focus {
766 outline: none;
767 border: 1px solid var(--action);
768 }
769
770 textarea {
771 min-height: 100px;
772 resize: vertical;
773 }
774
775 select {
776 cursor: pointer;
777 }
778
779 input[type="submit"],
780 form button {
781 font-family: var(--font-display);
782 font-size: var(--text-body);
783 color: var(--content);
784 background: var(--surface-overlay);
785 padding: 12px 24px;
786 border: 1px solid var(--content);
787 cursor: pointer;
788 box-shadow: var(--shadow-raised);
789 transition: box-shadow 0.1s ease, background 0.2s ease;
790 }
791
792 input[type="submit"]:hover,
793 form button:hover {
794 background: var(--hover-surface);
795 }
796
797 input[type="submit"]:active,
798 form button:active {
799 box-shadow: var(--shadow-inset);
800 }
801
802 .form-group {
803 margin-bottom: var(--gap-pane);
804 }
805
806 .form-group label {
807 display: block;
808 margin-bottom: var(--gap-peer);
809 font-size: var(--text-note);
810 }
811
812 .form-group input,
813 .form-group textarea,
814 .form-group select {
815 width: 100%;
816 }
817
818 .form-row {
819 display: grid;
820 grid-template-columns: 1fr 1fr;
821 gap: var(--gap-section);
822 }
823
824 .hint {
825 font-size: var(--text-note);
826 opacity: 0.6;
827 margin-top: var(--gap-peer);
828 }
829
830 /* Field-level validation error, paired with .form-group--error.
831 Canonical: `partials/_ui.html` ui::form_field macro. */
832 .form-group--error label {
833 color: var(--danger);
834 }
835
836 .form-group--error input,
837 .form-group--error textarea,
838 .form-group--error select {
839 border-color: var(--danger);
840 }
841
842 .field-error {
843 font-size: var(--text-note);
844 color: var(--danger);
845 margin-top: var(--gap-peer);
846 }
847
848 /* Checkbox group */
849 .checkbox-group {
850 display: flex;
851 align-items: start;
852 gap: var(--gap-section);
853 cursor: pointer;
854 transition: background 0.2s ease;
855 margin-bottom: var(--gap-peer);
856 }
857
858 .checkbox-group input[type="checkbox"] {
859 width: auto;
860 margin-top: var(--gap-bound);
861 margin-bottom: var(--gap-bound);
862 cursor: pointer;
863 }
864 /* Radio group */
865 .radio-group {
866 display: flex;
867 flex-direction: column;
868 gap: var(--gap-section);
869 margin-top: var(--gap-peer);
870 }
871 /* ===========================================
872 TABS
873 =========================================== */
874
875 .tabs {
876 display: flex;
877 flex-wrap: nowrap;
878 gap: 0;
879 margin-bottom: 0;
880 }
881
882 /* Geometry and type only. The surface is layout.css's: `.tab` is sunken,
883 `:hover` steps to --hover-surface on a fine pointer, `:active` insets. This
884 rule used to restate the sunken background verbatim and then carry a second,
885 redundant selection signal on top of it — opacity 0.6 unselected, 1 selected
886 — so a tab said "not chosen" twice and the generated surface step did
887 nothing. The opacity idiom is gone and the surface contrast is the signal. */
888 .tab {
889 color: var(--content);
890 border: none;
891 padding: var(--gap-section) var(--gap-page);
892 font-family: var(--font-mono);
893 font-size: var(--text-body);
894 cursor: pointer;
895 /* Tabs navigate, they don't commit, stay flat. Overrides the base
896 `button` shadow if a tab is a <button>. */
897 box-shadow: none;
898 transition: background 0.2s ease;
899 white-space: nowrap;
900 flex-shrink: 0;
901 }
902
903 /* A tab's selected state is `.tab.chosen` in layout.css and nowhere else now.
904 This file used to carry a `.tab.is-selected` rule setting the same two
905 declarations byte for byte, on the argument that `.is-selected` is the
906 site's own idiom across git nav links, editor tabs, section tabs, issue tabs
907 and filter buttons, so renaming the tabs alone would split it. That is true
908 and it is still the wrong trade: a second name for a state the design system
909 already names is invisible only while both say the same thing, and stops
910 being invisible the moment makeover changes what a chosen tab looks like.
911 The other components keep `.is-selected`, because makeover has no primitive
912 for a git nav link; the tab has one.
913
914 The chosen arm's box-shadow comes back through the layer rather than being
915 restated. `.tab` below zeroes the base `button` shadow, and this file is in
916 @layer components, which the order statement puts after `makeover`, so it
917 outranks it whatever the specificity: without the revert, the generated
918 bevel never applies and a chosen tab reads flat.
919 `revert-layer` hands the property back instead of naming a value, which is
920 the difference between deferring to the design system and copying it. */
921 .tab.chosen {
922 /* respec-ok: a layer handoff, not a value. See the comment above. */
923 box-shadow: revert-layer;
924 }
925
926 .tab-more-wrap {
927 position: relative;
928 flex-shrink: 0;
929 }
930
931 .tab-overflow-menu {
932 position: absolute;
933 top: 100%;
934 right: 0;
935 z-index: var(--z-dropdown);
936 background: var(--surface-page);
937 border: 1px solid var(--border);
938 min-width: 180px;
939 box-shadow: var(--elevation-overlay);
940 }
941
942 /* The overflow menu restates the tab as a menu row: block, full width, left
943 aligned, tighter. It carried the same opacity pair the strip did and it goes
944 for the same reason; hover and selection are the generated surface steps. */
945 .tab-overflow-menu .tab {
946 display: block;
947 width: 100%;
948 text-align: left;
949 padding: var(--gap-group) var(--gap-section);
950 }
951
952
953 .tab-content {
954 display: none;
955 background: var(--surface-overlay);
956 padding: var(--gap-page);
957 }
958
959 .tab-content.active {
960 display: block;
961 }
962
963 /* ===========================================
964 TABLES
965 =========================================== */
966
967 .data-table {
968 width: 100%;
969 border-collapse: collapse;
970 font-size: var(--text-note);
971 font-family: var(--font-sans);
972 margin-bottom: var(--gap-section);
973 }
974
975 .data-table thead {
976 background: var(--surface-sunken);
977 }
978
979 .data-table th {
980 text-align: left;
981 padding: var(--gap-section);
982 font-weight: normal;
983 }
984
985 /* `.data-table th.sortable` and its four rules were here and had no consumer:
986 nothing in templates/ ever put `sortable` on a `th`. The one sortable table
987 in the app is templates/partials/tabs/project_content.html, which used
988 `.sortable-th` further down this file. Both are gone; the affordance and the
989 caret are `.table-heading[data-sortable]` in the generated static/layout.css.
990 See the block that replaced `.sortable-th` for what stayed behind. */
991
992 .data-table tbody tr {
993 transition: background 0.1s ease;
994 }
995
996 /* No zebra. There were two rules here, :nth-child(odd) and (even), and both
997 set --surface-overlay — the same value, so the stripe never existed and the
998 effect was every row painted the panel's own colour. The table is a .well
999 now and the rows sit on it unpainted, which is what the banding was
1000 reaching for. makeover derives no row-stripe intent, so if one is ever
1001 wanted it is a makeover-side question, not a literal here. */
1002
1003 .data-table tbody tr:hover {
1004 background: var(--surface-sunken);
1005 }
1006
1007 .data-table td {
1008 padding: var(--gap-section);
1009 border-top: 1px solid var(--border);
1010 }
1011
1012 /* Marks the row a table is about, used across the dashboard and library
1013 tables. Lived in the pricing block until the fee calculator replaced it. */
1014 .data-table .highlight {
1015 background: var(--surface-overlay);
1016 border-left: 3px solid var(--action);
1017 }
1018
1019 /* ===========================================
1020 COMPACT TABLES (dashboard/admin, lighter than .data-table)
1021 =========================================== */
1022
1023 .compact-table {
1024 width: 100%;
1025 border-collapse: collapse;
1026 }
1027
1028 .compact-table thead tr {
1029 font-size: var(--text-note);
1030 opacity: 0.7;
1031 }
1032
1033 .compact-table th {
1034 padding: var(--gap-peer) var(--gap-section);
1035 text-align: left;
1036 }
1037
1038 .compact-table td {
1039 padding: var(--gap-section);
1040 }
1041
1042 .compact-table tbody tr {
1043 border-bottom: 1px solid var(--border);
1044 }
1045
1046 /* ===========================================
1047 UTILITIES
1048 =========================================== */
1049
1050 .text-sm { font-size: var(--text-note); }
1051 .text-xs { font-size: var(--text-fine); }
1052 .muted { opacity: 0.7; }
1053 .dimmed { opacity: 0.6; }
1054 .dimmer { opacity: 0.5; }
1055 .meta { font-size: var(--text-note); opacity: 0.7; }
1056 .nowrap { white-space: nowrap; }
1057 .scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
1058 .text-center { text-align: center; }
1059 .text-right { text-align: right; }
1060 .fw-bold { font-weight: bold; }
1061 .unstyled-link { text-decoration: none; color: inherit; }
1062 .square-cover { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
1063
1064 /* Spacing utilities.
1065 mt = margin-top, mb = margin-bottom, my = vertical, py = vertical padding.
1066 Named for the relationship, same vocabulary as the --gap-* tokens they
1067 read. Use sparingly; prefer parent layout primitives. */
1068 .m-0 { margin: 0; }
1069 .mt-0 { margin-top: 0; }
1070 .mb-0 { margin-bottom: 0; }
1071 .mt-peer { margin-top: var(--gap-peer); }
1072 .mt-section { margin-top: var(--gap-section); }
1073 .mt-pane { margin-top: var(--gap-pane); }
1074 .mt-page { margin-top: var(--gap-page); }
1075 .mb-peer { margin-bottom: var(--gap-peer); }
1076 .mb-section { margin-bottom: var(--gap-section); }
1077 .mb-pane { margin-bottom: var(--gap-pane); }
1078 .my-peer { margin-block: var(--gap-peer); }
1079 .my-section { margin-block: var(--gap-section); }
1080 .py-section { padding-block: var(--gap-section); }
1081
1082 /* Min-width utilities for tables inside .scroll-x wrappers. */
1083 .minw-300 { min-width: 300px; }
1084 .minw-400 { min-width: 400px; }
1085 .minw-500 { min-width: 500px; }
1086 .minw-600 { min-width: 600px; }
1087 .minw-700 { min-width: 700px; }
1088 .minw-800 { min-width: 800px; }
1089
1090 /* Compact button sizes. Canonical: .small (broad use, 0.25rem 0.6rem 0.8rem).
1091 The tiny / row-btn / cart-row-btn aliases are visually equivalent, within
1092 sub-pixel of each other in the original templates, and folded into .small. */
1093 .small { padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); }
1094
1095 /* Canonical empty-state primitive. Use modifiers for tight/sized contexts.
1096 Markup: <div class="empty-state">…</div> (optionally with .empty-state--compact
1097 inside dropdowns or .empty-state--chart inside a fixed-height chart slot). */
1098 .empty-state { text-align: center; padding: var(--gap-page); opacity: 0.6; }
1099 .empty-state--compact { padding: var(--gap-section); font-size: var(--text-note); }
1100 .empty-state--chart { height: 200px; padding: 0; display: flex; align-items: center; justify-content: center; opacity: 0.5; }
1101 .empty-state-hint { font-family: var(--font-mono); font-size: var(--text-fine); margin-top: var(--gap-peer); opacity: 0.6; }
1102
1103 .tab-docs {
1104 display: flex;
1105 justify-content: flex-end;
1106 margin-bottom: var(--gap-section);
1107 }
1108
1109 .tab-docs a {
1110 font-family: var(--font-mono);
1111 font-size: var(--text-fine);
1112 opacity: 0.5;
1113 transition: opacity 0.2s ease;
1114 }
1115
1116 .tab-docs a:hover {
1117 opacity: 1;
1118 }
1119
1120 /* ===========================================
1121 BADGES
1122 =========================================== */
1123
1124 /* Geometry and type only. The colour is layout.css's: a badge is coloured
1125 text on the page, and its status colour comes from `data-tone` — see
1126 src/types/badge.rs, which maps every status enum onto a tone name.
1127
1128 This used to be a filled pill (near-black ground, --primary-light label)
1129 with a local `.badge--live/pending/failed` set painting the fill per
1130 status. All of it went in wave 2 tier B2: the fill was carrying exactly
1131 what the generated `color` carries, so it was the same thing said in a
1132 different channel, and keeping it meant the tone attribute could never do
1133 anything. */
1134 .badge {
1135 display: inline-block;
1136 padding: var(--gap-bound) var(--gap-peer);
1137 font-size: var(--text-fine);
1138 font-family: var(--font-mono);
1139 }
1140
1141 /* AI disclosure tier badges. See site-docs/public/about/generative-ai.md.
1142 Colours track the brand palette: violet for the cleanest case, warm-tan for
1143 disclosed AI use, charcoal for primarily generated.
1144
1145 respec-ok: this is a taxonomy makeover has no vocabulary for. Its four
1146 tones are info / success / warning / danger, which all read as alarm, and
1147 disclosure is not alarm — a handmade item is not a "success" and a
1148 generated one is not a "danger". So the three hues stay local. They set
1149 `color` rather than a background, because the badge form is the generated
1150 one now and only the hue is ours. */
1151 .badge.ai-tier {
1152 letter-spacing: 0.02em;
1153 }
1154 .badge.ai-tier-handmade {
1155 color: var(--action);
1156 }
1157 .badge.ai-tier-assisted {
1158 color: var(--warning);
1159 }
1160 .badge.ai-tier-generated {
1161 color: var(--content);
1162 }
1163
1164 .item-ai-tier {
1165 margin: var(--gap-bound) 0 var(--gap-section);
1166 }
1167
1168 .ai-disclosure {
1169 margin: var(--gap-section) 0;
1170 padding: var(--gap-section);
1171 background: var(--surface-sunken);
1172 border-left: 3px solid var(--warning);
1173 }
1174 .ai-disclosure-label {
1175 font-family: var(--font-mono);
1176 font-size: var(--text-fine);
1177 text-transform: uppercase;
1178 letter-spacing: 0.05em;
1179 color: var(--content-muted);
1180 margin-bottom: var(--gap-bound);
1181 }
1182 .ai-disclosure-text {
1183 font-size: var(--text-note);
1184 color: var(--content);
1185 }
1186
1187 /* Discover row: plain text mention per design decision 2026-06-03:
1188 no pill in the row, so fans skim the listing without disclosure noise
1189 and use the explicit filter when they care. */
1190 .discover-row-ai-tier {
1191 font-family: var(--font-mono);
1192 font-size: var(--text-fine);
1193 color: var(--content-muted);
1194 }
1195
1196 /* ===========================================
1197 TAGS
1198 =========================================== */
1199
1200 .tag {
1201 display: inline-block;
1202 background: var(--primary-dark);
1203 color: var(--primary-light);
1204 padding: var(--gap-bound) var(--gap-peer);
1205 font-size: var(--text-fine);
1206 margin-right: var(--gap-bound);
1207 }
1208
1209 .tag-input {
1210 background: var(--surface-sunken);
1211 padding: var(--gap-peer);
1212 margin-bottom: var(--gap-peer);
1213 display: flex;
1214 flex-wrap: wrap;
1215 gap: var(--gap-peer);
1216 min-height: 40px;
1217 }
1218
1219 .tag-input .tag {
1220 display: flex;
1221 align-items: center;
1222 gap: var(--gap-peer);
1223 padding: var(--gap-bound) var(--gap-peer);
1224 font-size: var(--text-note);
1225 }
1226
1227 .tag-input .tag button {
1228 background: none;
1229 border: none;
1230 color: var(--primary-light);
1231 cursor: pointer;
1232 padding: 0;
1233 font-size: var(--text-body);
1234 }
1235
1236 /* ===========================================
1237 CARDS
1238 =========================================== */
1239
1240 /* Canonical card primitive (charter: docs/design-system.md).
1241 Variants:
1242 .card: filled, hover-step (default content card).
1243 .card.card-muted: surface-muted fill, no hover (dashboard stat / analytics blocks).
1244 .card.card--bordered: bordered, padded, no fill (marketing cards: feature/tier/use-case).
1245 .card.card--selectable: radio-card pattern; pair with .is-selected.
1246 .card.card--grid: grid-cell card (discover grid).
1247 No aliases. A template names the modifier it wants and adds its own
1248 component class beside it; a recipe reached only through a per-page name is
1249 a recipe nobody can find from the charter. */
1250 /* Fill and depth come from layout.css, generated by makeover-webview: the card
1251 is `--surface-raised` plus `--bevel-raised`, and `:hover` goes to
1252 `--hover-surface` on a fine pointer only. Declaring either here would take
1253 them back, since style.css is in @layer components, which the order
1254 statement puts after `makeover`, whatever the specificity. The box-shadow this used to carry was
1255 `--shadow-card`, which already aliases `--bevel-raised`, so only the fill
1256 actually moved: `--surface-overlay` to `--surface-raised`. Those are equal on
1257 the platform default and a theme may set them apart. */
1258 .card {
1259 padding: var(--gap-pane);
1260 margin-bottom: var(--gap-section);
1261 text-decoration: none;
1262 color: var(--content);
1263 display: block;
1264 border: 1px solid var(--border);
1265 transition: background 0.2s ease;
1266 }
1267
1268 /* Muted card primitive. */
1269 .card-muted {
1270 background: var(--surface-sunken);
1271 padding: var(--gap-pane);
1272 }
1273 /* Bordered card. Templates put this modifier on the element alongside any
1274 component class of their own (.tier-card, .use-case-card, .fork-card), so
1275 the name the charter teaches is the name in the markup. */
1276 .card--bordered {
1277 background: transparent;
1278 padding: var(--gap-section);
1279 border: 1px solid var(--border);
1280 margin-bottom: 0;
1281 }
1282 .use-case-card { padding: var(--gap-section); }
1283 .fork-card { padding: var(--gap-pane); display: flex; flex-direction: column; }
1284
1285 .card-title {
1286 font-size: var(--text-subhead);
1287 margin-bottom: var(--gap-peer);
1288 font-family: var(--font-display);
1289 }
1290
1291 .card-meta {
1292 font-size: var(--text-note);
1293 color: var(--content-muted);
1294 margin-bottom: var(--gap-peer);
1295 font-family: var(--font-mono);
1296 }
1297
1298 .card-description {
1299 font-size: var(--text-note);
1300 opacity: 0.8;
1301 font-family: var(--font-sans);
1302 }
1303
1304 /* ===========================================
1305 STATS
1306 =========================================== */
1307
1308 .stats-grid {
1309 display: grid;
1310 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1311 gap: var(--gap-section);
1312 margin-bottom: var(--gap-pane);
1313 }
1314
1315 .stats-row {
1316 display: flex;
1317 gap: var(--gap-pane);
1318 margin: var(--gap-pane) 0;
1319 }
1320
1321 .stat-box {
1322 background: var(--surface-overlay);
1323 padding: var(--gap-section) var(--gap-pane);
1324 flex: 1;
1325 text-align: center;
1326 }
1327
1328 .stat-box .number {
1329 font-family: var(--font-display);
1330 font-weight: bold;
1331 font-size: var(--text-head);
1332 }
1333
1334 .stat-box .label {
1335 font-size: var(--text-fine);
1336 opacity: 0.7;
1337 }
1338
1339 /* .stat-card base recipe defined above with .card-muted */
1340
1341 .stat-label {
1342 font-size: var(--text-note);
1343 opacity: 0.7;
1344 margin-bottom: var(--gap-bound);
1345 }
1346
1347 .stat-value {
1348 font-size: var(--text-head);
1349 }
1350
1351 .stat-change {
1352 font-size: var(--text-note);
1353 opacity: 0.6;
1354 }
1355
1356 .stat-change.positive {
1357 color: var(--success);
1358 }
1359
1360 .stat-change.negative {
1361 color: var(--danger);
1362 }
1363
1364 /* ===========================================
1365 FILTER BAR
1366 =========================================== */
1367
1368 .filter-bar {
1369 display: flex;
1370 gap: var(--gap-peer);
1371 margin: var(--gap-pane) 0;
1372 flex-wrap: wrap;
1373 align-items: center;
1374 }
1375
1376 .filter-bar > .label {
1377 font-size: var(--text-note);
1378 opacity: 0.7;
1379 margin-right: var(--gap-peer);
1380 }
1381
1382 .filter-bar button {
1383 padding: var(--gap-bound) var(--gap-section);
1384 font-size: var(--text-note);
1385 }
1386
1387 /* ===========================================
1388 DISCUSSION SECTION
1389 =========================================== */
1390
1391 /* Sits inside the parent container; previously had max-width: 720px and
1392 margin: 2rem auto, which looked indented on wide pages (.library-page,
1393 .item-page) where every neighbouring card filled the container. Let the
1394 parent dictate width, article-page already constrains via
1395 .article-container; player pages have their own narrow layout. */
1396 .discussion-section {
1397 margin: var(--gap-page) 0;
1398 padding: var(--gap-pane);
1399 border: 1px solid var(--border);
1400 font-family: var(--font-mono);
1401 }
1402
1403 .discussion-section h3 {
1404 font-family: var(--font-display);
1405 font-size: var(--text-subhead);
1406 margin: 0 0 var(--gap-peer);
1407 }
1408
1409 .discussion-section p {
1410 margin: 0 0 var(--gap-section);
1411 color: var(--content-muted);
1412 font-size: var(--text-note);
1413 }
1414
1415 .discussion-section a {
1416 color: var(--action);
1417 text-decoration: none;
1418 font-size: var(--text-note);
1419 }
1420
1421 /* ===========================================
1422 NOTIFICATION SURFACES (info-box, warning-box, error-message)
1423 =========================================== */
1424
1425 /* Notification surface roles (charter: docs/design-system.md).
1426 Five distinct components. Keep them distinct, don't merge:
1427 .toast: transient, JS-dismissible, bottom-right corner.
1428 .banner: full-bleed page-top notice (sandbox, restart,
1429 founder pricing). One short sentence + optional link.
1430 .alert: inline directive callout with uppercase mono title
1431 (NOTE / TIP / IMPORTANT / WARNING / CAUTION). Used in
1432 docs and longer-form bodies.
1433 .info-box: informational headed block with h3 + bullet list
1434 inside forms / wizards (distinct register from .alert).
1435 .warning-box: loud yellow-on-yellow attention-grabber for
1436 page-level warnings (delete confirms, account
1437 warnings). Louder than .alert-warning intentionally.
1438 .error-message: inline form-field error; hidden by default, shown
1439 with .is-active. Distinct from page-level .alert. */
1440 .info-box {
1441 background: var(--surface-sunken);
1442 padding: var(--gap-section);
1443 margin-bottom: var(--gap-section);
1444 font-size: var(--text-note);
1445 font-family: var(--font-sans);
1446 }
1447
1448 .info-box h3 {
1449 font-size: var(--text-body);
1450 font-weight: normal;
1451 margin-bottom: var(--gap-peer);
1452 }
1453
1454 .info-box ul {
1455 list-style: none;
1456 margin-top: var(--gap-peer);
1457 }
1458
1459 .info-box li {
1460 padding: var(--gap-bound) 0;
1461 }
1462
1463 .info-box li::before {
1464 content: "- ";
1465 opacity: 0.5;
1466 }
1467
1468 .warning-box {
1469 background: var(--warning);
1470 color: var(--primary-light);
1471 padding: var(--gap-section);
1472 margin-bottom: var(--gap-section);
1473 font-size: var(--text-note);
1474 font-family: var(--font-sans);
1475 }
1476
1477 .error-message {
1478 font-family: var(--font-mono);
1479 color: var(--danger);
1480 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
1481 padding: 12px;
1482 border: 1px solid var(--danger);
1483 border-radius: var(--radius-fine);
1484 text-align: center;
1485 font-size: var(--text-note);
1486 display: none;
1487 }
1488
1489 /* ===========================================
1490 ERROR PAGE
1491 =========================================== */
1492
1493 /* Full-page error layout (templates/pages/error.html). Distinct from
1494 the inline `.error-message` form-error class above; named to avoid
1495 collision. */
1496 .error-page {
1497 min-height: 100vh;
1498 display: flex;
1499 align-items: center;
1500 justify-content: center;
1501 padding: var(--gap-page);
1502 background: var(--surface-page);
1503 }
1504
1505 .error-container {
1506 text-align: center;
1507 max-width: 560px;
1508 }
1509
1510 .error-status {
1511 font-size: var(--text-note);
1512 font-weight: 600;
1513 letter-spacing: 0.08em;
1514 text-transform: uppercase;
1515 color: var(--content-muted);
1516 margin-bottom: var(--gap-section);
1517 }
1518
1519 .error-page-message {
1520 font-size: var(--text-head);
1521 line-height: 1.4;
1522 color: var(--content);
1523 margin-bottom: var(--gap-page);
1524 }
1525
1526 .error-actions {
1527 display: flex;
1528 gap: var(--gap-section);
1529 justify-content: center;
1530 }
1531
1532
1533 /* ===========================================
1534 BUY PAGE (standalone direct purchase)
1535 =========================================== */
1536
1537 /* Minimal direct-purchase page (templates/pages/buy.html). Standalone
1538 page used for link-in-bio sharing, no site chrome, just a card.
1539 Scoped under .buy-page so the rules don't bleed into the rest of
1540 the app. Tokenized; previously lived as a page-isolated <style>. */
1541 .buy-page {
1542 min-height: 100vh;
1543 display: flex;
1544 align-items: center;
1545 justify-content: center;
1546 padding: var(--gap-page);
1547 }
1548
1549 /* The one former --shadow-3 site that is not a float. The card is the whole
1550 page's content rather than something laid over it, so it takes .raised
1551 from layout.css for its surface and its edge, and keeps only the geometry
1552 the generated primitive does not carry. */
1553 .buy-page .buy-card {
1554 border-radius: var(--radius-panel);
1555 padding: var(--gap-page);
1556 max-width: 420px;
1557 width: 100%;
1558 }
1559
1560 .buy-page .cover,
1561 .buy-page .no-cover {
1562 width: 100%;
1563 aspect-ratio: 1;
1564 max-height: 280px;
1565 border-radius: var(--radius-panel);
1566 margin-bottom: var(--gap-pane);
1567 background: var(--surface-raised);
1568 }
1569
1570 .buy-page .cover {
1571 object-fit: cover;
1572 }
1573
1574 .buy-page .no-cover {
1575 display: flex;
1576 align-items: center;
1577 justify-content: center;
1578 font-size: var(--text-hero);
1579 opacity: 0.2;
1580 }
1581
1582 .buy-page h1 {
1583 text-align: left;
1584 font-size: var(--text-head);
1585 margin-bottom: var(--gap-bound);
1586 }
1587
1588 .buy-page .creator {
1589 font-family: var(--font-mono);
1590 font-size: var(--text-note);
1591 opacity: 0.6;
1592 margin-bottom: var(--gap-pane);
1593 }
1594
1595 .buy-page .creator a {
1596 color: inherit;
1597 }
1598
1599 .buy-page .price {
1600 font-family: var(--font-mono);
1601 font-size: var(--text-subhead);
1602 margin-bottom: var(--gap-pane);
1603 }
1604
1605 .buy-page .description {
1606 font-size: var(--text-note);
1607 line-height: 1.5;
1608 opacity: 0.8;
1609 margin-bottom: var(--gap-pane);
1610 max-height: 4.5em;
1611 overflow: hidden;
1612 }
1613
1614 .buy-page .buy-btn {
1615 display: block;
1616 width: 100%;
1617 padding: 14px;
1618 background: var(--action);
1619 color: var(--primary-light);
1620 border: none;
1621 border-radius: var(--radius-panel);
1622 font-size: var(--text-body);
1623 font-weight: 600;
1624 cursor: pointer;
1625 text-align: center;
1626 text-decoration: none;
1627 transition: opacity 0.2s ease;
1628 }
1629
1630 .buy-page .buy-btn:hover {
1631 opacity: 0.85;
1632 }
1633
1634 .buy-page .buy-btn:disabled {
1635 opacity: 0.6;
1636 cursor: wait;
1637 }
1638
1639 .buy-page .pwyw-input {
1640 display: flex;
1641 align-items: center;
1642 gap: var(--gap-bound);
1643 margin-bottom: var(--gap-section);
1644 }
1645
1646 .buy-page .pwyw-input input {
1647 width: 100px;
1648 padding: 8px;
1649 border: 1px solid var(--border);
1650 border-radius: var(--radius-control);
1651 font-size: var(--text-body);
1652 }
1653
1654 .buy-page .note {
1655 font-size: var(--text-fine);
1656 opacity: 0.5;
1657 text-align: center;
1658 margin-top: var(--gap-section);
1659 }
1660
1661 .buy-page .note a {
1662 color: var(--action);
1663 }
1664
1665 .buy-page .footer {
1666 text-align: center;
1667 margin-top: var(--gap-pane);
1668 padding-top: var(--gap-section);
1669 border-top: 1px solid var(--border);
1670 font-size: var(--text-fine);
1671 opacity: 0.5;
1672 }
1673
1674 .buy-page .footer a {
1675 color: inherit;
1676 }
1677
1678 /* ===========================================
1679 SECTIONED CONTENT (tabs + panels, shared by three pages)
1680 =========================================== */
1681
1682 /* Sectioned content (templates/pages/item.html, project.html,
1683 library_downloads.html). Tab+panel pattern shared by all three
1684 pages, kept global, NOT scoped under .item-page. */
1685 .section-tabs {
1686 display: flex;
1687 gap: 0;
1688 border-bottom: 1px solid var(--border);
1689 margin-bottom: var(--gap-pane);
1690 }
1691
1692 .section-tab {
1693 background: none;
1694 border: none;
1695 padding: var(--gap-group) var(--gap-section);
1696 cursor: pointer;
1697 font-size: var(--text-body);
1698 opacity: 0.6;
1699 border-bottom: 2px solid transparent;
1700 font-family: var(--font-sans);
1701 color: var(--content);
1702 }
1703
1704 .section-tab.is-selected {
1705 opacity: 1;
1706 border-bottom-color: var(--content);
1707 }
1708
1709 .section-tab:hover { opacity: 0.9; }
1710
1711 /* .section-panel.active is a visibility toggle (display: none / block),
1712 not a selection, kept as .active. */
1713 .section-panel { display: none; }
1714 .section-panel.active { display: block; }
1715 .section-panel p { margin-bottom: var(--gap-section); }
1716 .section-panel ul,
1717 .section-panel ol {
1718 margin-left: var(--gap-pane);
1719 margin-bottom: var(--gap-section);
1720 }
1721 .section-panel li { margin-bottom: var(--gap-peer); }
1722 .section-panel h1,
1723 .section-panel h2,
1724 .section-panel h3 {
1725 margin-top: var(--gap-section);
1726 margin-bottom: var(--gap-peer);
1727 }
1728 .section-panel pre {
1729 background: var(--surface-sunken);
1730 padding: var(--gap-section);
1731 overflow-x: auto;
1732 margin-bottom: var(--gap-section);
1733 }
1734 .section-panel code { font-size: var(--text-note); }
1735
1736 /* ===========================================
1737 ITEM PAGE
1738 =========================================== */
1739
1740 /* Item detail page (templates/pages/item.html). Scoped under .item-page
1741 body class so the .item-title / .item-meta / .item-price / .item-footer
1742 names don't collide with the store-front item-card on project.html. */
1743 .item-page .item-layout {
1744 display: grid;
1745 grid-template-columns: 400px 1fr;
1746 gap: var(--gap-page);
1747 margin-bottom: var(--gap-page);
1748 }
1749
1750 .item-page .item-layout.no-media { grid-template-columns: 1fr; }
1751
1752 .item-page .item-title {
1753 font-size: var(--text-title);
1754 margin-bottom: var(--gap-peer);
1755 }
1756
1757 .item-page .item-creator {
1758 font-size: var(--text-body);
1759 opacity: 0.7;
1760 margin-bottom: var(--gap-pane);
1761 }
1762
1763 .item-page .item-creator a { color: var(--content); }
1764
1765 .item-page .item-price {
1766 font-size: var(--text-display);
1767 margin-bottom: var(--gap-pane);
1768 }
1769
1770 .item-page .item-meta {
1771 display: grid;
1772 grid-template-columns: 1fr 1fr;
1773 gap: var(--gap-section);
1774 margin-bottom: var(--gap-pane);
1775 padding-bottom: var(--gap-pane);
1776 border-bottom: 1px solid var(--border);
1777 }
1778
1779 .item-page .meta-item { font-size: var(--text-note); }
1780 .item-page .meta-label { opacity: 0.7; margin-bottom: var(--gap-bound); }
1781 .item-page .meta-value { font-weight: bold; }
1782
1783 .item-page .item-tags { margin-bottom: var(--gap-pane); }
1784
1785 /* purchase-box uses .card-muted for the box; only its h3/ul/li specifics remain. */
1786 .purchase-box h3 {
1787 font-size: var(--text-body);
1788 font-weight: normal;
1789 margin-bottom: var(--gap-section);
1790 }
1791 .purchase-box ul { list-style: none; margin-bottom: var(--gap-section); }
1792 .purchase-box li { padding: var(--gap-bound) 0; }
1793 .purchase-box li::before {
1794 content: "";
1795 opacity: 0.5;
1796 }
1797
1798 .item-page .btn-primary {
1799 width: 100%;
1800 padding: var(--gap-section) var(--gap-page);
1801 font-size: var(--text-lead);
1802 }
1803
1804 .item-page .btn-secondary {
1805 width: 100%;
1806 margin-top: var(--gap-peer);
1807 }
1808
1809 .item-page .payment-note {
1810 font-size: var(--text-note);
1811 opacity: 0.6;
1812 text-align: center;
1813 margin-top: var(--gap-section);
1814 }
1815
1816 /* bundle-child uses .list-row co-class; only the gap-4 override remains. */
1817 .bundle-child { gap: var(--gap-section); padding: var(--gap-section) 0; }
1818
1819 .item-page .bundle-child-type {
1820 font-size: var(--text-fine);
1821 padding: var(--gap-bound) var(--gap-group);
1822 background: var(--surface-sunken);
1823 white-space: nowrap;
1824 }
1825
1826 .item-page .bundle-child-title { flex: 1; }
1827 .item-page .bundle-child-title a { color: var(--content); }
1828 .item-page .bundle-child-price { font-size: var(--text-note); opacity: 0.7; }
1829
1830 .item-page .bundle-notice {
1831 background: var(--surface-sunken);
1832 padding: var(--gap-pane);
1833 margin-bottom: var(--gap-pane);
1834 text-align: center;
1835 }
1836
1837 .item-page .bundle-notice a { color: var(--content); }
1838
1839 .item-page .item-description p { margin-bottom: var(--gap-section); text-align: left; }
1840 .item-page .item-description ul { margin-left: var(--gap-pane); margin-bottom: var(--gap-section); }
1841 .item-page .item-description li { margin-bottom: var(--gap-peer); }
1842
1843 .item-page .features-grid {
1844 display: grid;
1845 grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
1846 gap: var(--gap-pane);
1847 }
1848 .item-page .version-number { font-size: var(--text-lead); }
1849 .item-page .item-footer {
1850 margin-top: var(--gap-page);
1851 padding-top: var(--gap-page);
1852 border-top: 1px solid var(--border);
1853 text-align: center;
1854 opacity: 0.6;
1855 font-size: var(--text-note);
1856 }
1857
1858 .item-page .item-footer a { color: var(--content); }
1859
1860 @media (max-width: 839px) {
1861 .item-page .item-layout { grid-template-columns: 1fr; }
1862 }
1863
1864 @media (max-width: 599px) {
1865 .item-page .item-title { font-size: var(--text-head); }
1866 .item-page .item-price { font-size: var(--text-title); }
1867 .item-page .item-meta { grid-template-columns: 1fr; gap: var(--gap-peer); }
1868 .item-page .item-media,
1869 .item-page .item-details { padding: var(--gap-section); }
1870 .item-page .item-description { padding: var(--gap-section); }
1871 .item-page .item-description h2 { font-size: var(--text-subhead); }
1872 .item-page .purchase-box { padding: var(--gap-section); }
1873 }
1874
1875 /* ===========================================
1876 PROJECT PAGE (store front)
1877 =========================================== */
1878
1879 /* Project store-front page (templates/pages/project.html). Scoped under
1880 .project-page body class, `.item-card`, `.item-title`, `.item-meta`,
1881 `.item-price`, etc. here mean store-front item cards, distinct from
1882 the item-detail-page versions scoped under .item-page above. */
1883 .project-page .store-header { margin-bottom: var(--gap-page); }
1884 .project-page .store-title { font-size: var(--text-hero); margin-bottom: var(--gap-peer); }
1885 .project-page .store-meta {
1886 font-size: var(--text-note);
1887 opacity: 0.7;
1888 margin-bottom: var(--gap-pane);
1889 }
1890 .project-page .store-meta a { color: var(--content); }
1891 .project-page .store-description {
1892 font-size: var(--text-lead);
1893 max-width: 800px;
1894 margin-bottom: var(--gap-page);
1895 text-align: left;
1896 }
1897 .project-page .store-actions {
1898 display: flex;
1899 gap: var(--gap-section);
1900 flex-wrap: wrap;
1901 }
1902
1903 .project-page .items-section { margin-bottom: var(--gap-page); }
1904
1905 .project-page .items-grid {
1906 display: grid;
1907 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1908 gap: var(--gap-page);
1909 }
1910
1911 .project-page .item-card {
1912 background: var(--surface-overlay);
1913 transition: background 0.2s ease;
1914 cursor: pointer;
1915 display: flex;
1916 flex-direction: column;
1917 height: 100%;
1918 }
1919
1920 .project-page .item-card:hover { background: var(--surface-sunken); }
1921
1922 .project-page a.item-thumbnail {
1923 width: 100%;
1924 height: 300px;
1925 background: var(--border);
1926 display: flex;
1927 align-items: center;
1928 justify-content: center;
1929 font-size: var(--text-hero);
1930 text-decoration: none;
1931 overflow: hidden;
1932 }
1933
1934 .project-page a.item-thumbnail img {
1935 width: 100%;
1936 height: 100%;
1937 object-fit: cover;
1938 display: block;
1939 }
1940
1941 /* The dimming belongs to the placeholder, not to the slot. It was on the slot,
1942 so the day covers started rendering here they would have rendered at 30%. */
1943 .project-page a.item-thumbnail-empty { opacity: 0.3; }
1944 .project-page a.item-thumbnail-empty:hover { opacity: 0.5; }
1945
1946 .project-page .item-content {
1947 padding: var(--gap-pane);
1948 display: flex;
1949 flex-direction: column;
1950 flex-grow: 1;
1951 }
1952
1953 .project-page .item-title {
1954 font-size: var(--text-subhead);
1955 margin-bottom: var(--gap-peer);
1956 font-family: var(--font-display);
1957 font-weight: bold;
1958 }
1959
1960 .project-page .item-title a:hover { text-decoration: underline; }
1961
1962 .project-page .item-meta {
1963 font-size: var(--text-note);
1964 opacity: 0.7;
1965 margin-bottom: var(--gap-section);
1966 }
1967
1968 .project-page .item-tags { margin-bottom: var(--gap-section); }
1969
1970 .project-page .item-description {
1971 font-size: var(--text-note);
1972 margin-bottom: var(--gap-section);
1973 opacity: 0.8;
1974 text-align: left;
1975 flex-grow: 1;
1976 }
1977
1978 .project-page .item-footer {
1979 display: flex;
1980 justify-content: space-between;
1981 align-items: center;
1982 margin-top: auto;
1983 }
1984
1985 .project-page .item-price { font-size: var(--text-subhead); }
1986 .project-page .item-stats { font-size: var(--text-note); opacity: 0.6; }
1987
1988 .project-page .store-footer {
1989 margin-top: var(--gap-page);
1990 padding-top: var(--gap-page);
1991 border-top: 1px solid var(--border);
1992 opacity: 0.6;
1993 }
1994
1995 .project-page .store-footer a { color: var(--content); }
1996
1997 .project-page .item-content .btn-primary,
1998 .project-page .item-content .btn-secondary {
1999 width: 100%;
2000 margin-top: var(--gap-section);
2001 }
2002
2003 /* project.html allows .section-tabs to wrap (other pages don't). */
2004 .project-page .section-tabs { flex-wrap: wrap; }
2005
2006 .project-page .tiers-section { margin-bottom: var(--gap-page); }
2007
2008 .project-page .tiers-grid {
2009 display: grid;
2010 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
2011 gap: var(--gap-pane);
2012 }
2013
2014 .project-page .tier-card {
2015 background: var(--surface-overlay);
2016 padding: var(--gap-page);
2017 display: flex;
2018 flex-direction: column;
2019 }
2020
2021 .project-page .tier-name {
2022 font-family: var(--font-display);
2023 font-weight: bold;
2024 font-size: var(--text-subhead);
2025 margin-bottom: var(--gap-peer);
2026 }
2027
2028 .project-page .tier-price {
2029 font-size: var(--text-head);
2030 margin-bottom: var(--gap-section);
2031 }
2032
2033 .project-page .tier-description {
2034 font-size: var(--text-note);
2035 opacity: 0.8;
2036 margin-bottom: var(--gap-pane);
2037 flex-grow: 1;
2038 text-align: left;
2039 }
2040
2041 .project-page .tier-card form { margin-top: auto; }
2042 .project-page .tier-card button,
2043 .project-page .tier-card .btn-primary { width: 100%; }
2044
2045 .project-page .tier-active-badge {
2046 font-size: var(--text-note);
2047 padding: var(--gap-peer) var(--gap-section);
2048 background: var(--surface-sunken);
2049 text-align: center;
2050 opacity: 0.7;
2051 }
2052
2053 @media (max-width: 599px) {
2054 .project-page .store-title { font-size: var(--text-title); }
2055 .project-page a.item-thumbnail { height: 200px; }
2056 .project-page .store-description { font-size: var(--text-body); }
2057 }
2058
2059 /* ===========================================
2060 LIBRARY DOWNLOADS PAGE
2061 =========================================== */
2062
2063 /* Library downloads page (templates/pages/library_downloads.html).
2064 Scoped under .library-page body class. Class names like .item-footer,
2065 .bundle-child, etc. overlap with .item-page; the scope keeps them
2066 distinct. */
2067 .library-page .library-header {
2068 display: grid;
2069 grid-template-columns: 200px 1fr;
2070 gap: var(--gap-pane);
2071 margin-bottom: var(--gap-page);
2072 align-items: center;
2073 }
2074
2075 .library-page .library-header.no-cover { grid-template-columns: 1fr; }
2076
2077 .library-page .library-cover img {
2078 width: 100%;
2079 aspect-ratio: 1 / 1;
2080 object-fit: cover;
2081 display: block;
2082 }
2083
2084 .library-page .library-title {
2085 font-size: var(--text-title);
2086 margin-bottom: var(--gap-bound);
2087 }
2088
2089 /* Byline sits under the centered H1 (global `h1 { text-align: center }`),
2090 so center it too, otherwise it floats flush-left while the title is
2091 centered above. Same for the back-link breadcrumb. When a cover is
2092 present the grid scope below restores left-alignment in the text column. */
2093 .library-page .library-creator {
2094 font-size: var(--text-body);
2095 opacity: 0.7;
2096 margin-bottom: var(--gap-section);
2097 text-align: center;
2098 }
2099
2100 .library-page .library-creator a { color: var(--content); }
2101
2102 .library-page .library-back {
2103 font-size: var(--text-note);
2104 opacity: 0.7;
2105 text-align: center;
2106 }
2107 .library-page .library-back a { color: var(--content); }
2108
2109 /* When a cover is present, the title block sits in the right grid column;
2110 left-align in that context so the column reads as a coherent block next
2111 to the cover instead of a centered island. */
2112 .library-page .library-header:not(.no-cover) .library-title,
2113 .library-page .library-header:not(.no-cover) .library-creator {
2114 text-align: left;
2115 }
2116
2117 .library-page .downloads-hero h2 {
2118 font-size: var(--text-head);
2119 margin-bottom: var(--gap-section);
2120 padding-bottom: var(--gap-peer);
2121 border-bottom: 1px solid var(--border);
2122 }
2123
2124 .library-page .download-row {
2125 display: flex;
2126 align-items: center;
2127 gap: var(--gap-section);
2128 padding: var(--gap-group) 0;
2129 border-bottom: 1px solid var(--border);
2130 }
2131
2132 .library-page .download-row:last-child { border-bottom: none; }
2133
2134 .library-page .download-version {
2135 min-width: 4em;
2136 font-family: var(--font-mono);
2137 font-size: var(--text-note);
2138 }
2139
2140 .library-page .download-label { flex: 1; }
2141
2142 .library-page .download-size {
2143 font-size: var(--text-note);
2144 opacity: 0.6;
2145 min-width: 5em;
2146 text-align: right;
2147 }
2148
2149 .library-page .download-notice {
2150 background: var(--surface-sunken);
2151 padding: var(--gap-pane);
2152 margin-bottom: var(--gap-page);
2153 font-size: var(--text-note);
2154 opacity: 0.8;
2155 }
2156
2157 .library-page .download-notice strong { font-family: var(--font-mono); }
2158
2159 /* Inline download list on library_audio.html / library_video.html
2160 (below the media player). */
2161
2162 .library-page .library-downloads h3 {
2163 font-size: var(--text-lead);
2164 margin-bottom: var(--gap-section);
2165 }
2166
2167 .library-page .library-section h2 {
2168 font-size: var(--text-subhead);
2169 margin-bottom: var(--gap-section);
2170 padding-bottom: var(--gap-peer);
2171 border-bottom: 1px solid var(--border);
2172 }
2173
2174 .library-page .bundle-child {
2175 display: flex;
2176 align-items: center;
2177 gap: var(--gap-section);
2178 padding: var(--gap-section) 0;
2179 border-bottom: 1px solid var(--border);
2180 }
2181
2182 .library-page .bundle-child:last-child { border-bottom: none; }
2183
2184 .library-page .bundle-child-type {
2185 font-size: var(--text-fine);
2186 padding: var(--gap-bound) var(--gap-group);
2187 background: var(--surface-sunken);
2188 white-space: nowrap;
2189 }
2190
2191 .library-page .bundle-child-title { flex: 1; }
2192 .library-page .bundle-child-title a { color: var(--content); }
2193
2194 .library-page .item-footer {
2195 margin-top: var(--gap-page);
2196 padding-top: var(--gap-page);
2197 border-top: 1px solid var(--border);
2198 text-align: center;
2199 opacity: 0.6;
2200 font-size: var(--text-note);
2201 }
2202
2203 .library-page .item-footer a { color: var(--content); }
2204
2205 @media (max-width: 599px) {
2206 .library-page .library-header { grid-template-columns: 1fr; }
2207 .library-page .library-cover img { max-width: 200px; }
2208 }
2209
2210 /* ===========================================
2211 ARTICLE & BLOG READER
2212 =========================================== */
2213
2214 /* Article reader pages (templates/pages/text_reader.html, blog_post.html,
2215 library_text.html). Long-form article layout, centered narrow column,
2216 author header, large heading, magazine-style body type. Scoped under
2217 .article-page body class. The container itself takes the shared reading
2218 column near the top of this file. */
2219
2220 .article-page .author-header {
2221 display: flex;
2222 align-items: center;
2223 gap: var(--gap-section);
2224 margin-bottom: var(--gap-page);
2225 }
2226
2227 .article-page .author-avatar {
2228 width: 48px;
2229 height: 48px;
2230 background: var(--surface-sunken);
2231 border-radius: var(--radius-round);
2232 display: flex;
2233 align-items: center;
2234 justify-content: center;
2235 font-family: var(--font-display);
2236 font-size: var(--text-subhead);
2237 color: var(--content);
2238 }
2239
2240 .article-page .author-info { flex: 1; }
2241
2242 .article-page .author-name {
2243 font-family: var(--font-mono);
2244 font-size: var(--text-body);
2245 color: var(--content);
2246 }
2247
2248
2249 .article-page .author-name a:hover { text-decoration: underline; }
2250
2251 .article-page .article-meta {
2252 font-family: var(--font-mono);
2253 font-size: var(--text-note);
2254 color: var(--content-muted);
2255 }
2256
2257 .article-page .article-title {
2258 font-family: var(--font-display);
2259 font-size: var(--text-display);
2260 font-weight: normal;
2261 line-height: 1.2;
2262 margin-bottom: var(--gap-pane);
2263 color: var(--content);
2264 text-align: left;
2265 }
2266
2267 .article-page .article-deck {
2268 font-family: var(--font-mono);
2269 font-size: var(--text-subhead);
2270 color: var(--content-muted);
2271 margin-bottom: var(--gap-page);
2272 line-height: 1.6;
2273 }
2274
2275 .article-page .article-body {
2276 font-family: var(--font-sans);
2277 font-size: var(--text-lead);
2278 line-height: 1.9;
2279 }
2280
2281 .article-page .article-body p { margin-bottom: var(--gap-pane); }
2282
2283 .article-page .article-body h1 {
2284 font-family: var(--font-display);
2285 font-size: var(--text-title);
2286 font-weight: normal;
2287 margin-top: var(--gap-page);
2288 margin-bottom: var(--gap-section);
2289 color: var(--content);
2290 text-align: left;
2291 }
2292
2293 .article-page .article-body h2 {
2294 font-family: var(--font-mono);
2295 font-size: var(--text-head);
2296 font-weight: normal;
2297 margin-top: var(--gap-page);
2298 margin-bottom: var(--gap-section);
2299 color: var(--content);
2300 }
2301
2302 .article-page .article-body h3 {
2303 font-family: var(--font-mono);
2304 font-size: var(--text-subhead);
2305 font-weight: normal;
2306 margin-top: var(--gap-page);
2307 margin-bottom: var(--gap-section);
2308 color: var(--content);
2309 }
2310
2311 .article-page .article-body blockquote {
2312 border-left: 3px solid var(--action);
2313 padding-left: var(--gap-pane);
2314 margin: var(--gap-page) 0;
2315 font-style: italic;
2316 color: var(--content-muted);
2317 }
2318
2319 .article-page .article-body ul,
2320 .article-page .article-body ol {
2321 margin-bottom: var(--gap-pane);
2322 padding-left: var(--gap-pane);
2323 }
2324
2325 .article-page .article-body li { margin-bottom: var(--gap-peer); }
2326
2327 .article-page .article-body a {
2328 color: var(--action);
2329 text-decoration: underline;
2330 text-decoration-color: color-mix(in oklch, var(--action) 40%, transparent);
2331 text-underline-offset: 2px;
2332 }
2333
2334 .article-page .article-body a:hover {
2335 text-decoration-color: var(--action);
2336 }
2337
2338 .article-page .article-body strong { font-weight: bold; }
2339 .article-page .article-body em { font-style: italic; }
2340
2341 .article-page .article-body code {
2342 font-family: var(--font-mono);
2343 background: var(--surface-sunken);
2344 padding: var(--gap-bound) var(--gap-peer);
2345 font-size: var(--text-body);
2346 }
2347
2348 .article-page .article-body pre {
2349 background: var(--surface-sunken);
2350 padding: var(--gap-section);
2351 overflow-x: auto;
2352 margin-bottom: var(--gap-pane);
2353 }
2354
2355 .article-page .article-body pre code {
2356 background: none;
2357 padding: 0;
2358 }
2359
2360 .article-page .article-body hr {
2361 border: none;
2362 text-align: center;
2363 margin: var(--gap-page) 0;
2364 }
2365
2366 .article-page .article-body hr::before {
2367 content: "* * *";
2368 color: var(--content-muted);
2369 letter-spacing: 1rem;
2370 }
2371
2372 .article-page .article-footer {
2373 margin-top: var(--gap-page);
2374 padding-top: var(--gap-page);
2375 border-top: 1px solid var(--border);
2376 }
2377
2378 .article-page .author-bio {
2379 display: flex;
2380 gap: var(--gap-section);
2381 padding: var(--gap-pane);
2382 background: var(--surface-overlay);
2383 }
2384
2385 .article-page .author-bio .author-avatar {
2386 width: 64px;
2387 height: 64px;
2388 font-size: var(--text-head);
2389 flex-shrink: 0;
2390 }
2391
2392 .article-page .author-bio-content { flex: 1; }
2393
2394 .article-page .author-bio-name {
2395 font-family: var(--font-mono);
2396 font-size: var(--text-lead);
2397 margin-bottom: var(--gap-peer);
2398 }
2399
2400
2401 .article-page .author-bio-name a:hover { text-decoration: underline; }
2402
2403 .article-page .article-tags {
2404 margin-top: var(--gap-page);
2405 display: flex;
2406 gap: var(--gap-peer);
2407 flex-wrap: wrap;
2408 }
2409
2410 .article-page .article-tag {
2411 font-family: var(--font-mono);
2412 background: var(--surface-sunken);
2413 color: var(--content);
2414 padding: var(--gap-peer) var(--gap-section);
2415 font-size: var(--text-fine);
2416 text-decoration: none;
2417 }
2418
2419 /* Share controls at the end of the article, inside .article-footer beside
2420 the tags or the blog crumb. Was a page-bottom footer below the comments
2421 until the site-footer consolidation; the control belongs with the piece
2422 it copies, not under the discussion. */
2423 .article-page .reader-actions {
2424 font-family: var(--font-mono);
2425 font-size: var(--text-note);
2426 margin-top: var(--gap-section);
2427 }
2428
2429 .article-page .reader-actions a { color: var(--content); text-decoration: none; }
2430 .article-page .reader-actions a:hover { text-decoration: underline; }
2431
2432 /* Blog-specific: nav crumb above the article. */
2433 .article-page .blog-nav {
2434 font-family: var(--font-mono);
2435 font-size: var(--text-note);
2436 margin-bottom: var(--gap-page);
2437 }
2438
2439 .article-page .blog-nav a { color: var(--content); text-decoration: none; }
2440 .article-page .blog-nav a:hover { text-decoration: underline; }
2441
2442 @media (max-width: 599px) {
2443 .article-page .article-title { font-size: var(--text-title); }
2444 .article-page .article-deck { font-size: var(--text-lead); }
2445 .article-page .article-body { font-size: var(--text-body); }
2446 }
2447
2448 /* ===========================================
2449 ADMIN & HEALTH DASHBOARDS
2450 =========================================== */
2451
2452 /* Admin dashboards (templates/dashboards/admin-*.html). Every admin
2453 page had a duplicate `h1 { font-size: 2rem; margin-bottom: 0.5rem; }`
2454 inline, consolidated here. */
2455
2456 /* System health dashboard (templates/pages/health.html). */
2457 .health-page {
2458 padding: var(--gap-page);
2459 }
2460
2461 .health-page .health-container {
2462 max-width: 900px;
2463 margin: 0 auto;
2464 }
2465
2466 .health-page .health-grid {
2467 display: grid;
2468 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
2469 gap: var(--gap-pane);
2470 margin-bottom: var(--gap-page);
2471 }
2472
2473 .health-page .health-card {
2474 background: var(--surface-sunken);
2475 padding: var(--gap-pane);
2476 }
2477
2478 .health-page .health-card h3 {
2479 font-family: var(--font-mono);
2480 font-size: var(--text-note);
2481 margin-bottom: var(--gap-section);
2482 display: flex;
2483 align-items: center;
2484 gap: var(--gap-peer);
2485 }
2486
2487 .health-page .status-indicator {
2488 width: 10px;
2489 height: 10px;
2490 border-radius: var(--radius-round);
2491 display: inline-block;
2492 }
2493
2494 .health-page .status-ok { background: var(--success); }
2495 .health-page .status-warn { background: var(--warning); }
2496 .health-page .status-error { background: var(--danger); }
2497 .health-page .status-unknown { background: var(--content-muted); }
2498
2499 .health-page .health-card dl {
2500 display: grid;
2501 grid-template-columns: auto 1fr;
2502 gap: var(--gap-peer) var(--gap-section);
2503 font-size: var(--text-note);
2504 }
2505
2506 .health-page .health-card dt { opacity: 0.7; }
2507 .health-page .health-card dd {
2508 font-family: var(--font-mono);
2509 text-align: right;
2510 }
2511
2512 .health-page .section-title {
2513 font-family: var(--font-mono);
2514 font-size: var(--text-body);
2515 margin: var(--gap-page) 0 var(--gap-section);
2516 padding-bottom: var(--gap-peer);
2517 border-bottom: 1px solid var(--border);
2518 }
2519 .health-page .test-list {
2520 list-style: none;
2521 padding: 0;
2522 margin: 0;
2523 }
2524
2525 .health-page .test-list li {
2526 display: flex;
2527 justify-content: space-between;
2528 padding: var(--gap-peer) 0;
2529 border-bottom: 1px solid var(--border);
2530 font-size: var(--text-note);
2531 }
2532
2533 .health-page .test-list li:last-child { border-bottom: none; }
2534
2535 .health-page .test-name { font-family: var(--font-mono); }
2536
2537 .health-page .test-pass { color: var(--success); }
2538 .health-page .test-fail { color: var(--danger); }
2539 .health-page .summary-bar {
2540 display: flex;
2541 gap: var(--gap-page);
2542 padding: var(--gap-section);
2543 background: var(--surface-sunken);
2544 margin-bottom: var(--gap-page);
2545 font-family: var(--font-mono);
2546 font-size: var(--text-note);
2547 }
2548
2549 .health-page .summary-item {
2550 display: flex;
2551 align-items: center;
2552 gap: var(--gap-peer);
2553 }
2554
2555 .health-page .timestamp {
2556 font-size: var(--text-fine);
2557 opacity: 0.6;
2558 text-align: right;
2559 margin-top: var(--gap-page);
2560 }
2561
2562 .health-page details.check-list {
2563 background: var(--surface-overlay);
2564 padding: var(--gap-pane);
2565 margin-bottom: var(--gap-page);
2566 }
2567
2568 .health-page details.check-list summary {
2569 font-family: var(--font-mono);
2570 font-size: var(--text-note);
2571 cursor: pointer;
2572 list-style: none;
2573 display: flex;
2574 align-items: center;
2575 gap: var(--gap-peer);
2576 }
2577
2578 .health-page details.check-list summary::-webkit-details-marker { display: none; }
2579
2580 .health-page details.check-list summary::before {
2581 content: "";
2582 font-size: var(--text-fine);
2583 transition: transform 0.15s;
2584 }
2585
2586 .health-page details.check-list[open] summary::before {
2587 transform: rotate(90deg);
2588 }
2589
2590 .health-page details.check-list .test-list {
2591 margin-top: var(--gap-section);
2592 }
2593 /* ===========================================
2594 IMPORT WIZARD
2595 =========================================== */
2596
2597 /* Import data wizard (templates/dashboards/dashboard-import.html).
2598 Page-specific upload + column-mapping + progress flow. Migration also
2599 fixed token bypasses: --border-color → --border, --accent → --highlight,
2600 --success-background → --success-bg, --error-background → --error-bg. */
2601
2602 .import-page .back-link {
2603 display: inline-block;
2604 margin-bottom: var(--gap-pane);
2605 font-size: var(--text-note);
2606 opacity: 0.7;
2607 }
2608
2609 .import-page .back-link:hover { opacity: 1; }
2610
2611 .import-page .form-group { margin-bottom: var(--gap-pane); }
2612
2613 .import-page .form-group label {
2614 display: block;
2615 font-family: var(--font-mono);
2616 font-size: var(--text-note);
2617 font-weight: bold;
2618 margin-bottom: var(--gap-peer);
2619 }
2620
2621 .import-page .form-group select,
2622 .import-page .form-group input[type="file"] {
2623 width: 100%;
2624 padding: var(--gap-peer);
2625 font-size: var(--text-note);
2626 border: 1px solid var(--border);
2627 background: var(--surface-page);
2628 }
2629
2630 .import-page .csv-preview {
2631 margin-top: var(--gap-section);
2632 overflow-x: auto;
2633 }
2634
2635 .import-page .csv-preview table {
2636 width: 100%;
2637 border-collapse: collapse;
2638 font-size: var(--text-note);
2639 }
2640
2641 .import-page .csv-preview th,
2642 .import-page .csv-preview td {
2643 padding: var(--gap-peer) var(--gap-group);
2644 border: 1px solid var(--border);
2645 text-align: left;
2646 }
2647
2648 .import-page .csv-preview th {
2649 background: var(--surface-sunken);
2650 font-family: var(--font-mono);
2651 }
2652
2653 .import-page .column-mapping { margin-top: var(--gap-section); }
2654
2655 .import-page .mapping-row {
2656 display: flex;
2657 align-items: center;
2658 gap: var(--gap-section);
2659 margin-bottom: var(--gap-peer);
2660 }
2661
2662 .import-page .mapping-row label {
2663 min-width: 100px;
2664 font-size: var(--text-note);
2665 font-family: var(--font-mono);
2666 margin-bottom: 0;
2667 }
2668
2669 .import-page .mapping-row select {
2670 flex: 1;
2671 padding: var(--gap-bound);
2672 font-size: var(--text-note);
2673 }
2674
2675 .import-page .progress-stats {
2676 display: flex;
2677 gap: var(--gap-page);
2678 font-size: var(--text-note);
2679 font-family: var(--font-mono);
2680 }
2681
2682 .import-page .progress-stats .stat-value { font-weight: bold; }
2683
2684 .import-page .import-result {
2685 padding: var(--gap-section);
2686 margin-bottom: var(--gap-section);
2687 font-size: var(--text-note);
2688 }
2689
2690 .import-page .import-result.success { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); }
2691 .import-page .import-result.error { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); }
2692
2693 .import-page .error-log {
2694 margin-top: var(--gap-section);
2695 padding: var(--gap-peer);
2696 background: var(--surface-sunken);
2697 font-family: var(--font-mono);
2698 font-size: var(--text-fine);
2699 max-height: 200px;
2700 overflow-y: auto;
2701 white-space: pre-wrap;
2702 }
2703
2704 .import-page .import-history { margin-top: var(--gap-page); }
2705
2706 .import-page .history-table {
2707 width: 100%;
2708 border-collapse: collapse;
2709 font-size: var(--text-note);
2710 }
2711
2712 .import-page .history-table th,
2713 .import-page .history-table td {
2714 padding: var(--gap-peer);
2715 border-bottom: 1px solid var(--border);
2716 text-align: left;
2717 }
2718
2719 .import-page .history-table th {
2720 font-family: var(--font-mono);
2721 font-weight: bold;
2722 }
2723
2724 .import-page .import-note {
2725 background: var(--surface-sunken);
2726 padding: var(--gap-section);
2727 margin-top: var(--gap-page);
2728 font-size: var(--text-note);
2729 }
2730
2731 .import-page .import-note h3 {
2732 font-family: var(--font-mono);
2733 font-size: var(--text-note);
2734 margin-bottom: var(--gap-peer);
2735 }
2736
2737 /* ===========================================
2738 DELETE ACCOUNT PAGE
2739 =========================================== */
2740
2741 /* Delete account page (templates/dashboards/dashboard-delete-account.html).
2742 Overrides .warning-box to use --danger (delete is danger, not warning)
2743 and the .form-status partial styling. Scoped to keep the global versions
2744 intact for other pages. */
2745
2746 .delete-account-page .warning-box {
2747 background: var(--danger);
2748 color: var(--primary-light);
2749 padding: var(--gap-pane);
2750 margin-bottom: var(--gap-page);
2751 }
2752
2753 .delete-account-page .warning-box h3 {
2754 font-family: var(--font-mono);
2755 font-size: var(--text-body);
2756 margin-bottom: var(--gap-peer);
2757 }
2758
2759 .delete-account-page .warning-box ul {
2760 margin: var(--gap-peer) 0 0 var(--gap-pane);
2761 font-size: var(--text-note);
2762 }
2763
2764 .delete-account-page .warning-box li { margin-bottom: var(--gap-bound); }
2765
2766 .delete-account-page .export-prompt {
2767 background: var(--surface-sunken);
2768 padding: var(--gap-pane);
2769 margin-bottom: var(--gap-page);
2770 }
2771
2772 .delete-account-page .export-prompt h3 {
2773 font-family: var(--font-mono);
2774 font-size: var(--text-body);
2775 margin-bottom: var(--gap-peer);
2776 }
2777
2778 .delete-account-page .export-prompt p {
2779 font-size: var(--text-note);
2780 margin-bottom: var(--gap-section);
2781 }
2782
2783 .delete-account-page .delete-form {
2784 background: var(--surface-overlay);
2785 padding: var(--gap-pane);
2786 }
2787
2788 .delete-account-page .delete-form .form-group { margin-bottom: var(--gap-pane); }
2789
2790 .delete-account-page .delete-form label {
2791 display: block;
2792 margin-bottom: var(--gap-peer);
2793 font-family: var(--font-mono);
2794 font-size: var(--text-note);
2795 }
2796
2797 .delete-account-page .delete-form .hint {
2798 font-size: var(--text-note);
2799 opacity: 0.7;
2800 margin-top: var(--gap-bound);
2801 }
2802
2803 .delete-account-page .delete-form input[type="text"] {
2804 width: 100%;
2805 padding: var(--gap-section);
2806 font-size: var(--text-body);
2807 border: 2px solid var(--border);
2808 background: var(--surface-sunken);
2809 }
2810
2811 .delete-account-page .delete-form input[type="text"]:focus {
2812 border-color: var(--danger);
2813 outline: none;
2814 }
2815
2816 .delete-account-page .back-link {
2817 display: inline-block;
2818 margin-bottom: var(--gap-pane);
2819 font-size: var(--text-note);
2820 opacity: 0.7;
2821 }
2822
2823 .delete-account-page .back-link:hover { opacity: 1; }
2824
2825 .delete-account-page .form-actions {
2826 display: flex;
2827 gap: var(--gap-section);
2828 align-items: center;
2829 }
2830
2831 .delete-account-page .form-status { font-size: var(--text-note); }
2832 .delete-account-page .form-status.error { color: var(--danger); }
2833 .delete-account-page .form-status.success { color: var(--success); }
2834
2835 /* ===========================================
2836 EXPORT PAGE
2837 =========================================== */
2838
2839 /* Export data page (templates/dashboards/dashboard-export.html).
2840 `.export-card` and its inner classes (`-info`, `-title`, `-desc`, `-meta`)
2841 are intentionally page-scoped: this is a row-layout card (info on left,
2842 action button on right, flex justify-between) that doesn't fit `.card-muted`
2843 cleanly. Background uses `--light-background` rather than `--surface-muted`
2844 for visual distinction from the surrounding dashboard surface. */
2845
2846 .export-page .export-cards {
2847 display: grid;
2848 gap: var(--gap-section);
2849 }
2850
2851 .export-page .export-card {
2852 background: var(--surface-overlay);
2853 padding: var(--gap-pane);
2854 display: flex;
2855 justify-content: space-between;
2856 align-items: flex-start;
2857 gap: var(--gap-section);
2858 }
2859
2860 .export-page .export-card-info { flex: 1; }
2861
2862 .export-page .export-card-title {
2863 font-family: var(--font-mono);
2864 font-weight: bold;
2865 font-size: var(--text-lead);
2866 margin-bottom: var(--gap-peer);
2867 }
2868
2869 .export-page .export-card-desc {
2870 font-size: var(--text-note);
2871 opacity: 0.8;
2872 margin-bottom: var(--gap-peer);
2873 }
2874
2875 .export-page .export-card-meta {
2876 font-size: var(--text-fine);
2877 opacity: 0.6;
2878 }
2879
2880 .export-page .export-card button { white-space: nowrap; }
2881
2882 .export-page .export-note {
2883 background: var(--surface-sunken);
2884 padding: var(--gap-section);
2885 margin-top: var(--gap-page);
2886 font-size: var(--text-note);
2887 }
2888
2889 .export-page .export-note h3 {
2890 font-family: var(--font-mono);
2891 font-size: var(--text-note);
2892 margin-bottom: var(--gap-peer);
2893 }
2894
2895 .export-page .back-link {
2896 display: inline-block;
2897 margin-bottom: var(--gap-pane);
2898 font-size: var(--text-note);
2899 opacity: 0.7;
2900 }
2901
2902 .export-page .back-link:hover { opacity: 1; }
2903
2904 .export-page .export-status {
2905 margin-top: var(--gap-peer);
2906 font-size: var(--text-note);
2907 }
2908
2909 .export-page .export-status.success { color: var(--success); }
2910 .export-page .export-status.error { color: var(--danger); }
2911
2912 /* ===========================================
2913 RECEIPT PAGE
2914 =========================================== */
2915
2916 /* Receipt page (templates/pages/receipt.html). */
2917
2918 .receipt-page .receipt-header {
2919 display: flex;
2920 justify-content: space-between;
2921 align-items: flex-start;
2922 margin-bottom: var(--gap-page);
2923 padding-bottom: var(--gap-pane);
2924 border-bottom: 1px solid var(--border);
2925 }
2926
2927 .receipt-page .receipt-row {
2928 display: flex;
2929 justify-content: space-between;
2930 padding: var(--gap-peer) 0;
2931 font-size: var(--text-body);
2932 }
2933
2934 .receipt-page .receipt-row.total {
2935 font-weight: bold;
2936 font-size: var(--text-lead);
2937 border-top: 1px solid var(--border);
2938 padding-top: var(--gap-section);
2939 margin-top: var(--gap-peer);
2940 }
2941
2942 .receipt-page .receipt-label { opacity: 0.7; }
2943
2944 .receipt-page .receipt-footer {
2945 margin-top: var(--gap-page);
2946 padding-top: var(--gap-pane);
2947 border-top: 1px solid var(--border);
2948 font-size: var(--text-note);
2949 opacity: 0.6;
2950 }
2951
2952 .receipt-page .back-link {
2953 display: inline-block;
2954 margin-bottom: var(--gap-pane);
2955 font-size: var(--text-note);
2956 opacity: 0.7;
2957 }
2958
2959 .receipt-page .back-link:hover { opacity: 1; }
2960
2961 @media print {
2962 .receipt-page .back-link,
2963 .receipt-page .site-header,
2964 .receipt-page .print-btn { display: none !important; }
2965 .receipt-page .receipt-box { border: 1px solid var(--border); }
2966 }
2967
2968 /* ===========================================
2969 FAN+ PAGE
2970 =========================================== */
2971
2972 /* Fan+ subscription page (templates/pages/fan_plus.html). */
2973 .fan-plus-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
2974 .fan-plus-page h2 { font-size: var(--text-head); margin-top: var(--gap-page); margin-bottom: var(--gap-section); }
2975
2976 .fan-plus-page .fan-plus-section { margin-bottom: var(--gap-page); line-height: 1.7; }
2977 .fan-plus-page .fan-plus-section ul { padding-left: var(--gap-pane); margin: var(--gap-section) 0; }
2978 .fan-plus-page .fan-plus-section li { margin-bottom: var(--gap-peer); }
2979
2980 .fan-plus-page .price-callout {
2981 font-size: var(--text-subhead);
2982 font-family: var(--font-mono);
2983 margin: var(--gap-pane) 0;
2984 }
2985
2986 .fan-plus-page .subscribe-form { margin: var(--gap-page) 0; }
2987
2988 .fan-plus-page .subscribe-btn {
2989 display: inline-block;
2990 padding: var(--gap-section) var(--gap-page);
2991 background: var(--action);
2992 color: var(--primary-light);
2993 border: none;
2994 font-size: var(--text-lead);
2995 cursor: pointer;
2996 font-family: var(--font-mono);
2997 }
2998
2999 .fan-plus-page .subscribe-btn:hover { opacity: 0.9; }
3000
3001 .fan-plus-page .status-box {
3002 padding: var(--gap-pane);
3003 background: var(--surface-overlay);
3004 margin: var(--gap-pane) 0;
3005 }
3006
3007 .fan-plus-page .status-box p { margin: var(--gap-peer) 0; }
3008
3009 .fan-plus-page .success-banner {
3010 padding: var(--gap-section) var(--gap-pane);
3011 background: color-mix(in oklch, var(--success) 12%, var(--surface-page));
3012 margin-bottom: var(--gap-pane);
3013 font-family: var(--font-mono);
3014 }
3015
3016 .fan-plus-page .login-link { font-family: var(--font-mono); }
3017
3018 /* ===========================================
3019 PURCHASE PAGE
3020 =========================================== */
3021
3022 /* Purchase confirmation page (templates/pages/purchase.html). */
3023 .purchase-page .container {
3024 max-width: 800px;
3025 margin: var(--gap-page) auto;
3026 padding: 0 var(--gap-page);
3027 }
3028
3029 .purchase-page .tagline {
3030 font-size: var(--text-note);
3031 opacity: 0.7;
3032 margin-bottom: var(--gap-page);
3033 text-align: center;
3034 }
3035
3036 .purchase-page .checkout-box { margin-bottom: var(--gap-pane); }
3037
3038 .purchase-page h2 { font-size: var(--text-title); margin-bottom: var(--gap-peer); }
3039
3040 .purchase-page .step-indicator {
3041 font-size: var(--text-note);
3042 opacity: 0.6;
3043 margin-bottom: var(--gap-pane);
3044 }
3045
3046 .purchase-page .item-summary {
3047 background: var(--surface-page);
3048 padding: var(--gap-pane);
3049 margin-bottom: var(--gap-pane);
3050 display: flex;
3051 gap: var(--gap-pane);
3052 }
3053
3054 .purchase-page .item-thumbnail {
3055 width: 120px;
3056 height: 120px;
3057 background: var(--surface-sunken);
3058 flex-shrink: 0;
3059 display: flex;
3060 align-items: center;
3061 justify-content: center;
3062 font-size: var(--text-hero);
3063 overflow: hidden;
3064 }
3065
3066 .purchase-page .item-thumbnail img {
3067 width: 100%;
3068 height: 100%;
3069 object-fit: cover;
3070 display: block;
3071 }
3072
3073 /* See the storefront card: the dimming is the placeholder's, not the slot's. */
3074 .purchase-page .item-thumbnail-empty { opacity: 0.3; }
3075
3076 .purchase-page .item-details { flex: 1; }
3077
3078 .purchase-page .item-title {
3079 font-size: var(--text-subhead);
3080 margin-bottom: var(--gap-peer);
3081 font-family: var(--font-display);
3082 font-weight: bold;
3083 }
3084
3085 .purchase-page .item-creator {
3086 font-size: var(--text-note);
3087 opacity: 0.7;
3088 margin-bottom: var(--gap-section);
3089 }
3090
3091 .purchase-page .item-creator a { color: var(--content); }
3092 .purchase-page .item-description { font-size: var(--text-note); opacity: 0.8; }
3093
3094 .purchase-page .price-breakdown {
3095 background: var(--surface-page);
3096 padding: var(--gap-pane);
3097 margin-bottom: var(--gap-pane);
3098 }
3099
3100 .purchase-page .price-breakdown h2 { font-size: var(--text-lead); margin-bottom: var(--gap-section); }
3101
3102 .purchase-page .price-row {
3103 display: flex;
3104 justify-content: space-between;
3105 padding: var(--gap-peer) 0;
3106 border-bottom: 1px solid var(--border);
3107 }
3108
3109 .purchase-page .price-row:last-child {
3110 border-bottom: none;
3111 margin-top: var(--gap-peer);
3112 font-size: var(--text-subhead);
3113 padding-top: var(--gap-section);
3114 border-top: 2px solid var(--border);
3115 }
3116
3117 .purchase-page .price-row.total { font-weight: bold; }
3118
3119 .purchase-page .payment-section { margin-bottom: var(--gap-pane); }
3120 .purchase-page .payment-section h2 { font-size: var(--text-lead); margin-bottom: var(--gap-section); }
3121
3122 .purchase-page .btn-primary { width: 100%; }
3123 .purchase-page .btn-secondary { width: 100%; margin-top: var(--gap-section); }
3124
3125 .purchase-page .security-note {
3126 font-size: var(--text-note);
3127 opacity: 0.6;
3128 text-align: center;
3129 margin-top: var(--gap-section);
3130 }
3131
3132 /* Replaces inline styles in pages/purchase.html. */
3133 .purchase-page h1 { text-align: center; }
3134
3135 .purchase-page .price-breakdown-note {
3136 margin-top: var(--gap-peer);
3137 font-size: var(--text-fine);
3138 opacity: 0.6;
3139 }
3140
3141 .purchase-page .creator-breakdown {
3142 background: var(--surface-page);
3143 padding: var(--gap-section) var(--gap-pane);
3144 margin-bottom: var(--gap-pane);
3145 font-size: var(--text-note);
3146 }
3147 .purchase-page .creator-breakdown h2 {
3148 font-size: var(--text-body);
3149 margin-bottom: var(--gap-section);
3150 opacity: 0.7;
3151 }
3152 .purchase-page .creator-breakdown .price-row { opacity: 0.8; }
3153 .purchase-page .creator-breakdown .price-row.is-total {
3154 opacity: 1;
3155 font-weight: bold;
3156 border-top: 1px solid var(--border);
3157 margin-top: var(--gap-peer);
3158 padding-top: var(--gap-peer);
3159 /* override .price-row:last-child upsize */
3160 font-size: inherit;
3161 border-bottom: none;
3162 }
3163 .purchase-page .creator-breakdown .platform-fee-amount { color: var(--content); }
3164 .purchase-page .creator-breakdown-note {
3165 margin-top: var(--gap-section);
3166 font-size: var(--text-note);
3167 opacity: 0.6;
3168 }
3169
3170 .purchase-page .payment-section-lead {
3171 margin-bottom: var(--gap-pane);
3172 opacity: 0.8;
3173 }
3174
3175 .purchase-page .pending-checkout {
3176 margin-bottom: var(--gap-pane);
3177 background: var(--surface-sunken);
3178 padding: var(--gap-section);
3179 }
3180 .purchase-page .pending-checkout p { margin: 0 0 var(--gap-section); }
3181 .purchase-page .pending-checkout p.is-sub {
3182 font-size: var(--text-note);
3183 opacity: 0.8;
3184 }
3185 .purchase-page .pending-checkout form { margin: 0; }
3186
3187 /* Checkout form field wrappers. */
3188 .purchase-page .checkout-field { margin-bottom: var(--gap-section); }
3189 .purchase-page .checkout-field-label {
3190 font-size: var(--text-note);
3191 display: block;
3192 margin-bottom: var(--gap-bound);
3193 }
3194 .purchase-page .pwyw-row {
3195 display: flex;
3196 align-items: center;
3197 gap: var(--gap-bound);
3198 }
3199 .purchase-page .pwyw-symbol { font-size: var(--text-lead); }
3200 .purchase-page .pwyw-input { width: 120px; }
3201 .purchase-page .pwyw-min-note {
3202 font-size: var(--text-fine);
3203 opacity: 0.6;
3204 margin-top: var(--gap-bound);
3205 }
3206 .purchase-page .promo-input {
3207 width: 200px;
3208 text-transform: uppercase;
3209 }
3210 .purchase-page .share-contact-label {
3211 font-size: var(--text-note);
3212 display: flex;
3213 align-items: center;
3214 gap: var(--gap-peer);
3215 cursor: pointer;
3216 }
3217 .purchase-page .share-contact-checkbox { width: auto; }
3218
3219 .purchase-page .cart-alt-link {
3220 text-align: center;
3221 margin-top: var(--gap-section);
3222 font-size: var(--text-note);
3223 opacity: 0.7;
3224 }
3225 .purchase-page .guest-hint {
3226 font-size: var(--text-fine);
3227 opacity: 0.6;
3228 margin-top: var(--gap-section);
3229 text-align: center;
3230 }
3231 .purchase-page .guest-login-prompt {
3232 font-size: var(--text-fine);
3233 margin-top: var(--gap-peer);
3234 text-align: center;
3235 }
3236
3237 /* ===========================================
3238 PROJECT SETTINGS TAB
3239 (replaces inline styles in partials/tabs/project_settings.html and
3240 related innerHTML in static/project-sections.js)
3241 =========================================== */
3242
3243 .proj-image-hint {
3244 font-size: var(--text-note);
3245 opacity: 0.7;
3246 margin: 0 0 var(--gap-peer);
3247 }
3248
3249 /* Canonical inline form-status text following a Save / Change button.
3250 Two class names because templates evolved with both. They're aliases.
3251 States via .success / .error / .saving modifier. */
3252 .field-status,
3253 .save-status {
3254 margin-left: var(--gap-peer);
3255 font-size: var(--text-note);
3256 }
3257 .field-status.success, .save-status.success { color: var(--success); }
3258 .field-status.error, .save-status.error { color: var(--danger); }
3259 .field-status.saving, .save-status.saving { opacity: 0.6; }
3260
3261 /* Muted lead paragraph above a form button (monetization / features / delete). */
3262 .section-lead {
3263 margin-bottom: var(--gap-section);
3264 opacity: 0.7;
3265 text-align: left;
3266 }
3267
3268 /* Large intro paragraph under a page h1 (creators, policy, changelog, fan_plus). */
3269 .page-intro {
3270 font-size: var(--text-lead);
3271 margin-bottom: var(--gap-page);
3272 line-height: 1.6;
3273 }
3274
3275 .subscription-note p {
3276 opacity: 0.7;
3277 font-size: var(--text-note);
3278 }
3279
3280 .features-grid-status {
3281 margin-top: var(--gap-peer);
3282 display: inline-block;
3283 }
3284
3285 /* "Pages" disclosure (project-level markdown sections). */
3286 .pages-toggle { margin-top: var(--gap-page); }
3287 .pages-toggle > summary { cursor: pointer; }
3288 .pages-toggle > summary h2 { display: inline; }
3289 .pages-intro {
3290 font-size: var(--text-note);
3291 opacity: 0.7;
3292 margin: var(--gap-section) 0 var(--gap-section);
3293 }
3294
3295 /* psection-* mirrors section-mgmt-* (used by item_details) but psection
3296 class hooks are also queried by static/project-sections.js. */
3297 .psection-row-title { flex: 1; font-weight: bold; }
3298 .psection-row-anchor { font-size: var(--text-fine); opacity: 0.6; }
3299 .psection-row-length { font-size: var(--text-fine); opacity: 0.6; }
3300
3301 .psection-edit-btn,
3302 .psection-del-btn {
3303 padding: var(--gap-bound) var(--gap-group);
3304 font-size: var(--text-fine);
3305 }
3306
3307 .psection-add-details { margin-top: var(--gap-section); }
3308 .psection-add-details > summary {
3309 cursor: pointer;
3310 font-size: var(--text-body);
3311 }
3312 .psection-add-fields { margin-top: var(--gap-section); }
3313
3314 .psection-edit-modal {
3315 margin-top: var(--gap-section);
3316 padding: var(--gap-section);
3317 background: var(--surface-sunken);
3318 }
3319 .psection-edit-actions {
3320 display: flex;
3321 gap: var(--gap-peer);
3322 }
3323
3324 /* ===========================================
3325 CART PAGE (replaces inline styles in pages/cart.html)
3326 =========================================== */
3327
3328 .cart-empty {
3329 text-align: center;
3330 padding: var(--gap-page) var(--gap-section);
3331 }
3332 .cart-empty-hint {
3333 font-size: var(--text-note);
3334 opacity: 0.7;
3335 margin-bottom: var(--gap-pane);
3336 }
3337
3338 /* Multi-creator summary bar (only renders when seller_groups.len() > 1). */
3339 .cart-multi-bar-note {
3340 opacity: 0.7;
3341 margin-left: var(--gap-peer);
3342 }
3343 .cart-multi-bar-form {
3344 display: flex;
3345 align-items: center;
3346 gap: var(--gap-section);
3347 }
3348 .cart-share-label {
3349 font-size: var(--text-note);
3350 display: flex;
3351 align-items: center;
3352 gap: var(--gap-peer);
3353 }
3354
3355 /* Per-seller group card. */
3356 .cart-group { margin-bottom: var(--gap-page); }
3357 .cart-group-title { margin-bottom: var(--gap-bound); }
3358 .cart-group-count {
3359 font-size: var(--text-note);
3360 opacity: 0.6;
3361 margin-bottom: var(--gap-section);
3362 }
3363
3364 /* PWYW editable price cell. */
3365 .cart-pwyw-cell {
3366 display: flex;
3367 align-items: center;
3368 justify-content: flex-end;
3369 gap: var(--gap-bound);
3370 }
3371 .cart-pwyw-symbol { font-size: var(--text-note); }
3372
3373 /* Compact action button used in cart/wishlist tables. */
3374
3375 /* Bottom summary row of each seller group. */
3376 .cart-group-summary {
3377 margin-top: var(--gap-section);
3378 display: flex;
3379 justify-content: space-between;
3380 align-items: center;
3381 flex-wrap: wrap;
3382 gap: var(--gap-section);
3383 }
3384 .cart-subtotal {
3385 font-family: var(--font-display);
3386 font-size: var(--text-lead);
3387 }
3388 .cart-fee-line {
3389 font-size: var(--text-note);
3390 opacity: 0.6;
3391 }
3392 .cart-savings {
3393 font-size: var(--text-note);
3394 color: var(--action);
3395 margin-top: var(--gap-bound);
3396 }
3397 .cart-stripe-not-ready {
3398 font-size: var(--text-note);
3399 opacity: 0.6;
3400 }
3401
3402 /* Checkout form within a seller group. */
3403 .cart-promo-row { margin-bottom: var(--gap-peer); }
3404 .cart-checkout-share {
3405 display: flex;
3406 align-items: center;
3407 gap: var(--gap-peer);
3408 font-size: var(--text-note);
3409 margin-bottom: var(--gap-peer);
3410 }
3411
3412 /* Wishlist suggestions section below the cart. */
3413 .cart-wishlist { margin-top: var(--gap-page); }
3414
3415 /* Generic "faded out" row state, used by Add-to-Cart click handler in cart.html
3416 to indicate the wishlist row has been moved into the cart. */
3417 .is-faded { opacity: 0.4; }
3418
3419 /* ===========================================
3420 PROFILE TAB (replaces inline styles in partials/tabs/user_profile.html)
3421 =========================================== */
3422
3423 /* Inline form row: input(s) + button laid out horizontally, bottom-aligned.
3424 Distinct from .form-row (which is a 1fr 1fr grid). */
3425 .field-row {
3426 display: flex;
3427 gap: var(--gap-section);
3428 align-items: flex-end;
3429 }
3430 .field-row > .form-group { margin-bottom: 0; }
3431 .field-row > .form-group.is-grow { flex: 1; }
3432 .field-row > .form-group.is-grow-2 { flex: 2; }
3433 .field-row > button { margin-bottom: 0; }
3434
3435 /* Custom domain section. */
3436 .profile-domain-header { margin-bottom: var(--gap-section); }
3437 .profile-domain-status {
3438 margin-left: var(--gap-peer);
3439 }
3440 .profile-domain-status.is-verified { color: var(--success); }
3441 .profile-domain-status.is-pending { color: var(--warning); }
3442
3443 .dns-row {
3444 display: flex;
3445 align-items: center;
3446 gap: var(--gap-peer);
3447 margin-bottom: var(--gap-peer);
3448 }
3449 .dns-row:last-of-type { margin-bottom: 0; }
3450 .dns-row-label {
3451 font-size: var(--text-fine);
3452 opacity: 0.7;
3453 min-width: 70px;
3454 }
3455 .dns-row-value {
3456 font-size: var(--text-note);
3457 flex: 1;
3458 }
3459 .dns-row-copy {
3460 padding: var(--gap-bound) var(--gap-peer);
3461 font-size: var(--text-fine);
3462 }
3463 .profile-domain-actions {
3464 display: flex;
3465 gap: var(--gap-section);
3466 }
3467 .profile-domain-result { margin-top: var(--gap-section); }
3468 .profile-domain-lead {
3469 font-size: var(--text-note);
3470 opacity: 0.8;
3471 }
3472 .profile-domain-add-result { margin-top: var(--gap-section); }
3473
3474 /* Personal feed row: URL input + Copy button. */
3475 .profile-feed-row {
3476 display: flex;
3477 gap: var(--gap-peer);
3478 align-items: center;
3479 }
3480 .profile-feed-input {
3481 flex: 1;
3482 font-size: var(--text-note);
3483 font-family: var(--font-mono);
3484 opacity: 0.8;
3485 }
3486 .profile-feed-lead { margin-bottom: var(--gap-section); }
3487
3488 .purchase-page .checkout-footer {
3489 margin-top: var(--gap-page);
3490 padding-top: var(--gap-pane);
3491 border-top: 1px solid var(--border);
3492 opacity: 0.6;
3493 text-align: center;
3494 font-size: var(--text-note);
3495 }
3496
3497 .purchase-page .checkout-footer a { color: var(--content); }
3498
3499 @media (max-width: 599px) {
3500 .purchase-page .container { padding: 0 var(--gap-section); }
3501 .purchase-page .item-summary { flex-direction: column; }
3502 .purchase-page .item-thumbnail { width: 100%; height: 160px; }
3503 .purchase-page .checkout-box { padding: var(--gap-pane); }
3504 .purchase-page h2 { font-size: var(--text-head); }
3505 }
3506
3507 /* Feed page (templates/pages/feed.html). Compact tabular layout of
3508 items from followed users / projects / tags. */
3509 .feed-page .page-title { font-size: var(--text-head); margin-bottom: var(--gap-section); }
3510 .feed-page .feed-meta { font-size: var(--text-fine); opacity: 0.6; margin-bottom: var(--gap-section); }
3511
3512 .feed-page .table-header {
3513 display: grid;
3514 grid-template-columns: 50px 1fr 100px 70px 70px;
3515 gap: var(--gap-peer);
3516 padding: var(--gap-peer) var(--gap-section);
3517 background: var(--surface-overlay);
3518 font-size: var(--text-fine);
3519 opacity: 0.7;
3520 text-transform: uppercase;
3521 letter-spacing: 0.03em;
3522 }
3523
3524 .feed-page .col-right { text-align: right; }
3525
3526 .feed-page .results-table {
3527 border: 1px solid var(--border);
3528 border-top: none;
3529 }
3530
3531 .feed-page .table-row {
3532 /* respec-ok: a five-column grid template. The generated `.table-row` sets a
3533 display but has no vocabulary for column tracks, so the tracks and the
3534 display that carries them are stated together here. */
3535 display: grid;
3536 grid-template-columns: 50px 1fr 100px 70px 70px;
3537 gap: var(--gap-peer);
3538 padding: var(--gap-peer) var(--gap-section);
3539 align-items: center;
3540 text-decoration: none;
3541 color: var(--content);
3542 font-size: var(--text-note);
3543 border-bottom: 1px solid var(--border);
3544 transition: background 0.1s ease;
3545 }
3546
3547 .feed-page .table-row:last-child { border-bottom: none; }
3548 .feed-page .table-row:hover { background: var(--surface-sunken); }
3549
3550 .feed-page .item-name-cell {
3551 display: flex;
3552 flex-direction: column;
3553 gap: var(--gap-bound);
3554 min-width: 0;
3555 }
3556
3557 .feed-page .item-name {
3558 white-space: nowrap;
3559 overflow: hidden;
3560 text-overflow: ellipsis;
3561 }
3562
3563 .feed-page .item-creator { font-size: var(--text-fine); opacity: 0.5; }
3564
3565 /* The feed's badges shout a little: caps and tracking. The sunken fill that
3566 used to come with them went with the filled-badge form in B2. */
3567 .feed-page .badge {
3568 text-transform: uppercase;
3569 letter-spacing: 0.03em;
3570 }
3571
3572 .feed-page .empty-state {
3573 text-align: center;
3574 padding: var(--gap-page) var(--gap-pane);
3575 background: var(--surface-overlay);
3576 border: 1px solid var(--border);
3577 }
3578
3579 .feed-page .empty-state h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-peer); }
3580 .feed-page .empty-state p { opacity: 0.7; margin-bottom: var(--gap-pane); }
3581
3582 .feed-page .pagination {
3583 display: flex;
3584 gap: var(--gap-bound);
3585 justify-content: center;
3586 margin-top: var(--gap-section);
3587 }
3588
3589 .feed-page .pagination a,
3590 .feed-page .pagination span {
3591 padding: var(--gap-peer) var(--gap-section);
3592 font-size: var(--text-note);
3593 text-decoration: none;
3594 color: var(--content);
3595 border: 1px solid var(--border);
3596 }
3597
3598 .feed-page .pagination span.current {
3599 background: var(--primary-dark);
3600 color: var(--primary-light);
3601 border-color: var(--primary-dark);
3602 }
3603
3604 .feed-page .pagination a:hover { background: var(--surface-sunken); }
3605
3606 @media (max-width: 599px) {
3607 .feed-page .table-header,
3608 .feed-page .table-row { grid-template-columns: 1fr 70px; }
3609 .feed-page .table-header span:nth-child(1),
3610 .feed-page .table-row .badge:first-child,
3611 .feed-page .table-header span:nth-child(3),
3612 .feed-page .table-header span:nth-child(4),
3613 .feed-page .table-row span:nth-child(3),
3614 .feed-page .table-row span:nth-child(3),
3615 }
3616
3617 /* (Stripe Connect disclaimer page rules defined in the canonical
3618 STRIPE DISCLAIMER PAGE section near end of file.) */
3619
3620 /* User profile page (templates/pages/user.html). */
3621 .user-page .profile-header { text-align: center; margin-bottom: var(--gap-page); }
3622
3623 .user-page .profile-avatar {
3624 width: 120px;
3625 height: 120px;
3626 background: var(--surface-overlay);
3627 margin: 0 auto var(--gap-section);
3628 display: flex;
3629 align-items: center;
3630 justify-content: center;
3631 font-size: var(--text-hero);
3632 opacity: 0.5;
3633 }
3634
3635 .user-page .profile-name { font-size: var(--text-title); margin-bottom: var(--gap-peer); }
3636
3637 .user-page .profile-username {
3638 font-size: var(--text-note);
3639 opacity: 0.7;
3640 margin-bottom: var(--gap-section);
3641 }
3642
3643 .user-page .profile-bio {
3644 font-size: var(--text-body);
3645 max-width: 500px;
3646 margin: 0 auto;
3647 text-align: center;
3648 }
3649
3650 .user-page .links-section { margin-bottom: var(--gap-page); }
3651 .user-page .link-item { text-align: center; }
3652
3653 .user-page .link-title {
3654 font-size: var(--text-lead);
3655 margin-bottom: var(--gap-bound);
3656 font-family: var(--font-display);
3657 font-weight: bold;
3658 }
3659
3660 .user-page .link-description { font-size: var(--text-note); opacity: 0.7; }
3661
3662 .user-page .project-title {
3663 font-size: var(--text-lead);
3664 margin-bottom: var(--gap-peer);
3665 font-family: var(--font-display);
3666 font-weight: bold;
3667 }
3668
3669 .user-page .project-meta {
3670 font-size: var(--text-note);
3671 opacity: 0.7;
3672 margin-bottom: var(--gap-peer);
3673 }
3674
3675 .user-page .project-description { font-size: var(--text-note); opacity: 0.8; }
3676
3677 /* Creators / become-a-creator page (templates/pages/creators.html). */
3678 .creators-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
3679 .creators-page h2 { font-size: var(--text-head); margin-top: var(--gap-page); margin-bottom: var(--gap-section); }
3680
3681 .creators-page .how-it-works { margin-bottom: var(--gap-page); }
3682 .creators-page .how-it-works ol { padding-left: var(--gap-pane); line-height: 1.8; }
3683
3684 .creators-page .stats-row { gap: var(--gap-page); margin: var(--gap-page) 0; }
3685 .creators-page .stat-box { padding: var(--gap-pane); }
3686 .creators-page .stat-box .number { font-size: var(--text-title); }
3687 .creators-page .stat-box .label { font-size: var(--text-note); margin-top: var(--gap-bound); }
3688
3689 .creators-page .wave-table {
3690 width: 100%;
3691 border-collapse: collapse;
3692 margin: var(--gap-section) 0;
3693 }
3694
3695 .creators-page .wave-table th,
3696 .creators-page .wave-table td {
3697 padding: var(--gap-section);
3698 text-align: left;
3699 border-bottom: 1px solid var(--border);
3700 }
3701
3702 .creators-page .wave-table th {
3703 font-family: var(--font-mono);
3704 font-size: var(--text-note);
3705 opacity: 0.7;
3706 }
3707
3708 .creators-page .cta {
3709 margin-top: var(--gap-page);
3710 padding: var(--gap-pane);
3711 background: var(--surface-overlay);
3712 text-align: center;
3713 }
3714
3715 .creators-page .cta a { margin: 0 var(--gap-peer); }
3716
3717 /* Tag tree browse page (templates/pages/tag_tree.html). */
3718 .tag-tree-page .breadcrumbs {
3719 font-size: var(--text-note);
3720 margin-bottom: var(--gap-pane);
3721 opacity: 0.7;
3722 }
3723 .tag-tree-page .breadcrumbs a:hover { opacity: 0.6; }
3724 .tag-tree-page .breadcrumbs .sep { margin: 0 var(--gap-bound); opacity: 0.4; }
3725
3726 .tag-tree-page .tag-grid {
3727 display: grid;
3728 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
3729 gap: var(--gap-section);
3730 }
3731
3732 .tag-tree-page .tag-card {
3733 background: var(--surface-sunken);
3734 padding: var(--gap-section);
3735 text-decoration: none;
3736 color: var(--content);
3737 border: 1px solid var(--border);
3738 transition: background 0.1s ease;
3739 }
3740
3741 .tag-tree-page .tag-card:hover {
3742 background: var(--surface-sunken);
3743 text-decoration: none;
3744 }
3745
3746 .tag-tree-page .tag-card-name {
3747 font-family: var(--font-display);
3748 font-weight: bold;
3749 font-size: var(--text-body);
3750 }
3751
3752 .tag-tree-page .tag-card-meta {
3753 font-size: var(--text-fine);
3754 opacity: 0.5;
3755 margin-top: var(--gap-bound);
3756 }
3757
3758 .tag-tree-page .empty-state {
3759 opacity: 0.5;
3760 font-size: var(--text-note);
3761 padding: var(--gap-page) 0;
3762 }
3763
3764 /* Confirm delete prompt (templates/pages/confirm_delete.html). Overrides
3765 the global .warning-box to use --danger-bg (matches the delete-account
3766 semantic). Also tokenized previously-broken fallbacks. */
3767 .confirm-delete-page .warning-box {
3768 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
3769 border: 1px solid var(--danger);
3770 border-radius: var(--radius-panel);
3771 padding: var(--gap-pane);
3772 margin-bottom: var(--gap-pane);
3773 }
3774
3775 .confirm-delete-page .warning-box p {
3776 margin: 0;
3777 color: var(--danger);
3778 }
3779
3780 /* Project paywall page (templates/pages/project_paywall.html). */
3781 .project-paywall-page .paywall-cover { margin-bottom: var(--gap-pane); }
3782 .project-paywall-page .paywall-cover img {
3783 width: 120px;
3784 height: 120px;
3785 border-radius: var(--radius-panel);
3786 object-fit: cover;
3787 }
3788
3789 .project-paywall-page .paywall-title {
3790 font-family: var(--font-display);
3791 font-size: var(--text-hero);
3792 margin-bottom: var(--gap-peer);
3793 }
3794
3795 .project-paywall-page .paywall-creator {
3796 font-size: var(--text-body);
3797 opacity: 0.7;
3798 margin-bottom: var(--gap-pane);
3799 }
3800
3801 .project-paywall-page .paywall-creator a { color: var(--content); }
3802
3803 .project-paywall-page .paywall-description {
3804 font-size: var(--text-lead);
3805 max-width: 800px;
3806 margin: 0 auto var(--gap-page);
3807 text-align: left;
3808 }
3809
3810 .project-paywall-page .paywall-item-count {
3811 font-size: var(--text-body);
3812 opacity: 0.7;
3813 margin-bottom: var(--gap-peer);
3814 }
3815
3816 .project-paywall-page .paywall-tiers {
3817 display: grid;
3818 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
3819 gap: var(--gap-pane);
3820 margin-top: var(--gap-pane);
3821 text-align: left;
3822 }
3823
3824 .project-paywall-page .paywall-tiers .tier-card {
3825 background: var(--surface-overlay);
3826 padding: var(--gap-page);
3827 display: flex;
3828 flex-direction: column;
3829 }
3830
3831 .project-paywall-page .paywall-tiers .tier-card h3 {
3832 font-family: var(--font-display);
3833 font-weight: bold;
3834 font-size: var(--text-subhead);
3835 margin-bottom: var(--gap-peer);
3836 }
3837
3838 .project-paywall-page .paywall-tiers .tier-price {
3839 font-size: var(--text-head);
3840 margin-bottom: var(--gap-section);
3841 }
3842
3843 .project-paywall-page .paywall-tiers .tier-card form,
3844 .project-paywall-page .paywall-tiers .tier-card .button { margin-top: auto; }
3845
3846 .project-paywall-page .paywall-tiers .tier-card button,
3847 .project-paywall-page .paywall-tiers .tier-card .button { width: 100%; }
3848
3849 /* Collection detail page (templates/pages/collection.html). */
3850 .collection-page .container { max-width: 700px; }
3851 .collection-page .collection-header { margin-bottom: var(--gap-page); }
3852 .collection-page .collection-title { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
3853
3854 .collection-page .collection-owner {
3855 font-size: var(--text-note);
3856 opacity: 0.7;
3857 margin-bottom: var(--gap-section);
3858 }
3859
3860 .collection-page .collection-description {
3861 font-size: var(--text-body);
3862 opacity: 0.85;
3863 margin-bottom: var(--gap-section);
3864 }
3865
3866 .collection-page .collection-meta { font-size: var(--text-note); opacity: 0.6; }
3867
3868 .collection-page .collection-item {
3869 display: flex;
3870 justify-content: space-between;
3871 align-items: flex-start;
3872 padding: var(--gap-section);
3873 background: var(--surface-overlay);
3874 margin-bottom: var(--gap-peer);
3875 }
3876
3877 .collection-page .collection-item-info { flex: 1; }
3878
3879 .collection-page .collection-item-title {
3880 font-family: var(--font-display);
3881 font-weight: bold;
3882 font-size: var(--text-body);
3883 margin-bottom: var(--gap-bound);
3884 }
3885
3886 .collection-page .collection-item-title a { text-decoration: none; }
3887
3888 .collection-page .collection-item-meta { font-size: var(--text-note); opacity: 0.7; }
3889
3890 .collection-page .collection-item-price {
3891 font-size: var(--text-note);
3892 white-space: nowrap;
3893 margin-left: var(--gap-section);
3894 }
3895
3896 .collection-page .empty-state {
3897 text-align: center;
3898 padding: var(--gap-page) var(--gap-section);
3899 opacity: 0.6;
3900 }
3901
3902 /* Project blog index page (templates/pages/project_blog.html). */
3903 .project-blog-page .blog-header { margin-bottom: var(--gap-page); }
3904 .project-blog-page .blog-title { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
3905
3906 .project-blog-page .blog-meta {
3907 font-size: var(--text-note);
3908 opacity: 0.7;
3909 margin-bottom: var(--gap-pane);
3910 }
3911
3912 .project-blog-page .blog-meta a { color: var(--content); }
3913
3914 .project-blog-page .blog-actions {
3915 display: flex;
3916 gap: var(--gap-section);
3917 }
3918
3919 .project-blog-page .posts-list { max-width: 800px; }
3920
3921 .project-blog-page .post-entry {
3922 padding: var(--gap-pane) 0;
3923 border-bottom: 1px solid var(--border);
3924 }
3925
3926 .project-blog-page .post-entry:first-child { padding-top: 0; }
3927 .project-blog-page .post-title { font-size: var(--text-head); margin-bottom: var(--gap-peer); }
3928 .project-blog-page .post-title a:hover { text-decoration: underline; }
3929
3930 .project-blog-page .post-date {
3931 font-size: var(--text-note);
3932 opacity: 0.6;
3933 font-family: var(--font-mono);
3934 }
3935
3936 .project-blog-page .empty-state { padding: var(--gap-page) 0; opacity: 0.6; }
3937
3938 /* Canonical "page-level h1", left-aligned, smaller than the global centered
3939 default. Used by dashboards, admin, health, import, delete-account, export,
3940 receipt. Aliased via comma-grouped selectors so each page picks it up via
3941 its body class without redefining. */
3942 .dashboard-page h1,
3943 .admin-page h1,
3944 .health-page h1,
3945 .import-page h1,
3946 .delete-account-page h1,
3947 .export-page h1,
3948 .receipt-page h1 {
3949 font-size: var(--text-title);
3950 margin-bottom: var(--gap-peer);
3951 text-align: left;
3952 }
3953 .delete-account-page h1 { color: var(--danger); }
3954
3955 /* Canonical subtitle under a page h1, muted, with bottom space. */
3956 .health-page .subtitle,
3957 .import-page .subtitle,
3958 .delete-account-page .subtitle,
3959 .export-page .subtitle {
3960 opacity: 0.7;
3961 margin-bottom: var(--gap-page);
3962 text-align: left;
3963 }
3964 .health-page .subtitle { text-align: center; }
3965
3966 /* User Settings tab (templates/partials/tabs/user_settings.html).
3967 Left-rail nav layout for the Settings dashboard tab. Selection uses the
3968 canonical .is-selected modifier. */
3969 .settings-layout { display: flex; gap: var(--gap-page); }
3970 .settings-nav { flex-shrink: 0; min-width: 140px; }
3971 .settings-nav a {
3972 display: block;
3973 padding: var(--gap-peer) var(--gap-section);
3974 text-decoration: none;
3975 color: var(--content);
3976 opacity: 0.6;
3977 font-family: var(--font-mono);
3978 font-size: var(--text-note);
3979 transition: opacity 0.15s ease;
3980 }
3981 .settings-nav a:hover { opacity: 1; }
3982 .settings-nav a.is-selected { opacity: 1; background: var(--surface-sunken); }
3983 .settings-body { flex: 1; min-width: 0; }
3984 @media (max-width: 839px) {
3985 .settings-layout { flex-direction: column; gap: var(--gap-section); }
3986 .settings-nav { display: flex; flex-wrap: wrap; gap: 0; min-width: 0; }
3987 .settings-nav a { padding: var(--gap-peer) var(--gap-group); font-size: var(--text-note); }
3988 }.library-page .feed-table-header {
3989 display: grid;
3990 grid-template-columns: 50px 1fr 100px 70px 70px;
3991 gap: var(--gap-peer);
3992 padding: var(--gap-peer) var(--gap-section);
3993 background: var(--surface-overlay);
3994 font-size: var(--text-fine);
3995 opacity: 0.7;
3996 text-transform: uppercase;
3997 letter-spacing: 0.03em;
3998 }
3999 .library-page .feed-col-right { text-align: right; }
4000 .library-page .feed-results-table { border: 1px solid var(--border); border-top: none; }
4001 .library-page .feed-table-row {
4002 display: grid;
4003 grid-template-columns: 50px 1fr 100px 70px 70px;
4004 gap: var(--gap-peer);
4005 padding: var(--gap-peer) var(--gap-section);
4006 align-items: center;
4007 text-decoration: none;
4008 color: var(--content);
4009 font-size: var(--text-note);
4010 border-bottom: 1px solid var(--border);
4011 transition: background 0.1s ease;
4012 }
4013 .library-page .feed-table-row:last-child { border-bottom: none; }
4014 .library-page .feed-table-row:hover { background: var(--surface-sunken); }
4015 .library-page .feed-item-name-cell {
4016 display: flex;
4017 flex-direction: column;
4018 gap: var(--gap-bound);
4019 min-width: 0;
4020 }
4021 .library-page .feed-item-name {
4022 white-space: nowrap;
4023 overflow: hidden;
4024 text-overflow: ellipsis;
4025 }
4026 .library-page .feed-item-creator { font-size: var(--text-fine); opacity: 0.5; }
4027 .library-page .feed-pagination {
4028 display: flex;
4029 gap: var(--gap-bound);
4030 justify-content: center;
4031 margin-top: var(--gap-section);
4032 }
4033 .library-page .feed-pagination a,
4034 .library-page .feed-pagination span {
4035 padding: var(--gap-peer) var(--gap-section);
4036 font-size: var(--text-note);
4037 text-decoration: none;
4038 color: var(--content);
4039 border: 1px solid var(--border);
4040 }
4041 .library-page .feed-pagination span.current {
4042 background: var(--primary-dark);
4043 color: var(--primary-light);
4044 border-color: var(--primary-dark);
4045 }
4046 .library-page .feed-pagination a:hover { background: var(--surface-sunken); }
4047 @media (max-width: 599px) {
4048 .library-page .feed-table-header,
4049 .library-page .feed-table-row {
4050 grid-template-columns: 1fr 70px;
4051 }
4052 .library-page .feed-table-header span:nth-child(1),
4053 .library-page .feed-table-row .badge:first-child,
4054 .library-page .feed-table-header span:nth-child(3),
4055 .library-page .feed-table-header span:nth-child(4),
4056 .library-page .feed-table-row span:nth-child(3),
4057 .library-page .feed-table-row span:nth-child(4) {
4058 display: none;
4059 }
4060 }
4061
4062 /* User-level dashboard (templates/dashboards/dashboard-user.html).
4063 Scoped under .dashboard-user-page to avoid colliding with .project-card
4064 / .project-title / .project-meta on the public profile (user.html) and
4065 the user_projects tab partial. */
4066 .dashboard-user-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
4067
4068 .dashboard-user-page .project-card {
4069 background: var(--surface-overlay);
4070 padding: var(--gap-pane);
4071 margin-bottom: var(--gap-section);
4072 display: flex;
4073 justify-content: space-between;
4074 align-items: flex-start;
4075 }
4076
4077 .dashboard-user-page .project-info { flex: 1; }
4078
4079 .dashboard-user-page .project-title {
4080 font-family: var(--font-display);
4081 font-weight: bold;
4082 font-size: var(--text-subhead);
4083 margin-bottom: var(--gap-bound);
4084 }
4085
4086 .dashboard-user-page .project-meta {
4087 font-size: var(--text-note);
4088 opacity: 0.7;
4089 margin-bottom: var(--gap-peer);
4090 }
4091
4092 .dashboard-user-page .project-stats { font-size: var(--text-note); }
4093 .dashboard-user-page .project-actions {
4094 display: flex;
4095 gap: var(--gap-peer);
4096 }
4097
4098 .dashboard-user-page .project-actions button {
4099 padding: var(--gap-peer) var(--gap-section);
4100 font-size: var(--text-note);
4101 }
4102 @media (max-width: 839px) {
4103 .dashboard-user-page .project-card { flex-direction: column; }
4104 .dashboard-user-page .project-actions { margin-top: var(--gap-section); }
4105 }
4106
4107 /* Project dashboard (templates/dashboards/dashboard-project.html). */
4108 .dashboard-project-page .project-meta { font-size: var(--text-note); opacity: 0.7; }
4109 .dashboard-project-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
4110
4111 .dashboard-project-page .content-header {
4112 display: flex;
4113 justify-content: space-between;
4114 align-items: center;
4115 margin-bottom: var(--gap-pane);
4116 }
4117
4118 .dashboard-project-page .content-header h2 { font-size: var(--text-subhead); }
4119
4120 .dashboard-project-page .item-thumbnail {
4121 width: 40px;
4122 height: 40px;
4123 background: var(--border);
4124 display: inline-block;
4125 vertical-align: middle;
4126 margin-right: var(--gap-peer);
4127 }
4128
4129 .dashboard-project-page .item-actions {
4130 display: flex;
4131 gap: var(--gap-peer);
4132 }
4133
4134 .dashboard-project-page .item-actions button {
4135 padding: var(--gap-peer) var(--gap-section);
4136 font-size: var(--text-note);
4137 }
4138
4139 .dashboard-project-page .data-section { margin-bottom: var(--gap-page); }
4140
4141 .dashboard-project-page .data-section h2 {
4142 font-size: var(--text-subhead);
4143 margin-bottom: var(--gap-section);
4144 padding-bottom: var(--gap-peer);
4145 border-bottom: 1px solid var(--border);
4146 }
4147
4148 /* Item dashboard (templates/dashboards/dashboard-item.html). */
4149 .dashboard-item-page .item-meta {
4150 font-size: var(--text-note);
4151 opacity: 0.7;
4152 display: flex;
4153 gap: var(--gap-section);
4154 align-items: center;
4155 }
4156
4157 .dashboard-item-page .stat-value { font-size: var(--text-head); }
4158
4159 .dashboard-item-page .section-header {
4160 display: flex;
4161 justify-content: space-between;
4162 align-items: center;
4163 margin-bottom: var(--gap-pane);
4164 padding-bottom: var(--gap-section);
4165 border-bottom: 1px solid var(--border);
4166 }
4167
4168 .dashboard-item-page .section-header h2 { font-size: var(--text-subhead); }
4169
4170 @media (max-width: 839px) {
4171 .dashboard-item-page .section-header {
4172 flex-direction: column;
4173 align-items: flex-start;
4174 gap: var(--gap-section);
4175 }
4176 .dashboard-item-page .section-header .action-buttons { width: 100%; }
4177 }
4178
4179 /* Changelog page (templates/pages/changelog.html). */
4180 .changelog-page .container { max-width: 900px; margin: 0 auto; }
4181 .changelog-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
4182 .changelog-page .changelog-entry {
4183 margin-bottom: var(--gap-page);
4184 padding-bottom: var(--gap-page);
4185 border-bottom: 1px solid var(--border);
4186 }
4187 .changelog-page .changelog-entry:last-child { border-bottom: none; }
4188 .changelog-page .changelog-date {
4189 font-family: var(--font-mono);
4190 font-size: var(--text-note);
4191 opacity: 0.6;
4192 margin-bottom: var(--gap-peer);
4193 }
4194 .changelog-page .changelog-entry h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
4195 .changelog-page .changelog-entry ul {
4196 padding-left: var(--gap-pane);
4197 margin: var(--gap-peer) 0;
4198 line-height: 1.7;
4199 }
4200 .changelog-page .changelog-entry li { margin-bottom: var(--gap-bound); }
4201
4202 /* Policy page (templates/pages/policy.html). */
4203 .policy-page .container { max-width: 900px; margin: 0 auto; }
4204 .policy-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
4205 .policy-page h2 {
4206 font-size: var(--text-head);
4207 margin-top: var(--gap-page);
4208 margin-bottom: var(--gap-section);
4209 }
4210 .policy-page .policy-section { margin-bottom: var(--gap-page); line-height: 1.7; }
4211 .policy-page .policy-section ul {
4212 padding-left: var(--gap-pane);
4213 margin: var(--gap-section) 0;
4214 }
4215 .policy-page .policy-section li { margin-bottom: var(--gap-bound); }
4216 .policy-page .contact {
4217 margin-top: var(--gap-page);
4218 padding: var(--gap-pane);
4219 background: var(--surface-overlay);
4220 }
4221
4222 /* Admin page extras (in addition to global .admin-page h1). */
4223 .admin-page .metrics-grid {
4224 display: grid;
4225 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
4226 gap: var(--gap-section);
4227 margin-bottom: var(--gap-page);
4228 }
4229
4230 .admin-page .metric-card {
4231 padding: var(--gap-section);
4232 border: 1px solid var(--border);
4233 border-radius: var(--radius-square);
4234 }
4235
4236 .admin-page .metric-card .value {
4237 font-size: var(--text-title);
4238 font-family: var(--font-mono);
4239 font-weight: 600;
4240 }
4241
4242 .admin-page .metric-card .label {
4243 font-size: var(--text-note);
4244 color: var(--content-muted);
4245 margin-top: var(--gap-bound);
4246 }
4247
4248 .admin-page .metric-card.warn .value { color: var(--warning); }
4249 .admin-page .metric-card.error .value { color: var(--danger); }
4250
4251 .admin-page .section-heading {
4252 font-size: var(--text-subhead);
4253 margin: var(--gap-pane) 0 var(--gap-section);
4254 font-family: var(--font-mono);
4255 }
4256
4257 /* Scan pipeline dashboard. See docs/scan-pipeline-audit.md § 5. */
4258
4259 .admin-scan-section {
4260 margin-top: var(--gap-page);
4261 }
4262
4263 .admin-section-header {
4264 display: flex;
4265 justify-content: space-between;
4266 align-items: baseline;
4267 margin-bottom: var(--gap-section);
4268 gap: var(--gap-section);
4269 }
4270
4271 /* Typeface comes from .subsection-title on the heading itself; this sets only
4272 what the flex row needs. */
4273 .admin-section-header h2 {
4274 font-size: var(--text-lead);
4275 margin: 0;
4276 }
4277
4278 .layer-health-grid {
4279 display: grid;
4280 grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
4281 gap: var(--gap-section);
4282 }
4283
4284 .layer-health-card {
4285 padding: var(--gap-section);
4286 border: 1px solid var(--border);
4287 border-radius: var(--radius-square);
4288 border-left-width: 4px;
4289 font-size: var(--text-note);
4290 }
4291
4292 .layer-health-card .layer-name {
4293 font-family: var(--font-mono);
4294 font-weight: 600;
4295 font-size: var(--text-body);
4296 margin-bottom: var(--gap-peer);
4297 }
4298
4299 .layer-health-card .layer-stat {
4300 display: flex;
4301 justify-content: space-between;
4302 margin-bottom: 2px;
4303 }
4304
4305 .layer-health-card .layer-stat .dimmed {
4306 color: var(--content-muted);
4307 }
4308
4309 .layer-health-card.layer-ok { border-left-color: var(--success); }
4310 .layer-health-card.layer-degraded { border-left-color: var(--warning); }
4311 .layer-health-card.layer-down { border-left-color: var(--danger); }
4312
4313 .layer-chips {
4314 display: flex;
4315 flex-wrap: wrap;
4316 gap: 4px;
4317 max-width: 360px;
4318 }
4319
4320 .layer-chip {
4321 display: inline-block;
4322 font-family: var(--font-mono);
4323 font-size: var(--text-fine);
4324 padding: 1px 6px;
4325 border-radius: var(--radius-control);
4326 border: 1px solid transparent;
4327 cursor: help;
4328 line-height: 1.4;
4329 }
4330
4331 .layer-chip-pass { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); border-color: var(--success); color: var(--success); }
4332 .layer-chip-skip { background: var(--surface-page); border-color: var(--border); color: var(--content-muted); }
4333 .layer-chip-fail { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); border-color: var(--danger); color: var(--danger); font-weight: 600; }
4334 .layer-chip-error { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); border-color: var(--warning); color: var(--warning); font-weight: 600; }
4335
4336 .scan-history-grid table { font-size: var(--text-note); }
4337
4338 .scan-history-grid summary {
4339 cursor: pointer;
4340 font-family: var(--font-mono);
4341 padding: var(--gap-peer) 0;
4342 user-select: none;
4343 }
4344
4345 .scan-row-meta {
4346 font-size: var(--text-fine);
4347 color: var(--content-muted);
4348 margin-top: 2px;
4349 }
4350
4351 .audit-filter-form {
4352 display: flex;
4353 flex-wrap: wrap;
4354 align-items: flex-end;
4355 gap: var(--gap-section);
4356 padding: var(--gap-section) 0;
4357 margin-bottom: var(--gap-section);
4358 border-bottom: 1px solid var(--border);
4359 }
4360
4361 .audit-filter-form label {
4362 display: flex;
4363 flex-direction: column;
4364 gap: 2px;
4365 }
4366
4367 .audit-filter-form input,
4368 .audit-filter-form select {
4369 font-size: var(--text-note);
4370 }
4371
4372 .layer-chips-cell { vertical-align: top; }
4373
4374 .layer-detail-disclosure {
4375 margin-top: var(--gap-peer);
4376 }
4377
4378 .layer-detail-disclosure summary {
4379 cursor: pointer;
4380 user-select: none;
4381 padding: 2px 0;
4382 }
4383
4384 .layer-detail-list {
4385 margin: var(--gap-peer) 0 0;
4386 display: grid;
4387 grid-template-columns: max-content 1fr;
4388 gap: 2px var(--gap-section);
4389 font-size: var(--text-fine);
4390 }
4391
4392 .layer-detail-list dt {
4393 font-family: var(--font-mono);
4394 }
4395
4396 .layer-detail-list dd {
4397 margin: 0;
4398 color: var(--content-muted);
4399 word-break: break-word;
4400 }
4401
4402 .admin-page table {
4403 width: 100%;
4404 border-collapse: collapse;
4405 font-size: var(--text-note);
4406 }
4407
4408 .admin-page th,
4409 .admin-page td {
4410 text-align: left;
4411 padding: var(--gap-peer) var(--gap-group);
4412 border-bottom: 1px solid var(--border);
4413 }
4414
4415 .admin-page th {
4416 font-family: var(--font-mono);
4417 font-size: var(--text-fine);
4418 text-transform: uppercase;
4419 color: var(--content-muted);
4420 }
4421
4422 .admin-page td.mono { font-family: var(--font-mono); }
4423
4424 .admin-page .refresh-note {
4425 font-size: var(--text-fine);
4426 color: var(--content-muted);
4427 margin-bottom: var(--gap-section);
4428 font-family: var(--font-mono);
4429 }
4430
4431 .admin-page .appeal-count {
4432 font-size: var(--text-note);
4433 opacity: 0.7;
4434 margin-bottom: var(--gap-pane);
4435 }
4436
4437 .admin-page .decide-form {
4438 display: flex;
4439 gap: var(--gap-peer);
4440 align-items: flex-end;
4441 flex-wrap: wrap;
4442 }
4443
4444 .admin-page .decide-form textarea {
4445 min-width: 200px;
4446 font-size: var(--text-note);
4447 }
4448
4449 .admin-page .suspend-form {
4450 display: flex;
4451 gap: var(--gap-peer);
4452 align-items: flex-end;
4453 }
4454
4455 .admin-page .suspend-form input[type="text"] {
4456 min-width: 200px;
4457 font-size: var(--text-note);
4458 }
4459
4460 .admin-page .lottery-form {
4461 background: var(--surface-overlay);
4462 padding: var(--gap-pane);
4463 margin: var(--gap-pane) 0;
4464 display: flex;
4465 gap: var(--gap-section);
4466 align-items: flex-end;
4467 }
4468
4469 .admin-page .lottery-form .form-group { margin-bottom: 0; }
4470
4471 .admin-page .lottery-form label {
4472 font-size: var(--text-note);
4473 display: block;
4474 margin-bottom: var(--gap-bound);
4475 }
4476
4477 .admin-page .lottery-form input[type="number"] { width: 80px; }
4478
4479 /* Page-top notice (charter primitive, replaces #restart-banner and
4480 the inline sandbox banner in dashboard-project). Full-bleed across
4481 the viewport, monospace, one short sentence + optional action.
4482 Hero callouts on the landing page use `.alert` instead (multi-paragraph). */
4483 .banner {
4484 padding: var(--gap-section) var(--gap-pane);
4485 font-family: var(--font-mono);
4486 font-size: var(--text-note);
4487 text-align: center;
4488 color: var(--content);
4489 }
4490
4491 .banner--info {
4492 background: var(--highlight-faint);
4493 border-bottom: 2px solid var(--focus-ring);
4494 }
4495
4496 .banner--warning {
4497 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
4498 border-bottom: 2px solid var(--warning);
4499 }
4500
4501 .banner a {
4502 color: inherit;
4503 text-decoration: underline;
4504 }
4505
4506 /* ===========================================
4507 SECTIONS
4508 =========================================== */
4509
4510 .section-header {
4511 font-size: var(--text-head);
4512 font-weight: normal;
4513 margin-bottom: var(--gap-section);
4514 padding-bottom: var(--gap-peer);
4515 border-bottom: 1px solid var(--border);
4516 font-family: var(--font-mono);
4517 }
4518
4519 /* Canonical "page section box", light-background card with padding + bottom
4520 space. One of these in the codebase. Aliased to the per-page class names
4521 that were just relabels of this same shape. Templates can use the alias
4522 name OR add `.content-section` as a co-class. */
4523 /* The canonical panel. Every one of these carries `.raised` in the markup
4524 now, so the fill and the bevel come from layout.css and this rule is the
4525 spacing only. The background it used to declare was --surface-overlay,
4526 which .raised spells --surface-raised; equal on the platform default and a
4527 theme may set them apart, the same move .card and .buy-card made. */
4528 .content-section,
4529 .import-page .import-form,
4530 .import-page .import-progress,
4531 .library-page .library-section,
4532 .library-page .library-downloads,
4533 .library-page .downloads-hero,
4534 .receipt-page .receipt-box,
4535 .stripe-disclaimer-page .disclaimer-box,
4536 .project-page .project-sections {
4537 padding: var(--gap-page);
4538 margin-bottom: var(--gap-page);
4539 }
4540
4541 /* Variants of the canonical shape that want top-spacing instead of bottom,
4542 or a tighter --gap-pane padding. */
4543 .library-page .library-downloads,
4544 .receipt-page .receipt-box { margin-bottom: 0; margin-top: var(--gap-pane); }
4545 .import-page .import-progress { padding: var(--gap-pane); margin-bottom: var(--gap-section); }
4546 .stripe-disclaimer-page .disclaimer-box { margin-bottom: var(--gap-pane); }
4547
4548 .form-section {
4549 margin-bottom: var(--gap-page);
4550 }
4551
4552 .section-group-label {
4553 font-family: var(--font-mono);
4554 font-size: var(--text-fine);
4555 font-weight: bold;
4556 text-transform: uppercase;
4557 letter-spacing: 0.05em;
4558 color: var(--content-muted);
4559 margin: var(--gap-page) 0 var(--gap-section) 0;
4560 padding-top: var(--gap-pane);
4561 border-top: 2px solid var(--border);
4562 }
4563
4564 .form-section h2 {
4565 font-size: var(--text-subhead);
4566 font-weight: normal;
4567 margin-bottom: var(--gap-section);
4568 padding-bottom: var(--gap-peer);
4569 border-bottom: 1px solid var(--border);
4570 }
4571
4572 details.form-section > summary {
4573 cursor: pointer;
4574 list-style: disclosure-closed;
4575 }
4576
4577 details.form-section[open] > summary {
4578 list-style: disclosure-open;
4579 margin-bottom: var(--gap-section);
4580 }
4581
4582 details.form-section > summary > h2 {
4583 display: inline;
4584 }
4585
4586 details.form-section > summary:hover > h2 {
4587 color: var(--content);
4588 }
4589
4590 /* ===========================================
4591 NAVIGATION
4592 =========================================== */
4593
4594 nav {
4595 margin-bottom: var(--gap-page);
4596 }
4597
4598 .nav-links {
4599 display: flex;
4600 gap: var(--gap-page);
4601 flex-wrap: wrap;
4602 }
4603
4604 .nav-links a {
4605 color: var(--content);
4606 text-decoration: none;
4607 font-family: var(--font-mono);
4608 transition: opacity 0.2s ease;
4609 }
4610
4611 .nav-links a:hover {
4612 opacity: 0.6;
4613 }
4614
4615 .shortcuts-help-btn {
4616 font-family: var(--font-mono);
4617 color: var(--content-muted);
4618 font-size: var(--text-note);
4619 width: 1.5rem;
4620 height: 1.5rem;
4621 border: 1px solid var(--border);
4622 border-radius: var(--radius-control);
4623 display: inline-flex;
4624 align-items: center;
4625 justify-content: center;
4626 cursor: pointer;
4627 transition: opacity 0.2s ease;
4628 padding: 0;
4629 background: none;
4630 }
4631
4632 .shortcuts-help-btn:hover {
4633 opacity: 0.6;
4634 }
4635
4636 .breadcrumb {
4637 font-size: var(--text-note);
4638 color: var(--content-muted);
4639 margin-bottom: var(--gap-peer);
4640 font-family: var(--font-mono);
4641 }
4642
4643 .breadcrumb a {
4644 color: var(--content);
4645 text-decoration: none;
4646 }
4647
4648 .breadcrumb a:hover {
4649 text-decoration: underline;
4650 }
4651
4652 /* ===========================================
4653 HEADER & FOOTER
4654 =========================================== */
4655
4656 header {
4657 background: transparent;
4658 border-bottom: none;
4659 padding: 0;
4660 margin-bottom: var(--gap-page);
4661 }
4662
4663 footer {
4664 text-align: center;
4665 font-family: var(--font-mono);
4666 color: var(--content-muted);
4667 }
4668
4669 .profile-footer {
4670 margin-top: var(--gap-page);
4671 padding-top: var(--gap-pane);
4672 border-top: 1px solid var(--border);
4673 text-align: center;
4674 opacity: 0.6;
4675 font-size: var(--text-note);
4676 }
4677
4678 .profile-footer a {
4679 color: var(--content);
4680 text-decoration: none;
4681 }
4682
4683 .profile-footer a:hover {
4684 text-decoration: underline;
4685 }
4686
4687 /* Site-wide header */
4688 .site-header {
4689 display: flex;
4690 justify-content: space-between;
4691 align-items: center;
4692 padding: var(--gap-section) var(--gap-pane);
4693 margin-bottom: var(--gap-pane);
4694 }
4695
4696 .site-header nav {
4697 margin: 0;
4698 }
4699
4700 .site-header .nav-links {
4701 gap: var(--gap-pane);
4702 }
4703
4704 .site-logo {
4705 font-family: var(--font-display);
4706 font-size: var(--text-subhead);
4707 text-decoration: none;
4708 color: var(--content);
4709 }
4710
4711 .site-logo:hover {
4712 opacity: 0.8;
4713 }
4714
4715 .nav-form {
4716 display: contents;
4717 }
4718
4719 /* Header search */
4720 .header-search {
4721 flex: 0 1 220px;
4722 margin: 0 var(--gap-section);
4723 }
4724
4725 .header-search input {
4726 width: 100%;
4727 padding: var(--gap-peer) var(--gap-group);
4728 font-size: var(--text-note);
4729 font-family: var(--font-mono);
4730 border: 1px solid var(--border);
4731 background: var(--surface-page);
4732 color: var(--content);
4733 border-radius: var(--radius-control);
4734 }
4735
4736 .header-search input:focus {
4737 outline: 2px solid var(--focus-ring);
4738 outline-offset: -1px;
4739 }
4740
4741 /* Hamburger toggle, hidden by default, shown at 768px */
4742 .nav-toggle-checkbox {
4743 display: none;
4744 }
4745
4746 .nav-toggle-label {
4747 display: none;
4748 cursor: pointer;
4749 padding: var(--gap-peer);
4750 z-index: var(--z-header);
4751 }
4752
4753 .nav-toggle-label span {
4754 display: block;
4755 width: 22px;
4756 height: 2px;
4757 background: var(--content);
4758 margin: 5px 0;
4759 transition: transform 0.2s ease, opacity 0.2s ease;
4760 }
4761
4762 /* ===========================================
4763 LAYOUT UTILITIES
4764 =========================================== */
4765
4766 .action-buttons {
4767 display: flex;
4768 gap: var(--gap-section);
4769 flex-wrap: wrap;
4770 }
4771 /* ===========================================
4772 SPECIFIC COMPONENTS
4773 =========================================== */
4774
4775 /* Login container */
4776 .login-container {
4777 display: flex;
4778 flex-direction: column;
4779 max-width: 400px;
4780 width: 100%;
4781 margin: 0 auto;
4782 background: var(--surface-overlay);
4783 padding: var(--gap-page);
4784 }
4785 /* Logo */
4786 .logo {
4787 font-family: var(--font-display);
4788 color: var(--content);
4789 font-size: var(--text-head);
4790 }
4791
4792 .tagline {
4793 font-family: var(--font-mono);
4794 color: var(--content-muted);
4795 text-align: center;
4796 }
4797
4798 .centered-page .tagline {
4799 margin-bottom: var(--gap-bound);
4800 }
4801 /* Skip link */
4802 .skip-link {
4803 text-align: center;
4804 margin-top: var(--gap-section);
4805 }
4806
4807 .skip-link a {
4808 color: var(--primary-dark);
4809 text-decoration: none;
4810 font-size: var(--text-note);
4811 opacity: 0.7;
4812 }
4813
4814 .skip-link a:hover {
4815 text-decoration: underline;
4816 }
4817
4818 /* Footer link */
4819 .foot-link {
4820 margin-top: 0.5em;
4821 }
4822
4823 /* Status indicators */
4824 .status-indicator {
4825 display: inline-block;
4826 width: 8px;
4827 height: 8px;
4828 background: var(--primary-dark);
4829 margin-right: var(--gap-peer);
4830 }
4831
4832 .status-indicator.active {
4833 background: var(--success);
4834 }
4835
4836 /* Amount styling */
4837 .amount-in {
4838 color: var(--success);
4839 }
4840
4841 .amount-out {
4842 opacity: 0.7;
4843 }
4844
4845 /* File upload */
4846 .file-upload-area {
4847 background: var(--surface-sunken);
4848 padding: var(--gap-page);
4849 text-align: center;
4850 cursor: pointer;
4851 transition: background 0.2s ease;
4852 margin-bottom: var(--gap-section);
4853 }
4854
4855 .file-upload-area:hover,
4856 .file-upload-area.dragover {
4857 background: var(--border);
4858 }
4859 /* Chart placeholder */
4860 .chart-container {
4861 background: var(--surface-sunken);
4862 padding: var(--gap-pane);
4863 margin-bottom: var(--gap-pane);
4864 }
4865
4866 .chart-header {
4867 display: flex;
4868 justify-content: space-between;
4869 align-items: center;
4870 margin-bottom: var(--gap-section);
4871 }
4872
4873 .chart-header h2 {
4874 font-size: var(--text-subhead);
4875 font-weight: normal;
4876 }
4877
4878 .chart-bars {
4879 display: flex;
4880 align-items: flex-end;
4881 gap: 2px;
4882 height: 200px;
4883 padding: 0 var(--gap-peer);
4884 }
4885
4886 .chart-bar-col {
4887 flex: 1;
4888 display: flex;
4889 flex-direction: column;
4890 align-items: center;
4891 min-width: 0;
4892 cursor: default;
4893 position: relative;
4894 }
4895
4896 .chart-bar-col[data-tooltip]:hover::before {
4897 content: attr(data-tooltip);
4898 position: absolute;
4899 bottom: 100%;
4900 left: 50%;
4901 transform: translateX(-50%);
4902 background: var(--primary-dark);
4903 color: var(--primary-light);
4904 font-size: var(--text-fine);
4905 font-family: var(--font-mono);
4906 padding: var(--gap-bound) var(--gap-peer);
4907 white-space: nowrap;
4908 z-index: var(--z-dropdown);
4909 pointer-events: none;
4910 margin-bottom: var(--gap-bound);
4911 }
4912
4913 .chart-bar {
4914 width: 100%;
4915 height: var(--fill, 0%);
4916 background: var(--primary-dark);
4917 min-height: 2px;
4918 transition: opacity 0.15s;
4919 }
4920
4921 .chart-bar-col:hover .chart-bar {
4922 opacity: 0.8;
4923 }
4924
4925 .chart-bar-label {
4926 font-size: var(--text-fine);
4927 opacity: 0.5;
4928 margin-top: var(--gap-bound);
4929 white-space: nowrap;
4930 overflow: hidden;
4931 text-overflow: ellipsis;
4932 max-width: 100%;
4933 }
4934
4935 /* Time selector */
4936 .time-selector {
4937 display: flex;
4938 gap: var(--gap-peer);
4939 }
4940
4941 /* The range buttons are makeover's `.segment`, which is what a segmented
4942 control is called there, and B3's question about whether this codebase has
4943 an analogue: it does, and this is it. Fill, bevel, hover, active and focus
4944 are the generated ones; this rule is geometry and type.
4945
4946 Selection was a --primary-dark fill with a --primary-light label, which is
4947 a second thing meaning "chosen" beside the generated `.chosen` (the segment
4948 presses IN — well fill, inset bevel). Taking the generated one; only the
4949 name stays local, same as the tabs. */
4950 .time-selector button {
4951 border: none;
4952 padding: var(--gap-peer) var(--gap-section);
4953 font-family: inherit;
4954 font-size: var(--text-note);
4955 cursor: pointer;
4956 }
4957
4958 .time-selector button.is-selected {
4959 background: var(--surface-well, var(--surface-page));
4960 box-shadow: var(--bevel-inset);
4961 }
4962
4963 /* Analytics */
4964 .analytics-grid {
4965 display: grid;
4966 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4967 gap: var(--gap-pane);
4968 margin-bottom: var(--gap-pane);
4969 }
4970 .analytics-list {
4971 list-style: none;
4972 }
4973
4974 .analytics-list li {
4975 padding: var(--gap-peer) 0;
4976 border-bottom: 1px solid var(--border);
4977 display: flex;
4978 justify-content: space-between;
4979 }
4980
4981 .analytics-list li:last-child {
4982 border-bottom: none;
4983 }
4984
4985 /* Filter controls */
4986 .filter-controls {
4987 display: flex;
4988 gap: var(--gap-section);
4989 margin-bottom: var(--gap-section);
4990 flex-wrap: wrap;
4991 align-items: center;
4992 }
4993
4994 .filter-group {
4995 display: flex;
4996 gap: var(--gap-peer);
4997 align-items: center;
4998 }
4999 /* UUID styling */
5000 .uuid {
5001 font-family: inherit;
5002 opacity: 0.5;
5003 font-size: var(--text-note);
5004 }
5005 /* ===========================================
5006 HTMX LOADING INDICATORS
5007 =========================================== */
5008
5009 .htmx-indicator {
5010 display: none;
5011 animation: htmx-spin 1s ease-in-out infinite;
5012 }
5013
5014 .htmx-request .htmx-indicator {
5015 display: inline;
5016 }
5017
5018 .htmx-request.htmx-indicator {
5019 display: inline;
5020 }
5021
5022 /* Loading spinner animation. Applied on .htmx-indicator above. */
5023 @keyframes htmx-spin {
5024 0% { opacity: 0.3; }
5025 50% { opacity: 1; }
5026 100% { opacity: 0.3; }
5027 }
5028
5029 /* Disable buttons during request */
5030 .htmx-request button[type="submit"] {
5031 opacity: 0.6;
5032 cursor: wait;
5033 }
5034
5035 /* ===========================================
5036 TOAST NOTIFICATIONS
5037 =========================================== */
5038
5039 .toast-container {
5040 position: fixed;
5041 bottom: 1.5rem;
5042 right: 1.5rem;
5043 z-index: var(--z-toast);
5044 display: flex;
5045 flex-direction: column;
5046 gap: var(--gap-peer);
5047 }
5048
5049 .toast {
5050 background: var(--primary-dark);
5051 color: var(--primary-light);
5052 padding: var(--gap-section);
5053 font-family: var(--font-mono);
5054 font-size: var(--text-note);
5055 box-shadow: var(--elevation-overlay);
5056 animation: toast-in 0.3s ease-out;
5057 max-width: 300px;
5058 display: flex;
5059 align-items: center;
5060 gap: var(--gap-section);
5061 }
5062
5063 .toast-dismiss {
5064 background: none;
5065 border: none;
5066 color: inherit;
5067 font-size: var(--text-lead);
5068 cursor: pointer;
5069 opacity: 0.7;
5070 padding: 0;
5071 line-height: 1;
5072 flex-shrink: 0;
5073 }
5074
5075 .toast-dismiss:hover {
5076 opacity: 1;
5077 }
5078
5079 .toast-success {
5080 background: var(--success);
5081 }
5082
5083 .toast-error {
5084 background: var(--danger);
5085 }
5086
5087 .toast-warning {
5088 background: var(--warning);
5089 }
5090
5091 .toast-info {
5092 background: var(--primary-dark);
5093 }
5094
5095 .toast-retry-btn {
5096 background: none;
5097 border: 1px solid currentColor;
5098 color: inherit;
5099 font-family: var(--font-mono);
5100 font-size: var(--text-fine);
5101 font-weight: 600;
5102 padding: var(--gap-bound) var(--gap-peer);
5103 margin-left: var(--gap-section);
5104 cursor: pointer;
5105 }
5106
5107 .toast-retry-btn:hover {
5108 background: color-mix(in oklch, var(--primary-light) 15%, transparent);
5109 }
5110
5111 .toast.fade-out {
5112 animation: toast-out 0.3s ease-in forwards;
5113 }
5114
5115 @keyframes toast-in {
5116 from {
5117 transform: translateX(100%);
5118 opacity: 0;
5119 }
5120 to {
5121 transform: translateX(0);
5122 opacity: 1;
5123 }
5124 }
5125
5126 @keyframes toast-out {
5127 from {
5128 transform: translateX(0);
5129 opacity: 1;
5130 }
5131 to {
5132 transform: translateX(100%);
5133 opacity: 0;
5134 }
5135 }
5136
5137 /* ===========================================
5138 "NEW" BADGE (data-new-until)
5139 ===========================================
5140 Applied by whats_new.js while today's date is <= the element's
5141 data-new-until. Renders a small violet dot to the right of the
5142 element's content. Pair with relative-positioned parents. */
5143
5144 .is-new {
5145 position: relative;
5146 }
5147
5148 .is-new::after {
5149 content: "";
5150 position: absolute;
5151 top: -2px;
5152 right: -8px;
5153 width: 6px;
5154 height: 6px;
5155 border-radius: var(--radius-round);
5156 background: var(--action);
5157 box-shadow: 0 0 0 2px var(--surface-page);
5158 }
5159
5160 /* ===========================================
5161 RESTART WARNING BANNER
5162 =========================================== */
5163
5164 /* Page-top behavior extensions on top of .banner.banner--warning.
5165 Color / typography come from the .banner primitive. */
5166 #restart-banner {
5167 position: fixed;
5168 top: 0;
5169 left: 0;
5170 right: 0;
5171 z-index: var(--z-banner);
5172 padding: var(--gap-group) var(--gap-section);
5173 animation: banner-slide-down 0.3s ease-out;
5174 }
5175
5176 @keyframes banner-slide-down {
5177 from {
5178 transform: translateY(-100%);
5179 }
5180 to {
5181 transform: translateY(0);
5182 }
5183 }
5184
5185 /* ===========================================
5186 SAVE STATUS INDICATORS
5187 =========================================== */
5188
5189 /* Username availability status */
5190 .username-status {
5191 display: block;
5192 margin-top: var(--gap-bound);
5193 font-size: var(--text-note);
5194 }
5195
5196 .username-status.available {
5197 color: var(--success);
5198 }
5199
5200 .username-status.taken {
5201 color: var(--danger);
5202 }
5203
5204 /* ===========================================
5205 HTMX TRANSITIONS
5206 =========================================== */
5207
5208 /* Fade in for swapped content */
5209 .htmx-added {
5210 animation: htmx-fade-in 0.2s ease-out;
5211 }
5212
5213 @keyframes htmx-fade-in {
5214 from {
5215 opacity: 0;
5216 }
5217 to {
5218 opacity: 1;
5219 }
5220 }
5221
5222 /* Settling animation for swapped content */
5223 .htmx-settling {
5224 opacity: 0.8;
5225 }
5226
5227 /* Swap animation */
5228 .htmx-swapping {
5229 opacity: 0.5;
5230 transition: opacity 0.2s ease-out;
5231 }
5232
5233 /* ===========================================
5234 ACCESSIBILITY - FOCUS STYLES
5235 =========================================== */
5236
5237 /* Focus-visible for keyboard navigation */
5238 *:focus-visible {
5239 outline: 2px solid var(--focus-ring);
5240 outline-offset: 2px;
5241 }
5242
5243 /* Remove default focus outline since we use focus-visible */
5244 *:focus:not(:focus-visible) {
5245 outline: none;
5246 }
5247
5248 /* Ensure buttons and links are keyboard accessible */
5249 button:focus-visible,
5250 a:focus-visible,
5251 input:focus-visible,
5252 select:focus-visible,
5253 textarea:focus-visible {
5254 outline: 2px solid var(--focus-ring);
5255 outline-offset: 2px;
5256 }
5257
5258 /* Tab focus is layout.css's, same ring as everything else at outline-offset
5259 2px. The local rule inset it to -2px, which is only worth doing where a
5260 strip clips its overflow; `.tabs` is flex/nowrap with no overflow set, so
5261 there was nothing to clip and the tabs read differently from every other
5262 focusable thing on the page for no reason. */
5263
5264 /* Canonical "selected" recipe (charter: docs/design-system.md).
5265 Apply `.is-selected` to any interactive container to mark it as the
5266 currently-selected option. A badge never takes it: a badge reports a status
5267 and nobody selects "Enabled", so badges take a `data-tone` instead.
5268 Visibility toggles (`.tab-content.active`, `.section-panel.active`) also
5269 keep `.active` since they're not selection state. */
5270 .is-selected {
5271 background: var(--highlight-faint);
5272 border-color: var(--focus-ring);
5273 }
5274
5275 /* Skip to main content link (for screen readers) */
5276 .skip-to-main {
5277 position: absolute;
5278 top: -100px;
5279 left: 0;
5280 background: var(--primary-dark);
5281 color: var(--primary-light);
5282 padding: 8px;
5283 z-index: var(--z-skip-link);
5284 transition: top 0.2s ease;
5285 }
5286
5287 .skip-to-main:focus {
5288 top: 0;
5289 }
5290
5291 /* Visually hidden but accessible to screen readers */
5292 .sr-only {
5293 position: absolute;
5294 width: 1px;
5295 height: 1px;
5296 padding: 0;
5297 margin: -1px;
5298 overflow: hidden;
5299 clip: rect(0, 0, 0, 0);
5300 white-space: nowrap;
5301 border: 0;
5302 }
5303
5304 /* Status indicator with text for accessibility */
5305 .status-indicator::after {
5306 content: attr(data-status);
5307 position: absolute;
5308 width: 1px;
5309 height: 1px;
5310 padding: 0;
5311 margin: -1px;
5312 overflow: hidden;
5313 clip: rect(0, 0, 0, 0);
5314 white-space: nowrap;
5315 border: 0;
5316 }
5317
5318 /* ===========================================
5319 PAYWALL
5320 =========================================== */
5321
5322 .paywall-section {
5323 margin: var(--gap-page) 0;
5324 padding: var(--gap-page);
5325 background: var(--surface-sunken);
5326 text-align: center;
5327 }
5328
5329 .paywall-box h2 {
5330 font-family: var(--font-display);
5331 font-weight: normal;
5332 margin-bottom: var(--gap-peer);
5333 }
5334
5335 .paywall-price {
5336 font-family: var(--font-mono);
5337 font-size: var(--text-head);
5338 margin-bottom: var(--gap-peer);
5339 }
5340
5341 .paywall-desc {
5342 font-family: var(--font-sans);
5343 color: var(--content-muted);
5344 margin-bottom: var(--gap-pane);
5345 }
5346
5347 .paywall-note {
5348 font-family: var(--font-mono);
5349 font-size: var(--text-note);
5350 color: var(--content-muted);
5351 margin-top: var(--gap-section);
5352 }
5353
5354 .paywall-note a {
5355 color: var(--action);
5356 }
5357
5358 /* ===========================================
5359 MODALS
5360 =========================================== */
5361
5362 .modal-overlay {
5363 position: fixed;
5364 inset: 0;
5365 background: var(--overlay);
5366 display: flex;
5367 align-items: center;
5368 justify-content: center;
5369 z-index: var(--z-modal);
5370 }
5371
5372 .modal {
5373 background: var(--surface-page);
5374 padding: var(--gap-page);
5375 max-width: 500px;
5376 width: 90%;
5377 max-height: 90vh;
5378 overflow-y: auto;
5379 }
5380
5381 .modal h2 {
5382 margin-bottom: var(--gap-pane);
5383 }
5384
5385 .modal .form-actions {
5386 display: flex;
5387 gap: var(--gap-section);
5388 justify-content: flex-end;
5389 margin-top: var(--gap-pane);
5390 }
5391
5392 /* ===========================================
5393 LINK ROWS (Dashboard Links Section)
5394 =========================================== */
5395
5396 .link-row {
5397 display: flex;
5398 gap: var(--gap-section);
5399 align-items: center;
5400 margin-bottom: var(--gap-section);
5401 padding: var(--gap-section);
5402 background: var(--surface-sunken);
5403 transition: opacity 0.2s ease, background 0.2s ease;
5404 }
5405
5406 .link-row input[type="text"],
5407 .link-row input[type="url"] {
5408 flex: 1;
5409 min-width: 0;
5410 }
5411
5412 .link-row .btn-danger {
5413 padding: var(--gap-peer) var(--gap-section);
5414 font-size: var(--text-note);
5415 }
5416
5417 /* Link order buttons */
5418 .link-order-buttons {
5419 display: flex;
5420 flex-direction: column;
5421 gap: 2px;
5422 }
5423
5424 .order-btn {
5425 background: var(--surface-sunken);
5426 border: 1px solid var(--border);
5427 padding: var(--gap-bound) var(--gap-peer);
5428 font-size: var(--text-fine);
5429 line-height: 1;
5430 cursor: pointer;
5431 opacity: 0.6;
5432 transition: opacity 0.15s ease, background 0.15s ease;
5433 }
5434
5435 .order-btn:hover {
5436 opacity: 1;
5437 background: var(--border);
5438 }
5439
5440 .order-btn:active {
5441 box-shadow: var(--shadow-inset);
5442 }
5443
5444 /* ===========================================
5445 TEXT EDITOR
5446 =========================================== */
5447
5448 .text-editor {
5449 width: 100%;
5450 }
5451
5452 .editor-footer {
5453 display: flex;
5454 justify-content: space-between;
5455 padding: var(--gap-peer) var(--gap-section);
5456 background: var(--surface-sunken);
5457 font-size: var(--text-note);
5458 opacity: 0.7;
5459 }
5460
5461 /* ===========================================
5462 RICH FIELD (crate::quasi::rich_field)
5463 =========================================== */
5464
5465 /* The wrapper is this app's; the group inside it is the renderer's, and
5466 `static/layout.css` carries every rule that decides what is showing. What is
5467 left for this file is the two things a description does not say: how tall the
5468 editor stands, and how the rendered preview reads.
5469
5470 Selected through the `[data-format="markdown"]` mark rather than through
5471 makeover's class names, so nothing here overlaps the generated sheet. */
5472
5473 .rich-field [data-format="markdown"] > textarea {
5474 padding: var(--gap-section);
5475 font-family: var(--font-mono);
5476 font-size: var(--text-note);
5477 line-height: 1.6;
5478 resize: vertical;
5479 border: none;
5480 background: var(--surface-sunken);
5481 }
5482
5483 /* Three sizes for four surfaces: the item editor and the blog editor were
5484 already the same surface written two ways (400px of CSS, and rows="20"). */
5485 .rich-field--compact [data-format="markdown"] > textarea,
5486 .rich-field--compact [data-editor-preview] {
5487 min-height: 9rem;
5488 }
5489
5490 .rich-field--standard [data-format="markdown"] > textarea,
5491 .rich-field--standard [data-editor-preview] {
5492 min-height: 15rem;
5493 }
5494
5495 .rich-field--tall [data-format="markdown"] > textarea,
5496 .rich-field--tall [data-editor-preview] {
5497 min-height: 400px;
5498 }
5499
5500 /* The pane stands where the control stood, so it reads as prose rather than as
5501 source. The rules below are the retired `.markdown-preview` set, unchanged
5502 except for what selects them. */
5503 .rich-field [data-editor-preview] {
5504 padding: var(--gap-pane);
5505 font-family: var(--font-sans);
5506 line-height: 1.8;
5507 }
5508
5509 .rich-field [data-editor-preview] h1,
5510 .rich-field [data-editor-preview] h2,
5511 .rich-field [data-editor-preview] h3 {
5512 margin-top: var(--gap-pane);
5513 margin-bottom: var(--gap-peer);
5514 }
5515
5516 .rich-field [data-editor-preview] h1 {
5517 font-size: var(--text-title);
5518 }
5519
5520 .rich-field [data-editor-preview] h2 {
5521 font-size: var(--text-head);
5522 }
5523
5524 .rich-field [data-editor-preview] h3 {
5525 font-size: var(--text-lead);
5526 }
5527
5528 .rich-field [data-editor-preview] p {
5529 margin-bottom: var(--gap-section);
5530 }
5531
5532 .rich-field [data-editor-preview] code {
5533 background: var(--surface-sunken);
5534 padding: var(--gap-bound);
5535 font-family: var(--font-mono);
5536 font-size: var(--text-note);
5537 }
5538
5539 /* ===========================================
5540 AUDIO UPLOAD
5541 =========================================== */
5542
5543 .audio-upload {
5544 width: 100%;
5545 }
5546
5547 .current-audio {
5548 display: flex;
5549 justify-content: space-between;
5550 align-items: center;
5551 padding: var(--gap-section);
5552 background: var(--surface-sunken);
5553 margin-bottom: var(--gap-section);
5554 }
5555
5556 .audio-info {
5557 display: flex;
5558 align-items: center;
5559 gap: var(--gap-section);
5560 }
5561
5562 .audio-icon {
5563 font-size: var(--text-head);
5564 opacity: 0.6;
5565 }
5566
5567 .audio-details {
5568 display: flex;
5569 flex-direction: column;
5570 }
5571
5572 .audio-filename {
5573 font-family: var(--font-mono);
5574 font-size: var(--text-note);
5575 }
5576
5577 .audio-duration {
5578 font-size: var(--text-note);
5579 opacity: 0.6;
5580 }
5581
5582 .upload-area .file-upload-area {
5583 padding: var(--gap-page);
5584 }
5585
5586 .upload-icon {
5587 font-size: var(--text-title);
5588 opacity: 0.4;
5589 margin-bottom: var(--gap-peer);
5590 }
5591
5592 .upload-text {
5593 font-size: var(--text-lead);
5594 margin-bottom: var(--gap-peer);
5595 }
5596
5597 .upload-hint {
5598 font-size: var(--text-note);
5599 opacity: 0.6;
5600 }
5601
5602 .upload-progress {
5603 padding: var(--gap-pane);
5604 background: var(--surface-sunken);
5605 }
5606
5607 .progress-info {
5608 display: flex;
5609 justify-content: space-between;
5610 margin-bottom: var(--gap-section);
5611 font-size: var(--text-note);
5612 }
5613
5614 /* Canonical progress bar primitive. One of these in the codebase.
5615 - .progress-bar-container is the track (8px default; --slim = 6px)
5616 - .progress-bar is the fill (success-green default; --highlight = purple)
5617 - Add .progress-bar-container--rounded for the 3px-radius pill variant
5618 Spacing (mb-4 / mt-2 etc.) is added by the caller, not baked in. */
5619 .progress-bar-container {
5620 height: 8px;
5621 background: var(--border);
5622 overflow: hidden;
5623 }
5624 .progress-bar-container--slim { height: 6px; }
5625 .progress-bar-container--rounded { border-radius: var(--radius-control); }
5626
5627 /* Fill proportion comes from the template as --fill, because it is
5628 per-render data. The property that reads it lives here. */
5629 .progress-bar {
5630 width: var(--fill, 0%);
5631 height: 100%;
5632 background: var(--success);
5633 transition: width 0.2s ease;
5634 }
5635 .progress-bar--highlight { background: var(--action); }
5636 .progress-bar--warn { background: var(--warning); }
5637 .progress-bar--danger { background: var(--danger); }
5638
5639 /* Upload status block (filename + percent label + progress bar). One of these
5640 in the codebase. Used by wizard upload flows and standalone audio/video upload partials. */
5641 .upload-status { margin-top: var(--gap-peer); }
5642 .upload-status-row {
5643 display: flex;
5644 align-items: center;
5645 gap: var(--gap-peer);
5646 }
5647 .upload-status-row span { font-size: var(--text-note); }
5648 .upload-status-msg { margin-top: var(--gap-peer); }
5649 .upload-status-msg.is-success { color: var(--success); }
5650 .upload-status-msg.is-error { color: var(--danger); }
5651
5652 /* Inline progress cell rendered inside a table-row status cell (batch uploads). */
5653 .upload-progress-row {
5654 display: flex;
5655 align-items: center;
5656 gap: var(--gap-peer);
5657 }
5658 .upload-progress-row .progress-bar-container { flex: 1; min-width: 60px; }
5659 .upload-progress-pct {
5660 font-size: var(--text-fine);
5661 min-width: 2.5em;
5662 text-align: right;
5663 }
5664
5665 .upload-success {
5666 padding: var(--gap-pane);
5667 background: var(--surface-sunken);
5668 text-align: center;
5669 }
5670 .upload-error {
5671 padding: var(--gap-pane);
5672 background: var(--surface-sunken);
5673 text-align: center;
5674 }
5675
5676 .hidden {
5677 display: none !important;
5678 }
5679
5680 /* ===========================================
5681 VIEW TOGGLE (Grid/List)
5682 =========================================== */
5683
5684 .view-controls {
5685 display: flex;
5686 gap: var(--gap-bound);
5687 margin-left: auto;
5688 }
5689
5690 .view-btn {
5691 background: var(--surface-sunken);
5692 border: none;
5693 padding: var(--gap-peer) var(--gap-group);
5694 font-family: var(--font-mono);
5695 font-size: var(--text-note);
5696 cursor: pointer;
5697 opacity: 0.5;
5698 transition: opacity 0.2s ease;
5699 }
5700
5701 .view-btn:hover {
5702 opacity: 0.8;
5703 }
5704
5705 .view-btn.is-selected {
5706 opacity: 1;
5707 background: var(--primary-dark);
5708 color: var(--primary-light);
5709 }
5710 .results-container.results-list .results-table {
5711 display: block;
5712 }
5713
5714 .results-container.results-list .results-grid {
5715 display: none;
5716 }
5717
5718 .results-container.results-grid .results-table {
5719 display: none;
5720 }
5721
5722 .results-container.results-grid .results-grid {
5723 display: grid;
5724 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
5725 gap: var(--gap-pane);
5726 border: 1px solid var(--border);
5727 border-top: none;
5728 padding: var(--gap-pane);
5729 background: var(--surface-overlay);
5730 }
5731
5732 /* Grid cards */
5733 .grid-card {
5734 background: var(--surface-overlay);
5735 text-decoration: none;
5736 color: var(--content);
5737 display: flex;
5738 flex-direction: column;
5739 transition: background 0.2s ease;
5740 }
5741
5742 .grid-card:hover {
5743 background: var(--surface-sunken);
5744 text-decoration: none;
5745 }
5746
5747 .grid-card-thumbnail {
5748 height: 120px;
5749 background: var(--border);
5750 display: flex;
5751 align-items: center;
5752 justify-content: center;
5753 font-size: var(--text-fine);
5754 opacity: 0.5;
5755 text-transform: uppercase;
5756 }
5757
5758 .grid-card-content {
5759 padding: var(--gap-section);
5760 display: flex;
5761 flex-direction: column;
5762 gap: var(--gap-bound);
5763 }
5764
5765 .grid-card-title {
5766 font-family: var(--font-display);
5767 font-size: var(--text-lead);
5768 }
5769
5770 .grid-card-meta {
5771 font-size: var(--text-note);
5772 opacity: 0.6;
5773 }
5774
5775 .grid-card-footer {
5776 display: flex;
5777 justify-content: space-between;
5778 align-items: center;
5779 margin-top: var(--gap-peer);
5780 font-size: var(--text-note);
5781 }
5782
5783 .grid-card-category {
5784 opacity: 0.6;
5785 }
5786
5787 .grid-card-price {
5788 font-weight: bold;
5789 }
5790
5791 .grid-card-stat {
5792 opacity: 0.6;
5793 }
5794
5795 /* ===========================================
5796 PROJECT PAGE VIEW TOGGLE
5797 =========================================== */
5798
5799 .items-header {
5800 display: flex;
5801 justify-content: space-between;
5802 align-items: center;
5803 margin-bottom: var(--gap-pane);
5804 }
5805
5806 .items-header .section-header {
5807 margin-bottom: 0;
5808 border-bottom: none;
5809 padding-bottom: 0;
5810 }
5811
5812 /* Grid view is default on project page */
5813 .items-container.items-grid-view .items-grid {
5814 display: grid;
5815 }
5816
5817 .items-container.items-grid-view .items-list {
5818 display: none;
5819 }
5820
5821 .items-container.items-list-view .items-grid {
5822 display: none;
5823 }
5824
5825 .items-container.items-list-view .items-list {
5826 display: block;
5827 }
5828
5829 /* List items styling */
5830 .items-list {
5831 display: none;
5832 }
5833
5834 .list-item {
5835 display: flex;
5836 justify-content: space-between;
5837 align-items: center;
5838 padding: var(--gap-section);
5839 background: var(--surface-overlay);
5840 margin-bottom: var(--gap-peer);
5841 text-decoration: none;
5842 color: var(--content);
5843 transition: background 0.2s ease;
5844 }
5845
5846 .list-item:hover {
5847 background: var(--surface-sunken);
5848 text-decoration: none;
5849 }
5850
5851 .list-item-info {
5852 display: flex;
5853 flex-direction: column;
5854 gap: var(--gap-bound);
5855 }
5856
5857 .list-item-title {
5858 font-family: var(--font-display);
5859 font-size: var(--text-lead);
5860 }
5861
5862 .list-item-meta {
5863 font-size: var(--text-note);
5864 opacity: 0.6;
5865 }
5866
5867 .list-item-price {
5868 font-size: var(--text-lead);
5869 font-weight: bold;
5870 }
5871
5872 /* ===========================================
5873 LANDING PAGE
5874 =========================================== */
5875
5876 .landing-hero {
5877 max-width: 720px;
5878 width: 100%;
5879 text-align: center;
5880 }
5881
5882 .landing-headline {
5883 font-family: var(--font-display);
5884 font-size: var(--text-head);
5885 margin-bottom: var(--gap-peer);
5886 }
5887
5888 .landing-sub {
5889 font-size: var(--text-body);
5890 line-height: 1.6;
5891 opacity: 0.75;
5892 margin-bottom: 0;
5893 }
5894
5895 .fork-revenue-line {
5896 font-size: var(--text-note);
5897 opacity: 0.8;
5898 margin: var(--gap-section) 0 var(--gap-peer);
5899 line-height: 1.5;
5900 }
5901
5902 .fork-revenue-line a {
5903 border-bottom: 1px solid transparent;
5904 transition: border-color 0.2s ease;
5905 }
5906
5907 .fork-revenue-line a:hover {
5908 border-bottom-color: currentColor;
5909 }
5910
5911 .landing-fork {
5912 display: grid;
5913 grid-template-columns: 1fr 1fr;
5914 gap: var(--gap-section);
5915 margin-top: var(--gap-page);
5916 text-align: left;
5917 }
5918
5919 .fork-heading {
5920 font-family: var(--font-mono);
5921 font-size: var(--text-note);
5922 font-weight: 600;
5923 text-transform: uppercase;
5924 letter-spacing: 0.05em;
5925 margin-bottom: var(--gap-section);
5926 }
5927
5928 .fork-list {
5929 list-style: none;
5930 margin: 0;
5931 padding: 0;
5932 flex: 1;
5933 }
5934
5935 .fork-list li {
5936 margin-bottom: var(--gap-group);
5937 font-size: var(--text-note);
5938 line-height: 1.5;
5939 }
5940
5941 .fork-list strong {
5942 font-family: var(--font-mono);
5943 font-size: var(--text-fine);
5944 }
5945
5946 .fork-actions {
5947 margin-top: auto;
5948 padding-top: var(--gap-section);
5949 display: flex;
5950 flex-direction: column;
5951 align-items: center;
5952 gap: var(--gap-peer);
5953 }
5954
5955 .fork-actions .big-button {
5956 width: 100%;
5957 }
5958
5959 .fork-secondary-link {
5960 font-family: var(--font-mono);
5961 font-size: var(--text-fine);
5962 opacity: 0.6;
5963 transition: opacity 0.2s ease;
5964 }
5965
5966 .fork-secondary-link:hover {
5967 opacity: 1;
5968 }
5969
5970 .fork-lede {
5971 font-size: var(--text-body);
5972 line-height: 1.55;
5973 margin: 0 0 var(--gap-section);
5974 }
5975
5976 .landing-browse-cta {
5977 text-align: center;
5978 margin: var(--gap-section) 0 0;
5979 }
5980
5981 .landing-mobile-note {
5982 text-align: center;
5983 margin: var(--gap-peer) 0 0;
5984 font-size: var(--text-fine);
5985 color: var(--content-muted);
5986 }
5987
5988 .founder-tagline {
5989 font-family: var(--font-sans);
5990 font-size: var(--text-body);
5991 line-height: 1.55;
5992 margin: var(--gap-section) auto 0;
5993 max-width: 560px;
5994 color: var(--content);
5995 display: flex;
5996 flex-direction: column;
5997 align-items: center;
5998 gap: var(--gap-peer);
5999 }
6000
6001 .landing-velocity {
6002 font-family: var(--font-mono);
6003 font-size: var(--text-fine);
6004 text-align: center;
6005 margin: var(--gap-section) auto 0;
6006 color: var(--content-muted);
6007 }
6008
6009 .landing-velocity a {
6010 color: var(--content);
6011 text-decoration: none;
6012 border-bottom: 1px solid var(--border);
6013 }
6014
6015 .landing-velocity a:hover {
6016 border-bottom-color: var(--content);
6017 }
6018
6019 .founder-tagline-detail {
6020 display: block;
6021 }
6022
6023 .founder-tagline-mark {
6024 font-family: var(--font-mono);
6025 font-size: var(--text-fine);
6026 font-weight: 600;
6027 text-transform: uppercase;
6028 letter-spacing: 0.08em;
6029 color: var(--action);
6030 padding: var(--gap-bound) var(--gap-peer);
6031 border: 1px solid var(--action);
6032 border-radius: var(--radius-control);
6033 white-space: nowrap;
6034 }
6035
6036 .fork-card--founder {
6037 border-color: var(--action);
6038 box-shadow: 0 0 0 1px var(--highlight-faint);
6039 }
6040
6041 .founder-tier-grid {
6042 display: grid;
6043 grid-template-columns: 1fr 1fr;
6044 gap: var(--gap-peer) var(--gap-section);
6045 margin-bottom: var(--gap-section);
6046 padding-bottom: var(--gap-section);
6047 border-bottom: 1px dashed var(--border);
6048 }
6049
6050 .founder-tier {
6051 display: flex;
6052 flex-direction: column;
6053 gap: var(--gap-bound);
6054 }
6055
6056 .founder-tier-name {
6057 font-family: var(--font-mono);
6058 font-size: var(--text-fine);
6059 font-weight: 600;
6060 text-transform: uppercase;
6061 letter-spacing: 0.05em;
6062 opacity: 0.7;
6063 }
6064
6065 .founder-tier-price {
6066 font-family: var(--font-display);
6067 font-size: var(--text-body);
6068 line-height: 1.1;
6069 }
6070
6071 .founder-tier-price del {
6072 opacity: 0.4;
6073 margin-right: var(--gap-bound);
6074 font-weight: 400;
6075 }
6076
6077 .founder-tier-price strong {
6078 color: var(--action);
6079 font-weight: 700;
6080 }
6081
6082 .founder-tier-unit {
6083 font-family: var(--font-mono);
6084 font-size: var(--text-fine);
6085 opacity: 0.6;
6086 margin-left: var(--gap-bound);
6087 }
6088
6089 .founder-slots {
6090 font-family: var(--font-mono);
6091 font-size: var(--text-fine);
6092 margin: 0 0 var(--gap-section);
6093 line-height: 1.3;
6094 }
6095
6096 .founder-slots-number {
6097 font-family: var(--font-display);
6098 font-size: var(--text-head);
6099 color: var(--action);
6100 font-weight: 700;
6101 margin-right: var(--gap-bound);
6102 }
6103
6104 .founder-slots-cap {
6105 opacity: 0.55;
6106 margin-left: var(--gap-bound);
6107 }
6108
6109 .founder-slots--cap {
6110 opacity: 0.7;
6111 font-size: var(--text-fine);
6112 }
6113
6114 .landing-stats-line {
6115 font-family: var(--font-mono);
6116 font-size: var(--text-note);
6117 opacity: 0.6;
6118 margin-top: var(--gap-page);
6119 }
6120
6121 .landing-stats-line a {
6122 opacity: 0.8;
6123 transition: opacity 0.2s ease;
6124 }
6125
6126 .landing-stats-line a:hover {
6127 opacity: 1;
6128 }
6129
6130 .landing-principles {
6131 margin-top: var(--gap-page);
6132 width: 100%;
6133 }
6134
6135 .landing-use-cases {
6136 margin-top: var(--gap-page);
6137 width: 100%;
6138 }
6139
6140 .use-case-line {
6141 font-family: var(--font-mono);
6142 font-size: var(--text-note);
6143 opacity: 0.7;
6144 letter-spacing: 0.02em;
6145 }
6146
6147 .landing-prose {
6148 font-size: var(--text-body);
6149 line-height: 1.7;
6150 margin-bottom: var(--gap-peer);
6151 }
6152
6153 .notify-form {
6154 display: flex;
6155 gap: var(--gap-peer);
6156 max-width: 400px;
6157 margin: 0 auto;
6158 }
6159
6160 .notify-input {
6161 flex: 1;
6162 padding: var(--gap-peer) var(--gap-section);
6163 border: 1px solid var(--border);
6164 border-radius: var(--radius-control);
6165 font-family: var(--font-sans);
6166 font-size: var(--text-note);
6167 background: var(--surface-page);
6168 color: var(--content);
6169 }
6170
6171 .notify-status {
6172 font-family: var(--font-mono);
6173 font-size: var(--text-fine);
6174 text-align: center;
6175 margin-top: var(--gap-peer);
6176 min-height: 1.2em;
6177 }
6178
6179 .notify-status.success {
6180 color: var(--content);
6181 }
6182
6183 .notify-status.error {
6184 color: var(--danger);
6185 }
6186
6187 .section-label {
6188 font-family: var(--font-mono);
6189 font-size: var(--text-note);
6190 letter-spacing: 0.05em;
6191 text-transform: uppercase;
6192 opacity: 0.6;
6193 margin-bottom: var(--gap-section);
6194 }
6195
6196 .tier-section {
6197 margin: var(--gap-page) 0;
6198 width: 100%;
6199 }
6200 /* Marketing pricing variant of .card--bordered. */
6201 .tier-name {
6202 font-family: var(--font-mono);
6203 font-size: var(--text-note);
6204 font-weight: 600;
6205 margin-bottom: var(--gap-bound);
6206 }
6207
6208 .tier-price {
6209 font-family: var(--font-display);
6210 font-size: var(--text-lead);
6211 margin-bottom: var(--gap-bound);
6212 }
6213
6214 .tier-desc {
6215 font-size: var(--text-note);
6216 opacity: 0.7;
6217 }
6218
6219 .tier-card.planned {
6220 opacity: 0.5;
6221 border-style: dashed;
6222 position: relative;
6223 }
6224 .landing-cta {
6225 display: flex;
6226 align-items: center;
6227 justify-content: center;
6228 gap: var(--gap-page);
6229 width: fit-content;
6230 margin: var(--gap-pane) auto var(--gap-page);
6231 }
6232 .secondary-links {
6233 display: flex;
6234 gap: var(--gap-pane);
6235 justify-content: center;
6236 margin-top: var(--gap-section);
6237 }
6238
6239 .secondary-links a {
6240 font-size: var(--text-note);
6241 opacity: 0.6;
6242 transition: opacity 0.2s ease;
6243 }
6244
6245 .secondary-links a:hover {
6246 opacity: 1;
6247 }
6248 .section-link {
6249 display: block;
6250 margin-top: var(--gap-section);
6251 font-family: var(--font-mono);
6252 font-size: var(--text-fine);
6253 opacity: 0.55;
6254 transition: opacity 0.2s ease;
6255 }
6256
6257 .section-link:hover {
6258 opacity: 1;
6259 }
6260
6261 .feature-grid {
6262 display: grid;
6263 grid-template-columns: 1fr 1fr;
6264 gap: var(--gap-section);
6265 text-align: left;
6266 }
6267
6268 .feature-name {
6269 font-family: var(--font-mono);
6270 font-size: var(--text-note);
6271 font-weight: 600;
6272 margin-bottom: var(--gap-bound);
6273 }
6274
6275 .feature-desc {
6276 font-size: var(--text-note);
6277 opacity: 0.7;
6278 }
6279
6280 .how-list {
6281 list-style: none;
6282 margin: 0;
6283 padding: 0;
6284 text-align: left;
6285 }
6286
6287 .how-list li {
6288 margin-bottom: var(--gap-group);
6289 font-size: var(--text-body);
6290 }
6291
6292 .how-list strong {
6293 font-family: var(--font-mono);
6294 font-size: var(--text-note);
6295 }
6296
6297 .do-rail {
6298 list-style: none;
6299 margin: var(--gap-page) 0 var(--gap-pane);
6300 padding: 0;
6301 display: grid;
6302 /* A do-card stops being readable under about 200px, which is a fact about
6303 the card. Three across is what that yields at full width. */
6304 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
6305 gap: var(--gap-section);
6306 width: 100%;
6307 }
6308
6309 .do-card {
6310 background: transparent;
6311 border: 1px solid var(--border);
6312 padding: var(--gap-section);
6313 text-align: left;
6314 display: flex;
6315 flex-direction: column;
6316 }
6317
6318 .do-card-title {
6319 font-family: var(--font-mono);
6320 font-size: var(--text-note);
6321 font-weight: 600;
6322 text-transform: uppercase;
6323 letter-spacing: 0.05em;
6324 margin: 0 0 var(--gap-peer);
6325 }
6326
6327 .do-card-lede {
6328 font-size: var(--text-note);
6329 line-height: 1.5;
6330 opacity: 0.78;
6331 margin: 0 0 var(--gap-section);
6332 }
6333
6334 .do-card-links {
6335 list-style: none;
6336 margin: auto 0 0;
6337 padding: var(--gap-section) 0 0;
6338 border-top: 1px solid var(--border);
6339 display: flex;
6340 flex-wrap: wrap;
6341 gap: var(--gap-peer) var(--gap-section);
6342 }
6343
6344 .do-card-links a {
6345 font-size: var(--text-note);
6346 border-bottom: 1px solid transparent;
6347 transition: border-color 0.2s ease;
6348 }
6349
6350 .do-card-links a:hover {
6351 border-bottom-color: currentColor;
6352 }
6353
6354 .do-card--wide {
6355 margin-top: var(--gap-page);
6356 width: 100%;
6357 }
6358
6359 .contrast-rail {
6360 list-style: none;
6361 margin: var(--gap-section) 0 0;
6362 padding: 0;
6363 display: grid;
6364 /* Title plus a short link list, so it holds up narrower than a do-card.
6365 Four across at full width. */
6366 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
6367 gap: var(--gap-section);
6368 width: 100%;
6369 }
6370
6371 .contrast-card {
6372 border-top: 1px solid var(--border);
6373 padding: var(--gap-section) var(--gap-bound) 0;
6374 text-align: left;
6375 }
6376
6377 .contrast-card-title {
6378 font-family: var(--font-mono);
6379 font-size: var(--text-fine);
6380 letter-spacing: 0.04em;
6381 text-transform: uppercase;
6382 opacity: 0.5;
6383 text-decoration: line-through;
6384 text-decoration-thickness: 1px;
6385 margin: 0 0 var(--gap-peer);
6386 font-weight: 600;
6387 }
6388
6389 .contrast-card-links {
6390 list-style: none;
6391 margin: 0;
6392 padding: 0;
6393 display: flex;
6394 flex-wrap: wrap;
6395 gap: var(--gap-peer) var(--gap-section);
6396 }
6397
6398 .contrast-card-links a {
6399 font-size: var(--text-note);
6400 border-bottom: 1px solid transparent;
6401 transition: border-color 0.2s ease;
6402 }
6403
6404 .contrast-card-links a:hover {
6405 border-bottom-color: currentColor;
6406 }
6407
6408 .explore-links {
6409 display: flex;
6410 flex-wrap: wrap;
6411 gap: var(--gap-section) var(--gap-pane);
6412 justify-content: center;
6413 }
6414
6415 .explore-links a {
6416 font-family: var(--font-mono);
6417 font-size: var(--text-note);
6418 opacity: 0.7;
6419 transition: opacity 0.2s ease;
6420 }
6421
6422 .explore-links a:hover {
6423 opacity: 1;
6424 }
6425
6426 /* ===========================================
6427 PRICING CALCULATOR
6428 =========================================== */
6429
6430 .pricing-page {
6431 max-width: 800px;
6432 }
6433
6434 .pricing-input-wrap {
6435 display: flex;
6436 align-items: baseline;
6437 justify-content: center;
6438 gap: var(--gap-bound);
6439 }
6440
6441 .pricing-currency {
6442 font-family: var(--font-display);
6443 font-size: var(--text-display);
6444 opacity: 0.5;
6445 }
6446
6447 .pricing-input {
6448 font-family: var(--font-display);
6449 font-size: var(--text-display);
6450 width: 5ch;
6451 text-align: center;
6452 background: transparent;
6453 border: none;
6454 border-bottom: 2px solid var(--border);
6455 color: var(--content);
6456 padding: var(--gap-bound) 0;
6457 -moz-appearance: textfield;
6458 }
6459
6460 .pricing-input::-webkit-inner-spin-button,
6461 .pricing-input::-webkit-outer-spin-button {
6462 -webkit-appearance: none;
6463 margin: 0;
6464 }
6465
6466 .pricing-input:focus {
6467 outline: none;
6468 border-bottom-color: var(--action);
6469 }
6470
6471 .pricing-period {
6472 font-family: var(--font-mono);
6473 font-size: var(--text-body);
6474 opacity: 0.5;
6475 }
6476
6477 /* <mnw-price-mode> is a custom element (default display:inline); it wraps the
6478 toggle and the tier grid, so it has to lay out as a block. */
6479 mnw-price-mode {
6480 display: block;
6481 }
6482
6483 /* List-vs-founder toggle above the tier cards, rendered only while the founder
6484 window is open. Segmented control rather than a checkbox: both options are
6485 real prices the visitor can be shown, and neither is an "off" state. Follows
6486 .tier-option's pattern of hiding the radio and styling the label off
6487 :has(:checked), so the selection needs no JS class. */
6488 .price-mode {
6489 display: inline-flex;
6490 gap: 0;
6491 margin-bottom: var(--gap-section);
6492 border: 1px solid var(--border);
6493 border-radius: var(--radius-control);
6494 overflow: hidden;
6495 }
6496
6497 .price-mode-option {
6498 cursor: pointer;
6499 font-family: var(--font-mono);
6500 font-size: var(--text-note);
6501 padding: var(--gap-bound) var(--gap-section);
6502 transition: background-color 0.15s ease;
6503 }
6504
6505 .price-mode-option input[type="radio"] {
6506 position: absolute;
6507 opacity: 0;
6508 pointer-events: none;
6509 }
6510
6511 .price-mode-option:has(input[type="radio"]:checked) {
6512 background: var(--action);
6513 color: var(--content-on-action);
6514 }
6515
6516 .price-mode-option:focus-within {
6517 outline: 2px solid var(--focus-ring);
6518 outline-offset: -2px;
6519 }
6520
6521 .tier-selector {
6522 display: grid;
6523 grid-template-columns: 1fr 1fr;
6524 gap: var(--gap-section);
6525 text-align: left;
6526 }
6527
6528 .tier-option {
6529 cursor: pointer;
6530 transition: border-color 0.15s ease;
6531 }
6532
6533 .tier-option input[type="radio"] {
6534 position: absolute;
6535 opacity: 0;
6536 pointer-events: none;
6537 }
6538
6539 .tier-option:focus-within {
6540 outline: 2px solid var(--focus-ring);
6541 outline-offset: 2px;
6542 }
6543
6544 /* Selected tier, driven purely by the checked radio (no JS toggling a
6545 class). The `.is-selected` alias is retained for any server-rendered use. */
6546 .tier-option.is-selected,
6547 .tier-option:has(input[type="radio"]:checked) {
6548 border-color: var(--action);
6549 border-width: 2px;
6550 padding: calc(var(--gap-section) - 1px);
6551 }
6552
6553 /* Two dials side by side (price + volume, then the other platform's two).
6554 Stacks on narrow screens via the mobile block below. */
6555 .calc-dials {
6556 display: grid;
6557 grid-template-columns: 1fr 1fr;
6558 gap: var(--gap-section);
6559 align-items: end;
6560 }
6561
6562 .calc-dial {
6563 display: flex;
6564 flex-direction: column;
6565 align-items: center;
6566 gap: var(--gap-bound);
6567 }
6568
6569 .calc-dial-label {
6570 font-family: var(--font-mono);
6571 font-size: var(--text-note);
6572 opacity: 0.7;
6573 }
6574
6575 /* Who comes out ahead at the current dials. The three states are the only
6576 place the calculator uses color to make a claim, so they carry the same
6577 weight in both directions: losing is stated as plainly as winning. */
6578 .calc-verdict {
6579 font-family: var(--font-display);
6580 font-size: var(--text-title);
6581 text-align: center;
6582 padding: var(--gap-pane);
6583 background: var(--surface-overlay);
6584 border: 1px solid var(--border);
6585 border-left-width: 3px;
6586 }
6587
6588 .verdict--ahead {
6589 border-left-color: var(--success);
6590 }
6591
6592 .verdict--even {
6593 border-left-color: var(--border);
6594 }
6595
6596 .verdict--behind {
6597 border-left-color: var(--warning);
6598 }
6599
6600 .calc-table th[scope="row"] {
6601 text-align: left;
6602 font-weight: 400;
6603 opacity: 0.7;
6604 }
6605
6606 .calc-table .calc-gross,
6607 .calc-keep-row td {
6608 font-family: var(--font-mono);
6609 }
6610
6611 .calc-keep-row td {
6612 font-size: var(--text-title);
6613 }
6614
6615 /* The win-region bar. Two segments tile the full width; the server sends both
6616 widths as percentages, so nothing here is computed in the browser. */
6617 .calc-scale {
6618 display: flex;
6619 flex-direction: column;
6620 gap: var(--gap-bound);
6621 }
6622
6623 .calc-bar {
6624 position: relative;
6625 display: flex;
6626 height: 1.5rem;
6627 border: 1px solid var(--border);
6628 overflow: hidden;
6629 }
6630
6631 .calc-bar-seg {
6632 height: 100%;
6633 }
6634
6635 .calc-bar-seg--other {
6636 background: var(--warning);
6637 opacity: 0.35;
6638 }
6639
6640 .calc-bar-seg--mnw {
6641 background: var(--success);
6642 opacity: 0.35;
6643 }
6644
6645 /* "You are here" on the sales axis. */
6646 .calc-marker {
6647 position: absolute;
6648 top: 0;
6649 bottom: 0;
6650 width: 2px;
6651 margin-left: -1px;
6652 background: var(--content);
6653 }
6654
6655 .calc-crossover {
6656 position: absolute;
6657 top: 0;
6658 bottom: 0;
6659 width: 1px;
6660 background: var(--content);
6661 opacity: 0.4;
6662 }
6663
6664 .calc-axis {
6665 display: flex;
6666 justify-content: space-between;
6667 gap: var(--gap-bound);
6668 font-family: var(--font-mono);
6669 font-size: var(--text-fine);
6670 opacity: 0.6;
6671 }
6672
6673 .calc-axis-crossover {
6674 opacity: 0.9;
6675 }
6676
6677 .calc-legend {
6678 display: flex;
6679 flex-wrap: wrap;
6680 gap: var(--gap-section);
6681 font-size: var(--text-fine);
6682 opacity: 0.7;
6683 }
6684
6685 .calc-legend-item::before {
6686 content: "";
6687 display: inline-block;
6688 width: 0.75rem;
6689 height: 0.75rem;
6690 margin-right: var(--gap-bound);
6691 vertical-align: baseline;
6692 }
6693
6694 .calc-legend-item--other::before {
6695 background: var(--warning);
6696 opacity: 0.35;
6697 }
6698
6699 .calc-legend-item--mnw::before {
6700 background: var(--success);
6701 opacity: 0.35;
6702 }
6703
6704 .calc-note {
6705 font-family: var(--font-mono);
6706 font-size: var(--text-note);
6707 padding: var(--gap-section);
6708 background: var(--surface-overlay);
6709 border-left: 3px solid var(--border);
6710 line-height: 1.6;
6711 }
6712
6713 /* ===========================================
6714 DOCUMENTATION PAGES
6715 =========================================== */
6716
6717 /* .doc-container takes the shared reading column near the top of this file. */
6718
6719 .doc-breadcrumb {
6720 font-family: var(--font-mono);
6721 font-size: var(--text-note);
6722 margin-bottom: var(--gap-page);
6723 }
6724
6725 .doc-breadcrumb a { color: var(--content); text-decoration: none; }
6726 .doc-breadcrumb a:hover { text-decoration: underline; }
6727
6728 .doc-title {
6729 font-family: var(--font-display);
6730 font-size: var(--text-display);
6731 font-weight: normal;
6732 line-height: 1.2;
6733 margin-bottom: var(--gap-pane);
6734 color: var(--content);
6735 text-align: left;
6736 }
6737
6738 .doc-body {
6739 font-family: var(--font-sans);
6740 font-size: var(--text-lead);
6741 line-height: 1.9;
6742 }
6743
6744 .doc-body p { margin-bottom: var(--gap-pane); }
6745
6746 .doc-body h1 {
6747 font-family: var(--font-display);
6748 font-size: var(--text-title);
6749 font-weight: normal;
6750 margin-top: var(--gap-page);
6751 margin-bottom: var(--gap-section);
6752 color: var(--content);
6753 text-align: left;
6754 }
6755
6756 .doc-body h2 {
6757 font-family: var(--font-mono);
6758 font-size: var(--text-head);
6759 font-weight: normal;
6760 margin-top: var(--gap-page);
6761 margin-bottom: var(--gap-section);
6762 color: var(--content);
6763 }
6764
6765 .doc-body h3 {
6766 font-family: var(--font-mono);
6767 font-size: var(--text-subhead);
6768 font-weight: normal;
6769 margin-top: var(--gap-page);
6770 margin-bottom: var(--gap-section);
6771 color: var(--content);
6772 }
6773
6774 .doc-body blockquote {
6775 border-left: 3px solid var(--action);
6776 padding-left: var(--gap-pane);
6777 margin: var(--gap-page) 0;
6778 font-style: italic;
6779 color: var(--content-muted);
6780 }
6781
6782 .doc-body ul, .doc-body ol { margin-bottom: var(--gap-pane); padding-left: var(--gap-pane); }
6783 .doc-body li { margin-bottom: var(--gap-peer); }
6784
6785 .doc-body a {
6786 color: var(--action);
6787 text-decoration: underline;
6788 text-decoration-color: color-mix(in oklch, var(--action) 40%, transparent);
6789 text-underline-offset: 2px;
6790 }
6791
6792 .doc-body a:hover { text-decoration-color: var(--action); }
6793 .doc-body strong { font-weight: bold; }
6794 .doc-body em { font-style: italic; }
6795
6796 .doc-body code {
6797 font-family: var(--font-mono);
6798 background: var(--surface-sunken);
6799 padding: var(--gap-bound) var(--gap-peer);
6800 font-size: var(--text-body);
6801 }
6802
6803 .doc-body pre {
6804 background: var(--surface-sunken);
6805 padding: var(--gap-section);
6806 overflow-x: auto;
6807 margin-bottom: var(--gap-pane);
6808 }
6809
6810 .doc-body pre code { background: none; padding: 0; }
6811
6812 .doc-body hr { border: none; text-align: center; margin: var(--gap-page) 0; }
6813 .doc-body hr::before { content: "* * *"; color: var(--content-muted); letter-spacing: 1rem; }
6814
6815 .doc-body table { width: 100%; border-collapse: collapse; margin-bottom: var(--gap-pane); }
6816 .doc-body th, .doc-body td { padding: var(--gap-section); text-align: left; border-bottom: 1px solid var(--border); }
6817 .doc-body th { font-family: var(--font-mono); font-weight: 600; }
6818
6819 .doc-backlinks {
6820 margin-top: var(--gap-page);
6821 padding-top: var(--gap-pane);
6822 border-top: 1px solid var(--border);
6823 }
6824
6825 .doc-backlinks-title {
6826 font-family: var(--font-mono);
6827 font-size: var(--text-note);
6828 font-weight: 600;
6829 text-transform: uppercase;
6830 letter-spacing: 0.05em;
6831 color: var(--content-muted);
6832 margin-bottom: var(--gap-section);
6833 }
6834
6835 .doc-backlinks-list {
6836 list-style: none;
6837 margin: 0;
6838 padding: 0;
6839 }
6840
6841 .doc-backlinks-list li { margin-bottom: var(--gap-peer); }
6842 .doc-backlinks-list a { color: var(--content); text-decoration: none; }
6843 .doc-backlinks-list a:hover { text-decoration: underline; }
6844
6845 /* Docs index page */
6846 /* .docs-index takes the shared reading column near the top of this file. */
6847
6848 .docs-index-title {
6849 font-family: var(--font-display);
6850 font-size: var(--text-display);
6851 font-weight: normal;
6852 line-height: 1.2;
6853 margin-bottom: var(--gap-page);
6854 color: var(--content);
6855 }
6856
6857 .docs-section {
6858 margin-bottom: var(--gap-page);
6859 }
6860
6861 .docs-section h2 {
6862 font-family: var(--font-mono);
6863 font-size: var(--text-subhead);
6864 font-weight: normal;
6865 color: var(--content-muted);
6866 margin-bottom: var(--gap-section);
6867 }
6868
6869 .docs-section ul {
6870 list-style: none;
6871 padding: 0;
6872 margin: 0;
6873 }
6874
6875 .docs-section li {
6876 margin-bottom: var(--gap-peer);
6877 }
6878
6879 .docs-section a {
6880 font-family: var(--font-sans);
6881 font-size: var(--text-lead);
6882 color: var(--content);
6883 text-decoration: none;
6884 }
6885
6886 .docs-section a:hover {
6887 color: var(--action);
6888 text-decoration: underline;
6889 }
6890
6891 .docs-section details {
6892 margin-bottom: var(--gap-section);
6893 }
6894
6895 .docs-section summary {
6896 font-family: var(--font-mono);
6897 font-size: var(--text-body);
6898 color: var(--content-muted);
6899 cursor: pointer;
6900 padding: var(--gap-bound) 0;
6901 list-style: disclosure-closed;
6902 }
6903
6904 .docs-section details[open] > summary {
6905 list-style: disclosure-open;
6906 margin-bottom: var(--gap-bound);
6907 }
6908
6909 .docs-section summary:hover {
6910 color: var(--content);
6911 }
6912
6913 .docs-section details ul {
6914 padding-left: var(--gap-section);
6915 }
6916
6917 /* Docs search */
6918 .docs-search-container {
6919 position: relative;
6920 margin-bottom: var(--gap-page);
6921 }
6922 .docs-search-inline {
6923 display: inline-block;
6924 margin-bottom: 0;
6925 margin-left: auto;
6926 float: right;
6927 }
6928 .docs-search-input {
6929 width: 100%;
6930 max-width: 320px;
6931 padding: var(--gap-peer) var(--gap-section);
6932 font-family: var(--font-sans);
6933 font-size: var(--text-body);
6934 background: var(--surface-sunken);
6935 border: 1px solid var(--border);
6936 color: var(--content);
6937 }
6938 .docs-search-input:focus {
6939 outline: 2px solid var(--action);
6940 border-color: var(--action);
6941 }
6942 .docs-search-input::placeholder { opacity: 0.5; }
6943 .docs-search-results {
6944 position: absolute;
6945 top: 100%;
6946 left: 0;
6947 right: 0;
6948 max-width: 320px;
6949 background: var(--surface-page);
6950 border: 1px solid var(--border);
6951 border-top: none;
6952 max-height: 300px;
6953 overflow-y: auto;
6954 z-index: var(--z-dropdown);
6955 box-shadow: var(--elevation-overlay);
6956 }
6957 .docs-search-result {
6958 display: flex;
6959 justify-content: space-between;
6960 align-items: baseline;
6961 padding: var(--gap-peer) var(--gap-section);
6962 text-decoration: none;
6963 color: var(--content);
6964 font-size: var(--text-note);
6965 border-bottom: 1px solid var(--border);
6966 }
6967 .docs-search-result:last-child { border-bottom: none; }
6968 .docs-search-result:hover { background: var(--surface-sunken); }
6969 .docs-search-result-title { font-family: var(--font-sans); }
6970 .docs-search-result-section { font-family: var(--font-mono); font-size: var(--text-fine); opacity: 0.5; }.alert .alert-title {
6971 font-family: var(--font-mono);
6972 font-size: var(--text-note);
6973 font-weight: bold;
6974 text-transform: uppercase;
6975 letter-spacing: 0.04em;
6976 margin-bottom: var(--gap-bound);
6977 }
6978 .alert p:last-child { margin-bottom: 0; }
6979 .alert-note { border-left-color: var(--focus-ring); }
6980 .alert-note .alert-title { color: var(--focus-ring); }
6981 .alert-tip { border-left-color: var(--success); }
6982 .alert-tip .alert-title { color: var(--success); }
6983 .alert-important { border-left-color: var(--action); }
6984 .alert-important .alert-title { color: var(--action); }
6985 .alert-warning { border-left-color: var(--warning); }
6986 .alert-warning .alert-title { color: var(--warning); }
6987 .alert-caution { border-left-color: var(--danger); }
6988 .alert-caution .alert-title { color: var(--danger); }
6989
6990 /* ===========================================
6991 USE CASES PAGE
6992 =========================================== */
6993
6994 .use-cases-page {
6995 max-width: 900px;
6996 margin: 0 auto;
6997 }
6998
6999 .use-cases-intro {
7000 font-size: var(--text-lead);
7001 margin-bottom: var(--gap-page);
7002 line-height: 1.6;
7003 }
7004
7005 .use-case-grid {
7006 display: grid;
7007 grid-template-columns: 1fr 1fr 1fr;
7008 gap: var(--gap-section);
7009 text-align: left;
7010 }
7011
7012 .use-case-grid-2col {
7013 grid-template-columns: 1fr 1fr;
7014 }
7015
7016 .use-case-title {
7017 font-family: var(--font-display);
7018 font-size: var(--text-lead);
7019 margin-bottom: var(--gap-bound);
7020 }
7021
7022 .use-case-who {
7023 font-family: var(--font-mono);
7024 font-size: var(--text-fine);
7025 opacity: 0.6;
7026 margin-bottom: var(--gap-peer);
7027 }
7028
7029 .use-case-desc {
7030 font-size: var(--text-note);
7031 line-height: 1.5;
7032 margin-bottom: var(--gap-peer);
7033 }
7034
7035 .use-case-features {
7036 list-style: none;
7037 margin: 0 0 var(--gap-section);
7038 padding: 0;
7039 }
7040
7041 .use-case-features li {
7042 font-size: var(--text-note);
7043 padding-left: var(--gap-section);
7044 position: relative;
7045 margin-bottom: var(--gap-bound);
7046 }
7047
7048 .use-case-features li::before {
7049 content: "-";
7050 position: absolute;
7051 left: 0;
7052 opacity: 0.5;
7053 }
7054
7055 .use-case-tier {
7056 font-family: var(--font-mono);
7057 font-size: var(--text-fine);
7058 opacity: 0.5;
7059 }
7060
7061 /* ===========================================
7062 RESPONSIVE, 768px (tablet)
7063 =========================================== */
7064
7065 /* ==========================================================================
7066 Git Source Browser
7067 ========================================================================== */
7068
7069 .git-repo-header { margin-bottom: var(--gap-pane); }
7070 .git-repo-name { font-size: var(--text-head); margin: 0 0 var(--gap-bound); }
7071 .git-repo-name a { color: var(--content); text-decoration: none; }
7072 .git-repo-name a:hover { opacity: 0.6; }
7073 .git-repo-name .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
7074 .git-repo-desc { opacity: 0.6; margin: 0 0 var(--gap-section); font-size: var(--text-note); }
7075 .git-clone-url {
7076 font-family: var(--font-mono);
7077 font-size: var(--text-fine);
7078 padding: var(--gap-peer) var(--gap-group);
7079 background: var(--surface-overlay);
7080 border: 1px solid var(--border);
7081 border-radius: var(--radius-control);
7082 display: inline-block;
7083 user-select: all;
7084 margin-bottom: var(--gap-section);
7085 }
7086 .git-linked-project { margin-top: var(--gap-peer); font-size: var(--text-note); }
7087 .git-linked-project a { color: var(--content); }
7088
7089 /* Ref bar (branch/tag selector + nav links) */
7090 .git-ref-bar {
7091 display: flex;
7092 align-items: center;
7093 gap: var(--gap-section);
7094 margin-bottom: var(--gap-section);
7095 font-size: var(--text-note);
7096 }
7097 .git-ref-select {
7098 font-family: var(--font-mono);
7099 font-size: var(--text-fine);
7100 padding: var(--gap-bound) var(--gap-peer);
7101 background: var(--surface-overlay);
7102 border: 1px solid var(--border);
7103 border-radius: var(--radius-control);
7104 }
7105 .git-nav-links { display: flex; gap: var(--gap-section); }
7106 .git-nav-links a { text-decoration: none; color: var(--content); opacity: 0.6; }
7107 .git-nav-links a:hover,
7108 .git-nav-links a.is-selected { opacity: 1; }
7109
7110 /* Breadcrumb navigation */
7111 .git-breadcrumb {
7112 font-family: var(--font-mono);
7113 font-size: var(--text-note);
7114 margin-bottom: var(--gap-section);
7115 }
7116 .git-breadcrumb a { color: var(--content); text-decoration: none; }
7117 .git-breadcrumb a:hover { text-decoration: underline; }
7118 .git-breadcrumb .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
7119
7120 /* File tree table */
7121 .git-tree { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
7122 .git-tree th {
7123 text-align: left;
7124 padding: var(--gap-peer) var(--gap-group);
7125 border-bottom: 2px solid var(--border);
7126 font-family: var(--font-mono);
7127 font-size: var(--text-fine);
7128 opacity: 0.5;
7129 text-transform: uppercase;
7130 }
7131 .git-tree td { padding: var(--gap-peer) var(--gap-group); border-bottom: 1px solid var(--border); }
7132 .git-tree tr:last-child td { border-bottom: none; }
7133 .git-tree .icon { width: 1.5rem; text-align: center; opacity: 0.4; font-family: var(--font-mono); }
7134 .tree-icon-dir { font-weight: 700; color: var(--content); }
7135 .tree-icon-file { font-size: var(--text-note); }
7136 .git-tree .name a { text-decoration: none; color: var(--content); }
7137 .git-tree .name a:hover { text-decoration: underline; }
7138 .git-tree .size {
7139 text-align: right;
7140 opacity: 0.5;
7141 font-family: var(--font-mono);
7142 font-size: var(--text-fine);
7143 }
7144
7145 /* README rendering */
7146 .git-readme { margin-top: var(--gap-page); padding-top: var(--gap-pane); border-top: 1px solid var(--border); }
7147 .git-readme h2 { font-size: var(--text-body); opacity: 0.5; margin-bottom: var(--gap-section); }
7148 .git-readme-body { line-height: 1.6; }
7149 .git-readme-body h1,
7150 .git-readme-body h2,
7151 .git-readme-body h3 { margin-top: var(--gap-pane); margin-bottom: var(--gap-peer); }
7152 .git-readme-body pre {
7153 background: var(--surface-overlay);
7154 padding: var(--gap-section);
7155 border-radius: var(--radius-control);
7156 overflow-x: auto;
7157 font-family: var(--font-mono);
7158 font-size: var(--text-fine);
7159 }
7160 .git-readme-body code {
7161 font-family: var(--font-mono);
7162 font-size: var(--text-note);
7163 background: var(--surface-overlay);
7164 padding: var(--gap-bound);
7165 border-radius: var(--radius-fine);
7166 }
7167 .git-readme-body pre code { background: none; padding: 0; }
7168 .git-readme-body table { border-collapse: collapse; margin: var(--gap-section) 0; }
7169 .git-readme-body th,
7170 .git-readme-body td { border: 1px solid var(--border); padding: var(--gap-peer) var(--gap-group); }
7171
7172 /* File viewer */
7173 .git-file-header {
7174 display: flex;
7175 justify-content: space-between;
7176 align-items: center;
7177 padding: var(--gap-peer) var(--gap-group);
7178 background: var(--surface-overlay);
7179 border: 1px solid var(--border);
7180 border-bottom: none;
7181 border-radius: var(--radius-control) var(--radius-control) 0 0;
7182 font-size: var(--text-fine);
7183 }
7184 .git-file-meta {
7185 font-family: var(--font-mono);
7186 opacity: 0.6;
7187 }
7188 .git-file-actions a {
7189 font-family: var(--font-mono);
7190 text-decoration: none;
7191 color: var(--content);
7192 opacity: 0.6;
7193 font-size: var(--text-fine);
7194 }
7195 .git-file-actions a:hover { opacity: 1; }
7196 .git-file-content {
7197 border: 1px solid var(--border);
7198 border-radius: 0 0 var(--radius-control) var(--radius-control);
7199 overflow-x: auto;
7200 }
7201 .git-file-content table { border-collapse: collapse; width: 100%; }
7202 .git-file-content .line-number {
7203 width: 1px;
7204 white-space: nowrap;
7205 padding: 0 var(--gap-section);
7206 text-align: right;
7207 user-select: none;
7208 opacity: 0.3;
7209 font-family: var(--font-mono);
7210 font-size: var(--text-fine);
7211 border-right: 1px solid var(--border);
7212 vertical-align: top;
7213 }
7214 .git-file-content .line-code {
7215 padding: 0 var(--gap-section);
7216 white-space: pre;
7217 font-family: var(--font-mono);
7218 font-size: var(--text-fine);
7219 line-height: 1.5;
7220 }
7221 .git-file-content .line-code span {
7222 font-family: var(--font-mono);
7223 }
7224 .git-binary-notice {
7225 padding: var(--gap-page);
7226 text-align: center;
7227 opacity: 0.5;
7228 font-family: var(--font-mono);
7229 font-size: var(--text-note);
7230 border: 1px solid var(--border);
7231 border-radius: 0 0 var(--radius-control) var(--radius-control);
7232 }
7233
7234 /* Syntax highlighting (syntect class-based) */
7235 .git-file-content .source { color: var(--content); }
7236 .git-file-content .keyword,
7237 .git-file-content .storage { color: var(--syntax-keyword); }
7238 .git-file-content .string { color: var(--syntax-string); }
7239 .git-file-content .comment { color: var(--syntax-comment); font-style: italic; }
7240 .git-file-content .constant,
7241 .git-file-content .constant.numeric { color: var(--syntax-constant); }
7242 .git-file-content .entity.name { color: var(--syntax-entity); }
7243 .git-file-content .variable { color: var(--syntax-variable); }
7244 .git-file-content .support { color: var(--syntax-support); }
7245 .git-file-content .meta.preprocessor { color: var(--syntax-keyword); }
7246
7247 /* Commit list */
7248 .git-commit-list { list-style: none; }
7249 .git-commit { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7250 .git-commit:last-child { border-bottom: none; }
7251 .git-commit-message { font-size: var(--text-note); margin: 0 0 var(--gap-bound); }
7252 .git-commit-message .summary { font-weight: 600; }
7253 .git-commit-meta {
7254 font-family: var(--font-mono);
7255 font-size: var(--text-fine);
7256 opacity: 0.5;
7257 display: flex;
7258 gap: var(--gap-section);
7259 flex-wrap: wrap;
7260 }
7261 .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); }
7262 .git-commit-oid a { color: var(--content); text-decoration: none; }
7263 .git-commit-oid a:hover { text-decoration: underline; }
7264 .git-pagination {
7265 display: flex;
7266 justify-content: center;
7267 gap: var(--gap-section);
7268 margin-top: var(--gap-pane);
7269 padding-top: var(--gap-section);
7270 font-size: var(--text-note);
7271 }
7272 .git-pagination a { color: var(--content); text-decoration: none; }
7273 .git-pagination a:hover { text-decoration: underline; }
7274
7275 /* Release items on repo page */
7276 .git-release { margin-bottom: var(--gap-pane); padding: var(--gap-section); background: var(--surface-overlay); }
7277 .git-release-title { margin: 0 0 var(--gap-bound); font-size: var(--text-lead); }
7278 .git-release-title a { color: var(--content); text-decoration: none; }
7279 .git-release-meta { font-size: var(--text-note); opacity: 0.6; margin-bottom: var(--gap-peer); }
7280 .git-release-desc { font-size: var(--text-note); opacity: 0.8; margin-bottom: var(--gap-section); }
7281 .git-release-versions { font-size: var(--text-fine); }
7282 .git-release-version {
7283 display: flex;
7284 gap: var(--gap-section);
7285 align-items: center;
7286 padding: var(--gap-bound) 0;
7287 border-top: 1px solid var(--border);
7288 }
7289 .git-release-version .version-number { font-family: var(--font-mono); }
7290 .git-release-version .version-badge {
7291 font-size: var(--text-fine);
7292 padding: var(--gap-bound) var(--gap-peer);
7293 background: var(--content);
7294 color: var(--surface-page);
7295 border-radius: var(--radius-fine);
7296 }
7297 .git-release-version .version-meta { opacity: 0.5; }
7298
7299 /* Git commit detail + diff */
7300 .git-commit-detail { margin-bottom: var(--gap-pane); }
7301 .git-commit-full-message {
7302 font-family: var(--font-mono);
7303 font-size: var(--text-note);
7304 white-space: pre-wrap;
7305 line-height: 1.5;
7306 padding: var(--gap-section);
7307 background: var(--surface-overlay);
7308 border: 1px solid var(--border);
7309 margin-bottom: var(--gap-section);
7310 }
7311 .git-commit-detail-meta { font-size: var(--text-note); opacity: 0.7; line-height: 1.8; }
7312 .git-commit-detail-meta .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); word-break: break-all; }
7313 .git-commit-parents a { font-family: var(--font-mono); color: var(--content); text-decoration: none; }
7314 .git-commit-parents a:hover { text-decoration: underline; }
7315 /* Commit trailers, lifted out of the message body and shown as what they are */
7316 .git-commit-trailers {
7317 display: grid;
7318 grid-template-columns: max-content 1fr;
7319 gap: var(--gap-bound) var(--gap-section);
7320 font-size: var(--text-note);
7321 margin-bottom: var(--gap-section);
7322 }
7323 .git-commit-trailers dt { opacity: 0.6; }
7324 .git-commit-trailers dd { margin: 0; font-family: var(--font-mono); font-size: var(--text-fine); }
7325 /* Tags, with the annotation an annotated tag carries */
7326 .git-tag-list { list-style: none; }
7327 .git-tag { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7328 .git-tag:last-child { border-bottom: none; }
7329 .git-tag-name { font-size: var(--text-note); font-weight: 600; margin: 0 0 var(--gap-bound); }
7330 .git-tag-name a { color: var(--content); text-decoration: none; }
7331 .git-tag-name a:hover { text-decoration: underline; }
7332 .git-tag-lightweight { font-weight: 400; font-size: var(--text-fine); opacity: 0.5; }
7333 .git-tag-message {
7334 font-family: var(--font-mono);
7335 font-size: var(--text-note);
7336 white-space: pre-wrap;
7337 line-height: 1.5;
7338 padding: var(--gap-peer) var(--gap-section);
7339 border-left: 2px solid var(--border);
7340 margin-bottom: var(--gap-bound);
7341 }
7342 .git-tag-meta {
7343 font-family: var(--font-mono);
7344 font-size: var(--text-fine);
7345 opacity: 0.5;
7346 display: flex;
7347 gap: var(--gap-section);
7348 flex-wrap: wrap;
7349 }
7350 .git-tag-meta a { color: var(--content); text-decoration: none; }
7351 .git-tag-meta a:hover { text-decoration: underline; }
7352 /* Signature state. Neutral by default: only a bad signature gets a colour. */
7353 .git-signature { font-size: var(--text-fine); opacity: 0.7; }
7354 .git-signature a { color: var(--content); }
7355 .git-signature.is-verified { opacity: 1; }
7356 .git-signature.is-bad { color: var(--danger); opacity: 1; }
7357 /* refs/replace/*: object substitutions, listed but not applied while browsing */
7358 .git-replace-intro { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7359 .git-replace-intro p { margin: 0; }
7360 .git-replace-notice {
7361 font-size: var(--text-note);
7362 border-left: 2px solid var(--border);
7363 padding: var(--gap-peer) var(--gap-section);
7364 margin: 0 0 var(--gap-section);
7365 }
7366 .git-replace-table { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
7367 .git-replace-table th {
7368 text-align: left;
7369 font-weight: 400;
7370 opacity: 0.5;
7371 font-size: var(--text-fine);
7372 padding: var(--gap-bound) var(--gap-section) var(--gap-bound) 0;
7373 border-bottom: 1px solid var(--border);
7374 }
7375 .git-replace-table td { padding: var(--gap-peer) var(--gap-section) var(--gap-peer) 0; border-bottom: 1px solid var(--border); }
7376 .git-replace-table a { color: var(--content); text-decoration: none; }
7377 .git-replace-table a:hover { text-decoration: underline; }
7378 /* Git notes (refs/notes/*) shown against the object they annotate */
7379 .git-notes { margin-bottom: var(--gap-pane); }
7380 .git-note {
7381 border: 1px solid var(--border);
7382 border-left: 2px solid var(--content);
7383 margin-bottom: var(--gap-section);
7384 }
7385 .git-note-header {
7386 display: flex;
7387 align-items: baseline;
7388 justify-content: space-between;
7389 gap: var(--gap-group);
7390 padding: var(--gap-peer) var(--gap-section);
7391 border-bottom: 1px solid var(--border);
7392 font-size: var(--text-fine);
7393 }
7394 .git-note-namespace { font-family: var(--font-mono); }
7395 /* Sits beside the namespace rather than being spread across the header: the
7396 auto margin absorbs the free space `space-between` would otherwise put
7397 between the two, leaving the attribution on the right where it was. */
7398 .git-note-owned { margin-right: auto; opacity: 0.6; }
7399 .git-note-attribution { opacity: 0.6; }
7400 .git-note-namespaces {
7401 display: flex;
7402 flex-wrap: wrap;
7403 gap: var(--gap-group);
7404 padding: var(--gap-section) 0;
7405 border-bottom: 1px solid var(--border);
7406 margin-bottom: var(--gap-section);
7407 font-size: var(--text-note);
7408 }
7409 .git-note-namespaces a { color: var(--content); text-decoration: none; opacity: 0.6; }
7410 .git-note-namespaces a:hover { opacity: 1; }
7411 .git-note-namespaces a.is-selected { opacity: 1; text-decoration: underline; }
7412 .git-note-target-kind { opacity: 0.6; }
7413 .git-explore-intro { margin: 0 0 var(--gap-section); font-size: var(--text-note); opacity: 0.8; }
7414 .git-notes-fetch { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7415 .git-notes-fetch p { margin: 0 0 var(--gap-peer); }
7416 .git-note-feed-cap { font-size: var(--text-fine); opacity: 0.6; padding-top: var(--gap-section); }
7417 .git-note-search {
7418 display: flex;
7419 flex-wrap: wrap;
7420 align-items: center;
7421 gap: var(--gap-group);
7422 padding-bottom: var(--gap-section);
7423 font-size: var(--text-note);
7424 }
7425 .git-note-search input[type="search"] { flex: 1 1 16rem; }
7426 .git-note-search label { display: flex; align-items: center; gap: var(--gap-bound); opacity: 0.8; }
7427 .git-note-excerpt {
7428 margin: var(--gap-bound) 0 0;
7429 font-size: var(--text-note);
7430 opacity: 0.7;
7431 }
7432 .git-note-paging {
7433 display: flex;
7434 gap: var(--gap-group);
7435 align-items: center;
7436 padding-top: var(--gap-section);
7437 font-size: var(--text-note);
7438 }
7439 .git-note-paging span { font-size: var(--text-fine); opacity: 0.6; }
7440 .git-note-feed-link { padding-top: var(--gap-peer); font-size: var(--text-fine); }
7441 .git-note-badge {
7442 font-size: var(--text-fine);
7443 padding: var(--gap-bound) var(--gap-peer);
7444 border: 1px solid var(--border);
7445 border-radius: var(--radius-fine);
7446 color: var(--content);
7447 text-decoration: none;
7448 opacity: 0.7;
7449 }
7450 .git-note-badge:hover { opacity: 1; }
7451 .git-note-body { padding: var(--gap-section); font-size: var(--text-note); }
7452 .git-note-body > :first-child { margin-top: 0; }
7453 .git-note-body > :last-child { margin-bottom: 0; }
7454 /* Writing a note. Only rendered for a reader who can push. */
7455 .git-notes-edit {
7456 border-top: 1px solid var(--border);
7457 padding-top: var(--gap-section);
7458 margin-bottom: var(--gap-pane);
7459 }
7460 .git-notes-edit textarea { width: 100%; font-family: var(--font-mono); font-size: var(--text-note); }
7461 .git-note-form { margin-bottom: var(--gap-section); }
7462 .git-note-delete { margin-bottom: var(--gap-pane); }
7463 .git-note-merged {
7464 border-left: 2px solid var(--content);
7465 padding: var(--gap-peer) var(--gap-section);
7466 margin-bottom: var(--gap-section);
7467 font-size: var(--text-note);
7468 }
7469 .git-diff-stats {
7470 font-size: var(--text-note);
7471 padding: var(--gap-section) 0;
7472 margin-bottom: var(--gap-section);
7473 border-bottom: 1px solid var(--border);
7474 }
7475 .git-diff-stats .additions { color: var(--diff-add); }
7476 .git-diff-stats .deletions { color: var(--diff-del); }
7477 .git-diff-file { margin-bottom: var(--gap-pane); border: 1px solid var(--border); }
7478 .git-diff-file-header {
7479 display: flex;
7480 align-items: center;
7481 gap: var(--gap-peer);
7482 padding: var(--gap-peer) var(--gap-section);
7483 background: var(--surface-overlay);
7484 border-bottom: 1px solid var(--border);
7485 font-size: var(--text-note);
7486 flex-wrap: wrap;
7487 }
7488 .git-diff-file-header a { color: var(--content); text-decoration: none; }
7489 .git-diff-file-header a:hover { text-decoration: underline; }
7490 .git-diff-file-stats { margin-left: auto; font-size: var(--text-fine); }
7491 .git-diff-file-stats .additions { color: var(--diff-add); }
7492 .git-diff-file-stats .deletions { color: var(--diff-del); margin-left: var(--gap-bound); }
7493 .diff-status-added, .diff-status-deleted, .diff-status-modified, .diff-status-renamed {
7494 display: inline-block;
7495 width: 1.2rem;
7496 text-align: center;
7497 font-size: var(--text-fine);
7498 font-weight: 700;
7499 font-family: var(--font-mono);
7500 border-radius: var(--radius-fine);
7501 line-height: 1.4;
7502 }
7503 .diff-status-added { background: color-mix(in oklch, var(--success) 14%, var(--surface-page)); color: var(--success); }
7504 .diff-status-deleted { background: color-mix(in oklch, var(--danger) 14%, var(--surface-page)); color: var(--danger); }
7505 .diff-status-modified { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
7506 .diff-status-renamed { background: color-mix(in oklch, var(--info) 14%, var(--surface-page)); color: var(--info); }
7507 .git-diff-table {
7508 width: 100%;
7509 border-collapse: collapse;
7510 font-family: var(--font-mono);
7511 font-size: var(--text-fine);
7512 table-layout: fixed;
7513 }
7514 .git-diff-hunk-header td {
7515 padding: var(--gap-bound) var(--gap-peer);
7516 background: var(--highlight-faint);
7517 color: var(--content);
7518 opacity: 0.6;
7519 font-size: var(--text-fine);
7520 }
7521 .git-diff-lineno {
7522 width: 3.5rem;
7523 padding: 0 var(--gap-peer);
7524 text-align: right;
7525 color: var(--content);
7526 opacity: 0.35;
7527 user-select: none;
7528 vertical-align: top;
7529 }
7530 .git-diff-origin {
7531 width: 1rem;
7532 text-align: center;
7533 user-select: none;
7534 vertical-align: top;
7535 }
7536 .git-diff-content {
7537 padding: 0 var(--gap-peer);
7538 white-space: pre-wrap;
7539 word-break: break-all;
7540 }
7541 .git-diff-line-add { background: var(--diff-add-bg); }
7542 .git-diff-line-add .git-diff-origin { color: var(--diff-add); }
7543 .git-diff-line-del { background: var(--diff-del-bg); }
7544 .git-diff-line-del .git-diff-origin { color: var(--diff-del); }
7545 .git-diff-line-ctx { background: transparent; }
7546 .git-diff-truncated { padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.5; font-style: italic; }
7547
7548 /* Git file view line linking */
7549 .git-file-content .line-number a {
7550 color: inherit;
7551 text-decoration: none;
7552 display: block;
7553 }
7554 .git-file-content .line-number a:hover { opacity: 0.7; }
7555 .git-file-content tr:target td,
7556 .git-file-content tr.highlighted td { background: var(--highlight-faint); }
7557
7558 /* Git blame view */
7559 .git-blame-content { overflow-x: auto; border: 1px solid var(--border); }
7560 .git-blame-content table { border-collapse: collapse; width: 100%; font-size: var(--text-fine); }
7561 .git-blame-info {
7562 padding: 0 var(--gap-peer);
7563 font-family: var(--font-mono);
7564 white-space: nowrap;
7565 width: 4.5rem;
7566 }
7567 .git-blame-info a { color: var(--content); text-decoration: none; }
7568 .git-blame-info a:hover { text-decoration: underline; }
7569 .git-blame-author {
7570 padding: 0 var(--gap-bound);
7571 font-size: var(--text-fine);
7572 opacity: 0.5;
7573 white-space: nowrap;
7574 max-width: 8rem;
7575 overflow: hidden;
7576 text-overflow: ellipsis;
7577 }
7578 .git-blame-date {
7579 padding: 0 var(--gap-bound);
7580 font-size: var(--text-fine);
7581 opacity: 0.35;
7582 white-space: nowrap;
7583 width: 5.5rem;
7584 }
7585 .git-blame-content .line-number {
7586 padding: 0 var(--gap-peer);
7587 text-align: right;
7588 opacity: 0.35;
7589 user-select: none;
7590 width: 3rem;
7591 font-family: var(--font-mono);
7592 }
7593 .git-blame-content .line-code {
7594 padding: 0 var(--gap-peer);
7595 white-space: pre;
7596 font-family: var(--font-mono);
7597 }
7598 .git-blame-boundary td { border-top: 1px solid var(--border); }
7599
7600 /* Git user repos listing */
7601 .git-repos-list { list-style: none; }
7602 .git-repos-item { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7603 .git-repos-item:last-child { border-bottom: none; }
7604 .git-repos-item-header { display: flex; align-items: center; gap: var(--gap-peer); }
7605 .git-repos-item-name {
7606 font-size: var(--text-lead);
7607 font-weight: 600;
7608 color: var(--content);
7609 text-decoration: none;
7610 }
7611 .git-repos-item-name:hover { text-decoration: underline; }
7612 .git-repos-visibility {
7613 font-size: var(--text-fine);
7614 padding: var(--gap-bound) var(--gap-peer);
7615 border: 1px solid var(--border);
7616 border-radius: var(--radius-fine);
7617 opacity: 0.5;
7618 }
7619 .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); }
7620 .git-repos-owner { opacity: 0.6; }
7621 .git-repos-item-name .sep { opacity: 0.4; }
7622 .git-file-log-label {
7623 font-family: var(--font-mono);
7624 font-size: var(--text-fine);
7625 opacity: 0.5;
7626 margin-right: var(--gap-bound);
7627 }
7628
7629 /* Site-wide footer */
7630 .site-footer {
7631 margin-top: var(--gap-page);
7632 padding: var(--gap-page) 0;
7633 text-align: center;
7634 font-family: var(--font-mono);
7635 font-size: var(--text-fine);
7636 opacity: 0.5;
7637 }
7638 .site-footer-links { margin-bottom: var(--gap-peer); }
7639 .site-footer-links a { margin: 0 var(--gap-section); color: inherit; text-decoration: none; }
7640 .site-footer-links a:hover { text-decoration: underline; }
7641
7642 @media (max-width: 839px) {
7643 /* Git browser mobile */
7644 .git-ref-bar { flex-wrap: wrap; gap: var(--gap-peer); }
7645 .git-repo-name { font-size: var(--text-subhead); }
7646 .git-clone-url { font-size: var(--text-fine); word-break: break-all; }
7647 .git-tree .icon { display: none; }
7648 .git-tree td, .git-tree th { padding: var(--gap-bound) var(--gap-peer); }
7649 .git-file-header { flex-direction: column; align-items: flex-start; gap: var(--gap-bound); }
7650 .git-file-content .line-number { padding: 0 var(--gap-peer); }
7651 .git-file-content .line-code { padding: 0 var(--gap-peer); font-size: var(--text-fine); }
7652 .git-commit-meta { flex-direction: column; gap: var(--gap-bound); }
7653 .git-release-version { flex-wrap: wrap; gap: var(--gap-peer); }
7654 .git-diff-file-header { font-size: var(--text-fine); }
7655 .git-diff-table { font-size: var(--text-fine); }
7656 .git-diff-lineno { width: 2.5rem; font-size: var(--text-fine); }
7657 .git-blame-author { display: none; }
7658 .git-blame-date { display: none; }
7659
7660 .container {
7661 padding: var(--gap-section);
7662 }
7663
7664 .padded-page {
7665 padding: var(--gap-section);
7666 }
7667
7668 .form-row {
7669 grid-template-columns: 1fr;
7670 }
7671
7672 .tab {
7673 padding: var(--gap-group) var(--gap-section);
7674 font-size: var(--text-note);
7675 }
7676
7677 .tab-content {
7678 padding: var(--gap-section);
7679 }
7680
7681 .data-table th,
7682 .data-table td {
7683 padding: var(--gap-peer) var(--gap-group);
7684 font-size: var(--text-note);
7685 }
7686
7687 .modal {
7688 max-width: 95%;
7689 }
7690
7691 .stats-grid {
7692 grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
7693 }
7694
7695 .content-section {
7696 padding: var(--gap-section);
7697 }
7698
7699 .action-buttons {
7700 flex-direction: column;
7701 }
7702
7703 .action-buttons button {
7704 width: 100%;
7705 }
7706
7707 .analytics-grid {
7708 grid-template-columns: 1fr;
7709 }
7710
7711 /* Hamburger menu */
7712 .nav-toggle-label {
7713 display: block;
7714 }
7715
7716 .header-search {
7717 display: none;
7718 flex: none;
7719 margin: 0;
7720 width: 100%;
7721 }
7722
7723 .nav-toggle-checkbox:checked ~ .header-search {
7724 display: block;
7725 position: absolute;
7726 top: 100%;
7727 left: 0;
7728 right: 0;
7729 padding: var(--gap-section) var(--gap-pane);
7730 background: var(--surface-page);
7731 border-bottom: 1px solid var(--border);
7732 z-index: var(--z-header);
7733 }
7734
7735 .site-header nav {
7736 display: none;
7737 position: absolute;
7738 top: 100%;
7739 left: 0;
7740 right: 0;
7741 background: var(--surface-page);
7742 border-bottom: 1px solid var(--border);
7743 padding: var(--gap-section) var(--gap-pane);
7744 z-index: var(--z-nav);
7745 }
7746
7747 .site-header {
7748 position: relative;
7749 }
7750
7751 .nav-toggle-checkbox:checked ~ nav {
7752 display: block;
7753 }
7754
7755 .site-header .nav-links {
7756 flex-direction: column;
7757 gap: var(--gap-section);
7758 }
7759
7760 /* Animate hamburger to X when open */
7761 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(1) {
7762 transform: rotate(45deg) translate(5px, 5px);
7763 }
7764
7765 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(2) {
7766 opacity: 0;
7767 }
7768
7769 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(3) {
7770 transform: rotate(-45deg) translate(5px, -5px);
7771 }
7772 }
7773
7774 /* Doc pages, mobile */
7775 @media (max-width: 599px) {
7776 .doc-title { font-size: var(--text-title); }
7777 .doc-body { font-size: var(--text-body); }
7778 .docs-index-title { font-size: var(--text-title); }
7779 }
7780
7781 /* ===========================================
7782 RESPONSIVE, 480px (phone)
7783 =========================================== */
7784
7785 @media (max-width: 599px) {
7786 .container {
7787 padding: var(--gap-section);
7788 }
7789
7790 .padded-page {
7791 padding: var(--gap-section);
7792 }
7793
7794 h1 {
7795 font-size: var(--text-title);
7796 }
7797
7798 .centered-page h1 {
7799 font-size: var(--text-hero);
7800 }
7801
7802 .login-container,
7803 .form-container {
7804 max-width: 100%;
7805 padding: var(--gap-section);
7806 }
7807
7808 .big-button {
7809 width: 100%;
7810 }
7811
7812 .stats-grid {
7813 grid-template-columns: repeat(2, 1fr);
7814 }
7815
7816 .tab {
7817 padding: var(--gap-peer) var(--gap-section);
7818 font-size: var(--text-note);
7819 }
7820
7821 .tab-content {
7822 padding: var(--gap-section);
7823 }
7824
7825 .content-section {
7826 padding: var(--gap-section);
7827 }
7828
7829 .section-header {
7830 flex-direction: column;
7831 align-items: flex-start;
7832 gap: var(--gap-section);
7833 }
7834
7835 /* Landing page (merged from 500px) */
7836 .landing-fork,
7837 .tier-selector,
7838 .feature-grid,
7839 .use-case-grid,
7840 .use-case-grid-2col {
7841 grid-template-columns: 1fr;
7842 }
7843
7844 .explore-links {
7845 flex-direction: column;
7846 align-items: center;
7847 gap: var(--gap-section);
7848 }
7849
7850 .pricing-input {
7851 font-size: var(--text-title);
7852 }
7853
7854 .pricing-currency {
7855 font-size: var(--text-title);
7856 }
7857
7858 .calc-verdict {
7859 font-size: var(--text-body);
7860 }
7861
7862 .calc-dials {
7863 grid-template-columns: 1fr;
7864 }
7865
7866 .secondary-links {
7867 flex-direction: column;
7868 align-items: center;
7869 gap: var(--gap-section);
7870 }
7871
7872 .landing-cta {
7873 flex-direction: column;
7874 gap: var(--gap-section);
7875 }
7876
7877 .toast-container {
7878 left: 0.75rem;
7879 right: 0.75rem;
7880 bottom: 0.75rem;
7881 }
7882
7883 .toast {
7884 max-width: 100%;
7885 }
7886 }
7887
7888 /* ===========================================
7889 DISCOVER PAGE
7890 =========================================== */
7891
7892 .discover-layout { display: grid; grid-template-columns: 200px 1fr; gap: var(--gap-pane); }
7893 .discover-layout.no-sidebar { grid-template-columns: 1fr; }
7894 .discover-layout.no-sidebar .discover-sidebar { display: none; }
7895 .discover-sidebar { font-size: var(--text-note); }
7896 .filter-section { margin-bottom: var(--gap-section); }
7897 .filter-title { font-family: var(--font-display); font-weight: bold; font-size: var(--text-note); margin-bottom: var(--gap-peer); color: var(--content); }
7898 .filter-list { list-style: none; }
7899 /* The row carries selection (`.is-selected` recipe) and hover; the filter itself
7900 is a real <button> child so Enter/Space work without scripting. */
7901 .filter-item { display: flex; align-items: center; gap: var(--gap-bound); transition: opacity 0.1s ease; }
7902 .filter-item:hover { opacity: 0.6; }
7903 .filter-item.is-selected { font-weight: bold; }
7904 .filter-item .count { opacity: 0.4; font-size: var(--text-fine); }
7905 .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); }
7906 .tag-follow-btn:hover { opacity: 1; }
7907 .tag-follow-btn.is-selected { opacity: 0.85; border-color: var(--focus-ring); background: var(--highlight-faint); }
7908 .filter-checkbox { display: flex; align-items: center; gap: var(--gap-peer); padding: var(--gap-bound) 0; cursor: pointer; }
7909 .filter-checkbox input { width: auto; margin: 0; }
7910 .price-inputs { display: flex; gap: var(--gap-bound); align-items: center; margin-bottom: var(--gap-peer); }
7911 .price-inputs input { width: 60px; padding: var(--gap-bound); font-size: var(--text-fine); background: var(--surface-sunken); border: none; }
7912 .price-inputs span { opacity: 0.4; }
7913 .discover-main { min-width: 0; }
7914
7915 /* ---- Discover sidebar: tag spine ----------------------------------------
7916 Tags are the sidebar's structure, not one facet among several, so the spine
7917 reads as the primary surface and .filter-refine below it is subordinate.
7918 Everything here composes existing primitives: .filter-item carries the row
7919 and its .is-selected recipe, the described field draws its own list, and
7920 .breadcrumb carries the crumb idiom. */
7921 .tag-spine { padding-bottom: var(--gap-section); border-bottom: 1px solid var(--border); }
7922
7923 /* Selected tags. Also the active-filter summary, so they use the canonical
7924 selected tint rather than a new colour. */
7925 .tag-chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--gap-bound); margin-bottom: var(--gap-section); }
7926 .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); }
7927 .tag-chip-label { overflow-wrap: anywhere; }
7928 .tag-chip-remove { text-decoration: none; color: var(--content-secondary); font-size: var(--text-body); line-height: 1; padding: 0 var(--gap-bound); }
7929 .tag-chip-remove:hover { color: var(--content); }
7930 .tag-chip-clear { background: none; border: 1px dashed var(--border-strong); }
7931 .tag-chip-clear a { text-decoration: none; color: var(--content-secondary); font-size: var(--text-fine); }
7932 .tag-chip-clear a:hover { color: var(--content); }
7933
7934 /* Typeahead. The box, the list and everything that opens and closes it are
7935 the described field's, so all this carries is the positioning context the
7936 list is drawn against. */
7937 .tag-combobox { position: relative; margin-bottom: var(--gap-section); }
7938
7939 /* Drill-down. A rung either selects (assignable leaf) or navigates (category);
7940 rows with both put the chevron after the label. */
7941 .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); }
7942 .tag-crumbs li + li::before { content: "/"; opacity: 0.4; margin: 0 var(--gap-bound); }
7943 .tag-crumbs a { color: var(--content); text-decoration: none; }
7944 .tag-crumbs a:hover { text-decoration: underline; }
7945 .tag-drill-select { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7946 .tag-drill-select input { width: auto; margin: 0; }
7947 .tag-drill-select span:first-of-type { flex: 1; }
7948 .tag-drill-into { display: flex; align-items: center; gap: var(--gap-peer); flex: 1; text-decoration: none; color: inherit; padding: var(--gap-bound) 0; }
7949 .tag-drill-into span:first-child { flex: 1; }
7950 .tag-drill-chevron { opacity: 0.4; }
7951
7952 /* ---- Discover sidebar: refine block -------------------------------------
7953 Subordinate to the spine: same controls, quieter. */
7954 .filter-refine { margin-top: var(--gap-section); }
7955 .filter-fieldset { border: none; padding: 0; margin: 0; min-width: 0; }
7956 .filter-check { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7957 .filter-check input { width: auto; margin: 0; }
7958 .filter-check span:first-of-type { flex: 1; }
7959
7960 /* A zero-count facet stays visible but inert, so the shape of the catalog
7961 stays legible as filters narrow. No existing rule covers a disabled input
7962 inside a label, so this is the checkbox counterpart of the button rule.
7963 `.is-empty` covers the drill rungs, which are links and carry no input at
7964 all; they were rendering identically to rungs with items. */
7965 .filter-item:has(input:disabled), .filter-item.is-empty { opacity: 0.5; }
7966 .filter-item:has(input:disabled) .filter-check { cursor: not-allowed; }
7967
7968 /* Price buckets are links carrying a range, not form state. */
7969 .price-buckets { margin-top: var(--gap-peer); }
7970 .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; }
7971
7972 .mode-toggle { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
7973 .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; }
7974 .toggle-btn:hover { opacity: 0.7; }
7975 .toggle-btn.is-selected { background: var(--primary-dark); color: var(--primary-light); }
7976 .table-controls { background: var(--surface-sunken); display: flex; align-items: center; gap: var(--gap-section); padding: var(--gap-peer) var(--gap-section); }
7977 .table-meta { font-size: var(--text-fine); opacity: 0.6; white-space: nowrap; flex-shrink: 0; }
7978 .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; }
7979 .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; }
7980 .table-header.projects-header { grid-template-columns: 1fr 100px 80px 70px; }
7981 /* Column headers describe the list layout only; hidden when the grid view is active. */
7982 .table-header.is-hidden { display: none; }
7983 .col-right { text-align: right; }
7984 .results-table { border: 1px solid var(--border); border-top: none; }
7985 .table-row { /* respec-ok: a five-column grid template. The generated `.table-row` sets a display
7986 but has no vocabulary for column tracks, so the tracks and the display that carries
7987 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; }
7988 .table-row-item { grid-template-columns: 1fr 30px; padding: 0; gap: 0; }
7989 .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; }
7990 .table-row-item .row-save { display: flex; align-items: center; justify-content: center; }
7991 .table-row.project-row { grid-template-columns: 1fr 100px 80px 70px; }
7992 .table-row:last-child { border-bottom: none; }
7993 .table-row:hover { background: var(--surface-sunken); text-decoration: none; }
7994 .row-type { font-size: var(--text-fine); background: var(--surface-sunken); padding: var(--gap-bound) var(--gap-peer); text-align: center; opacity: 0.7; }
7995 .row-info { min-width: 0; display: flex; flex-direction: column; gap: var(--gap-bound); }
7996 .row-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
7997 .row-creator { font-size: var(--text-fine); opacity: 0.5; }.row-category { font-size: var(--text-fine); opacity: 0.6; }
7998 .row-price { text-align: right; }
7999 .row-items { text-align: right; opacity: 0.6; font-size: var(--text-fine); }
8000 .row-date { text-align: right; opacity: 0.5; font-size: var(--text-fine); }.pagination { display: flex; gap: var(--gap-bound); }
8001 .pagination button { background: var(--surface-page); border: none; padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); cursor: pointer; }
8002 .pagination button:disabled { opacity: 0.3; cursor: not-allowed; }
8003 .pagination button.is-selected { background: var(--primary-dark); color: var(--primary-light); }
8004
8005 /* Issue tabs (open / closed) inside the git browser */
8006 .issue-tabs { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
8007 .issue-tab {
8008 padding: var(--gap-peer) var(--gap-section);
8009 border: 1px solid var(--border);
8010 background: var(--surface-overlay);
8011 font-family: var(--font-mono);
8012 font-size: var(--text-note);
8013 color: var(--content);
8014 text-decoration: none;
8015 opacity: 0.7;
8016 }
8017 .issue-tab:hover { opacity: 1; }
8018 .issue-tab.is-selected {
8019 opacity: 1;
8020 background: var(--highlight-faint);
8021 border-color: var(--focus-ring);
8022 }
8023 .page-info { opacity: 0.6; }
8024 .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); }
8025 .padded-page .page-header h1 { font-size: var(--text-head); text-align: center; position: absolute; left: 50%; transform: translateX(-50%); }
8026 .padded-page .page-header nav { margin: 0; }
8027 .padded-page .page-header .nav-links { gap: var(--gap-pane); }
8028 .discover-filter-toggle {
8029 display: none;
8030 font-family: var(--font-mono);
8031 font-size: var(--text-note);
8032 padding: var(--gap-peer) var(--gap-section);
8033 background: var(--surface-overlay);
8034 border: 1px solid var(--border);
8035 cursor: pointer;
8036 margin-bottom: var(--gap-section);
8037 }
8038
8039 .discover-filter-toggle.active { background: var(--surface-sunken); border-color: var(--border-strong); }
8040
8041 .discover-filter-toggle .filter-count {
8042 background: var(--action);
8043 color: var(--primary-light);
8044 font-size: var(--text-fine);
8045 padding: var(--gap-bound) var(--gap-peer);
8046 border-radius: var(--radius-control);
8047 margin-left: var(--gap-bound);
8048 }
8049
8050 @media (max-width: 839px) {
8051 .discover-filter-toggle { display: inline-block; }
8052 .discover-layout { grid-template-columns: 1fr; }
8053 .discover-sidebar { display: none; }
8054 /* Open state lives on the layout, not the sidebar: the sidebar is replaced
8055 by an out-of-band swap on every filter change and would lose the class. */
8056 .discover-layout.filters-open .discover-sidebar { display: block; margin-bottom: var(--gap-section); }
8057 .table-header, .table-row { grid-template-columns: 40px 1fr 60px; }
8058 .table-row-item { grid-template-columns: 1fr 30px; }
8059 .table-row-item .table-row-link { grid-template-columns: 40px 1fr 60px; }
8060 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr 70px; }
8061 .row-date, .row-items, .row-category { display: none; }
8062 .table-header span:nth-child(3), .table-header span:nth-child(4), .table-header span:nth-child(5) { display: none; }
8063 .table-header.projects-header span:nth-child(3), .table-header.projects-header span:nth-child(4) { display: none; }
8064 }
8065
8066 @media (max-width: 599px) {
8067 .table-header, .table-row { grid-template-columns: 1fr 60px; }
8068 .table-row-item { grid-template-columns: 1fr 30px; }
8069 .table-row-item .table-row-link { grid-template-columns: 1fr 60px; }
8070 .row-type { display: none; }
8071 .table-header span:first-child { display: none; }
8072 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr; }
8073 .table-header.projects-header span:nth-child(2) { display: none; }
8074 .row-price { font-size: var(--text-fine); }
8075 .table-controls { flex-wrap: wrap; }
8076 .mode-toggle { flex-wrap: wrap; }
8077 .table-footer { flex-direction: column; gap: var(--gap-peer); align-items: flex-start; }
8078 }
8079
8080 /* ── Feature Cards & Suggestion Input (shared by wizard + settings) ── */
8081
8082 .type-grid {
8083 display: grid;
8084 grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
8085 gap: var(--gap-section);
8086 margin-bottom: var(--gap-section);
8087 }
8088
8089 /* Canonical selectable-card primitive. One recipe; per-context size modifiers.
8090 Used by wizard type pickers, pricing-model pickers, content-choice pickers,
8091 monetization-mode pickers, anywhere a radio-like choice is rendered as a card. */
8092 .card--selectable {
8093 cursor: pointer;
8094 display: block;
8095 }
8096 .card--selectable input[type="radio"],
8097 .card--selectable input[type="checkbox"] {
8098 position: absolute;
8099 opacity: 0;
8100 pointer-events: none;
8101 }
8102
8103 .card--selectable-inner {
8104 display: flex;
8105 flex-direction: column;
8106 align-items: center;
8107 justify-content: center;
8108 padding: var(--gap-section);
8109 border: 2px solid var(--border);
8110 border-radius: var(--radius-square);
8111 background: var(--surface-page);
8112 text-align: center;
8113 transition: border-color 0.15s, background 0.15s;
8114 min-height: 60px;
8115 }
8116 /* Variant: column-aligned content (text-heavy pricing cards). */
8117 .card--selectable.is-text-heavy .card--selectable-inner {
8118 align-items: stretch;
8119 text-align: left;
8120 padding: var(--gap-section);
8121 }
8122
8123 .card--selectable input:checked + .card--selectable-inner,
8124 .card--selectable.is-selected .card--selectable-inner {
8125 border-color: var(--action);
8126 background: var(--highlight-faint);
8127 }
8128
8129 .card--selectable:hover .card--selectable-inner {
8130 background: var(--surface-sunken);
8131 }
8132
8133 /* Focus. The card is a <label> wrapping a visually hidden radio, so the focus
8134 lands on the input and the ring has to be drawn on the inner surface. The
8135 <a> form of the card takes focus itself. */
8136 .card--selectable:focus-visible,
8137 .card--selectable input:focus-visible + .card--selectable-inner {
8138 outline: 2px solid var(--focus-ring);
8139 outline-offset: 2px;
8140 }
8141
8142 .card--selectable-label {
8143 font-weight: bold;
8144 font-size: var(--text-note);
8145 }
8146
8147 .card--selectable-desc {
8148 font-size: var(--text-fine);
8149 color: var(--content-muted);
8150 margin-top: var(--gap-bound);
8151 }
8152
8153 /* Disabled selectable card, visually dimmed, ignores pointer events. */
8154 .card--selectable.is-disabled {
8155 opacity: 0.7;
8156 pointer-events: none;
8157 }
8158
8159 .suggestion-input {
8160 position: relative;
8161 }
8162
8163 .suggestion-dropdown {
8164 display: none;
8165 position: absolute;
8166 top: 100%;
8167 left: 0;
8168 right: 0;
8169 background: var(--surface-page);
8170 border: 1px solid var(--border);
8171 border-top: none;
8172 border-radius: 0 0 var(--radius-square) var(--radius-square);
8173 max-height: 200px;
8174 overflow-y: auto;
8175 z-index: var(--z-dropdown);
8176 box-shadow: var(--elevation-overlay);
8177 }
8178
8179 .suggestion-dropdown.open {
8180 display: block;
8181 }
8182
8183 .suggestion-item {
8184 padding: var(--gap-peer) var(--gap-section);
8185 cursor: pointer;
8186 font-size: var(--text-note);
8187 }
8188
8189 .suggestion-item:hover {
8190 background: var(--surface-overlay);
8191 }
8192
8193 .suggestion-create {
8194 font-style: italic;
8195 opacity: 0.8;
8196 border-top: 1px solid var(--border);
8197 }
8198
8199 /* ── Tips ── */
8200
8201 .tip-section {
8202 margin-top: var(--gap-section);
8203 text-align: center;
8204 }
8205
8206 .tip-toggle {
8207 font-family: var(--font-mono);
8208 font-size: var(--text-note);
8209 padding: var(--gap-peer) var(--gap-pane);
8210 background: none;
8211 border: 1px solid var(--content);
8212 color: var(--content);
8213 cursor: pointer;
8214 text-decoration: none;
8215 display: inline-block;
8216 }
8217
8218 .tip-toggle:hover {
8219 background: var(--content);
8220 color: var(--surface-page);
8221 }
8222
8223 .tip-form {
8224 margin-top: var(--gap-section);
8225 display: flex;
8226 flex-direction: column;
8227 gap: var(--gap-section);
8228 max-width: 300px;
8229 margin-left: auto;
8230 margin-right: auto;
8231 text-align: left;
8232 }
8233
8234 .tip-form.hidden {
8235 display: none;
8236 }
8237
8238 .tip-amount-row {
8239 display: flex;
8240 align-items: center;
8241 gap: var(--gap-bound);
8242 }
8243
8244 .tip-amount-row .pricing-currency {
8245 font-size: var(--text-subhead);
8246 opacity: 0.7;
8247 }
8248
8249 .tip-amount-input {
8250 width: 80px;
8251 font-size: var(--text-subhead);
8252 padding: var(--gap-peer);
8253 border: 1px solid var(--border);
8254 background: var(--surface-overlay);
8255 font-family: var(--font-mono);
8256 }
8257
8258 .tip-message-input {
8259 width: 100%;
8260 font-size: var(--text-note);
8261 padding: var(--gap-peer);
8262 border: 1px solid var(--border);
8263 background: var(--surface-overlay);
8264 font-family: var(--font-sans);
8265 resize: vertical;
8266 }
8267
8268 .tip-submit {
8269 font-family: var(--font-mono);
8270 font-size: var(--text-note);
8271 padding: var(--gap-peer) var(--gap-section);
8272 background: var(--content);
8273 color: var(--surface-page);
8274 border: none;
8275 cursor: pointer;
8276 }
8277
8278 .tip-submit:hover {
8279 opacity: 0.85;
8280 }
8281
8282 /* ===========================================
8283 COLLECTION PICKER (shared dropdown)
8284 =========================================== */
8285
8286 .collection-picker-anchor { position: relative; }
8287 .collection-picker {
8288 position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
8289 background: var(--surface-page); border: 1px solid var(--border);
8290 box-shadow: var(--elevation-overlay); min-width: 220px;
8291 }
8292 .collection-picker-list { max-height: 200px; overflow-y: auto; padding: var(--gap-bound) 0; }
8293 .collection-picker-item {
8294 display: flex; align-items: center; gap: var(--gap-peer);
8295 padding: var(--gap-peer) var(--gap-section); cursor: pointer; font-size: var(--text-note);
8296 }
8297 .collection-picker-item:hover { background: var(--surface-sunken); }
8298 .collection-picker-loading,
8299 .collection-picker-create { border-top: 1px solid var(--border); padding: var(--gap-peer) var(--gap-section); }
8300 .collection-picker-create form { display: flex; gap: var(--gap-peer); }
8301 .collection-picker-create input { flex: 1; padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-note); min-width: 0; }
8302 .collection-picker-create button { font-size: var(--text-note); white-space: nowrap; }
8303
8304 /* Save button on discover cards */
8305 .row-save, .grid-card-save {
8306 background: none; border: none; cursor: pointer;
8307 font-size: var(--text-note); padding: var(--gap-bound) var(--gap-peer); opacity: 0.4;
8308 color: var(--content); transition: opacity 0.15s;
8309 }
8310 .row-save:hover, .grid-card-save:hover { opacity: 1; }
8311 .grid-card-save {
8312 position: absolute; top: 0.4rem; right: 0.4rem; z-index: var(--z-raised);
8313 background: var(--surface-page); border-radius: var(--radius-control); padding: var(--gap-bound) var(--gap-peer);
8314 box-shadow: var(--elevation-overlay); opacity: 0;
8315 }
8316 .grid-card:hover .grid-card-save { opacity: 0.7; }
8317 .grid-card-save:hover { opacity: 1; }
8318
8319 /* Item page save button saved state */
8320 button.saved { border-color: var(--action); color: var(--action); }
8321
8322 /* ===========================================
8323 SEARCH BOX
8324 ===========================================
8325 The box and its suggestion list are a described Field (N8), which draws its
8326 own control and its own list. What is left here is where the toolbar puts
8327 it: a flex child that takes the rest of the row, and the positioning context
8328 the list is absolutely placed against. */
8329
8330 .search-wrapper { position: relative; flex: 1; min-width: 0; }
8331
8332 /* ===========================================
8333 DOC UI EXAMPLES (embedded live UI in docs)
8334 =========================================== */
8335
8336 .doc-ui {
8337 margin: var(--gap-pane) 0; border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden;
8338 }
8339 .doc-ui-frame {
8340 padding: var(--gap-section); background: var(--surface-overlay); pointer-events: none; user-select: none;
8341 }
8342 .doc-ui figcaption {
8343 padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.6;
8344 border-top: 1px solid var(--border); background: var(--surface-overlay);
8345 font-family: var(--font-mono);
8346 }
8347 .doc-ui-missing {
8348 padding: var(--gap-section); opacity: 0.4; font-style: italic;
8349 }
8350
8351 /* Library "New" badge for items with undownloaded versions. Not a status, so
8352 it takes no tone; it is the plain badge with the accent hue and a little
8353 more weight, since "New" is the one badge meant to catch the eye. */
8354 .badge-new {
8355 color: var(--action); font-weight: bold;
8356 margin-left: var(--gap-peer); vertical-align: middle;
8357 }
8358
8359 /* ===========================================
8360 BENEFIT LIST (bulleted feature lists, e.g. Stripe Connect prompt)
8361 =========================================== */
8362 .benefit-list {
8363 list-style: none;
8364 padding: 0;
8365 margin: 0 auto var(--gap-pane);
8366 max-width: 280px;
8367 text-align: left;
8368 display: flex;
8369 flex-direction: column;
8370 gap: var(--gap-peer);
8371 }
8372 .benefit-list li {
8373 position: relative;
8374 padding-left: var(--gap-section);
8375 font-size: var(--text-note);
8376 }
8377 .benefit-list li::before {
8378 content: "";
8379 position: absolute;
8380 left: 0.15rem;
8381 top: 0.55rem;
8382 width: 6px;
8383 height: 6px;
8384 border-radius: var(--radius-round);
8385 background: var(--success);
8386 }
8387
8388 /* ===========================================
8389 CHECKLIST (setup / onboarding step lists)
8390 =========================================== */
8391 .checklist {
8392 margin-bottom: var(--gap-pane);
8393 padding: var(--gap-section) var(--gap-pane);
8394 background: var(--surface-overlay);
8395 border: 1px solid var(--border);
8396 }
8397 .checklist-header {
8398 display: flex;
8399 justify-content: space-between;
8400 align-items: center;
8401 margin-bottom: var(--gap-section);
8402 }
8403 .checklist-title {
8404 margin: 0;
8405 font-family: var(--font-display);
8406 font-weight: bold;
8407 }
8408 .checklist-mark {
8409 width: 1.1rem;
8410 height: 1.1rem;
8411 border-radius: var(--radius-round);
8412 border: 2px solid var(--border);
8413 flex-shrink: 0;
8414 box-sizing: border-box;
8415 }
8416 .checklist-mark--done {
8417 border-color: var(--success);
8418 background: var(--success);
8419 }
8420 .checklist-label {
8421 flex: 1;
8422 font-size: var(--text-note);
8423 }
8424 .checklist-label--done {
8425 opacity: 0.5;
8426 text-decoration: line-through;
8427 }
8428
8429 /* ===========================================
8430 PAYMENTS TAB (replaces inline styles in user_payments.html)
8431 =========================================== */
8432 /* Canonical "stack row", title-on-left + actions-on-right header bar.
8433 One primitive; per-context modifiers handle gap, wrap, alignment, margins.
8434 Aliases below cover existing per-tab class names (kept as JS hooks). */
8435 .stack-row,
8436 .content-header,
8437 .user-media-header,
8438 .transactions-header,
8439 .analytics-range-bar,
8440 .cart-multi-bar {
8441 display: flex;
8442 justify-content: space-between;
8443 align-items: center;
8444 flex-wrap: wrap;
8445 gap: var(--gap-section);
8446 }
8447 .stack-row--tight { gap: var(--gap-section); flex-wrap: nowrap; }
8448 .stack-row--top { align-items: flex-start; }
8449 .stack-row--bordered,
8450 .transactions-header {
8451 margin-bottom: var(--gap-section);
8452 padding-bottom: var(--gap-section);
8453 border-bottom: 1px solid var(--border);
8454 }
8455 .content-header,
8456 .user-media-header,
8457 .analytics-range-bar { margin-bottom: var(--gap-section); }
8458 .cart-multi-bar {
8459 margin-bottom: var(--gap-pane);
8460 padding: var(--gap-section);
8461 background: var(--surface-sunken);
8462 border: 1px solid var(--border);
8463 }
8464
8465 /* Canonical list row (flex, gap, padding, bottom border). Aliases below cover
8466 the per-tab variants. They exist as JS hooks but share the same look. */
8467 .list-row,
8468 .psection-row,
8469 .section-mgmt-row,
8470 .bundle-picker-row,
8471 .checklist-row {
8472 display: flex;
8473 align-items: center;
8474 gap: var(--gap-section);
8475 padding: var(--gap-group) 0;
8476 border-bottom: 1px solid var(--border);
8477 }
8478 .list-row:last-child,
8479 .psection-row:last-child,
8480 .section-mgmt-row:last-child,
8481 .bundle-picker-row:last-child,
8482 .checklist-row:last-child { border-bottom: none; }
8483 .warn-glyph {
8484 font-size: var(--text-subhead);
8485 color: var(--warning);
8486 }
8487
8488 .card-h {
8489 margin: 0 0 var(--gap-section) 0;
8490 font-size: var(--text-body);
8491 }
8492
8493 .amount-big {
8494 font-size: var(--text-subhead);
8495 font-weight: bold;
8496 }
8497
8498 .account-details {
8499 margin-top: var(--gap-section);
8500 padding-top: var(--gap-section);
8501 border-top: 1px solid var(--border);
8502 }
8503 .account-details summary { cursor: pointer; }
8504 .account-details-id {
8505 font-size: var(--text-fine);
8506 opacity: 0.5;
8507 font-family: var(--font-mono);
8508 margin-top: var(--gap-bound);
8509 }
8510
8511 .stripe-actions {
8512 display: flex;
8513 gap: var(--gap-section);
8514 flex-wrap: wrap;
8515 }
8516 .stripe-actions-disconnect { margin-left: auto; }
8517
8518 .fee-breakdown {
8519 display: grid;
8520 grid-template-columns: 1fr 1fr;
8521 gap: var(--gap-peer);
8522 font-size: var(--text-note);
8523 }
8524 .fee-row {
8525 display: flex;
8526 justify-content: space-between;
8527 padding: var(--gap-peer) 0;
8528 border-bottom: 1px solid var(--border);
8529 }
8530
8531 .checkbox-row {
8532 display: flex;
8533 align-items: flex-start;
8534 gap: var(--gap-peer);
8535 cursor: pointer;
8536 font-size: var(--text-note);
8537 }
8538 .check-inline {
8539 width: auto;
8540 margin-top: var(--gap-bound);
8541 }
8542
8543 .stripe-connect-prompt { text-align: center; padding: var(--gap-pane); }
8544 .stripe-logo { opacity: 0.8; }
8545 .stripe-connect-btn {
8546 background: var(--stripe);
8547 padding: var(--gap-section) var(--gap-page);
8548 }
8549
8550 .export-row {
8551 display: flex;
8552 justify-content: flex-end;
8553 margin-bottom: var(--gap-section);
8554 }
8555
8556 .splits-grid {
8557 display: grid;
8558 grid-template-columns: 1fr 1fr;
8559 gap: var(--gap-section);
8560 margin-bottom: var(--gap-section);
8561 }
8562
8563 .tip-message {
8564 max-width: 300px;
8565 overflow: hidden;
8566 text-overflow: ellipsis;
8567 }
8568
8569 .transactions-title { font-size: var(--text-subhead); }
8570
8571 /* ===========================================
8572 CREATOR TAB (replaces inline styles in user_creator.html)
8573 =========================================== */
8574 .creator-tab-section {
8575 padding: var(--gap-page);
8576 }
8577 .creator-tab-section--centered { text-align: center; }
8578 .creator-tab-section--no-top { padding-top: 0; }
8579
8580 .creator-h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
8581 .creator-h2--sm { font-size: var(--text-subhead); }
8582
8583 .creator-section-label {
8584 margin-left: var(--gap-page);
8585 margin-right: var(--gap-page);
8586 }
8587 .creator-form-section { padding: 0 var(--gap-page); }
8588
8589 .creator-plan-box {
8590 background: var(--surface-overlay);
8591 padding: var(--gap-section) var(--gap-pane);
8592 }
8593 .creator-plan-head {
8594 display: flex;
8595 align-items: center;
8596 gap: var(--gap-section);
8597 margin-bottom: var(--gap-peer);
8598 flex-wrap: wrap;
8599 }
8600 .creator-plan-name {
8601 font-family: var(--font-display);
8602 font-weight: bold;
8603 font-size: var(--text-lead);
8604 }
8605
8606 /* Founder pricing, two states, and neither is a status tone: locked-for-life
8607 is emphasis and pending-lock-in is an aside. They were a --content fill and
8608 a --surface-sunken fill back when every badge was filled; as coloured text
8609 the same distinction is full-strength ink against muted. */
8610 .badge--founder-locked {
8611 color: var(--content);
8612 font-weight: bold;
8613 }
8614 .badge--founder-pending {
8615 color: var(--content-muted);
8616 }
8617
8618 .founder-callout {
8619 background: var(--surface-overlay);
8620 border-left: 3px solid var(--content);
8621 padding: var(--gap-section);
8622 }
8623
8624 .creator-tier-grid {
8625 display: grid;
8626 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
8627 gap: var(--gap-section);
8628 }
8629 .creator-tier-card {
8630 background: var(--surface-overlay);
8631 padding: var(--gap-section);
8632 text-align: center;
8633 }
8634 .creator-tier-name {
8635 font-family: var(--font-display);
8636 font-weight: bold;
8637 font-size: var(--text-body);
8638 margin-bottom: var(--gap-bound);
8639 }
8640 .creator-tier-link {
8641 color: inherit;
8642 text-decoration: none;
8643 border-bottom: 1px solid var(--border);
8644 }
8645 .creator-tier-sub {
8646 font-size: var(--text-fine);
8647 opacity: 0.7;
8648 }
8649 .creator-tier-storage { font-size: var(--text-fine); }
8650 .creator-tier-buttons {
8651 display: flex;
8652 flex-direction: column;
8653 gap: var(--gap-peer);
8654 }
8655 .creator-tier-btn {
8656 font-size: var(--text-note);
8657 width: 100%;
8658 }
8659
8660 .strike-dim {
8661 opacity: 0.5;
8662 text-decoration: line-through;
8663 }
8664
8665 .storage-box {
8666 background: var(--surface-overlay);
8667 padding: var(--gap-section) var(--gap-pane);
8668 }
8669 .storage-row {
8670 display: flex;
8671 justify-content: space-between;
8672 margin-bottom: var(--gap-peer);
8673 font-size: var(--text-note);
8674 }
8675 .storage-breakdown {
8676 display: grid;
8677 grid-template-columns: 1fr 1fr;
8678 gap: var(--gap-bound) var(--gap-pane);
8679 margin-top: var(--gap-section);
8680 font-size: var(--text-note);
8681 }
8682
8683 .broadcast-head {
8684 display: flex;
8685 justify-content: space-between;
8686 align-items: flex-start;
8687 }
8688 .broadcast-desc { flex: 1; }
8689 .broadcast-export { margin-left: var(--gap-section); }
8690
8691 .creator-divider {
8692 margin: 0 var(--gap-page);
8693 border-color: var(--border);
8694 }
8695
8696 .creator-intro { line-height: 1.6; }
8697 .creator-pitch-box {
8698 background: var(--surface-overlay);
8699 padding: var(--gap-pane);
8700 }
8701
8702 .mono-sm {
8703 font-family: var(--font-mono);
8704 font-size: var(--text-note);
8705 }
8706
8707 /* ===========================================
8708 PROJECT CONTENT TAB (replaces inline styles in project_content.html)
8709 =========================================== */
8710
8711 .empty-state--lg { padding: var(--gap-page) var(--gap-section); }
8712
8713 .content-filters {
8714 display: flex;
8715 gap: var(--gap-section);
8716 margin-bottom: var(--gap-section);
8717 align-items: center;
8718 flex-wrap: wrap;
8719 }
8720 .content-filter-search {
8721 flex: 1;
8722 min-width: 180px;
8723 padding: var(--gap-peer) var(--gap-group);
8724 font-size: var(--text-note);
8725 }
8726 .content-filter-select {
8727 padding: var(--gap-peer);
8728 font-size: var(--text-note);
8729 }
8730
8731 .bulk-action-bar {
8732 padding: var(--gap-section);
8733 margin-bottom: var(--gap-section);
8734 background: var(--surface-raised);
8735 border: 1px solid var(--border);
8736 border-radius: var(--radius-square);
8737 display: flex;
8738 align-items: center;
8739 gap: var(--gap-peer);
8740 flex-wrap: wrap;
8741 opacity: 0.4;
8742 }
8743 .bulk-action-bar--active { opacity: 1; }
8744 .bulk-count { font-weight: bold; min-width: 5rem; }
8745 .bulk-deselect { margin-left: auto; }
8746 .danger-text { color: var(--danger); }
8747
8748 .bulk-form {
8749 margin-bottom: var(--gap-section);
8750 padding: var(--gap-section);
8751 background: var(--surface-sunken);
8752 border: 1px solid var(--border);
8753 border-radius: var(--radius-square);
8754 }
8755 .bulk-form-row {
8756 display: flex;
8757 gap: var(--gap-section);
8758 align-items: end;
8759 }
8760 .bulk-form-label {
8761 font-size: var(--text-note);
8762 display: block;
8763 margin-bottom: var(--gap-bound);
8764 }
8765
8766 /* Sortable table column header
8767
8768 The cursor and the caret are NOT here. `.table-heading[data-sortable]` and
8769 `.table-heading[aria-sort="..."]::after` are generated into static/layout.css
8770 by makeover-webview out of `Column::sortable` and `Column::sorted`. This file
8771 carried two hand-written copies of the same fact -- `.sortable-th` here and
8772 `.data-table th.sortable` up at the data-table block -- and a third channel in
8773 tab-project-content.js, which wrote the glyph as text and toggled
8774 `.sort-arrow--active`. Three vocabularies, one state, and the generated pair
8775 matched nothing because no heading carried `.table-heading`.
8776
8777 The focus ring stays: it is this app's focus treatment, not a restatement of
8778 the caret, and makeover emits no focus rule for a heading. */
8779 .table-heading[data-sortable] {
8780 user-select: none;
8781 transition: background 0.2s ease;
8782 }
8783
8784 .table-heading[data-sortable]:hover {
8785 background: var(--border);
8786 }
8787
8788 .table-heading[data-sortable]:focus-visible {
8789 outline: 2px solid var(--focus-ring);
8790 outline-offset: 2px;
8791 }
8792
8793 /* Data-table column-width utilities (numeric width hints,
8794 kept as classes to avoid inline `style="width: NN%"`) */
8795 .col-3 { width: 3%; }
8796 .col-5 { width: 5%; }
8797 .col-8 { width: 8%; }
8798 .col-10 { width: 10%; }
8799 .col-12 { width: 12%; }
8800 .col-15 { width: 15%; }
8801 .col-20 { width: 20%; }
8802 .col-32 { width: 32%; }
8803 .col-45 { width: 45%; }
8804
8805 /* Pixel-width utilities for inputs and narrow fixed-width controls.
8806 Use on form inputs that can't be sized by their parent column. */
8807 .w-80 { width: 80px; }
8808 .w-100 { width: 100px; }
8809 .w-120 { width: 120px; }
8810 .w-180 { width: 180px; }
8811 .w-220 { width: 220px; }
8812 .w-260 { width: 260px; }
8813 .w-full { width: 100%; }
8814 .maxw-320 { max-width: 320px; }
8815
8816 /* Canonical input shape modifiers. One base form-input look (defined globally
8817 at line ~365 input[type], textarea, select) plus these size + feature variants.
8818 Apply alongside the per-tab class which acts as a JS hook. */
8819 .input--xs {
8820 padding: var(--gap-bound) var(--gap-peer);
8821 font-size: var(--text-note);
8822 }
8823 .input--sm {
8824 padding: var(--gap-bound) var(--gap-peer);
8825 font-size: var(--text-note);
8826 }
8827 .input--mono { font-family: var(--font-mono); }
8828 .input--upper { text-transform: uppercase; }
8829 .input--numeric { text-align: right; }
8830
8831 .order-arrow-btn {
8832 padding: var(--gap-bound);
8833 font-size: var(--text-fine);
8834 line-height: 1;
8835 }
8836
8837 .bundle-toggle {
8838 background: none;
8839 border: none;
8840 cursor: pointer;
8841 padding: 0 var(--gap-bound) 0 0;
8842 font-size: var(--text-fine);
8843 color: var(--content-muted);
8844 }
8845 .bundle-child-cell { padding-left: var(--gap-page); }
8846 .bundle-child-title { opacity: 0.85; }
8847 .bundle-child-arrow {
8848 color: var(--content-muted);
8849 margin-right: var(--gap-bound);
8850 }
8851
8852 .item-actions {
8853 display: flex;
8854 gap: var(--gap-peer);
8855 }
8856
8857 .badge--inline-tiny {
8858 font-size: var(--text-fine);
8859 margin-left: var(--gap-bound);
8860 opacity: 0.7;
8861 }
8862
8863 .deleted-items-details { margin-top: var(--gap-page); }
8864 .deleted-items-summary {
8865 cursor: pointer;
8866 font-size: var(--text-body);
8867 opacity: 0.7;
8868 }
8869
8870 .section-divider {
8871 margin: var(--gap-page) 0;
8872 border: none;
8873 border-top: 1px solid var(--border);
8874 }
8875
8876 .inline-rename-input {
8877 font-weight: bold;
8878 width: 100%;
8879 padding: var(--gap-bound) var(--gap-peer);
8880 font-size: inherit;
8881 }
8882
8883 /* ===========================================
8884 ITEM DETAILS TAB (replaces inline styles in item_details.html)
8885 =========================================== */
8886
8887 /* Compact button utility (small icon-side actions) */
8888 .btn-compact { padding: var(--gap-peer) var(--gap-section); }
8889
8890 .ml-2 { margin-left: var(--gap-peer); }
8891 .cursor-pointer { cursor: pointer; }
8892 .inline { display: inline; }
8893
8894 .tag-search-wrap { position: relative; }
8895 .tag-suggestions {
8896 position: absolute;
8897 z-index: var(--z-dropdown);
8898 background: var(--surface-sunken);
8899 border: 1px solid var(--border);
8900 width: 100%;
8901 max-height: 200px;
8902 overflow-y: auto;
8903 }
8904
8905 .advanced-details { margin-top: var(--gap-pane); }
8906 .advanced-summary {
8907 cursor: pointer;
8908 font-size: var(--text-body);
8909 opacity: 0.8;
8910 }
8911
8912 /* Bundle table inside item-details */
8913 .bundle-table {
8914 width: 100%;
8915 border-collapse: collapse;
8916 }
8917 .bundle-table-head {
8918 text-align: left;
8919 font-size: var(--text-fine);
8920 opacity: 0.7;
8921 text-transform: uppercase;
8922 letter-spacing: 0.03em;
8923 }
8924 .bundle-row { border-bottom: 1px solid var(--border); }
8925 .bundle-cell { padding: var(--gap-peer); }
8926 .bundle-cell--first { padding-left: 0; }
8927 .bundle-cell--actions { width: 60px; }
8928 .bundle-cell--desc { font-size: var(--text-note); opacity: 0.8; }
8929 .bundle-cell--file { font-size: var(--text-note); }
8930
8931 .bundle-remove-btn {
8932 padding: var(--gap-bound) var(--gap-peer);
8933 font-size: var(--text-fine);
8934 }
8935
8936 .bundle-add-row {
8937 margin-top: var(--gap-section);
8938 display: flex;
8939 gap: var(--gap-section);
8940 align-items: center;
8941 }
8942
8943 .bundle-existing-details { margin-top: var(--gap-section); }
8944 .bundle-existing-summary {
8945 cursor: pointer;
8946 font-size: var(--text-note);
8947 opacity: 0.7;
8948 }
8949 .bundle-existing-row {
8950 margin-top: var(--gap-peer);
8951 display: flex;
8952 gap: var(--gap-peer);
8953 align-items: center;
8954 }
8955 .bundle-existing-select {
8956 flex: 1;
8957 padding: var(--gap-peer);
8958 }
8959
8960 .sections-intro {
8961 font-size: var(--text-note);
8962 opacity: 0.7;
8963 margin: var(--gap-section) 0 var(--gap-section);
8964 }
8965
8966 .section-mgmt-title {
8967 flex: 1;
8968 font-weight: bold;
8969 }
8970 .section-edit-btn,
8971 .section-del-btn {
8972 padding: var(--gap-bound) var(--gap-group);
8973 font-size: var(--text-fine);
8974 }
8975
8976 .section-add-details { margin-top: var(--gap-section); }
8977 .section-add-summary {
8978 cursor: pointer;
8979 font-size: var(--text-body);
8980 }
8981
8982 .section-edit-modal {
8983 margin-top: var(--gap-section);
8984 padding: var(--gap-section);
8985 background: var(--surface-sunken);
8986 }
8987 .section-edit-actions {
8988 display: flex;
8989 gap: var(--gap-peer);
8990 }
8991
8992 .publish-note {
8993 margin: var(--gap-section) 0;
8994 opacity: 0.7;
8995 text-align: left;
8996 }
8997
8998 /* ===========================================
8999 CONTENT WIZARD STEP (replaces inline styles in wizards/steps/item/content.html)
9000 =========================================== */
9001
9002 /* (Wizard upload-status + slim progress moved into canonical .upload-status*
9003 / .progress-bar-container--slim primitives near top of file.) */
9004
9005 /* Bundle items picker, server-rendered rows and JS-appended rows share these. */
9006 .bundle-picker-row-title { flex: 1; }
9007 .bundle-picker-row-type { font-size: var(--text-fine); opacity: 0.6; }
9008 .bundle-picker-row-unlisted {
9009 font-size: var(--text-fine);
9010 display: flex;
9011 align-items: center;
9012 gap: var(--gap-bound);
9013 }
9014
9015 /* Batch upload queue table. */
9016 .batch-queue-table {
9017 width: 100%;
9018 border-collapse: collapse;
9019 }
9020 .batch-queue-table thead tr {
9021 border-bottom: 2px solid var(--border);
9022 text-align: left;
9023 }
9024 .batch-queue-table th,
9025 .batch-queue-table td {
9026 padding: var(--gap-peer);
9027 font-size: var(--text-note);
9028 }
9029 .batch-queue-table th:first-child,
9030 .batch-queue-table td:first-child { padding-left: 0; }
9031 .batch-queue-table th:last-child,
9032 .batch-queue-table td:last-child { padding-right: 0; text-align: right; }
9033 .batch-queue-table tbody tr { border-bottom: 1px solid var(--border); }
9034
9035 .batch-queue-name {
9036 max-width: 150px;
9037 overflow: hidden;
9038 text-overflow: ellipsis;
9039 white-space: nowrap;
9040 }
9041
9042 /* ===========================================
9043 ACCOUNT TAB (replaces inline styles in partials/tabs/user_account.html)
9044 =========================================== */
9045
9046 /* Canonical solid-tint callout (one of these in the codebase).
9047 Distinct from .alert (left-border) and .banner (full-bleed page notice).
9048 Variants:
9049 - .callout--danger (light bg, danger border, moderation, errors)
9050 - .callout--warning (light bg, warning border, DNS, email-not-verified)
9051 - .callout--solid-warning (full saturation warning bg, dark text, partial checkout) */
9052 .callout {
9053 padding: var(--gap-section);
9054 margin-bottom: var(--gap-section);
9055 font-size: var(--text-note);
9056 border: 1px solid;
9057 }
9058 .callout--danger {
9059 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
9060 border-color: var(--danger);
9061 }
9062 .callout--warning {
9063 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
9064 border-color: var(--warning);
9065 }
9066 .callout--solid-warning {
9067 background: var(--warning);
9068 color: var(--primary-dark);
9069 border-color: transparent;
9070 padding: var(--gap-section);
9071 }
9072 .callout-title { text-transform: capitalize; }
9073 .callout-meta {
9074 opacity: 0.6;
9075 margin-left: var(--gap-peer);
9076 }
9077 .callout-body { margin: var(--gap-peer) 0 0 0; }
9078 .callout-hint {
9079 font-size: var(--text-note);
9080 margin-top: var(--gap-peer);
9081 }
9082
9083 .account-mod-history { margin-top: var(--gap-section); }
9084 .account-mod-history summary {
9085 cursor: pointer;
9086 font-size: var(--text-note);
9087 opacity: 0.7;
9088 }
9089 .account-mod-history-list { margin-top: var(--gap-peer); }
9090 .account-mod-history-item {
9091 padding: var(--gap-peer) 0;
9092 border-bottom: 1px solid var(--border);
9093 font-size: var(--text-note);
9094 }
9095 .account-mod-history-reason {
9096 margin: var(--gap-bound) 0 0 0;
9097 opacity: 0.8;
9098 }
9099
9100 .account-tip-card-title {
9101 font-weight: bold;
9102 margin-bottom: var(--gap-peer);
9103 }
9104 .account-tip-list {
9105 display: flex;
9106 flex-direction: column;
9107 gap: var(--gap-bound);
9108 opacity: 0.8;
9109 }
9110
9111 /* Resend-verification button needs left spacing from preceding strong. */
9112 .account-resend-btn { margin-left: var(--gap-peer); }
9113
9114 /* Cluster of small inline forms (manage-billing / cancel / resume). */
9115 .inline-form { display: inline; }
9116
9117 /* Notification preferences, bare fieldset + monospace legend. */
9118 .pref-fieldset {
9119 border: none;
9120 padding: 0;
9121 margin: 0 0 var(--gap-section) 0;
9122 }
9123 .pref-fieldset--last { margin-bottom: var(--gap-peer); }
9124 .pref-legend {
9125 font-family: var(--font-mono);
9126 font-size: var(--text-note);
9127 font-weight: bold;
9128 margin-bottom: var(--gap-peer);
9129 }
9130
9131 /* The cannot-opt-out list, rendered from email::OperationalKind. Reads as
9132 reference material rather than as more settings: no controls, and the
9133 justification sits under its label rather than beside it. */
9134 .operational-mail {
9135 margin: var(--gap-peer) 0 0 0;
9136 }
9137 .operational-mail dt {
9138 font-weight: bold;
9139 font-size: var(--text-note);
9140 margin-top: var(--gap-peer);
9141 }
9142 .operational-mail dd {
9143 margin: 0;
9144 opacity: 0.8;
9145 font-size: var(--text-note);
9146 }
9147
9148 /* ===========================================
9149 PROJECT CODE TAB (replaces inline styles in partials/tabs/project_code.html)
9150 =========================================== */
9151
9152 .proj-code-docs-link { margin-left: var(--gap-section); }
9153
9154 .proj-code-linked-list { margin-bottom: var(--gap-pane); }
9155 .proj-code-linked-list > label { margin-bottom: var(--gap-peer); display: block; }
9156
9157 .proj-code-repo {
9158 margin-bottom: var(--gap-section);
9159 padding: var(--gap-section);
9160 background: var(--surface-overlay);
9161 }
9162 .proj-code-repo-row {
9163 display: flex;
9164 align-items: center;
9165 gap: var(--gap-section);
9166 }
9167 .proj-code-repo-link { flex: 1; }
9168 .proj-code-repo .danger { padding: var(--gap-bound) var(--gap-section); font-size: var(--text-note); }
9169
9170 .proj-code-collab-block {
9171 margin-top: var(--gap-peer);
9172 padding-top: var(--gap-peer);
9173 border-top: 1px solid var(--border);
9174 }
9175 .proj-code-collab-label { font-size: var(--text-note); margin-bottom: var(--gap-bound); display: block; }
9176 .proj-code-collab-row {
9177 display: flex;
9178 align-items: center;
9179 gap: var(--gap-peer);
9180 margin-bottom: var(--gap-bound);
9181 font-size: var(--text-note);
9182 }
9183 .proj-code-collab-name { flex: 1; }
9184 .proj-code-collab-readonly { opacity: 0.5; }
9185
9186 .proj-code-add-collab-row {
9187 display: flex;
9188 gap: var(--gap-peer);
9189 align-items: center;
9190 }
9191 .proj-code-add-collab-row .collab-username-input {
9192 flex: 1;
9193 font-size: var(--text-note);
9194 padding: var(--gap-bound) var(--gap-peer);
9195 }
9196 .proj-code-add-collab-row .collab-add-btn { white-space: nowrap; }
9197
9198 .proj-code-link-row {
9199 display: flex;
9200 gap: var(--gap-peer);
9201 align-items: center;
9202 }
9203 .proj-code-link-row #link-repo-select { flex: 1; }
9204 .proj-code-link-row #link-repo-btn { white-space: nowrap; }
9205
9206 .proj-code-create-section {
9207 margin-top: var(--gap-pane);
9208 padding-top: var(--gap-section);
9209 border-top: 1px solid var(--border);
9210 }
9211 .proj-code-create-section #create-repo-name { flex: 1; }
9212 .proj-code-create-section #create-repo-btn { white-space: nowrap; }
9213 .proj-code-create-status { margin-top: var(--gap-bound); }
9214 .proj-code-create-status.is-error { color: var(--danger); }
9215
9216 .proj-code-disabled {
9217 padding: var(--gap-page);
9218 text-align: center;
9219 opacity: 0.7;
9220 }
9221
9222 /* ===========================================
9223 ADMIN REPORT ENTRIES (replaces inline styles in partials/admin_report_entries.html)
9224 =========================================== */
9225
9226 .admin-report-table {
9227 width: 100%;
9228 border-collapse: collapse;
9229 font-size: var(--text-note);
9230 }
9231 .admin-report-table thead tr {
9232 border-bottom: 2px solid var(--border);
9233 text-align: left;
9234 }
9235 .admin-report-table tbody tr {
9236 border-bottom: 1px solid var(--border);
9237 }
9238 .admin-report-table th,
9239 .admin-report-table td {
9240 padding: var(--gap-peer);
9241 }
9242 .admin-report-table td.admin-report-reason {
9243 max-width: 200px;
9244 overflow: hidden;
9245 text-overflow: ellipsis;
9246 white-space: nowrap;
9247 }
9248 .admin-report-table td.admin-report-date {
9249 white-space: nowrap;
9250 }
9251
9252 .admin-report-target-type {
9253 opacity: 0.5;
9254 font-size: var(--text-fine);
9255 }
9256
9257 .admin-report-status-open {
9258 color: var(--action);
9259 font-weight: 600;
9260 }
9261 .admin-report-status-closed { opacity: 0.6; }
9262
9263 .admin-report-resolve-form {
9264 display: inline-flex;
9265 gap: var(--gap-bound);
9266 align-items: center;
9267 }
9268 .admin-report-resolve-form input[type="text"] {
9269 width: 120px;
9270 padding: var(--gap-bound) var(--gap-peer);
9271 font-size: var(--text-fine);
9272 }
9273 .admin-report-dismiss-form { display: inline; }
9274
9275 .admin-report-admin-notes {
9276 font-size: var(--text-fine);
9277 opacity: 0.6;
9278 }
9279
9280 /* ===========================================
9281 PROJECT PROMOTIONS TAB (replaces inline styles in partials/tabs/project_promotions.html)
9282 =========================================== */
9283
9284 .proj-promo-form {
9285 margin-bottom: var(--gap-page);
9286 }
9287
9288 .proj-promo-grid {
9289 display: grid;
9290 grid-template-columns: 1fr 1fr;
9291 gap: var(--gap-section) var(--gap-pane);
9292 max-width: 500px;
9293 }
9294
9295 .proj-promo-grid--bordered {
9296 margin-top: var(--gap-section);
9297 padding-top: var(--gap-section);
9298 border-top: 1px solid var(--border);
9299 }
9300
9301 .proj-promo-trial-fields {
9302 display: none;
9303 max-width: 500px;
9304 margin-top: var(--gap-section);
9305 }
9306
9307 .proj-promo-trial-fields.is-visible {
9308 display: block;
9309 }
9310
9311 .proj-promo-grid.is-hidden,
9312 .proj-promo-trial-fields.is-hidden {
9313 display: none;
9314 }
9315
9316 .proj-promo-form .form-group {
9317 margin-bottom: 0;
9318 }
9319
9320 .proj-promo-form .form-group label {
9321 font-size: var(--text-note);
9322 }
9323
9324 .proj-promo-trial-group {
9325 max-width: 240px;
9326 }
9327
9328 .proj-promo-scope {
9329 grid-column: 1 / -1;
9330 }
9331
9332 .proj-promo-actions {
9333 margin-top: var(--gap-section);
9334 }
9335
9336 .proj-promo-hint {
9337 margin-top: var(--gap-peer);
9338 }
9339
9340 /* ===========================================
9341 PROJECT SYNCKIT TAB (replaces inline styles in partials/tabs/project_synckit.html)
9342 =========================================== */
9343
9344 .proj-synckit-intro { margin-bottom: var(--gap-section); }
9345 .proj-synckit-intro-secondary { margin-bottom: var(--gap-pane); }
9346 .proj-synckit-create { margin-bottom: var(--gap-pane); }
9347 .proj-synckit-create-body {
9348 padding: var(--gap-section); background: var(--surface-overlay); border: 1px solid var(--border);
9349 }
9350 .proj-synckit-create-form {
9351 display: flex; flex-direction: row; gap: var(--gap-section); align-items: flex-end;
9352 }
9353 .proj-synckit-label {
9354 display: block; font-size: var(--text-note); margin-bottom: var(--gap-bound);
9355 }
9356 .proj-synckit-code { font-size: var(--text-fine); }
9357 .proj-synckit-code--wrap { font-size: var(--text-fine); word-break: break-all; }
9358 .proj-synckit-code--selectable { font-size: var(--text-note); word-break: break-all; user-select: all; }
9359 .proj-synckit-btn-tight {
9360 margin-left: var(--gap-peer); padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9361 }
9362 .proj-synckit-btn-row {
9363 padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9364 }
9365 .proj-synckit-btn-row + .proj-synckit-btn-row { margin-left: var(--gap-bound); }
9366 .proj-synckit-hint-inline {
9367 font-size: var(--text-fine); margin-left: var(--gap-bound);
9368 }
9369 .proj-synckit-hint-warn {
9370 font-size: var(--text-fine); color: var(--danger);
9371 }
9372 .proj-synckit-status-active { color: var(--success); }
9373 .proj-synckit-status-inactive { color: var(--danger); }
9374 .proj-synckit-new-key {
9375 padding: var(--gap-section); background: var(--surface-overlay);
9376 border: 1px solid var(--border); margin-top: var(--gap-peer);
9377 }
9378 .proj-synckit-new-key-heading {
9379 margin: 0 0 var(--gap-peer) 0; font-weight: 500; color: var(--danger);
9380 }
9381 .proj-synckit-create-status--error { color: var(--danger); margin-top: var(--gap-peer); }
9382 .proj-synckit-create-status--reset { margin-top: var(--gap-peer); }
9383
9384 /* ===========================================
9385 ITEM PAGE (replaces inline styles in pages/item.html)
9386 =========================================== */
9387
9388 .item-page .breadcrumb {
9389 display: flex;
9390 justify-content: space-between;
9391 align-items: center;
9392 }
9393
9394 .item-page .breadcrumb-edit-link {
9395 font-size: var(--text-note);
9396 color: var(--content);
9397 }
9398
9399 .item-page .video-player {
9400 width: 100%;
9401 max-height: 600px;
9402 background: var(--primary-dark);
9403 }
9404
9405 .item-page .item-cover-img {
9406 width: 100%;
9407 aspect-ratio: 1 / 1;
9408 object-fit: cover;
9409 display: block;
9410 }
9411
9412
9413 .item-page .bundle-notice-inline {
9414 background: var(--surface-sunken);
9415 padding: var(--gap-section);
9416 }
9417
9418 .item-page .bundle-notice-title {
9419 font-weight: bold;
9420 margin-bottom: var(--gap-peer);
9421 }
9422
9423 .item-page .bundle-notice-desc {
9424 opacity: 0.8;
9425 font-size: var(--text-note);
9426 }
9427
9428 .item-page .bundle-notice-entry {
9429 margin-top: var(--gap-section);
9430 }
9431
9432 .item-page .bundle-notice-entry a {
9433 font-weight: bold;
9434 }
9435
9436 .item-page .bundle-notice-price {
9437 opacity: 0.7;
9438 }
9439
9440 .item-page .bundle-notice-empty {
9441 margin-top: var(--gap-peer);
9442 opacity: 0.7;
9443 font-size: var(--text-note);
9444 }
9445
9446 .item-page .promo-details {
9447 margin-bottom: var(--gap-section);
9448 font-size: var(--text-note);
9449 }
9450
9451 .item-page .promo-summary {
9452 cursor: pointer;
9453 opacity: 0.7;
9454 }
9455
9456 .item-page .promo-form {
9457 display: flex;
9458 gap: var(--gap-peer);
9459 align-items: center;
9460 margin-top: var(--gap-peer);
9461 }
9462
9463 .item-page .promo-input {
9464 flex: 1;
9465 padding: var(--gap-peer) var(--gap-group);
9466 font-size: var(--text-note);
9467 }
9468
9469 .item-page .action-row {
9470 margin-top: var(--gap-section);
9471 }
9472
9473 .item-page .action-row-tight {
9474 margin-top: var(--gap-peer);
9475 }
9476
9477 .item-page .full-width-btn {
9478 width: 100%;
9479 font-size: var(--text-note);
9480 }
9481
9482 .item-page .license-preset-name {
9483 margin-bottom: var(--gap-section);
9484 }
9485
9486 .item-page .license-summary {
9487 cursor: pointer;
9488 opacity: 0.7;
9489 font-size: var(--text-note);
9490 }
9491
9492 .item-page .license-text {
9493 white-space: pre-wrap;
9494 font-family: var(--font-sans);
9495 font-size: var(--text-note);
9496 margin-top: var(--gap-section);
9497 padding: var(--gap-section);
9498 background: var(--surface-sunken);
9499 }
9500
9501 .item-page .license-download {
9502 margin-top: var(--gap-section);
9503 }
9504
9505 .item-page .license-download a {
9506 font-size: var(--text-note);
9507 color: var(--content);
9508 }
9509
9510 .item-page .bundle-list-entry {
9511 margin-top: var(--gap-peer);
9512 }
9513
9514 .item-page .footer-links {
9515 margin-top: var(--gap-peer);
9516 }
9517
9518 /* ===========================================
9519 JOIN COMPLETE STEP (replaces inline styles in wizards/steps/join/complete.html)
9520 =========================================== */
9521 .join-complete-intro { margin-bottom: var(--gap-pane); }
9522 .join-complete-choices {
9523 display: grid; grid-template-columns: 1fr 1fr;
9524 gap: var(--gap-section); max-width: 500px;
9525 }
9526 .join-complete-choice { text-decoration: none; color: inherit; display: block; }
9527 .join-complete-card {
9528 background: var(--surface-sunken); padding: var(--gap-section); text-align: center;
9529 border: 2px solid transparent; transition: border-color 0.15s;
9530 }
9531 .join-complete-choice:hover .join-complete-card,
9532 .join-complete-card--active { border-color: var(--content); }
9533 .join-complete-card-title {
9534 font-family: var(--font-display); font-weight: bold;
9535 font-size: var(--text-lead); margin-bottom: var(--gap-peer);
9536 }
9537 .join-complete-card-desc {
9538 font-size: var(--text-note); opacity: 0.7; margin: 0;
9539 }
9540 .join-complete-footnote {
9541 font-size: var(--text-note); opacity: 0.5;
9542 margin-top: var(--gap-section); text-align: center;
9543 }
9544
9545 /* ===========================================
9546 PROMO CODES LIST (replaces inline styles in partials/promo_codes_list.html)
9547 =========================================== */
9548 .promo-codes-row--expired { opacity: 0.5; }
9549 .promo-codes-row--pending { opacity: 0.7; }
9550 .promo-codes-schedule { font-size: var(--text-note); }
9551 .promo-codes-schedule-pending { color: var(--warning); }
9552 .promo-codes-schedule-expired { color: var(--danger); }
9553 .promo-codes-schedule-none { opacity: 0.5; }
9554 .promo-codes-actions { white-space: nowrap; }
9555 .promo-codes-edit-btn { font-size: var(--text-fine); }
9556 .promo-codes-edit-row { background: var(--surface-sunken); }
9557 .promo-codes-edit-cell { padding: var(--gap-section); }
9558 .promo-codes-edit-form {
9559 display: flex; gap: var(--gap-section); align-items: end; flex-wrap: wrap;
9560 }
9561 .promo-codes-edit-label {
9562 font-size: var(--text-fine); display: block; margin-bottom: var(--gap-bound);
9563 }
9564 .promo-codes-edit-save { font-size: var(--text-note); }
9565 .promo-codes-bulk-row { margin-top: var(--gap-section); }
9566 .promo-codes-bulk-btn { font-size: var(--text-note); opacity: 0.7; }
9567
9568 /* ===========================================
9569 USER MEDIA TAB (replaces inline styles in partials/tabs/user_media.html)
9570 =========================================== */
9571
9572 .user-media-intro {
9573 margin-bottom: var(--gap-section);
9574 }
9575
9576 .user-media-upload {
9577 border: 2px dashed var(--border);
9578 padding: var(--gap-pane);
9579 margin-bottom: var(--gap-pane);
9580 text-align: center;
9581 background: var(--surface-overlay);
9582 }
9583
9584 .user-media-upload--dragover {
9585 border-color: var(--action);
9586 }
9587
9588 .user-media-upload-row {
9589 margin-bottom: var(--gap-section);
9590 }
9591
9592 .user-media-upload-label {
9593 display: inline-block;
9594 font-size: var(--text-note);
9595 margin-right: var(--gap-section);
9596 }
9597
9598 .user-media-upload-folder-input {
9599 padding: var(--gap-bound);
9600 font-size: var(--text-note);
9601 width: 140px;
9602 margin-left: var(--gap-bound);
9603 }
9604
9605 .user-media-upload-progress {
9606 margin-top: var(--gap-section);
9607 }
9608
9609 .user-media-upload-status {
9610 font-size: var(--text-note);
9611 margin-bottom: var(--gap-bound);
9612 }
9613
9614 .user-media-upload-status--ok {
9615 color: var(--success);
9616 }
9617
9618 .user-media-upload-status--err {
9619 color: var(--danger);
9620 }
9621
9622 .user-media-folders {
9623 margin-bottom: var(--gap-section);
9624 display: flex;
9625 gap: var(--gap-peer);
9626 flex-wrap: wrap;
9627 align-items: center;
9628 }
9629
9630 .user-media-folders-label {
9631 font-size: var(--text-note);
9632 opacity: 0.7;
9633 }
9634
9635 .user-media-folders-btn {
9636 font-size: var(--text-fine);
9637 }
9638
9639 .user-media-grid {
9640 display: grid;
9641 grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
9642 gap: var(--gap-section);
9643 }
9644
9645 .user-media-card {
9646 background: var(--surface-overlay);
9647 border: 1px solid var(--border);
9648 padding: var(--gap-section);
9649 position: relative;
9650 }
9651
9652 .user-media-card-thumb {
9653 width: 100%;
9654 height: 120px;
9655 overflow: hidden;
9656 margin-bottom: var(--gap-peer);
9657 display: flex;
9658 align-items: center;
9659 justify-content: center;
9660 background: var(--surface-sunken);
9661 }
9662
9663 .user-media-card-thumb--video {
9664 font-size: var(--text-title);
9665 opacity: 0.4;
9666 }
9667
9668 .user-media-card-img {
9669 max-width: 100%;
9670 max-height: 120px;
9671 object-fit: contain;
9672 }
9673
9674 .user-media-card-name {
9675 font-size: var(--text-fine);
9676 word-break: break-all;
9677 margin-bottom: var(--gap-bound);
9678 }
9679
9680 .user-media-card-meta {
9681 font-size: var(--text-fine);
9682 opacity: 0.6;
9683 margin-bottom: var(--gap-peer);
9684 }
9685
9686 .user-media-card-actions {
9687 display: flex;
9688 gap: var(--gap-bound);
9689 }
9690
9691 .user-media-card-btn {
9692 font-size: var(--text-fine);
9693 padding: var(--gap-bound) var(--gap-peer);
9694 }
9695
9696 /* ===========================================
9697 ITEM VERSION UPLOAD (replaces inline styles in partials/item_version_upload.html)
9698 =========================================== */
9699
9700 .item-version-upload-cell-sm {
9701 font-size: var(--text-note);
9702 }
9703
9704 .item-version-upload-no-file {
9705 opacity: 0.5;
9706 }
9707
9708 .item-version-upload-btn {
9709 padding: var(--gap-peer) var(--gap-section);
9710 font-size: var(--text-note);
9711 }
9712
9713 .item-version-upload-add-btn {
9714 padding: var(--gap-peer) var(--gap-section);
9715 }
9716
9717 .item-version-upload-grid {
9718 display: grid;
9719 grid-template-columns: 1fr 1fr;
9720 gap: var(--gap-section);
9721 }
9722
9723 .item-version-upload-hint {
9724 margin-bottom: var(--gap-peer);
9725 }
9726
9727 .item-version-upload-file-table {
9728 width: 100%;
9729 border-collapse: collapse;
9730 }
9731
9732 .item-version-upload-file-table thead tr {
9733 text-align: left;
9734 font-size: var(--text-fine);
9735 opacity: 0.7;
9736 text-transform: uppercase;
9737 letter-spacing: 0.03em;
9738 }
9739
9740 .item-version-upload-file-table th:first-child {
9741 padding: var(--gap-peer) var(--gap-peer) var(--gap-peer) 0;
9742 }
9743
9744 .item-version-upload-file-table th + th {
9745 padding: var(--gap-peer);
9746 }
9747
9748 .item-version-upload-file-table th:last-child {
9749 width: 40px;
9750 }
9751
9752 .item-version-upload-add-row {
9753 margin-top: var(--gap-peer);
9754 }
9755
9756 .item-version-upload-queue {
9757 margin-bottom: var(--gap-section);
9758 }
9759
9760 .item-version-upload-speed {
9761 font-size: var(--text-fine);
9762 opacity: 0.6;
9763 margin-top: var(--gap-bound);
9764 }
9765
9766 /* ===========================================
9767 PROJECT PAGE (replaces inline styles in pages/project.html)
9768 =========================================== */
9769
9770 .project-page .store-cover {
9771 width: 120px;
9772 height: 120px;
9773 border-radius: var(--radius-panel);
9774 object-fit: cover;
9775 margin-bottom: var(--gap-pane);
9776 }
9777 .project-page .follow-btn.is-selected {
9778 opacity: 0.7;
9779 }
9780 .project-page .follower-count-muted {
9781 font-size: var(--text-note);
9782 opacity: 0.7;
9783 padding: var(--gap-peer) 0;
9784 }
9785 .project-page .promo-details {
9786 font-size: var(--text-note);
9787 margin-bottom: var(--gap-peer);
9788 }
9789 .project-page .promo-details summary {
9790 cursor: pointer;
9791 opacity: 0.7;
9792 }
9793 .project-page .promo-input {
9794 width: 100%;
9795 margin-top: var(--gap-peer);
9796 padding: var(--gap-bound) var(--gap-peer);
9797 font-size: var(--text-note);
9798 text-transform: uppercase;
9799 }
9800 .project-page .store-footer-links {
9801 margin-top: var(--gap-peer);
9802 }
9803
9804 /* ===========================================
9805 INSERTION LIST (replaces inline styles in partials/insertion_list.html)
9806 =========================================== */
9807
9808 .insertion-list-heading {
9809 font-family: var(--font-mono);
9810 font-size: var(--text-body);
9811 margin-bottom: var(--gap-section);
9812 }
9813 .insertion-list-toolbar {
9814 margin-bottom: var(--gap-section);
9815 }
9816 .insertion-list-upload {
9817 font-family: var(--font-mono);
9818 }
9819 .insertion-list-table {
9820 width: 100%;
9821 font-family: var(--font-sans);
9822 }
9823 .insertion-list-table th {
9824 text-align: left;
9825 font-family: var(--font-mono);
9826 font-size: var(--text-note);
9827 }
9828 .insertion-list-table th.insertion-list-th-actions {
9829 text-align: right;
9830 }
9831 .insertion-list-table .insertion-list-actions {
9832 text-align: right;
9833 }
9834 .insertion-list-table .insertion-list-action-btn {
9835 font-family: var(--font-mono);
9836 }
9837
9838 /* ===========================================
9839 PROJECT MEMBERS TAB (replaces inline styles in partials/tabs/project_members.html)
9840 =========================================== */
9841
9842 .proj-members-summary {
9843 background: var(--surface-sunken);
9844 padding: var(--gap-section);
9845 margin-bottom: var(--gap-section);
9846 }
9847
9848 .proj-members-summary-row {
9849 display: flex;
9850 justify-content: space-between;
9851 align-items: center;
9852 }
9853
9854 .proj-members-stat {
9855 font-size: var(--text-head);
9856 font-weight: bold;
9857 }
9858
9859 .proj-members-add {
9860 margin-bottom: var(--gap-page);
9861 }
9862
9863 .proj-members-add-grid {
9864 display: grid;
9865 grid-template-columns: 1fr auto auto;
9866 gap: var(--gap-section);
9867 align-items: end;
9868 }
9869
9870 .proj-members-field-label {
9871 font-size: var(--text-note);
9872 display: block;
9873 margin-bottom: var(--gap-bound);
9874 }
9875
9876 .proj-members-add-result {
9877 margin-top: var(--gap-peer);
9878 }
9879
9880 .proj-members-name-link {
9881 color: inherit;
9882 }
9883
9884 .proj-members-split-cell {
9885 font-weight: bold;
9886 }
9887
9888 /* ===========================================
9889 PLACEMENT LIST (replaces inline styles in partials/placement_list.html)
9890 =========================================== */
9891
9892 .placement-list-heading {
9893 font-family: var(--font-mono);
9894 font-size: var(--text-body);
9895 margin-bottom: var(--gap-section);
9896 }
9897 .placement-list-table {
9898 width: 100%;
9899 font-family: var(--font-sans);
9900 margin-bottom: var(--gap-section);
9901 }
9902 .placement-list-table th {
9903 text-align: left;
9904 font-family: var(--font-mono);
9905 font-size: var(--text-note);
9906 }
9907 .placement-list-table th.placement-list-th-actions {
9908 text-align: right;
9909 }
9910 .placement-list-table .placement-list-actions {
9911 text-align: right;
9912 }
9913 .placement-list-table .placement-list-remove {
9914 font-family: var(--font-mono);
9915 }
9916 .placement-list-form {
9917 display: flex;
9918 gap: var(--gap-peer);
9919 align-items: flex-end;
9920 flex-wrap: wrap;
9921 }
9922 .placement-list-label {
9923 display: block;
9924 font-family: var(--font-mono);
9925 font-size: var(--text-fine);
9926 margin-bottom: var(--gap-bound);
9927 }
9928 .placement-list-add {
9929 font-family: var(--font-mono);
9930 }
9931
9932 /* ===========================================
9933 USER SYNCKIT TAB (replaces inline styles in partials/tabs/user_synckit.html)
9934 =========================================== */
9935
9936 .user-synckit-create { margin-bottom: var(--gap-pane); }
9937 .user-synckit-create-body {
9938 padding: var(--gap-section);
9939 background: var(--surface-overlay);
9940 border: 1px solid var(--border);
9941 }
9942 .user-synckit-create-form {
9943 display: flex;
9944 flex-direction: row;
9945 gap: var(--gap-section);
9946 align-items: flex-end;
9947 }
9948 .user-synckit-create-form label {
9949 display: block;
9950 font-size: var(--text-note);
9951 margin-bottom: var(--gap-bound);
9952 }
9953 .user-synckit-create-form input[type="text"],
9954 .user-synckit-create-form select {
9955 padding: var(--gap-peer);
9956 font-size: var(--text-note);
9957 }
9958 .user-synckit-create-form input[type="text"] { width: 220px; }
9959 .user-synckit-create-form select { width: 180px; }
9960
9961 .user-synckit-code { font-size: var(--text-fine); }
9962 .user-synckit-hint {
9963 font-size: var(--text-fine);
9964 margin-left: var(--gap-bound);
9965 }
9966
9967 .user-synckit-inline-btn {
9968 margin-left: var(--gap-peer);
9969 padding: var(--gap-bound) var(--gap-peer);
9970 font-size: var(--text-fine);
9971 }
9972 .user-synckit-row-btn {
9973 padding: var(--gap-bound) var(--gap-peer);
9974 font-size: var(--text-fine);
9975 }
9976 .user-synckit-row-btn + .user-synckit-row-btn {
9977 margin-left: var(--gap-bound);
9978 }
9979
9980 .user-synckit-status-active { color: var(--success); }
9981 .user-synckit-status-inactive { color: var(--danger); }
9982
9983 .user-synckit-edit-btn {
9984 padding: var(--gap-bound) var(--gap-peer);
9985 font-size: var(--text-fine);
9986 }
9987
9988 .user-synckit-create-error {
9989 color: var(--danger);
9990 margin-top: var(--gap-peer);
9991 }
9992
9993 /* ===========================================
9994 TOTP (replaces inline styles in partials/totp_setup.html and totp_status.html)
9995 =========================================== */
9996
9997 /* Section wrapper between TOTP subsections (QR + backup codes + confirm,
9998 or regenerate + disable). */
9999 .totp-section { margin-bottom: var(--gap-pane); }
10000
10001 /* Confirm-setup section is separated from the backup codes by a top border. */
10002 .totp-confirm {
10003 border-top: 1px solid var(--border);
10004 padding-top: var(--gap-section);
10005 }
10006
10007 /* QR code container, centered above the manual-entry details. */
10008 .totp-qr {
10009 text-align: center;
10010 margin-bottom: var(--gap-section);
10011 }
10012 .totp-qr img {
10013 max-width: 200px;
10014 image-rendering: pixelated;
10015 }
10016
10017 /* Collapsible "Manual entry key" group. */
10018 .totp-manual { margin-bottom: var(--gap-section); }
10019 .totp-manual summary {
10020 cursor: pointer;
10021 opacity: 0.7;
10022 font-size: var(--text-note);
10023 }
10024 .totp-manual-secret {
10025 display: block;
10026 margin-top: var(--gap-peer);
10027 word-break: break-all;
10028 font-size: var(--text-note);
10029 padding: var(--gap-peer);
10030 background: var(--surface-sunken);
10031 }
10032
10033 /* Backup codes grid (rendered list of one-time codes). */
10034 .backup-codes-grid {
10035 display: grid;
10036 grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
10037 gap: var(--gap-peer);
10038 }
10039 .backup-codes-grid code {
10040 padding: var(--gap-peer);
10041 background: var(--surface-sunken);
10042 text-align: center;
10043 font-size: var(--text-note);
10044 }
10045
10046 /* Status row for the "Enabled" badge above the management forms. */
10047 .totp-status-row {
10048 display: flex;
10049 align-items: center;
10050 gap: var(--gap-peer);
10051 margin-bottom: var(--gap-section);
10052 }
10053
10054 /* Container where regenerated backup codes are swapped in via HTMX. */
10055 .totp-regen-output { margin-top: var(--gap-section); }
10056
10057 /* ===========================================
10058 DASHBOARD-USER SHELL (replaces inline styles in dashboards/dashboard-user.html)
10059 =========================================== */
10060
10061 .sandbox-banner {
10062 background: var(--action);
10063 color: var(--primary-light);
10064 padding: var(--gap-section) var(--gap-pane);
10065 text-align: center;
10066 font-family: var(--font-mono);
10067 font-size: var(--text-note);
10068 }
10069
10070 .sandbox-banner-link {
10071 color: var(--primary-light);
10072 text-decoration: underline;
10073 margin-left: var(--gap-peer);
10074 }
10075 .account-status-actions {
10076 display: flex;
10077 gap: var(--gap-section);
10078 flex-wrap: wrap;
10079 margin-top: var(--gap-section);
10080 }
10081
10082 .suspension-wrap {
10083 margin-bottom: var(--gap-section);
10084 }
10085
10086 .password-warning-banner {
10087 margin-bottom: var(--gap-section);
10088 padding: var(--gap-section);
10089 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
10090 border: 1px solid var(--warning);
10091 font-size: var(--text-body);
10092 }
10093
10094 .dash-user-header-links {
10095 margin-bottom: var(--gap-peer);
10096 display: flex;
10097 gap: var(--gap-pane);
10098 }
10099
10100 .dash-user-header-link {
10101 font-size: var(--text-note);
10102 opacity: 0.7;
10103 }
10104
10105 .checklist-recovery {
10106 padding: var(--gap-section) 0;
10107 text-align: right;
10108 }
10109
10110 .checklist-recovery-link {
10111 font-size: var(--text-note);
10112 opacity: 0.7;
10113 }
10114
10115 .tab-spinner {
10116 margin-left: var(--gap-section);
10117 }
10118
10119 /* ===========================================
10120 USER PROFILE PAGE (replaces inline styles in pages/user.html)
10121 =========================================== */
10122
10123 .user-page .creator-paused-notice {
10124 margin-bottom: var(--gap-pane);
10125 padding: var(--gap-section);
10126 background: var(--surface-sunken);
10127 border: 1px solid var(--border);
10128 text-align: center;
10129 font-size: var(--text-body);
10130 }
10131
10132 .user-page .profile-action-row {
10133 margin-top: var(--gap-section);
10134 }
10135
10136 .user-page .profile-edit-btn {
10137 display: inline-block;
10138 padding: var(--gap-peer) var(--gap-section);
10139 text-decoration: none;
10140 font-size: var(--text-note);
10141 }
10142
10143 .user-page .follow-btn.is-selected {
10144 opacity: 0.7;
10145 }
10146
10147 .user-page .follower-count {
10148 margin-top: var(--gap-section);
10149 font-size: var(--text-note);
10150 opacity: 0.7;
10151 }
10152
10153 .user-page .profile-share-row {
10154 margin-top: var(--gap-section);
10155 display: flex;
10156 gap: var(--gap-section);
10157 justify-content: center;
10158 font-size: var(--text-note);
10159 }
10160
10161 .user-page .profile-share-link {
10162 opacity: 0.7;
10163 }
10164
10165 .user-page .collections-section {
10166 margin-bottom: var(--gap-page);
10167 }
10168
10169 /* ===========================================
10170 SUSPENSION BANNER (replaces inline styles in partials/suspension_banner.html)
10171 =========================================== */
10172
10173 .suspension-banner {
10174 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
10175 border: 1px solid var(--danger);
10176 padding: var(--gap-pane);
10177 margin-bottom: var(--gap-pane);
10178 }
10179
10180 .suspension-banner-title {
10181 margin: 0 0 var(--gap-peer) 0;
10182 color: var(--danger);
10183 }
10184
10185 .suspension-banner-reason {
10186 font-size: var(--text-note);
10187 margin: 0 0 var(--gap-section) 0;
10188 }
10189
10190 .suspension-banner-denial {
10191 background: var(--surface-overlay);
10192 padding: var(--gap-section);
10193 margin-bottom: var(--gap-section);
10194 font-size: var(--text-note);
10195 }
10196
10197 .suspension-banner-denial-response {
10198 margin: var(--gap-bound) 0 0 0;
10199 }
10200
10201 .suspension-banner-denial-hint {
10202 margin: var(--gap-peer) 0 0 0;
10203 opacity: 0.7;
10204 }
10205
10206 .suspension-banner-pending {
10207 font-size: var(--text-note);
10208 opacity: 0.7;
10209 margin: 0;
10210 }
10211
10212 .suspension-banner-appeal {
10213 margin-top: var(--gap-peer);
10214 }
10215
10216 .suspension-banner-appeal-summary {
10217 cursor: pointer;
10218 font-size: var(--text-note);
10219 }
10220
10221 .suspension-banner-appeal-form {
10222 margin-top: var(--gap-section);
10223 }
10224
10225 .suspension-banner-appeal-text {
10226 width: 100%;
10227 margin-bottom: var(--gap-peer);
10228 font-size: var(--text-note);
10229 }
10230
10231 .suspension-banner-appeal-submit {
10232 font-size: var(--text-note);
10233 }
10234
10235 .suspension-banner-export {
10236 margin-top: var(--gap-section);
10237 }
10238
10239 .suspension-banner-export-link {
10240 font-size: var(--text-note);
10241 }
10242
10243 /* ===========================================
10244 REPORT MODAL (replaces inline styles in partials/report_modal.html)
10245 =========================================== */
10246
10247 .report-modal-content {
10248 background: var(--surface-page);
10249 padding: var(--gap-page);
10250 width: 90%;
10251 max-width: 480px;
10252 max-height: 90vh;
10253 overflow-y: auto;
10254 }
10255
10256 .report-modal-title {
10257 font-size: var(--text-subhead);
10258 margin: 0;
10259 }
10260
10261 .report-modal-fieldset {
10262 border: none;
10263 padding: 0;
10264 margin: 0 0 var(--gap-section) 0;
10265 }
10266
10267 .report-modal-legend {
10268 font-size: var(--text-note);
10269 font-weight: 600;
10270 margin-bottom: var(--gap-peer);
10271 }
10272
10273 .report-modal-option {
10274 display: block;
10275 margin-bottom: var(--gap-peer);
10276 cursor: pointer;
10277 }
10278
10279 .report-modal-details {
10280 margin-bottom: var(--gap-section);
10281 }
10282
10283 .report-modal-details-label {
10284 display: block;
10285 font-size: var(--text-note);
10286 margin-bottom: var(--gap-bound);
10287 }
10288
10289 .report-modal-textarea {
10290 width: 100%;
10291 font-size: var(--text-note);
10292 }
10293
10294 .report-modal-actions {
10295 display: flex;
10296 gap: var(--gap-peer);
10297 justify-content: flex-end;
10298 }
10299
10300 /* ===========================================
10301 LIBRARY COLLECTIONS TAB (partials/tabs/library_collections.html)
10302 =========================================== */
10303
10304 .badge.is-faded { opacity: 0.5; }
10305 .lib-coll-new { margin-top: var(--gap-pane); }
10306 .lib-coll-new-summary { cursor: pointer; font-weight: bold; }
10307 .lib-coll-new-form { margin-top: var(--gap-section); }
10308 .lib-coll-wishlist-heading {
10309 font-size: var(--text-lead);
10310 margin-top: var(--gap-page);
10311 margin-bottom: var(--gap-section);
10312 opacity: 0.8;
10313 }
10314 .lib-coll-wishlist-wrap {
10315 overflow-x: auto;
10316 -webkit-overflow-scrolling: touch;
10317 }
10318 .lib-coll-wishlist-table { min-width: 500px; }
10319 .lib-coll-row-actions { white-space: nowrap; }
10320 .lib-coll-row-actions .lib-coll-cart-btn { margin-right: var(--gap-bound); }
10321
10322 /* ===========================================
10323 USER ANALYTICS TAB (partials/tabs/user_analytics.html)
10324 =========================================== */
10325
10326 .analytics-export-link { margin-left: var(--gap-section); }
10327 .analytics-range-heading {
10328 font-size: var(--text-lead);
10329 margin: 0;
10330 }
10331 .analytics-section--comparison { margin-bottom: var(--gap-page); }
10332 .analytics-section--comparison h2 { margin-bottom: var(--gap-section); }
10333 .analytics-table-wrap {
10334 overflow-x: auto;
10335 -webkit-overflow-scrolling: touch;
10336 }
10337 .analytics-table { min-width: 500px; }
10338 .analytics-revenue-cell {
10339 display: flex;
10340 align-items: center;
10341 gap: var(--gap-peer);
10342 }
10343 .analytics-revenue-bar {
10344 width: var(--fill, 0%);
10345 height: 10px;
10346 min-width: 2px;
10347 max-width: 80px;
10348 background: var(--action);
10349 }
10350
10351 /* ===========================================
10352 ITEM EMBED TAB (templates/partials/tabs/item_embed.html)
10353 =========================================== */
10354 .embed-intro {
10355 opacity: 0.7;
10356 font-size: var(--text-note);
10357 margin-bottom: var(--gap-pane);
10358 }
10359 .embed-option {
10360 margin-bottom: var(--gap-pane);
10361 padding: var(--gap-section);
10362 border: 1px solid var(--border);
10363 border-radius: var(--radius-panel);
10364 }
10365 .embed-option h4 {
10366 margin-bottom: var(--gap-bound);
10367 font-size: var(--text-body);
10368 }
10369 .embed-option-desc {
10370 font-size: var(--text-note);
10371 opacity: 0.7;
10372 margin-bottom: var(--gap-peer);
10373 }
10374 .embed-preview {
10375 margin: var(--gap-section) 0;
10376 overflow: hidden;
10377 }
10378 .embed-iframe {
10379 border: none;
10380 border-radius: var(--radius-control);
10381 }
10382 .embed-layout-toggle {
10383 font-size: var(--text-note);
10384 margin-bottom: var(--gap-peer);
10385 display: flex;
10386 gap: var(--gap-section);
10387 }
10388 .embed-layout-toggle label {
10389 cursor: pointer;
10390 display: flex;
10391 align-items: center;
10392 gap: var(--gap-bound);
10393 }
10394 .embed-code-row {
10395 display: flex;
10396 align-items: center;
10397 gap: var(--gap-peer);
10398 }
10399 .embed-snippet {
10400 flex: 1;
10401 padding: var(--gap-peer) var(--gap-section);
10402 background: var(--surface-overlay);
10403 border-radius: var(--radius-control);
10404 font-size: var(--text-fine);
10405 overflow-x: auto;
10406 white-space: nowrap;
10407 font-family: var(--font-mono);
10408 border: 1px solid var(--border);
10409 }
10410 .copy-btn {
10411 padding: 6px 12px;
10412 font-size: var(--text-fine);
10413 background: var(--action);
10414 color: var(--primary-light);
10415 border: none;
10416 border-radius: var(--radius-control);
10417 cursor: pointer;
10418 white-space: nowrap;
10419 }
10420 .copy-btn:hover {
10421 opacity: 0.9;
10422 }
10423
10424 /* ===========================================
10425 ADMIN NAV (templates/partials/admin_nav.html)
10426 =========================================== */
10427 .admin-nav {
10428 display: flex;
10429 gap: var(--gap-peer);
10430 margin-bottom: var(--gap-pane);
10431 flex-wrap: wrap;
10432 font-family: var(--font-mono);
10433 font-size: var(--text-note);
10434 }
10435 .admin-nav a {
10436 padding: var(--gap-bound) var(--gap-section);
10437 text-decoration: none;
10438 }
10439
10440 /* Admin nav items and filter-bar toggles are .btn-primary / .btn-secondary,
10441 toggled by the templates to mark the current page or active filter. Colour
10442 and depth come from the button recipe; only the tighter admin metrics and
10443 the active weight are stated here. */
10444 .admin-nav a.btn-primary,
10445 .filter-bar button.btn-primary {
10446 font-weight: 700;
10447 }
10448 .admin-nav a.btn-secondary:hover,
10449 .filter-bar button.btn-secondary:hover {
10450 color: var(--content);
10451 }
10452
10453 /* ===========================================
10454 VIDEO PLAYER PAGE (templates/pages/video_player.html)
10455 =========================================== */
10456 .video-cover-img {
10457 width: 100%;
10458 display: block;
10459 background: var(--primary-dark);
10460 }
10461 .video-cover-placeholder {
10462 width: 100%;
10463 aspect-ratio: 16 / 9;
10464 background: var(--primary-dark);
10465 display: flex;
10466 align-items: center;
10467 justify-content: center;
10468 color: var(--content-muted);
10469 }
10470 .video-store-cta {
10471 margin: var(--gap-page) 0;
10472 padding: var(--gap-pane);
10473 background: var(--surface-sunken);
10474 text-align: center;
10475 }
10476 .video-cta-lead {
10477 font-size: var(--text-lead);
10478 margin-bottom: var(--gap-section);
10479 }
10480 .video-cta-price {
10481 font-size: var(--text-head);
10482 margin-bottom: var(--gap-section);
10483 }
10484 .video-cta-fineprint {
10485 font-size: var(--text-note);
10486 opacity: 0.7;
10487 margin-top: var(--gap-section);
10488 }
10489 .video-library-status {
10490 font-family: var(--font-mono);
10491 color: var(--content-muted);
10492 }
10493
10494 /* ===========================================
10495 LIBRARY LOCKED PAGE (templates/pages/library_locked.html)
10496 =========================================== */
10497 .library-locked-byline {
10498 opacity: 0.7;
10499 }
10500 .library-locked-cover {
10501 max-width: 320px;
10502 margin: var(--gap-section) 0;
10503 /* Reserve the space before the image loads (audit Run 17 Frontend). */
10504 aspect-ratio: 1 / 1;
10505 object-fit: cover;
10506 }
10507 .library-locked-purchase {
10508 margin-top: var(--gap-page);
10509 }
10510 .library-locked-lead {
10511 opacity: 0.85;
10512 }
10513 .library-locked-actions {
10514 margin-top: var(--gap-pane);
10515 display: flex;
10516 gap: var(--gap-section);
10517 flex-wrap: wrap;
10518 }
10519 .library-locked-bundles {
10520 margin-top: var(--gap-pane);
10521 }
10522 .library-locked-bundles-title {
10523 font-weight: bold;
10524 }
10525 .library-locked-bundle {
10526 margin-top: var(--gap-peer);
10527 }
10528 .library-locked-bundle-price {
10529 opacity: 0.7;
10530 }
10531
10532 /* ===========================================
10533 BLOG (replaces inline styles in project_blog.html
10534 and dashboard-blog-editor.html)
10535 =========================================== */
10536
10537 .blog-empty-state { padding: var(--gap-page) 0; }
10538 .blog-empty-hint {
10539 opacity: 0.7;
10540 font-size: var(--text-note);
10541 margin-top: var(--gap-peer);
10542 }
10543 .blog-post-title-link { font-weight: bold; }
10544 .blog-post-actions {
10545 display: flex;
10546 gap: var(--gap-peer);
10547 }
10548
10549 /* Blog editor dashboard (templates/dashboards/dashboard-blog-editor.html). */
10550 .blog-editor h1 {
10551 font-size: var(--text-title);
10552 margin-bottom: var(--gap-section);
10553 text-align: left;
10554 }
10555 .blog-editor .editor-form { max-width: 800px; }
10556 .blog-editor .editor-form .form-group { margin-bottom: var(--gap-section); }
10557 .blog-editor .editor-form textarea {
10558 font-family: var(--font-mono);
10559 font-size: var(--text-note);
10560 min-height: 400px;
10561 resize: vertical;
10562 }
10563 .blog-editor-slug-spinner { font-size: var(--text-note); }
10564 .blog-editor-actions {
10565 display: flex;
10566 gap: var(--gap-section);
10567 align-items: center;
10568 }
10569 .blog-editor-status { margin-top: var(--gap-peer); }
10570
10571 /* ===========================================
10572 AUDIO PLAYER (replaces inline styles in pages/audio_player.html)
10573 =========================================== */
10574
10575 .audio-store-cta {
10576 margin: var(--gap-page) 0;
10577 padding: var(--gap-pane);
10578 background: var(--surface-sunken);
10579 text-align: center;
10580 }
10581 .audio-store-cta .audio-access-msg {
10582 font-size: var(--text-lead);
10583 margin-bottom: var(--gap-section);
10584 }
10585 .audio-store-cta .audio-price {
10586 font-size: var(--text-head);
10587 margin-bottom: var(--gap-section);
10588 }
10589 .audio-store-cta .audio-fee-note {
10590 font-size: var(--text-note);
10591 opacity: 0.7;
10592 margin-top: var(--gap-section);
10593 }
10594 .audio-library-status {
10595 font-family: var(--font-mono);
10596 color: var(--content-muted);
10597 }
10598
10599 /* ===========================================
10600 SECTIONS WIZARD STEP (templates/wizards/steps/item/sections.html)
10601 Parallels .psection-row used by item_details + project-sections.js,
10602 but isolated so wizard markup can evolve independently.
10603 =========================================== */
10604
10605 .section-row {
10606 display: flex;
10607 align-items: center;
10608 gap: var(--gap-section);
10609 padding: var(--gap-group) 0;
10610 border-bottom: 1px solid var(--border);
10611 }
10612 .section-row-title { flex: 1; font-weight: bold; }
10613 .section-row-length { font-size: var(--text-fine); opacity: 0.6; }
10614 .section-row .section-delete-btn {
10615 padding: var(--gap-bound) var(--gap-group);
10616 font-size: var(--text-fine);
10617 }
10618
10619 .section-add-body { margin-top: var(--gap-section); }
10620
10621 .wizard-actions { margin-top: var(--gap-pane); }
10622
10623 /* ===========================================
10624 PROJECT SUBSCRIPTIONS TAB (templates/partials/tabs/project_subscriptions.html)
10625 =========================================== */
10626
10627 .subs-connect-info {
10628 margin-bottom: var(--gap-pane);
10629 padding: var(--gap-section);
10630 }
10631 .subs-new-tier { margin-bottom: var(--gap-page); }
10632
10633 .tier-row-desc {
10634 font-size: var(--text-note);
10635 opacity: 0.7;
10636 margin-top: var(--gap-bound);
10637 }
10638 .tier-action-btn {
10639 font-size: var(--text-note);
10640 padding: var(--gap-bound) var(--gap-group);
10641 }
10642
10643 .subs-members-bar {
10644 margin-top: var(--gap-page);
10645 display: flex;
10646 justify-content: space-between;
10647 align-items: center;
10648 }
10649
10650 /* ===========================================
10651 LIBRARY CONSUMPTION PAGES
10652 ===========================================
10653 Shared styles for the library-side consumption surfaces:
10654 text_reader.html, library_text.html (.article-page),
10655 library_downloads.html (.library-page),
10656 library_audio.html / library_video.html (.media-container + helpers).
10657 The .article-page and .library-page sections higher up in this file
10658 are activated by body classes set in the templates. */
10659
10660 /* Faded excerpt text on the public store-page text reader. */
10661 .article-page .article-body--excerpt { opacity: 0.85; }
10662
10663 /* Store-page call-to-action block (text_reader.html). */
10664 .article-page .store-cta {
10665 margin: var(--gap-page) 0;
10666 padding: var(--gap-pane);
10667 background: var(--surface-sunken);
10668 text-align: center;
10669 }
10670 .article-page .store-cta-lead {
10671 font-size: var(--text-lead);
10672 margin-bottom: var(--gap-section);
10673 }
10674 .article-page .store-cta-price {
10675 font-size: var(--text-head);
10676 margin-bottom: var(--gap-section);
10677 }
10678 .article-page .store-cta-note {
10679 font-size: var(--text-note);
10680 opacity: 0.7;
10681 margin-top: var(--gap-section);
10682 }
10683 .article-page .store-cta .library-status {
10684 font-family: var(--font-mono);
10685 color: var(--content-muted);
10686 }
10687
10688 /* Library back-link breadcrumb shared by the media library pages. */
10689 .media-container .library-back {
10690 font-size: var(--text-note);
10691 opacity: 0.7;
10692 margin: var(--gap-section) 0;
10693 }
10694 .media-container .library-back a { color: var(--content); }
10695
10696 /* Source-files download list under audio/video library pages.
10697 The .library-page version exists higher up; this block scopes the
10698 same look under .media-container for audio/video. */
10699 .media-container .library-downloads {
10700 background: var(--surface-overlay);
10701 padding: var(--gap-pane);
10702 margin-top: var(--gap-page);
10703 }
10704 .media-container .library-downloads h3 {
10705 font-size: var(--text-lead);
10706 margin-bottom: var(--gap-section);
10707 }
10708 .media-container .download-row {
10709 display: flex;
10710 align-items: center;
10711 gap: var(--gap-section);
10712 padding: var(--gap-peer) 0;
10713 border-bottom: 1px solid var(--border);
10714 }
10715 .media-container .download-row:last-child { border-bottom: none; }
10716 .media-container .download-version {
10717 min-width: 4em;
10718 font-family: var(--font-mono);
10719 font-size: var(--text-note);
10720 }
10721 .media-container .download-label { flex: 1; }
10722 .media-container .download-label--empty { flex: 1; opacity: 0.5; }
10723 .media-container .download-size {
10724 font-size: var(--text-note);
10725 opacity: 0.6;
10726 min-width: 5em;
10727 text-align: right;
10728 }
10729
10730 /* Compact download button used in both library_downloads.html and
10731 media library pages. */
10732 .btn-download-compact {
10733 padding: var(--gap-peer) var(--gap-section);
10734 font-size: var(--text-note);
10735 }
10736
10737 /* Faded "no label" placeholder on library_downloads.html download rows. */
10738 .library-page .download-label--empty { opacity: 0.5; }
10739
10740 /* Library-downloads description <details> summary + body. */
10741 .library-page .lib-summary {
10742 cursor: pointer;
10743 font-family: var(--font-display);
10744 font-size: var(--text-subhead);
10745 }
10746 .library-page .lib-summary-body { margin-top: var(--gap-section); }
10747
10748 /* License section on library_downloads.html. */
10749 .library-page .lib-license-name { margin-bottom: var(--gap-section); }
10750 .library-page .lib-license-download {
10751 font-size: var(--text-note);
10752 color: var(--content);
10753 }
10754
10755 /* Footer below the item-footer (was inline on library_downloads.html). */
10756 .library-page .item-footer-sub { margin-top: var(--gap-peer); }
10757
10758 /* library_text.html footer: a terse in-page footer for the library's own
10759 text view, unrelated to base.html's site-footer. */
10760 .article-page .lib-text-footer {
10761 text-align: center;
10762 padding: var(--gap-page);
10763 font-size: var(--text-note);
10764 opacity: 0.6;
10765 border-top: 1px solid var(--border);
10766 }
10767 .article-page .lib-text-footer a { color: var(--content); }
10768
10769 /* Media player play/pause icon initial state. The JS in media-player.js
10770 toggles via element.style.display directly, so we must NOT use
10771 !important here, element.style would still beat a normal-priority
10772 declaration, but !important in the stylesheet would defeat that. */
10773 .media-icon-hidden { display: none; }
10774
10775 /* Video element fills its .video-display parent. .video-display already
10776 provides a black background. */
10777 .video-display video { width: 100%; }
10778
10779 /* Cover image inside .cover-art on library_audio.html, the parent
10780 .cover-art already sets aspect ratio and centering; the inline
10781 width/aspect-ratio/object-fit was redundant with .cover-art img. */
10782
10783 /* ===========================================
10784 USER DASHBOARD TABS (support / sessions / buyer contacts)
10785 =========================================== */
10786
10787 /* Sessions tab (templates/partials/tabs/user_sessions.html). */
10788 .sessions-tab .sessions-table { width: 100%; }
10789 .sessions-tab .device-cell {
10790 max-width: 300px;
10791 overflow: hidden;
10792 text-overflow: ellipsis;
10793 white-space: nowrap;
10794 }
10795
10796 /* Buyer contacts tab (templates/partials/tabs/buyer_contacts.html). */
10797 .buyer-contacts .contacts-table { min-width: 500px; }
10798
10799 /* Creators page extras (templates/pages/creators.html). */
10800 .creators-page .pricing-fineprint {
10801 font-size: var(--text-note);
10802 opacity: 0.8;
10803 margin-top: var(--gap-peer);
10804 }
10805 .creators-page .trial-callout {
10806 font-size: var(--text-note);
10807 margin-top: var(--gap-pane);
10808 }
10809
10810 /* ===========================================
10811 LIBRARY TABS
10812 Shared classes for the library-dashboard tab partials:
10813 library_wishlists, library_contacts, library_purchases,
10814 library_subscriptions. Migrated from inline styles.
10815 =========================================== */
10816
10817 /* Tables in library tabs need a minimum width so columns stay readable
10818 while the outer container scrolls horizontally on narrow viewports. */
10819
10820 /* Empty-state hint paragraph (smaller, slightly faded). */
10821 .library-tab-empty-hint {
10822 font-size: var(--text-note);
10823 opacity: 0.7;
10824 margin-bottom: var(--gap-section);
10825 }
10826
10827 /* Compact row-action button (wishlist Add to Cart / Remove, etc.). */
10828
10829 .library-filter-wrap { margin-bottom: var(--gap-section); }
10830
10831 /* Purchases table "Open" action link + context menu trigger group. */
10832 .library-row-actions {
10833 display: flex;
10834 align-items: center;
10835 gap: var(--gap-peer);
10836 }
10837 .library-row-open {
10838 font-size: var(--text-fine);
10839 padding: var(--gap-bound) var(--gap-section);
10840 text-decoration: none;
10841 white-space: nowrap;
10842 }
10843
10844 /* Section sub-heading inside purchases tab (e.g. "Subscriptions"). */
10845 .library-tab-subheading {
10846 font-size: var(--text-lead);
10847 margin-top: var(--gap-page);
10848 margin-bottom: var(--gap-section);
10849 opacity: 0.8;
10850 }
10851
10852 /* Inline-block button styled as a link target on empty-state CTAs. */
10853 .library-tab-cta {
10854 display: inline-block;
10855 text-decoration: none;
10856 }
10857
10858 /* Context menu (dropdown) on each purchase row. */
10859 .context-menu-wrapper { position: relative; }
10860 .context-menu-btn {
10861 background: none;
10862 border: none;
10863 cursor: pointer;
10864 padding: var(--gap-peer);
10865 font-size: var(--text-subhead);
10866 line-height: 1;
10867 color: var(--content-muted);
10868 border-radius: var(--radius-control);
10869 }
10870 .context-menu-btn:hover {
10871 background: var(--surface-sunken);
10872 color: var(--content);
10873 }
10874 .context-menu {
10875 display: none;
10876 position: absolute;
10877 right: 0;
10878 top: 100%;
10879 background: var(--surface-overlay);
10880 border: 1px solid var(--border);
10881 box-shadow: var(--elevation-overlay);
10882 min-width: 160px;
10883 z-index: var(--z-context);
10884 }
10885 .context-menu.open { display: block; }
10886 .context-menu-item {
10887 display: block;
10888 width: 100%;
10889 padding: var(--gap-section);
10890 text-align: left;
10891 background: none;
10892 border: none;
10893 font-family: var(--font-sans);
10894 font-size: var(--text-note);
10895 color: var(--content);
10896 cursor: pointer;
10897 text-decoration: none;
10898 }
10899 .context-menu-item:hover { background: var(--surface-sunken); }
10900 .context-menu-item.danger { color: var(--danger); }
10901
10902 /* Inline license-key code chip shown beneath a purchase date. */
10903 .license-key-inline { margin-top: var(--gap-bound); }
10904 .key-code-small {
10905 font-size: var(--text-fine);
10906 padding: var(--gap-bound) var(--gap-peer);
10907 background: var(--surface-sunken);
10908 border-radius: var(--radius-control);
10909 cursor: pointer;
10910 user-select: all;
10911 }
10912 .key-code-small:hover { background: var(--border); }
10913
10914 /* Library row title link reads as plain text until hovered. */
10915 .library-title-link:hover { text-decoration: underline; }
10916 /* ===========================================
10917 ADMIN ENTRIES (replaces inline styles in partials/admin_user_entries.html,
10918 partials/admin_waitlist_entries.html,
10919 partials/admin_appeal_entries.html)
10920 =========================================== */
10921
10922 /* (`.badge-active` / `.badge-trusted` removed in wave 2 tier B2. They were a
10923 second, ad-hoc spelling of what data-tone="success" already says, and the
10924 admin entries take the tone now.) */
10925
10926 /* "Trust" toggle button sits next to a Trusted badge. */
10927 .admin-entries-trust-btn {
10928 margin-left: var(--gap-bound);
10929 }
10930
10931 /* Waitlist pitch cell, narrow body text. */
10932 .admin-entries-pitch {
10933 font-size: var(--text-note);
10934 max-width: 300px;
10935 }
10936 .admin-entries-pitch-empty {
10937 opacity: 0.5;
10938 font-style: italic;
10939 }
10940 .admin-entries-pitch-inviter {
10941 margin-top: var(--gap-bound);
10942 }
10943
10944 /* Appeal text columns. */
10945 .admin-entries-reason {
10946 font-size: var(--text-note);
10947 max-width: 200px;
10948 }
10949 .admin-entries-appeal {
10950 font-size: var(--text-note);
10951 max-width: 300px;
10952 }
10953
10954 /* Decide form textarea + stacked action buttons. */
10955 .admin-entries-decide-textarea {
10956 width: 200px;
10957 }
10958 .admin-entries-decide-actions {
10959 display: flex;
10960 flex-direction: column;
10961 gap: var(--gap-bound);
10962 }
10963
10964 /* Pagination row at the bottom of admin entry tables. */
10965 .admin-entries-pagination {
10966 display: flex;
10967 justify-content: center;
10968 align-items: center;
10969 gap: var(--gap-section);
10970 padding: var(--gap-section) 0;
10971 }
10972
10973 /* ===========================================
10974 ITEM TABS (item_settings, item_sales, item_overview partials)
10975 =========================================== */
10976
10977 /* Schedule form is hidden until "Schedule" button is clicked. JS toggles
10978 the `.hidden` utility class. */
10979 .item-settings-schedule-form {
10980 margin-top: var(--gap-section);
10981 }
10982
10983 .item-overview-embed {
10984 margin-top: var(--gap-pane);
10985 }
10986 .item-overview-embed > summary {
10987 cursor: pointer;
10988 font-weight: bold;
10989 font-size: var(--text-lead);
10990 }
10991
10992 /* ===========================================
10993 ITEM TEXT EDITOR (partials/item_text_editor.html)
10994 =========================================== */
10995
10996 .text-editor-actions {
10997 margin-top: var(--gap-section);
10998 }
10999
11000 /* ===========================================
11001 PROJECT ANALYTICS TAB (partials/tabs/project_analytics.html)
11002 =========================================== */
11003
11004 .tab-docs-extra {
11005 margin-left: var(--gap-section);
11006 }
11007
11008 .analytics-tab-header {
11009 display: flex;
11010 justify-content: space-between;
11011 align-items: center;
11012 margin-bottom: var(--gap-section);
11013 }
11014 .analytics-tab-header h2 {
11015 font-size: var(--text-lead);
11016 margin: 0;
11017 }
11018
11019 .analytics-empty-block {
11020 text-align: center;
11021 padding: var(--gap-page) var(--gap-section);
11022 opacity: 0.6;
11023 }
11024
11025 /* ===========================================
11026 RECEIPT PAGE INLINE FRAGMENTS (pages/receipt.html)
11027 =========================================== */
11028
11029 .receipt-page .receipt-brand {
11030 opacity: 0.6;
11031 font-size: var(--text-note);
11032 }
11033 .receipt-page .receipt-meta {
11034 text-align: right;
11035 }
11036 .receipt-page .receipt-meta-line {
11037 font-family: var(--font-mono);
11038 font-size: var(--text-note);
11039 opacity: 0.6;
11040 }
11041 .receipt-page .receipt-meta-sub {
11042 font-family: var(--font-mono);
11043 font-size: var(--text-fine);
11044 opacity: 0.4;
11045 margin-top: var(--gap-bound);
11046 }
11047 .receipt-page .print-btn {
11048 margin-top: var(--gap-section);
11049 font-size: var(--text-note);
11050 }
11051
11052 /* ===========================================
11053 IMPORT PAGE INLINE FRAGMENTS (dashboards/dashboard-import.html)
11054 =========================================== */
11055
11056 .import-page .import-preview-label,
11057 .import-page .import-mapping-label,
11058 .import-page .import-progress-label {
11059 font-family: var(--font-mono);
11060 font-size: var(--text-note);
11061 margin-bottom: var(--gap-peer);
11062 }
11063 .import-page .import-mapping-label {
11064 margin-bottom: var(--gap-section);
11065 }
11066 .import-page .import-progress-label {
11067 font-size: var(--text-note);
11068 }
11069 .import-page .import-start-btn {
11070 margin-top: var(--gap-section);
11071 }
11072 .import-page .import-history-heading {
11073 font-size: var(--text-subhead);
11074 margin-bottom: var(--gap-section);
11075 }
11076
11077 /* ===========================================
11078 SLUG STATUS PARTIAL (partials/slug_status.html)
11079 =========================================== */
11080
11081 .slug-status {
11082 font-size: var(--text-note);
11083 }
11084 .slug-status.available { color: var(--success); }
11085 .slug-status.taken { color: var(--danger); }
11086
11087 .slug-suggestions {
11088 font-size: var(--text-note);
11089 display: block;
11090 margin-top: var(--gap-bound);
11091 }
11092 .slug-suggestion-link {
11093 margin-right: var(--gap-peer);
11094 }
11095
11096 /* ===========================================
11097 STRIPE DISCLAIMER PAGE (pages/stripe_disclaimer.html)
11098 =========================================== */
11099
11100 .stripe-disclaimer-page .container {
11101 max-width: 700px;
11102 margin: var(--gap-pane) auto;
11103 padding: 0 var(--gap-page);
11104 }
11105 .stripe-disclaimer-page .stripe-disclaimer-title {
11106 text-align: center;
11107 }
11108 .stripe-disclaimer-page .tagline {
11109 font-size: var(--text-note);
11110 opacity: 0.7;
11111 margin-bottom: var(--gap-page);
11112 text-align: center;
11113 }
11114 .stripe-disclaimer-page h2 {
11115 font-size: var(--text-head);
11116 margin-bottom: var(--gap-pane);
11117 }
11118 .stripe-disclaimer-page .stripe-disclaimer-lead {
11119 margin-bottom: var(--gap-pane);
11120 opacity: 0.8;
11121 }
11122 .stripe-disclaimer-page .section { margin-bottom: var(--gap-page); }
11123 .stripe-disclaimer-page .section h3 {
11124 font-size: var(--text-lead);
11125 margin-bottom: var(--gap-section);
11126 font-family: var(--font-mono);
11127 }
11128 .stripe-disclaimer-page .section ul {
11129 margin: 0;
11130 padding-left: var(--gap-section);
11131 }
11132 .stripe-disclaimer-page .section li {
11133 margin-bottom: var(--gap-peer);
11134 line-height: 1.5;
11135 }
11136 .stripe-disclaimer-page .stripe-disclaimer-fee-lead {
11137 margin-bottom: var(--gap-section);
11138 }
11139 .stripe-disclaimer-page .fee-example {
11140 background: var(--surface-page);
11141 padding: var(--gap-section);
11142 margin: var(--gap-section) 0;
11143 font-family: var(--font-mono);
11144 font-size: var(--text-note);
11145 }
11146 .stripe-disclaimer-page .fee-row {
11147 display: flex;
11148 justify-content: space-between;
11149 padding: var(--gap-bound) 0;
11150 }
11151 .stripe-disclaimer-page .fee-row.highlight {
11152 font-weight: bold;
11153 border-top: 1px solid var(--border);
11154 margin-top: var(--gap-peer);
11155 padding-top: var(--gap-peer);
11156 }
11157 .stripe-disclaimer-page .fee-row .zero { color: var(--content); }
11158 .stripe-disclaimer-page .stripe-disclaimer-note {
11159 font-size: var(--text-note);
11160 opacity: 0.8;
11161 }
11162 .stripe-disclaimer-page .acceptance {
11163 background: var(--surface-page);
11164 padding: var(--gap-section);
11165 margin: var(--gap-pane) 0;
11166 }
11167 .stripe-disclaimer-page .checkbox-group {
11168 display: flex;
11169 align-items: flex-start;
11170 gap: var(--gap-section);
11171 }
11172 .stripe-disclaimer-page .checkbox-group input[type="checkbox"] {
11173 margin-top: var(--gap-bound);
11174 flex-shrink: 0;
11175 }
11176 .stripe-disclaimer-page .checkbox-group label {
11177 line-height: 1.5;
11178 cursor: pointer;
11179 }
11180 .stripe-disclaimer-page .button-row {
11181 display: flex;
11182 gap: var(--gap-section);
11183 margin-top: var(--gap-pane);
11184 }
11185 .stripe-disclaimer-page .button-row button { flex: 1; }
11186 .stripe-disclaimer-page button:disabled {
11187 opacity: 0.5;
11188 cursor: not-allowed;
11189 }
11190 .stripe-disclaimer-page .note {
11191 font-size: var(--text-note);
11192 opacity: 0.6;
11193 margin-top: var(--gap-section);
11194 text-align: center;
11195 }
11196
11197 /* ===========================================
11198 LONG-TAIL CLEANUP, shared utilities promoted from the final inline-style sweep.
11199 Each rule replaces ≥2 inline-style instances across templates.
11200 =========================================== */
11201
11202 /* Tab-spinner loading indicator (used by 3+ dashboards). */
11203
11204 /* Inline-block secondary action link styled as a button. */
11205 .btn-link {
11206 display: inline-block;
11207 padding: var(--gap-peer) var(--gap-section);
11208 text-decoration: none;
11209 }
11210 .btn-link--sm { padding: var(--gap-peer) var(--gap-section); }
11211
11212 /* Username-availability spinner (wizards + standalone account form). */
11213 .username-spinner { font-size: var(--text-note); }
11214
11215 /* Centered foot-link beneath wizard cards. */
11216 .foot-link--centered { margin-top: var(--gap-section); text-align: center; }
11217
11218 /* Health-page status dots. */
11219 .health-indicator-dot { width: 8px; height: 8px; margin-right: var(--gap-peer); }
11220 .health-error-text { color: var(--danger); }
11221
11222 /* Inline error fragments (login_error, error_fragment) toggle this on. */
11223 .error-message.is-active { display: block; }
11224
11225 /* Cart count badge in nav. */
11226 .cart-badge-count { font-size: var(--text-fine); }
11227
11228 /* Empty-row inside a data table. */
11229 .table-empty-row { text-align: center; opacity: 0.6; }
11230
11231 /* Discover grid: card wrapper + body anchor. */
11232 .discover-grid-card { position: relative; }
11233 .discover-card-link {
11234 text-decoration: none;
11235 color: inherit;
11236 display: flex;
11237 flex-direction: column;
11238 flex: 1;
11239 }
11240 .discover-filter-checkbox {
11241 display: flex;
11242 align-items: center;
11243 gap: var(--gap-peer);
11244 cursor: pointer;
11245 font-size: var(--text-note);
11246 }
11247
11248 /* Tag suggestion list. */
11249 .tag-suggest-label {
11250 font-size: var(--text-fine);
11251 opacity: 0.7;
11252 margin-bottom: var(--gap-peer);
11253 overflow-wrap: anywhere;
11254 }
11255 .tag-suggest-pill {
11256 background: var(--surface-sunken);
11257 border: 1px solid var(--border);
11258 padding: var(--gap-bound) var(--gap-peer);
11259 font-size: var(--text-fine);
11260 cursor: pointer;
11261 margin: var(--gap-bound);
11262 }
11263
11264 /* Dashboard-export header layout + Export-all CTA. */
11265 .dashboard-export-header {
11266 display: flex;
11267 justify-content: space-between;
11268 align-items: flex-start;
11269 }
11270 .export-all-btn {
11271 white-space: nowrap;
11272 margin-top: var(--gap-peer);
11273 }
11274
11275 /* Tiny inline action button (admin-metrics, dashboard-item share). */
11276 .dashboard-item-share-btn {
11277 font-size: var(--text-fine);
11278 padding: var(--gap-bound) var(--gap-peer);
11279 opacity: 0.6;
11280 }
11281
11282 /* Monospaced page sub-headers. */
11283 .section-mono-meta {
11284 font-family: var(--font-mono);
11285 font-size: var(--text-note);
11286 opacity: 0.6;
11287 margin-bottom: var(--gap-pane);
11288 }
11289 /* Email-result single-card page. */
11290 .email-result-wrap {
11291 max-width: 600px;
11292 margin: 50px auto;
11293 padding: 20px;
11294 }
11295
11296 /* Admin lottery form (admin-waitlist). */
11297 .lottery-form {
11298 display: flex;
11299 gap: var(--gap-section);
11300 align-items: flex-end;
11301 }
11302
11303 /* Login screen: "or" divider + passkey button + error slot. */
11304 .login-divider {
11305 text-align: center;
11306 margin: var(--gap-pane) 0 var(--gap-section);
11307 opacity: 0.5;
11308 font-size: var(--text-note);
11309 }
11310 .login-passkey-btn { width: 100%; }
11311 .login-passkey-error { margin-top: var(--gap-peer); }
11312
11313 /* Upload-progress sibling text (item_audio_upload). */
11314 .upload-speed-text {
11315 font-size: var(--text-fine);
11316 opacity: 0.6;
11317 margin-top: var(--gap-bound);
11318 }
11319
11320 /* Faded "Following" follow-button state. */
11321 .follow-btn.is-selected { opacity: 0.7; }
11322
11323 /* Project blog footer. */
11324 .project-blog-footer {
11325 margin-top: var(--gap-page);
11326 padding-top: var(--gap-page);
11327 border-top: 1px solid var(--border);
11328 opacity: 0.6;
11329 }
11330 .project-blog-footer a { color: var(--content); }
11331
11332 /* Compact alert / paragraph margin variants used 2+ times. */
11333
11334 /* Paywall trust line. */
11335 .paywall-trust {
11336 font-size: var(--text-note);
11337 opacity: 0.7;
11338 margin-top: var(--gap-section);
11339 line-height: 1.4;
11340 }
11341
11342 /* Confirm-delete: button background already covered by .danger class.
11343 The wizard step uses `.primary` and inlines the danger background; we just
11344 change the class instead. No CSS rule needed here. */
11345
11346 /* Paywall-style centered narrow form-group used inside project_paywall. */
11347 .form-group--centered-narrow {
11348 max-width: 240px;
11349 margin: var(--gap-section) auto;
11350 }
11351
11352 /* Inline-form margin-left for adjacent pills (collection.html private badge). */
11353 .badge--inline-l { margin-left: var(--gap-peer); }
11354
11355 /* ===== Team page ===== */
11356
11357 .team-page { max-width: 56rem; margin: 0 auto; }
11358 .team-intro {
11359 font-size: var(--text-body);
11360 line-height: 1.55;
11361 margin: 0 0 var(--gap-page);
11362 opacity: 0.85;
11363 }
11364 .team-section { margin-top: var(--gap-page); }
11365 .team-section + .team-section { margin-top: var(--gap-page); }
11366 .team-card-experience {
11367 margin: var(--gap-section) 0 var(--gap-peer);
11368 line-height: 1.5;
11369 }
11370 .team-card-links {
11371 margin: var(--gap-peer) 0;
11372 font-size: var(--text-body);
11373 }
11374 .team-card-link { font-weight: 500; }
11375 .team-card-bio {
11376 margin-top: var(--gap-section);
11377 border-top: 1px solid var(--border);
11378 padding-top: var(--gap-section);
11379 }
11380 .team-card-bio > summary {
11381 cursor: pointer;
11382 font-weight: 500;
11383 font-size: var(--text-body);
11384 list-style: none;
11385 }
11386 .team-card-bio > summary::-webkit-details-marker { display: none; }
11387 .team-card-bio > summary::before {
11388 content: "+ ";
11389 display: inline-block;
11390 width: 1.2em;
11391 opacity: 0.6;
11392 }
11393 .team-card-bio[open] > summary::before { content: ""; }
11394 .team-card-bio > p {
11395 margin-top: var(--gap-section);
11396 line-height: 1.6;
11397 }
11398
11399 /* ── SyncKit billing panel ──
11400 Per-app accordion shown inside the SyncKit dashboard tab (both user and
11401 project levels). The .synckit-billing root carries pricing constants as
11402 data-attrs that static/synckit-billing.js reads to mirror monthly_price_cents.
11403 */
11404 .synckit-billing {
11405 background: var(--surface-raised);
11406 border: 1px solid var(--border);
11407 border-radius: var(--radius-control);
11408 padding: var(--gap-peer) var(--gap-section);
11409 min-width: 220px;
11410 }
11411 .synckit-billing-summary {
11412 cursor: pointer;
11413 display: flex;
11414 align-items: center;
11415 gap: var(--gap-section);
11416 font-size: var(--text-note);
11417 }
11418 .synckit-billing-toggle { margin-left: auto; color: var(--content-muted); }
11419 .synckit-billing-status {
11420 display: inline-block;
11421 padding: 2px var(--gap-peer);
11422 border-radius: var(--radius-fine);
11423 font-size: var(--text-fine);
11424 font-weight: 600;
11425 }
11426 .synckit-billing-status--draft { background: var(--surface-sunken); }
11427 .synckit-billing-status--active { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); color: var(--success); }
11428 .synckit-billing-status--past-due { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
11429 .synckit-billing-status--canceled { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); color: var(--danger); }
11430 .synckit-billing-price { font-weight: 600; }
11431 .synckit-billing-body {
11432 padding-top: var(--gap-section);
11433 display: flex;
11434 flex-direction: column;
11435 gap: var(--gap-section);
11436 }
11437 .synckit-billing-section { display: flex; flex-direction: column; gap: var(--gap-peer); }
11438 .synckit-billing-subheading { font-size: var(--text-note); margin: 0; font-weight: 600; }
11439 .synckit-knob-row {
11440 display: flex;
11441 align-items: center;
11442 gap: var(--gap-peer);
11443 flex-wrap: wrap;
11444 }
11445 .synckit-knob-row label { min-width: 110px; }
11446 .synckit-knob-row input[type="range"] { flex: 1 1 160px; min-width: 120px; }
11447 .synckit-radio { display: inline-flex; align-items: center; gap: var(--gap-bound); min-width: 0; }
11448 .synckit-billing-summary-line {
11449 display: flex;
11450 align-items: baseline;
11451 gap: var(--gap-peer);
11452 padding-top: var(--gap-peer);
11453 border-top: 1px solid var(--border);
11454 }
11455 .synckit-price-preview { font-size: var(--text-lead); font-weight: 600; }
11456 .synckit-billing-actions { display: flex; gap: var(--gap-peer); }
11457 .synckit-billing-manage { flex-direction: row; gap: var(--gap-peer); }
11458 .synckit-billing-status-msg { font-size: var(--text-note); min-height: 1.25em; }
11459 .synckit-billing-status-msg--info { color: var(--content-muted); }
11460 .synckit-billing-status-msg--error { color: var(--danger); }
11461 .synckit-billing-period { margin: 0; }
11462 .synckit-gauge { display: flex; flex-direction: column; gap: 2px; }
11463 .synckit-gauge-label {
11464 display: flex;
11465 justify-content: space-between;
11466 font-size: var(--text-fine);
11467 }
11468 .synckit-gauge-value { color: var(--content-muted); }
11469
11470 /* ============================================================================
11471 Carousel: the widget tier's first consumer.
11472
11473 The markup is no longer written in a template. It comes from a description --
11474 `crate::quasi::widgets::carousel` says "an ordered set of pictures, called a
11475 carousel" -- and quasi-webview turns that into a region carrying
11476 `data-widget="carousel"` with one `.picture` or `.picture-img` per frame.
11477
11478 So these rules key on the widget name rather than on a hand-written class.
11479 That is what "a renderer that recognises the name draws it its own way"
11480 means on this host: the name is the hook, and the stylesheet plus the island
11481 are the browser's idea of a carousel.
11482
11483 THE FALLBACK RUNS THE OTHER WAY NOW. This used to render frame one and hide
11484 the rest, because the controls that reach them are the scripted part -- so
11485 with JS off, two of three screenshots were unreachable. Now the unenhanced
11486 page is the whole gallery, stacked in order.
11487
11488 THE CHROME IS DESCRIBED TOO, as of makeover-layout 0.23.0. The description
11489 says `Showing::One` -- an ordered set of pictures with one showing -- and
11490 quasi-webview derives prev/position/next from that, once, for every widget
11491 there will ever be. Nothing here or in any renderer matches on the name
11492 "carousel" to get it. The island stopped injecting chrome and now only binds
11493 what is already on the page; the dot strip and the overlaid arrows are gone.
11494
11495 The controls are still not shipped live to a reader with no script: makeover
11496 keeps `.showing` hidden until something sets [data-ready], so the markup no
11497 longer has to be absent to avoid lying.
11498 ========================================================================= */
11499
11500 /* Public gallery carousel sections (item + project pages). */
11501 .item-gallery,
11502 .project-gallery {
11503 margin: var(--gap-pane) 0;
11504 }
11505
11506 /* <mnw-carousel> wraps the described region rather than being it: a custom
11507 element is what the browser upgrades automatically after an htmx swap, and
11508 the description emits a plain div because it does not know this host's tag
11509 prefix. Default display for an unknown element is inline, so say otherwise. */
11510 mnw-carousel {
11511 display: block;
11512 }
11513
11514 /* The default is ONE frame, which is what a visitor with JavaScript sees, so
11515 the page is settled from first paint and nothing rearranges on load. Showing
11516 all three and collapsing them was a 141px -> 58px jump on every load that
11517 everyone with JS paid; `no-js.css`, loaded from a <noscript>, is what opens
11518 the stack out for a visitor without scripting.
11519
11520 This is the one half of the carousel that stays local, and it is host policy
11521 rather than a gap. makeover's generated sheet collapses the stack on
11522 [data-ready], which is the honest default for a renderer that cannot know
11523 whether this page has script: it ships every child and takes them away once
11524 something binds them. Trading a load-time jump for that is a decision about
11525 this site's landing page, and <noscript> is a mechanism a generated
11526 stylesheet has no way to reach.
11527
11528 No padding-bottom and no position: relative any more. Both were for chrome
11529 that is gone -- the strip that had to be reserved and the arrows that were
11530 absolutely positioned over the frame. */
11531 [data-widget="carousel"] > .showing-frame:not(.current) {
11532 /* respec-ok: the generated rule is the same claim gated on [data-ready], and
11533 what it cannot express is <noscript>. A renderer that cannot know whether a
11534 page has script has to ship every child and take them away once something
11535 binds them; this page collapses from first paint instead and lets no-js.css
11536 open it back, which is a trade about this landing page rather than about
11537 the vocabulary. */
11538 display: none;
11539 }
11540
11541 [data-widget="carousel"] .picture {
11542 margin: 0;
11543 }
11544
11545 /* Only the corner. `display`, `width`, `height` and the raised fill and bevel
11546 all come from makeover's own `.picture-img` rule, which layout.css puts on
11547 every page through crate::shell::head. Restating any of them here is a
11548 components-layer declaration quietly beating the design system, which is what the
11549 drift check in build.rs exists to catch -- and did, on the first attempt at
11550 this file. */
11551 [data-widget="carousel"] .picture-img {
11552 border-radius: var(--radius-control);
11553 }
11554
11555 /* No colour: makeover's `.picture-caption` already sets it to
11556 --content-muted, and this said the same thing one layer louder. What is
11557 local is the centring and the mono face, which are this widget's look and
11558 not a claim about what a caption is. */
11559 [data-widget="carousel"] .picture-caption {
11560 margin-top: var(--gap-group);
11561 font-family: var(--font-mono);
11562 font-size: var(--text-note);
11563 text-align: center;
11564 }
11565
11566 /* Landing showcase only: close the top edge.
11567
11568 The three landing frames are screenshots of this site, so the parchment they
11569 were shot on is the parchment they sit on, and a 1px border between two
11570 identical grounds is not an edge anyone sees.
11571
11572 THE ELEVATION IS GONE, and that is the point of the port. This carried
11573 `box-shadow: 0 4px 10px var(--elevation), 0 14px 36px var(--elevation)`,
11574 which is the defect `c0b63ea9` names: in-flow depth is a bevel and elevation
11575 is for what sits *over* the page. It survived this long because it was
11576 invisible -- a declaration in a later layer on the same class makeover
11577 sets, so it beat the design system's bevel silently. The drift check in build.rs found it
11578 the moment the frame became a described `.picture-img`.
11579
11580 So the frame is raised by makeover's bevel now, from `Depth::Raised`, and
11581 what is left here is the one thing that was never about depth: a stronger
11582 border colour, because a shadow falling downward does nothing for the top
11583 edge and the top edge is where a parchment screenshot dissolves into a
11584 parchment page. */
11585 .landing-showcase .picture-img {
11586 border-color: var(--border-strong);
11587 }
11588
11589 /* ---------------------------------------------------------------------------
11590 Enhanced. Everything below needs the island, and the island sets
11591 [data-ready] once it has upgraded and bound the controls.
11592 --------------------------------------------------------------------------- */
11593
11594 [data-widget="carousel"][data-ready]:focus-visible {
11595 outline: 2px solid var(--focus-ring);
11596 outline-offset: 3px;
11597 }
11598
11599 /* The control row is makeover's `.showing`, rendered by the description rather
11600 than injected here, and it needs no rules: it is two `.button`s and a muted
11601 readout, all three of which the generated sheet already styles. What used to
11602 sit here was `.carousel-nav`, `.carousel-prev`, `.carousel-next`,
11603 `.carousel-dots` and `.carousel-dot` -- 60 lines for chrome this host drew by
11604 hand because nothing could describe it.
11605
11606 The arrows were absolutely positioned over the frame, which Max called
11607 cluttered and which a terminal cannot honestly do at all; the dots had no
11608 form past a handful of frames, and two of the three galleries here are
11609 creator uploads of arbitrary length. Both halves of that were the same half. */
11610
11611
11612 /* ---------------------------------------------------------------------------
11613 Custom page editor (dashboard/custom_page_editor.html)
11614 Moved out of an inline <style> block per the extract-shared-CSS rule.
11615 --------------------------------------------------------------------------- */
11616 .cp-editor { max-width: 1200px; margin: 0 auto; padding: var(--gap-section); }
11617 .cp-editor-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--gap-section); flex-wrap: wrap; }
11618 .cp-editor-actions { display: flex; gap: var(--gap-section); }
11619 .cp-intro { color: var(--content-muted); margin: var(--gap-bound) 0 var(--gap-section); }
11620 /* Form beside preview. A code textarea below about 380px is not editable, so
11621 that number is the pane's, not the viewport's, and the pair stacks itself. */
11622 .cp-editor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: var(--gap-section); align-items: start; }
11623 .cp-editor-form { display: flex; flex-direction: column; gap: var(--gap-peer); }
11624 .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; }
11625 .cp-editor-buttons { display: flex; align-items: center; gap: var(--gap-section); margin-top: var(--gap-peer); }
11626 .cp-preview-pane { border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden; }
11627 .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); }
11628 .cp-preview { width: 100%; height: 520px; border: 0; background: var(--primary-light); display: block; }
11629 .cp-blocked { margin-top: var(--gap-section); }
11630 .cp-blocked-title { font-weight: 600; margin-bottom: var(--gap-peer); }
11631 .cp-blocked-empty { color: var(--content-muted); }
11632 .cp-blocked-list { list-style: none; padding: 0; margin: 0; }
11633 .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; }
11634 .cp-blocked-kind { font-weight: 600; }
11635 .cp-blocked-loc { color: var(--content-muted); }
11636 .cp-blocked-val { background: var(--surface-sunken); padding: 0 var(--gap-bound); }
11637 .cp-status.cp-ok { color: var(--success); }
11638 .cp-status.cp-err { color: var(--warning); }
11639 .cp-reset { margin-top: var(--gap-pane); }
11640 .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)); }
11641 /* Layout-shift fix (audit Run 17 Frontend): reserve space for a cover image
11642 whose class didn't already set an aspect-ratio / fixed size. */
11643 .proj-image-preview img { width: 120px; height: 120px; object-fit: cover; }
11644
11645 /* ---- Discover search: how well a result matched -------------------------
11646 Two surfaces, doing two different jobs (db/discover.rs, migration 179).
11647
11648 .results-tier-break is the single heading, and it sits exactly where the
11649 claim it makes turns true: above it every row holds at least one word the
11650 searcher typed, below it none of them do. .row-match-note is the per-row
11651 half, "Matched 3 of 5 words", because coverage varies row to row and one
11652 page can hold a 4-of-5 beside a 1-of-5.
11653
11654 Both are deliberately quiet. They explain a result the reader is already
11655 looking at; competing with the title would make search feel like it is
11656 apologising. Muted colour and small type, no accent, no border of their own
11657 beyond the rule that separates the tiers. */
11658 .results-tier-break {
11659 display: flex;
11660 align-items: baseline;
11661 gap: var(--gap-peer);
11662 flex-wrap: wrap;
11663 padding: var(--gap-section) var(--gap-section) var(--gap-peer);
11664 border-top: 1px solid var(--border);
11665 background: var(--surface-page);
11666 }
11667
11668 /* .results-table sets border-top: none and lets .table-row draw the rules, so
11669 the break supplies its own top border. Mid-list that sits against the
11670 preceding row's border-bottom and reads as one heavier 2px rule, which is
11671 wanted here: it is a section change, not another row. At the top of an
11672 all-fuzzy page there is no preceding row, and the same border supplies the
11673 table's missing top edge. */
11674 .results-tier-label {
11675 font-family: var(--font-display);
11676 font-weight: bold;
11677 font-size: var(--text-fine);
11678 text-transform: uppercase;
11679 letter-spacing: 0.03em;
11680 color: var(--content);
11681 }
11682
11683 .results-tier-hint {
11684 font-size: var(--text-fine);
11685 color: var(--content-muted);
11686 }
11687
11688 /* The grid is `repeat(auto-fill, minmax(280px, 1fr))`, so without this the
11689 heading takes one card's slot and the tiers interleave visually. */
11690 .results-container.results-grid .results-tier-break {
11691 grid-column: 1 / -1;
11692 padding-left: 0;
11693 padding-right: 0;
11694 background: none;
11695 }
11696
11697 /* In list view this rides inside .row-info, under the creator line, not as
11698 another child of .table-row-link: that is a fixed five-column grid and a
11699 sixth child would invent a column and shift every row that carried one. */
11700 .row-match-note {
11701 font-family: var(--font-mono);
11702 font-size: var(--text-fine);
11703 color: var(--content-muted);
11704 white-space: nowrap;
11705 overflow: hidden;
11706 text-overflow: ellipsis;
11707 }
11708
11709 /* Grid cards have room to breathe; give the note its own line under the meta. */
11710 .grid-card .row-match-note {
11711 display: block;
11712 margin-top: var(--gap-bound);
11713 }
11714
11715 /* The narrow list layouts drop columns rather than wrap, and .row-info is the
11716 column that survives. Keeping the note there would crowd the title on a
11717 phone, where the tier heading above still carries the important half. */
11718 @media (max-width: 599px) {
11719 .table-row .row-match-note { /* respec-ok: hiding a cell at a breakpoint. The
11720 generated sheet describes one `.table-row`; which of its children survive a
11721 narrow viewport is this page's call, not the design system's. */ display: none; }
11722 }
11723
11724
11725 /* ============================================================================
11726 LAYER HANDOFFS
11727
11728 Everything below gives a property back to @layer makeover rather than
11729 setting one. This file is in @layer components and the order statement puts
11730 it after `makeover`, so every rule here wins over the design system whatever
11731 the specificity -- including the bare `button`, `a` and `input` rules further
11732 up, which carry no class and so reach every described component the site
11733 renders. That is the whole reason this section exists: without it a described
11734 act takes this file's chrome instead of the one makeover drew for it, and the
11735 difference is invisible in the source of either file.
11736
11737 `revert-layer` is the deferral. It names no value, so the design system can
11738 change what a chip looks like without this file learning about it.
11739
11740 Two rules govern how these are written, and both are load-bearing:
11741
11742 - Hand a property back on the arms makeover actually paints, not on the
11743 plain class. Makeover gives a chip a colour only when it is disabled, so
11744 `.chip { color: revert-layer }` would roll a live chip back past this
11745 file's `button { color }` to the UA default rather than to anything
11746 makeover chose.
11747 - Outrank the rule being deferred past. Both rules are this file's and both
11748 are in the same layer, so that contest is decided by specificity and then
11749 by source order. `.field` does not beat `input[type="text"]`, which is
11750 why the field handoffs are element-qualified, and this section sits at
11751 the end of the file so an equal-specificity handoff still wins.
11752
11753 makeover-build's check_vocabulary reads both halves and fails the build on a
11754 pair that has neither a handoff nor a reviewed exception, so this section
11755 cannot fall behind the design system without somebody being told.
11756 ============================================================================ */
11757
11758 /* The surfaces a bare `button` was taking. `.card`, `.tab` and the two facet
11759 controls are all rendered as buttons by the described screens. */
11760 .card,
11761 .tab,
11762 .facet-take,
11763 .facet-prune {
11764 /* respec-ok: a layer handoff, not a value. */
11765 background: revert-layer;
11766 box-shadow: revert-layer;
11767 }
11768
11769 .card:hover,
11770 .tab:hover,
11771 .facet-take:hover,
11772 .facet-prune:hover {
11773 /* respec-ok: a layer handoff, not a value. */
11774 background: revert-layer;
11775 }
11776
11777 .card:active,
11778 .tab:active,
11779 .facet-take:active,
11780 .facet-prune:active {
11781 /* respec-ok: a layer handoff, not a value. */
11782 background: revert-layer;
11783 box-shadow: revert-layer;
11784 }
11785
11786 /* A facet control's colour is makeover's in every state: muted when disabled,
11787 secondary when the value it names is not standing, inherited otherwise.
11788 There is no arm of it this file has an opinion about. */
11789 .facet-take,
11790 .facet-prune {
11791 /* respec-ok: a layer handoff, not a value. */
11792 color: revert-layer;
11793 }
11794
11795 /* The disabled arm. Makeover paints all four properties here -- muted text, the
11796 not-allowed cursor, and the surface it keeps rather than dropping -- and this
11797 file's `button:disabled` was taking every one of them. */
11798 .button:disabled,
11799 .button[aria-disabled="true"],
11800 .card:disabled,
11801 .card[aria-disabled="true"],
11802 .chip:disabled,
11803 .chip[aria-disabled="true"],
11804 .tab:disabled,
11805 .tab[aria-disabled="true"],
11806 .segment:disabled,
11807 .segment[aria-disabled="true"],
11808 .toggle:disabled,
11809 .toggle[aria-disabled="true"],
11810 .field:disabled,
11811 .field[aria-disabled="true"],
11812 .facet-take:disabled,
11813 .facet-take[aria-disabled="true"],
11814 .facet-prune:disabled,
11815 .facet-prune[aria-disabled="true"] {
11816 /* respec-ok: a layer handoff, not a value. */
11817 background: revert-layer;
11818 box-shadow: revert-layer;
11819 color: revert-layer;
11820 cursor: revert-layer;
11821 }
11822
11823 /* The focus ring. Makeover insets a field's so it does not collide with the
11824 bevel and leaves every other one outside, and this file's `:focus-visible`
11825 rules were flattening the distinction on every described control. */
11826 .button:focus-visible,
11827 .card:focus-visible,
11828 .chip:focus-visible,
11829 .link:focus-visible,
11830 .tab:focus-visible,
11831 .segment:focus-visible,
11832 .toggle:focus-visible,
11833 .facet-take:focus-visible,
11834 .facet-prune:focus-visible {
11835 /* respec-ok: a layer handoff, not a value. */
11836 outline: revert-layer;
11837 outline-offset: revert-layer;
11838 }
11839
11840 /* A described field is an `<input>`, a `<select>` or a `<textarea>`, and the
11841 rules for those three carry an attribute, so the handoff has to as well: a
11842 bare `.field` is the weaker selector and loses inside this layer. The `:focus`
11843 arm is here because this file sets `outline: none` on it, which removes the
11844 ring makeover draws on `:focus-visible` rather than replacing it. */
11845 input.field,
11846 select.field,
11847 textarea.field,
11848 input.field:hover,
11849 select.field:hover,
11850 textarea.field:hover,
11851 input.field:active,
11852 select.field:active,
11853 textarea.field:active {
11854 /* respec-ok: a layer handoff, not a value. */
11855 background: revert-layer;
11856 box-shadow: revert-layer;
11857 }
11858
11859 input.field:focus,
11860 select.field:focus,
11861 textarea.field:focus,
11862 input.field:focus-visible,
11863 select.field:focus-visible,
11864 textarea.field:focus-visible {
11865 /* respec-ok: a layer handoff, not a value. */
11866 outline: revert-layer;
11867 outline-offset: revert-layer;
11868 }
11869
11870 /* A described toggle is an `<input>` too. The hover and active arms are here
11871 for the same reason as the field's: this file dresses `input[type="submit"]`
11872 as a raised button in every state, and a toggle is an input. */
11873 input.toggle,
11874 input.toggle:hover,
11875 input.toggle:active,
11876 input.toggle:focus,
11877 input.toggle:focus-visible {
11878 /* respec-ok: a layer handoff, not a value. */
11879 background: revert-layer;
11880 box-shadow: revert-layer;
11881 outline: revert-layer;
11882 outline-offset: revert-layer;
11883 }
11884
11885 /* A described link on hover. `.link` above hands the resting colour back; this
11886 file's `a:hover` is the more specific of the two and was taking the hover
11887 colour and the underline with it. */
11888 .link:hover {
11889 /* respec-ok: a layer handoff, not a value. */
11890 color: revert-layer;
11891 text-decoration: revert-layer;
11892 }
11893
11894 /* Text makeover mutes: a facet's name, and the line a placeholder shows when
11895 there is nothing to list. This file's bare `p` rule was giving both the body
11896 colour. */
11897 .facet-name,
11898 .placeholder-text {
11899 /* respec-ok: a layer handoff, not a value. */
11900 color: revert-layer;
11901 }
11902 }
11903
11904 /* ===========================================
11905 DESCRIBED MODAL (quasi RegionKind::Modal)
11906 ===========================================
11907 The first described modal on this host is the media picker; these two rules
11908 are what "modal" means here, and every one after it gets them for free. The
11909 description says the region IS a modal and says nothing about where it sits,
11910 which is the division the vocabulary draws: `role="dialog" aria-modal="true"`
11911 comes off the renderer, and a dialog that lays out in flow would make that
11912 attribute a lie.
11913
11914 Two boxes because a scrim and a panel are two boxes: the modal region covers
11915 the viewport and takes the presses the page behind it must not get, and the
11916 region inside it is the panel. */
11917 .region.modal {
11918 position: fixed;
11919 inset: 0;
11920 z-index: var(--z-overlay);
11921 display: flex;
11922 align-items: center;
11923 justify-content: center;
11924 background: var(--overlay);
11925 }
11926
11927 .region.modal > .region {
11928 position: relative;
11929 z-index: var(--z-modal);
11930 width: 90%;
11931 max-width: 700px;
11932 max-height: 80vh;
11933 overflow-y: auto;
11934 padding: var(--gap-section);
11935 background: var(--surface-overlay);
11936 box-shadow: var(--elevation-overlay);
11937 }
11938
11939 /* ===========================================
11940 MEDIA PICKER (crate::quasi::media_picker)
11941 ===========================================
11942 Addressed by the widget names the description carries, not by ids: a document
11943 holds one picker per markdown editor and their ids differ by destination,
11944 while what they are drawn like is the same assembly every time. */
11945 [data-widget="media-picker-grid"] {
11946 display: grid;
11947 grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
11948 gap: var(--gap-peer);
11949 margin-top: var(--gap-section);
11950 }
11951
11952 [data-widget="media-tile"] {
11953 display: flex;
11954 flex-direction: column;
11955 gap: var(--gap-peer);
11956 padding: var(--gap-peer);
11957 border: 1px solid var(--border);
11958 background: var(--surface-raised);
11959 min-width: 0;
11960 }
11961
11962 /* A cap, not a height, and never a bare `img`. The design system already sets
11963 the picture's width, height and background, and an app rule taking any of
11964 those wins over `@layer makeover` silently — which is what `makeover-build`'s
11965 drift check refuses, and it is right to. `max-height` is not one of them, and
11966 it is all this needs: `Fit::Contain` is in the description, so a capped
11967 picture letterboxes inside the cap rather than being cropped or stretched. */
11968 [data-widget="media-tile"] .picture-img {
11969 max-height: 80px;
11970 }
11971
11972 /* The file name is the control, so it wraps rather than pushing the tile wide.
11973 `Act` is a label and nothing in the vocabulary says a control reads as a
11974 picture, so the thumbnail above it is a sibling and only this half answers a
11975 press. Filed rather than faked; see the module header. */
11976 [data-widget="media-tile"] .button {
11977 overflow-wrap: anywhere;
11978 text-align: left;
11979 font-size: var(--text-note);
11980 }
11981