Skip to main content

max / makenotwork

329.5 KB · 12124 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 /* Quick actions */
4986 .quick-actions {
4987 background: var(--surface-sunken);
4988 padding: var(--gap-pane);
4989 }
4990
4991 .quick-actions h2 {
4992 font-size: var(--text-subhead);
4993 font-weight: normal;
4994 margin-bottom: var(--gap-section);
4995 }
4996
4997 /* Filter controls */
4998 .filter-controls {
4999 display: flex;
5000 gap: var(--gap-section);
5001 margin-bottom: var(--gap-section);
5002 flex-wrap: wrap;
5003 align-items: center;
5004 }
5005
5006 .filter-group {
5007 display: flex;
5008 gap: var(--gap-peer);
5009 align-items: center;
5010 }
5011 /* UUID styling */
5012 .uuid {
5013 font-family: inherit;
5014 opacity: 0.5;
5015 font-size: var(--text-note);
5016 }
5017 /* ===========================================
5018 HTMX LOADING INDICATORS
5019 =========================================== */
5020
5021 .htmx-indicator {
5022 display: none;
5023 animation: htmx-spin 1s ease-in-out infinite;
5024 }
5025
5026 .htmx-request .htmx-indicator {
5027 display: inline;
5028 }
5029
5030 .htmx-request.htmx-indicator {
5031 display: inline;
5032 }
5033
5034 /* Loading spinner animation. Applied on .htmx-indicator above. */
5035 @keyframes htmx-spin {
5036 0% { opacity: 0.3; }
5037 50% { opacity: 1; }
5038 100% { opacity: 0.3; }
5039 }
5040
5041 /* Disable buttons during request */
5042 .htmx-request button[type="submit"] {
5043 opacity: 0.6;
5044 cursor: wait;
5045 }
5046
5047 /* ===========================================
5048 TOAST NOTIFICATIONS
5049 =========================================== */
5050
5051 .toast-container {
5052 position: fixed;
5053 bottom: 1.5rem;
5054 right: 1.5rem;
5055 z-index: var(--z-toast);
5056 display: flex;
5057 flex-direction: column;
5058 gap: var(--gap-peer);
5059 }
5060
5061 .toast {
5062 background: var(--primary-dark);
5063 color: var(--primary-light);
5064 padding: var(--gap-section);
5065 font-family: var(--font-mono);
5066 font-size: var(--text-note);
5067 box-shadow: var(--elevation-overlay);
5068 animation: toast-in 0.3s ease-out;
5069 max-width: 300px;
5070 display: flex;
5071 align-items: center;
5072 gap: var(--gap-section);
5073 }
5074
5075 .toast-dismiss {
5076 background: none;
5077 border: none;
5078 color: inherit;
5079 font-size: var(--text-lead);
5080 cursor: pointer;
5081 opacity: 0.7;
5082 padding: 0;
5083 line-height: 1;
5084 flex-shrink: 0;
5085 }
5086
5087 .toast-dismiss:hover {
5088 opacity: 1;
5089 }
5090
5091 .toast-success {
5092 background: var(--success);
5093 }
5094
5095 .toast-error {
5096 background: var(--danger);
5097 }
5098
5099 .toast-warning {
5100 background: var(--warning);
5101 }
5102
5103 .toast-info {
5104 background: var(--primary-dark);
5105 }
5106
5107 .toast-retry-btn {
5108 background: none;
5109 border: 1px solid currentColor;
5110 color: inherit;
5111 font-family: var(--font-mono);
5112 font-size: var(--text-fine);
5113 font-weight: 600;
5114 padding: var(--gap-bound) var(--gap-peer);
5115 margin-left: var(--gap-section);
5116 cursor: pointer;
5117 }
5118
5119 .toast-retry-btn:hover {
5120 background: color-mix(in oklch, var(--primary-light) 15%, transparent);
5121 }
5122
5123 .toast.fade-out {
5124 animation: toast-out 0.3s ease-in forwards;
5125 }
5126
5127 @keyframes toast-in {
5128 from {
5129 transform: translateX(100%);
5130 opacity: 0;
5131 }
5132 to {
5133 transform: translateX(0);
5134 opacity: 1;
5135 }
5136 }
5137
5138 @keyframes toast-out {
5139 from {
5140 transform: translateX(0);
5141 opacity: 1;
5142 }
5143 to {
5144 transform: translateX(100%);
5145 opacity: 0;
5146 }
5147 }
5148
5149 /* ===========================================
5150 "NEW" BADGE (data-new-until)
5151 ===========================================
5152 Applied by whats_new.js while today's date is <= the element's
5153 data-new-until. Renders a small violet dot to the right of the
5154 element's content. Pair with relative-positioned parents. */
5155
5156 .is-new {
5157 position: relative;
5158 }
5159
5160 .is-new::after {
5161 content: "";
5162 position: absolute;
5163 top: -2px;
5164 right: -8px;
5165 width: 6px;
5166 height: 6px;
5167 border-radius: var(--radius-round);
5168 background: var(--action);
5169 box-shadow: 0 0 0 2px var(--surface-page);
5170 }
5171
5172 /* ===========================================
5173 RESTART WARNING BANNER
5174 =========================================== */
5175
5176 /* Page-top behavior extensions on top of .banner.banner--warning.
5177 Color / typography come from the .banner primitive. */
5178 #restart-banner {
5179 position: fixed;
5180 top: 0;
5181 left: 0;
5182 right: 0;
5183 z-index: var(--z-banner);
5184 padding: var(--gap-group) var(--gap-section);
5185 animation: banner-slide-down 0.3s ease-out;
5186 }
5187
5188 @keyframes banner-slide-down {
5189 from {
5190 transform: translateY(-100%);
5191 }
5192 to {
5193 transform: translateY(0);
5194 }
5195 }
5196
5197 /* ===========================================
5198 SAVE STATUS INDICATORS
5199 =========================================== */
5200
5201 /* Username availability status */
5202 .username-status {
5203 display: block;
5204 margin-top: var(--gap-bound);
5205 font-size: var(--text-note);
5206 }
5207
5208 .username-status.available {
5209 color: var(--success);
5210 }
5211
5212 .username-status.taken {
5213 color: var(--danger);
5214 }
5215
5216 /* ===========================================
5217 HTMX TRANSITIONS
5218 =========================================== */
5219
5220 /* Fade in for swapped content */
5221 .htmx-added {
5222 animation: htmx-fade-in 0.2s ease-out;
5223 }
5224
5225 @keyframes htmx-fade-in {
5226 from {
5227 opacity: 0;
5228 }
5229 to {
5230 opacity: 1;
5231 }
5232 }
5233
5234 /* Settling animation for swapped content */
5235 .htmx-settling {
5236 opacity: 0.8;
5237 }
5238
5239 /* Swap animation */
5240 .htmx-swapping {
5241 opacity: 0.5;
5242 transition: opacity 0.2s ease-out;
5243 }
5244
5245 /* ===========================================
5246 ACCESSIBILITY - FOCUS STYLES
5247 =========================================== */
5248
5249 /* Focus-visible for keyboard navigation */
5250 *:focus-visible {
5251 outline: 2px solid var(--focus-ring);
5252 outline-offset: 2px;
5253 }
5254
5255 /* Remove default focus outline since we use focus-visible */
5256 *:focus:not(:focus-visible) {
5257 outline: none;
5258 }
5259
5260 /* Ensure buttons and links are keyboard accessible */
5261 button:focus-visible,
5262 a:focus-visible,
5263 input:focus-visible,
5264 select:focus-visible,
5265 textarea:focus-visible {
5266 outline: 2px solid var(--focus-ring);
5267 outline-offset: 2px;
5268 }
5269
5270 /* Tab focus is layout.css's, same ring as everything else at outline-offset
5271 2px. The local rule inset it to -2px, which is only worth doing where a
5272 strip clips its overflow; `.tabs` is flex/nowrap with no overflow set, so
5273 there was nothing to clip and the tabs read differently from every other
5274 focusable thing on the page for no reason. */
5275
5276 /* Canonical "selected" recipe (charter: docs/design-system.md).
5277 Apply `.is-selected` to any interactive container to mark it as the
5278 currently-selected option. A badge never takes it: a badge reports a status
5279 and nobody selects "Enabled", so badges take a `data-tone` instead.
5280 Visibility toggles (`.tab-content.active`, `.section-panel.active`) also
5281 keep `.active` since they're not selection state. */
5282 .is-selected {
5283 background: var(--highlight-faint);
5284 border-color: var(--focus-ring);
5285 }
5286
5287 /* Skip to main content link (for screen readers) */
5288 .skip-to-main {
5289 position: absolute;
5290 top: -100px;
5291 left: 0;
5292 background: var(--primary-dark);
5293 color: var(--primary-light);
5294 padding: 8px;
5295 z-index: var(--z-skip-link);
5296 transition: top 0.2s ease;
5297 }
5298
5299 .skip-to-main:focus {
5300 top: 0;
5301 }
5302
5303 /* Visually hidden but accessible to screen readers */
5304 .sr-only {
5305 position: absolute;
5306 width: 1px;
5307 height: 1px;
5308 padding: 0;
5309 margin: -1px;
5310 overflow: hidden;
5311 clip: rect(0, 0, 0, 0);
5312 white-space: nowrap;
5313 border: 0;
5314 }
5315
5316 /* Status indicator with text for accessibility */
5317 .status-indicator::after {
5318 content: attr(data-status);
5319 position: absolute;
5320 width: 1px;
5321 height: 1px;
5322 padding: 0;
5323 margin: -1px;
5324 overflow: hidden;
5325 clip: rect(0, 0, 0, 0);
5326 white-space: nowrap;
5327 border: 0;
5328 }
5329
5330 /* ===========================================
5331 PAYWALL
5332 =========================================== */
5333
5334 .paywall-section {
5335 margin: var(--gap-page) 0;
5336 padding: var(--gap-page);
5337 background: var(--surface-sunken);
5338 text-align: center;
5339 }
5340
5341 .paywall-box h2 {
5342 font-family: var(--font-display);
5343 font-weight: normal;
5344 margin-bottom: var(--gap-peer);
5345 }
5346
5347 .paywall-price {
5348 font-family: var(--font-mono);
5349 font-size: var(--text-head);
5350 margin-bottom: var(--gap-peer);
5351 }
5352
5353 .paywall-desc {
5354 font-family: var(--font-sans);
5355 color: var(--content-muted);
5356 margin-bottom: var(--gap-pane);
5357 }
5358
5359 .paywall-note {
5360 font-family: var(--font-mono);
5361 font-size: var(--text-note);
5362 color: var(--content-muted);
5363 margin-top: var(--gap-section);
5364 }
5365
5366 .paywall-note a {
5367 color: var(--action);
5368 }
5369
5370 /* ===========================================
5371 MODALS
5372 =========================================== */
5373
5374 .modal-overlay {
5375 position: fixed;
5376 inset: 0;
5377 background: var(--overlay);
5378 display: flex;
5379 align-items: center;
5380 justify-content: center;
5381 z-index: var(--z-modal);
5382 }
5383
5384 .modal {
5385 background: var(--surface-page);
5386 padding: var(--gap-page);
5387 max-width: 500px;
5388 width: 90%;
5389 max-height: 90vh;
5390 overflow-y: auto;
5391 }
5392
5393 .modal h2 {
5394 margin-bottom: var(--gap-pane);
5395 }
5396
5397 .modal .form-actions {
5398 display: flex;
5399 gap: var(--gap-section);
5400 justify-content: flex-end;
5401 margin-top: var(--gap-pane);
5402 }
5403
5404 /* ===========================================
5405 LINK ROWS (Dashboard Links Section)
5406 =========================================== */
5407
5408 .link-row {
5409 display: flex;
5410 gap: var(--gap-section);
5411 align-items: center;
5412 margin-bottom: var(--gap-section);
5413 padding: var(--gap-section);
5414 background: var(--surface-sunken);
5415 transition: opacity 0.2s ease, background 0.2s ease;
5416 }
5417
5418 .link-row input[type="text"],
5419 .link-row input[type="url"] {
5420 flex: 1;
5421 min-width: 0;
5422 }
5423
5424 .link-row .btn-danger {
5425 padding: var(--gap-peer) var(--gap-section);
5426 font-size: var(--text-note);
5427 }
5428
5429 /* Link order buttons */
5430 .link-order-buttons {
5431 display: flex;
5432 flex-direction: column;
5433 gap: 2px;
5434 }
5435
5436 .order-btn {
5437 background: var(--surface-sunken);
5438 border: 1px solid var(--border);
5439 padding: var(--gap-bound) var(--gap-peer);
5440 font-size: var(--text-fine);
5441 line-height: 1;
5442 cursor: pointer;
5443 opacity: 0.6;
5444 transition: opacity 0.15s ease, background 0.15s ease;
5445 }
5446
5447 .order-btn:hover {
5448 opacity: 1;
5449 background: var(--border);
5450 }
5451
5452 .order-btn:active {
5453 box-shadow: var(--shadow-inset);
5454 }
5455
5456 /* ===========================================
5457 TEXT EDITOR
5458 =========================================== */
5459
5460 .text-editor {
5461 width: 100%;
5462 }
5463
5464 .editor-footer {
5465 display: flex;
5466 justify-content: space-between;
5467 padding: var(--gap-peer) var(--gap-section);
5468 background: var(--surface-sunken);
5469 font-size: var(--text-note);
5470 opacity: 0.7;
5471 }
5472
5473 /* ===========================================
5474 RICH FIELD (crate::quasi::rich_field)
5475 =========================================== */
5476
5477 /* The wrapper is this app's; the group inside it is the renderer's, and
5478 `static/layout.css` carries every rule that decides what is showing. What is
5479 left for this file is the two things a description does not say: how tall the
5480 editor stands, and how the rendered preview reads.
5481
5482 Selected through the `[data-format="markdown"]` mark rather than through
5483 makeover's class names, so nothing here overlaps the generated sheet. */
5484
5485 .rich-field [data-format="markdown"] > textarea {
5486 padding: var(--gap-section);
5487 font-family: var(--font-mono);
5488 font-size: var(--text-note);
5489 line-height: 1.6;
5490 resize: vertical;
5491 border: none;
5492 background: var(--surface-sunken);
5493 }
5494
5495 /* Three sizes for four surfaces: the item editor and the blog editor were
5496 already the same surface written two ways (400px of CSS, and rows="20"). */
5497 .rich-field--compact [data-format="markdown"] > textarea,
5498 .rich-field--compact [data-editor-preview] {
5499 min-height: 9rem;
5500 }
5501
5502 .rich-field--standard [data-format="markdown"] > textarea,
5503 .rich-field--standard [data-editor-preview] {
5504 min-height: 15rem;
5505 }
5506
5507 .rich-field--tall [data-format="markdown"] > textarea,
5508 .rich-field--tall [data-editor-preview] {
5509 min-height: 400px;
5510 }
5511
5512 /* The pane stands where the control stood, so it reads as prose rather than as
5513 source. The rules below are the retired `.markdown-preview` set, unchanged
5514 except for what selects them. */
5515 .rich-field [data-editor-preview] {
5516 padding: var(--gap-pane);
5517 font-family: var(--font-sans);
5518 line-height: 1.8;
5519 }
5520
5521 .rich-field [data-editor-preview] h1,
5522 .rich-field [data-editor-preview] h2,
5523 .rich-field [data-editor-preview] h3 {
5524 margin-top: var(--gap-pane);
5525 margin-bottom: var(--gap-peer);
5526 }
5527
5528 .rich-field [data-editor-preview] h1 {
5529 font-size: var(--text-title);
5530 }
5531
5532 .rich-field [data-editor-preview] h2 {
5533 font-size: var(--text-head);
5534 }
5535
5536 .rich-field [data-editor-preview] h3 {
5537 font-size: var(--text-lead);
5538 }
5539
5540 .rich-field [data-editor-preview] p {
5541 margin-bottom: var(--gap-section);
5542 }
5543
5544 .rich-field [data-editor-preview] code {
5545 background: var(--surface-sunken);
5546 padding: var(--gap-bound);
5547 font-family: var(--font-mono);
5548 font-size: var(--text-note);
5549 }
5550
5551 /* ===========================================
5552 AUDIO UPLOAD
5553 =========================================== */
5554
5555 .audio-upload {
5556 width: 100%;
5557 }
5558
5559 .current-audio {
5560 display: flex;
5561 justify-content: space-between;
5562 align-items: center;
5563 padding: var(--gap-section);
5564 background: var(--surface-sunken);
5565 margin-bottom: var(--gap-section);
5566 }
5567
5568 .audio-info {
5569 display: flex;
5570 align-items: center;
5571 gap: var(--gap-section);
5572 }
5573
5574 .audio-icon {
5575 font-size: var(--text-head);
5576 opacity: 0.6;
5577 }
5578
5579 .audio-details {
5580 display: flex;
5581 flex-direction: column;
5582 }
5583
5584 .audio-filename {
5585 font-family: var(--font-mono);
5586 font-size: var(--text-note);
5587 }
5588
5589 .audio-duration {
5590 font-size: var(--text-note);
5591 opacity: 0.6;
5592 }
5593
5594 .upload-area .file-upload-area {
5595 padding: var(--gap-page);
5596 }
5597
5598 .upload-icon {
5599 font-size: var(--text-title);
5600 opacity: 0.4;
5601 margin-bottom: var(--gap-peer);
5602 }
5603
5604 .upload-text {
5605 font-size: var(--text-lead);
5606 margin-bottom: var(--gap-peer);
5607 }
5608
5609 .upload-hint {
5610 font-size: var(--text-note);
5611 opacity: 0.6;
5612 }
5613
5614 .upload-progress {
5615 padding: var(--gap-pane);
5616 background: var(--surface-sunken);
5617 }
5618
5619 .progress-info {
5620 display: flex;
5621 justify-content: space-between;
5622 margin-bottom: var(--gap-section);
5623 font-size: var(--text-note);
5624 }
5625
5626 /* Canonical progress bar primitive. One of these in the codebase.
5627 - .progress-bar-container is the track (8px default; --slim = 6px)
5628 - .progress-bar is the fill (success-green default; --highlight = purple)
5629 - Add .progress-bar-container--rounded for the 3px-radius pill variant
5630 Spacing (mb-4 / mt-2 etc.) is added by the caller, not baked in. */
5631 .progress-bar-container {
5632 height: 8px;
5633 background: var(--border);
5634 overflow: hidden;
5635 }
5636 .progress-bar-container--slim { height: 6px; }
5637 .progress-bar-container--rounded { border-radius: var(--radius-control); }
5638
5639 /* Fill proportion comes from the template as --fill, because it is
5640 per-render data. The property that reads it lives here. */
5641 .progress-bar {
5642 width: var(--fill, 0%);
5643 height: 100%;
5644 background: var(--success);
5645 transition: width 0.2s ease;
5646 }
5647 .progress-bar--highlight { background: var(--action); }
5648 .progress-bar--warn { background: var(--warning); }
5649 .progress-bar--danger { background: var(--danger); }
5650
5651 /* Upload status block (filename + percent label + progress bar). One of these
5652 in the codebase. Used by wizard upload flows and standalone audio/video upload partials. */
5653 .upload-status { margin-top: var(--gap-peer); }
5654 .upload-status-row {
5655 display: flex;
5656 align-items: center;
5657 gap: var(--gap-peer);
5658 }
5659 .upload-status-row span { font-size: var(--text-note); }
5660 .upload-status-msg { margin-top: var(--gap-peer); }
5661 .upload-status-msg.is-success { color: var(--success); }
5662 .upload-status-msg.is-error { color: var(--danger); }
5663
5664 /* Inline progress cell rendered inside a table-row status cell (batch uploads). */
5665 .upload-progress-row {
5666 display: flex;
5667 align-items: center;
5668 gap: var(--gap-peer);
5669 }
5670 .upload-progress-row .progress-bar-container { flex: 1; min-width: 60px; }
5671 .upload-progress-pct {
5672 font-size: var(--text-fine);
5673 min-width: 2.5em;
5674 text-align: right;
5675 }
5676
5677 .upload-success {
5678 padding: var(--gap-pane);
5679 background: var(--surface-sunken);
5680 text-align: center;
5681 }
5682 .upload-error {
5683 padding: var(--gap-pane);
5684 background: var(--surface-sunken);
5685 text-align: center;
5686 }
5687
5688 .hidden {
5689 display: none !important;
5690 }
5691
5692 /* ===========================================
5693 VIEW TOGGLE (Grid/List)
5694 =========================================== */
5695
5696 .view-controls {
5697 display: flex;
5698 gap: var(--gap-bound);
5699 margin-left: auto;
5700 }
5701
5702 .view-btn {
5703 background: var(--surface-sunken);
5704 border: none;
5705 padding: var(--gap-peer) var(--gap-group);
5706 font-family: var(--font-mono);
5707 font-size: var(--text-note);
5708 cursor: pointer;
5709 opacity: 0.5;
5710 transition: opacity 0.2s ease;
5711 }
5712
5713 .view-btn:hover {
5714 opacity: 0.8;
5715 }
5716
5717 .view-btn.is-selected {
5718 opacity: 1;
5719 background: var(--primary-dark);
5720 color: var(--primary-light);
5721 }
5722 .results-container.results-list .results-table {
5723 display: block;
5724 }
5725
5726 .results-container.results-list .results-grid {
5727 display: none;
5728 }
5729
5730 .results-container.results-grid .results-table {
5731 display: none;
5732 }
5733
5734 .results-container.results-grid .results-grid {
5735 display: grid;
5736 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
5737 gap: var(--gap-pane);
5738 border: 1px solid var(--border);
5739 border-top: none;
5740 padding: var(--gap-pane);
5741 background: var(--surface-overlay);
5742 }
5743
5744 /* Grid cards */
5745 .grid-card {
5746 background: var(--surface-overlay);
5747 text-decoration: none;
5748 color: var(--content);
5749 display: flex;
5750 flex-direction: column;
5751 transition: background 0.2s ease;
5752 }
5753
5754 .grid-card:hover {
5755 background: var(--surface-sunken);
5756 text-decoration: none;
5757 }
5758
5759 .grid-card-thumbnail {
5760 height: 120px;
5761 background: var(--border);
5762 display: flex;
5763 align-items: center;
5764 justify-content: center;
5765 font-size: var(--text-fine);
5766 opacity: 0.5;
5767 text-transform: uppercase;
5768 }
5769
5770 .grid-card-content {
5771 padding: var(--gap-section);
5772 display: flex;
5773 flex-direction: column;
5774 gap: var(--gap-bound);
5775 }
5776
5777 .grid-card-title {
5778 font-family: var(--font-display);
5779 font-size: var(--text-lead);
5780 }
5781
5782 .grid-card-meta {
5783 font-size: var(--text-note);
5784 opacity: 0.6;
5785 }
5786
5787 .grid-card-footer {
5788 display: flex;
5789 justify-content: space-between;
5790 align-items: center;
5791 margin-top: var(--gap-peer);
5792 font-size: var(--text-note);
5793 }
5794
5795 .grid-card-category {
5796 opacity: 0.6;
5797 }
5798
5799 .grid-card-price {
5800 font-weight: bold;
5801 }
5802
5803 .grid-card-stat {
5804 opacity: 0.6;
5805 }
5806
5807 /* ===========================================
5808 PROJECT PAGE VIEW TOGGLE
5809 =========================================== */
5810
5811 .items-header {
5812 display: flex;
5813 justify-content: space-between;
5814 align-items: center;
5815 margin-bottom: var(--gap-pane);
5816 }
5817
5818 .items-header .section-header {
5819 margin-bottom: 0;
5820 border-bottom: none;
5821 padding-bottom: 0;
5822 }
5823
5824 /* Grid view is default on project page */
5825 .items-container.items-grid-view .items-grid {
5826 display: grid;
5827 }
5828
5829 .items-container.items-grid-view .items-list {
5830 display: none;
5831 }
5832
5833 .items-container.items-list-view .items-grid {
5834 display: none;
5835 }
5836
5837 .items-container.items-list-view .items-list {
5838 display: block;
5839 }
5840
5841 /* List items styling */
5842 .items-list {
5843 display: none;
5844 }
5845
5846 .list-item {
5847 display: flex;
5848 justify-content: space-between;
5849 align-items: center;
5850 padding: var(--gap-section);
5851 background: var(--surface-overlay);
5852 margin-bottom: var(--gap-peer);
5853 text-decoration: none;
5854 color: var(--content);
5855 transition: background 0.2s ease;
5856 }
5857
5858 .list-item:hover {
5859 background: var(--surface-sunken);
5860 text-decoration: none;
5861 }
5862
5863 .list-item-info {
5864 display: flex;
5865 flex-direction: column;
5866 gap: var(--gap-bound);
5867 }
5868
5869 .list-item-title {
5870 font-family: var(--font-display);
5871 font-size: var(--text-lead);
5872 }
5873
5874 .list-item-meta {
5875 font-size: var(--text-note);
5876 opacity: 0.6;
5877 }
5878
5879 .list-item-price {
5880 font-size: var(--text-lead);
5881 font-weight: bold;
5882 }
5883
5884 /* ===========================================
5885 LANDING PAGE
5886 =========================================== */
5887
5888 .landing-hero {
5889 max-width: 720px;
5890 width: 100%;
5891 text-align: center;
5892 }
5893
5894 .landing-headline {
5895 font-family: var(--font-display);
5896 font-size: var(--text-head);
5897 margin-bottom: var(--gap-peer);
5898 }
5899
5900 .landing-sub {
5901 font-size: var(--text-body);
5902 line-height: 1.6;
5903 opacity: 0.75;
5904 margin-bottom: 0;
5905 }
5906
5907 .fork-revenue-line {
5908 font-size: var(--text-note);
5909 opacity: 0.8;
5910 margin: var(--gap-section) 0 var(--gap-peer);
5911 line-height: 1.5;
5912 }
5913
5914 .fork-revenue-line a {
5915 border-bottom: 1px solid transparent;
5916 transition: border-color 0.2s ease;
5917 }
5918
5919 .fork-revenue-line a:hover {
5920 border-bottom-color: currentColor;
5921 }
5922
5923 .landing-fork {
5924 display: grid;
5925 grid-template-columns: 1fr 1fr;
5926 gap: var(--gap-section);
5927 margin-top: var(--gap-page);
5928 text-align: left;
5929 }
5930
5931 .fork-heading {
5932 font-family: var(--font-mono);
5933 font-size: var(--text-note);
5934 font-weight: 600;
5935 text-transform: uppercase;
5936 letter-spacing: 0.05em;
5937 margin-bottom: var(--gap-section);
5938 }
5939
5940 .fork-list {
5941 list-style: none;
5942 margin: 0;
5943 padding: 0;
5944 flex: 1;
5945 }
5946
5947 .fork-list li {
5948 margin-bottom: var(--gap-group);
5949 font-size: var(--text-note);
5950 line-height: 1.5;
5951 }
5952
5953 .fork-list strong {
5954 font-family: var(--font-mono);
5955 font-size: var(--text-fine);
5956 }
5957
5958 .fork-actions {
5959 margin-top: auto;
5960 padding-top: var(--gap-section);
5961 display: flex;
5962 flex-direction: column;
5963 align-items: center;
5964 gap: var(--gap-peer);
5965 }
5966
5967 .fork-actions .big-button {
5968 width: 100%;
5969 }
5970
5971 .fork-secondary-link {
5972 font-family: var(--font-mono);
5973 font-size: var(--text-fine);
5974 opacity: 0.6;
5975 transition: opacity 0.2s ease;
5976 }
5977
5978 .fork-secondary-link:hover {
5979 opacity: 1;
5980 }
5981
5982 .fork-lede {
5983 font-size: var(--text-body);
5984 line-height: 1.55;
5985 margin: 0 0 var(--gap-section);
5986 }
5987
5988 .landing-browse-cta {
5989 text-align: center;
5990 margin: var(--gap-section) 0 0;
5991 }
5992
5993 .landing-mobile-note {
5994 text-align: center;
5995 margin: var(--gap-peer) 0 0;
5996 font-size: var(--text-fine);
5997 color: var(--content-muted);
5998 }
5999
6000 .founder-tagline {
6001 font-family: var(--font-sans);
6002 font-size: var(--text-body);
6003 line-height: 1.55;
6004 margin: var(--gap-section) auto 0;
6005 max-width: 560px;
6006 color: var(--content);
6007 display: flex;
6008 flex-direction: column;
6009 align-items: center;
6010 gap: var(--gap-peer);
6011 }
6012
6013 .landing-velocity {
6014 font-family: var(--font-mono);
6015 font-size: var(--text-fine);
6016 text-align: center;
6017 margin: var(--gap-section) auto 0;
6018 color: var(--content-muted);
6019 }
6020
6021 .landing-velocity a {
6022 color: var(--content);
6023 text-decoration: none;
6024 border-bottom: 1px solid var(--border);
6025 }
6026
6027 .landing-velocity a:hover {
6028 border-bottom-color: var(--content);
6029 }
6030
6031 .founder-tagline-detail {
6032 display: block;
6033 }
6034
6035 .founder-tagline-mark {
6036 font-family: var(--font-mono);
6037 font-size: var(--text-fine);
6038 font-weight: 600;
6039 text-transform: uppercase;
6040 letter-spacing: 0.08em;
6041 color: var(--action);
6042 padding: var(--gap-bound) var(--gap-peer);
6043 border: 1px solid var(--action);
6044 border-radius: var(--radius-control);
6045 white-space: nowrap;
6046 }
6047
6048 .fork-card--founder {
6049 border-color: var(--action);
6050 box-shadow: 0 0 0 1px var(--highlight-faint);
6051 }
6052
6053 .founder-tier-grid {
6054 display: grid;
6055 grid-template-columns: 1fr 1fr;
6056 gap: var(--gap-peer) var(--gap-section);
6057 margin-bottom: var(--gap-section);
6058 padding-bottom: var(--gap-section);
6059 border-bottom: 1px dashed var(--border);
6060 }
6061
6062 .founder-tier {
6063 display: flex;
6064 flex-direction: column;
6065 gap: var(--gap-bound);
6066 }
6067
6068 .founder-tier-name {
6069 font-family: var(--font-mono);
6070 font-size: var(--text-fine);
6071 font-weight: 600;
6072 text-transform: uppercase;
6073 letter-spacing: 0.05em;
6074 opacity: 0.7;
6075 }
6076
6077 .founder-tier-price {
6078 font-family: var(--font-display);
6079 font-size: var(--text-body);
6080 line-height: 1.1;
6081 }
6082
6083 .founder-tier-price del {
6084 opacity: 0.4;
6085 margin-right: var(--gap-bound);
6086 font-weight: 400;
6087 }
6088
6089 .founder-tier-price strong {
6090 color: var(--action);
6091 font-weight: 700;
6092 }
6093
6094 .founder-tier-unit {
6095 font-family: var(--font-mono);
6096 font-size: var(--text-fine);
6097 opacity: 0.6;
6098 margin-left: var(--gap-bound);
6099 }
6100
6101 .founder-slots {
6102 font-family: var(--font-mono);
6103 font-size: var(--text-fine);
6104 margin: 0 0 var(--gap-section);
6105 line-height: 1.3;
6106 }
6107
6108 .founder-slots-number {
6109 font-family: var(--font-display);
6110 font-size: var(--text-head);
6111 color: var(--action);
6112 font-weight: 700;
6113 margin-right: var(--gap-bound);
6114 }
6115
6116 .founder-slots-cap {
6117 opacity: 0.55;
6118 margin-left: var(--gap-bound);
6119 }
6120
6121 .founder-slots--cap {
6122 opacity: 0.7;
6123 font-size: var(--text-fine);
6124 }
6125
6126 .landing-stats-line {
6127 font-family: var(--font-mono);
6128 font-size: var(--text-note);
6129 opacity: 0.6;
6130 margin-top: var(--gap-page);
6131 }
6132
6133 .landing-stats-line a {
6134 opacity: 0.8;
6135 transition: opacity 0.2s ease;
6136 }
6137
6138 .landing-stats-line a:hover {
6139 opacity: 1;
6140 }
6141
6142 .landing-principles {
6143 margin-top: var(--gap-page);
6144 width: 100%;
6145 }
6146
6147 .landing-use-cases {
6148 margin-top: var(--gap-page);
6149 width: 100%;
6150 }
6151
6152 .use-case-line {
6153 font-family: var(--font-mono);
6154 font-size: var(--text-note);
6155 opacity: 0.7;
6156 letter-spacing: 0.02em;
6157 }
6158
6159 .landing-prose {
6160 font-size: var(--text-body);
6161 line-height: 1.7;
6162 margin-bottom: var(--gap-peer);
6163 }
6164
6165 .notify-form {
6166 display: flex;
6167 gap: var(--gap-peer);
6168 max-width: 400px;
6169 margin: 0 auto;
6170 }
6171
6172 .notify-input {
6173 flex: 1;
6174 padding: var(--gap-peer) var(--gap-section);
6175 border: 1px solid var(--border);
6176 border-radius: var(--radius-control);
6177 font-family: var(--font-sans);
6178 font-size: var(--text-note);
6179 background: var(--surface-page);
6180 color: var(--content);
6181 }
6182
6183 .notify-status {
6184 font-family: var(--font-mono);
6185 font-size: var(--text-fine);
6186 text-align: center;
6187 margin-top: var(--gap-peer);
6188 min-height: 1.2em;
6189 }
6190
6191 .notify-status.success {
6192 color: var(--content);
6193 }
6194
6195 .notify-status.error {
6196 color: var(--danger);
6197 }
6198
6199 .section-label {
6200 font-family: var(--font-mono);
6201 font-size: var(--text-note);
6202 letter-spacing: 0.05em;
6203 text-transform: uppercase;
6204 opacity: 0.6;
6205 margin-bottom: var(--gap-section);
6206 }
6207
6208 .tier-section {
6209 margin: var(--gap-page) 0;
6210 width: 100%;
6211 }
6212 /* Marketing pricing variant of .card--bordered. */
6213 .tier-name {
6214 font-family: var(--font-mono);
6215 font-size: var(--text-note);
6216 font-weight: 600;
6217 margin-bottom: var(--gap-bound);
6218 }
6219
6220 .tier-price {
6221 font-family: var(--font-display);
6222 font-size: var(--text-lead);
6223 margin-bottom: var(--gap-bound);
6224 }
6225
6226 .tier-desc {
6227 font-size: var(--text-note);
6228 opacity: 0.7;
6229 }
6230
6231 .tier-card.planned {
6232 opacity: 0.5;
6233 border-style: dashed;
6234 position: relative;
6235 }
6236 .landing-cta {
6237 display: flex;
6238 align-items: center;
6239 justify-content: center;
6240 gap: var(--gap-page);
6241 width: fit-content;
6242 margin: var(--gap-pane) auto var(--gap-page);
6243 }
6244 .secondary-links {
6245 display: flex;
6246 gap: var(--gap-pane);
6247 justify-content: center;
6248 margin-top: var(--gap-section);
6249 }
6250
6251 .secondary-links a {
6252 font-size: var(--text-note);
6253 opacity: 0.6;
6254 transition: opacity 0.2s ease;
6255 }
6256
6257 .secondary-links a:hover {
6258 opacity: 1;
6259 }
6260 .section-link {
6261 display: block;
6262 margin-top: var(--gap-section);
6263 font-family: var(--font-mono);
6264 font-size: var(--text-fine);
6265 opacity: 0.55;
6266 transition: opacity 0.2s ease;
6267 }
6268
6269 .section-link:hover {
6270 opacity: 1;
6271 }
6272
6273 .feature-grid {
6274 display: grid;
6275 grid-template-columns: 1fr 1fr;
6276 gap: var(--gap-section);
6277 text-align: left;
6278 }
6279
6280 .feature-name {
6281 font-family: var(--font-mono);
6282 font-size: var(--text-note);
6283 font-weight: 600;
6284 margin-bottom: var(--gap-bound);
6285 }
6286
6287 .feature-desc {
6288 font-size: var(--text-note);
6289 opacity: 0.7;
6290 }
6291
6292 .how-list {
6293 list-style: none;
6294 margin: 0;
6295 padding: 0;
6296 text-align: left;
6297 }
6298
6299 .how-list li {
6300 margin-bottom: var(--gap-group);
6301 font-size: var(--text-body);
6302 }
6303
6304 .how-list strong {
6305 font-family: var(--font-mono);
6306 font-size: var(--text-note);
6307 }
6308
6309 .do-rail {
6310 list-style: none;
6311 margin: var(--gap-page) 0 var(--gap-pane);
6312 padding: 0;
6313 display: grid;
6314 /* A do-card stops being readable under about 200px, which is a fact about
6315 the card. Three across is what that yields at full width. */
6316 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
6317 gap: var(--gap-section);
6318 width: 100%;
6319 }
6320
6321 .do-card {
6322 background: transparent;
6323 border: 1px solid var(--border);
6324 padding: var(--gap-section);
6325 text-align: left;
6326 display: flex;
6327 flex-direction: column;
6328 }
6329
6330 .do-card-title {
6331 font-family: var(--font-mono);
6332 font-size: var(--text-note);
6333 font-weight: 600;
6334 text-transform: uppercase;
6335 letter-spacing: 0.05em;
6336 margin: 0 0 var(--gap-peer);
6337 }
6338
6339 .do-card-lede {
6340 font-size: var(--text-note);
6341 line-height: 1.5;
6342 opacity: 0.78;
6343 margin: 0 0 var(--gap-section);
6344 }
6345
6346 .do-card-links {
6347 list-style: none;
6348 margin: auto 0 0;
6349 padding: var(--gap-section) 0 0;
6350 border-top: 1px solid var(--border);
6351 display: flex;
6352 flex-wrap: wrap;
6353 gap: var(--gap-peer) var(--gap-section);
6354 }
6355
6356 .do-card-links a {
6357 font-size: var(--text-note);
6358 border-bottom: 1px solid transparent;
6359 transition: border-color 0.2s ease;
6360 }
6361
6362 .do-card-links a:hover {
6363 border-bottom-color: currentColor;
6364 }
6365
6366 .do-card--wide {
6367 margin-top: var(--gap-page);
6368 width: 100%;
6369 }
6370
6371 .contrast-rail {
6372 list-style: none;
6373 margin: var(--gap-section) 0 0;
6374 padding: 0;
6375 display: grid;
6376 /* Title plus a short link list, so it holds up narrower than a do-card.
6377 Four across at full width. */
6378 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
6379 gap: var(--gap-section);
6380 width: 100%;
6381 }
6382
6383 .contrast-card {
6384 border-top: 1px solid var(--border);
6385 padding: var(--gap-section) var(--gap-bound) 0;
6386 text-align: left;
6387 }
6388
6389 .contrast-card-title {
6390 font-family: var(--font-mono);
6391 font-size: var(--text-fine);
6392 letter-spacing: 0.04em;
6393 text-transform: uppercase;
6394 opacity: 0.5;
6395 text-decoration: line-through;
6396 text-decoration-thickness: 1px;
6397 margin: 0 0 var(--gap-peer);
6398 font-weight: 600;
6399 }
6400
6401 .contrast-card-links {
6402 list-style: none;
6403 margin: 0;
6404 padding: 0;
6405 display: flex;
6406 flex-wrap: wrap;
6407 gap: var(--gap-peer) var(--gap-section);
6408 }
6409
6410 .contrast-card-links a {
6411 font-size: var(--text-note);
6412 border-bottom: 1px solid transparent;
6413 transition: border-color 0.2s ease;
6414 }
6415
6416 .contrast-card-links a:hover {
6417 border-bottom-color: currentColor;
6418 }
6419
6420 .explore-links {
6421 display: flex;
6422 flex-wrap: wrap;
6423 gap: var(--gap-section) var(--gap-pane);
6424 justify-content: center;
6425 }
6426
6427 .explore-links a {
6428 font-family: var(--font-mono);
6429 font-size: var(--text-note);
6430 opacity: 0.7;
6431 transition: opacity 0.2s ease;
6432 }
6433
6434 .explore-links a:hover {
6435 opacity: 1;
6436 }
6437
6438 /* ===========================================
6439 PRICING CALCULATOR
6440 =========================================== */
6441
6442 .pricing-page {
6443 max-width: 800px;
6444 }
6445
6446 .pricing-input-wrap {
6447 display: flex;
6448 align-items: baseline;
6449 justify-content: center;
6450 gap: var(--gap-bound);
6451 }
6452
6453 .pricing-currency {
6454 font-family: var(--font-display);
6455 font-size: var(--text-display);
6456 opacity: 0.5;
6457 }
6458
6459 .pricing-input {
6460 font-family: var(--font-display);
6461 font-size: var(--text-display);
6462 width: 5ch;
6463 text-align: center;
6464 background: transparent;
6465 border: none;
6466 border-bottom: 2px solid var(--border);
6467 color: var(--content);
6468 padding: var(--gap-bound) 0;
6469 -moz-appearance: textfield;
6470 }
6471
6472 .pricing-input::-webkit-inner-spin-button,
6473 .pricing-input::-webkit-outer-spin-button {
6474 -webkit-appearance: none;
6475 margin: 0;
6476 }
6477
6478 .pricing-input:focus {
6479 outline: none;
6480 border-bottom-color: var(--action);
6481 }
6482
6483 .pricing-period {
6484 font-family: var(--font-mono);
6485 font-size: var(--text-body);
6486 opacity: 0.5;
6487 }
6488
6489 /* <mnw-price-mode> is a custom element (default display:inline); it wraps the
6490 toggle and the tier grid, so it has to lay out as a block. */
6491 mnw-price-mode {
6492 display: block;
6493 }
6494
6495 /* List-vs-founder toggle above the tier cards, rendered only while the founder
6496 window is open. Segmented control rather than a checkbox: both options are
6497 real prices the visitor can be shown, and neither is an "off" state. Follows
6498 .tier-option's pattern of hiding the radio and styling the label off
6499 :has(:checked), so the selection needs no JS class. */
6500 .price-mode {
6501 display: inline-flex;
6502 gap: 0;
6503 margin-bottom: var(--gap-section);
6504 border: 1px solid var(--border);
6505 border-radius: var(--radius-control);
6506 overflow: hidden;
6507 }
6508
6509 .price-mode-option {
6510 cursor: pointer;
6511 font-family: var(--font-mono);
6512 font-size: var(--text-note);
6513 padding: var(--gap-bound) var(--gap-section);
6514 transition: background-color 0.15s ease;
6515 }
6516
6517 .price-mode-option input[type="radio"] {
6518 position: absolute;
6519 opacity: 0;
6520 pointer-events: none;
6521 }
6522
6523 .price-mode-option:has(input[type="radio"]:checked) {
6524 background: var(--action);
6525 color: var(--content-on-action);
6526 }
6527
6528 .price-mode-option:focus-within {
6529 outline: 2px solid var(--focus-ring);
6530 outline-offset: -2px;
6531 }
6532
6533 .tier-selector {
6534 display: grid;
6535 grid-template-columns: 1fr 1fr;
6536 gap: var(--gap-section);
6537 text-align: left;
6538 }
6539
6540 .tier-option {
6541 cursor: pointer;
6542 transition: border-color 0.15s ease;
6543 }
6544
6545 .tier-option input[type="radio"] {
6546 position: absolute;
6547 opacity: 0;
6548 pointer-events: none;
6549 }
6550
6551 .tier-option:focus-within {
6552 outline: 2px solid var(--focus-ring);
6553 outline-offset: 2px;
6554 }
6555
6556 /* Selected tier, driven purely by the checked radio (no JS toggling a
6557 class). The `.is-selected` alias is retained for any server-rendered use. */
6558 .tier-option.is-selected,
6559 .tier-option:has(input[type="radio"]:checked) {
6560 border-color: var(--action);
6561 border-width: 2px;
6562 padding: calc(var(--gap-section) - 1px);
6563 }
6564
6565 /* Two dials side by side (price + volume, then the other platform's two).
6566 Stacks on narrow screens via the mobile block below. */
6567 .calc-dials {
6568 display: grid;
6569 grid-template-columns: 1fr 1fr;
6570 gap: var(--gap-section);
6571 align-items: end;
6572 }
6573
6574 .calc-dial {
6575 display: flex;
6576 flex-direction: column;
6577 align-items: center;
6578 gap: var(--gap-bound);
6579 }
6580
6581 .calc-dial-label {
6582 font-family: var(--font-mono);
6583 font-size: var(--text-note);
6584 opacity: 0.7;
6585 }
6586
6587 /* Who comes out ahead at the current dials. The three states are the only
6588 place the calculator uses color to make a claim, so they carry the same
6589 weight in both directions: losing is stated as plainly as winning. */
6590 .calc-verdict {
6591 font-family: var(--font-display);
6592 font-size: var(--text-title);
6593 text-align: center;
6594 padding: var(--gap-pane);
6595 background: var(--surface-overlay);
6596 border: 1px solid var(--border);
6597 border-left-width: 3px;
6598 }
6599
6600 .verdict--ahead {
6601 border-left-color: var(--success);
6602 }
6603
6604 .verdict--even {
6605 border-left-color: var(--border);
6606 }
6607
6608 .verdict--behind {
6609 border-left-color: var(--warning);
6610 }
6611
6612 .calc-table th[scope="row"] {
6613 text-align: left;
6614 font-weight: 400;
6615 opacity: 0.7;
6616 }
6617
6618 .calc-table .calc-gross,
6619 .calc-keep-row td {
6620 font-family: var(--font-mono);
6621 }
6622
6623 .calc-keep-row td {
6624 font-size: var(--text-title);
6625 }
6626
6627 /* The win-region bar. Two segments tile the full width; the server sends both
6628 widths as percentages, so nothing here is computed in the browser. */
6629 .calc-scale {
6630 display: flex;
6631 flex-direction: column;
6632 gap: var(--gap-bound);
6633 }
6634
6635 .calc-bar {
6636 position: relative;
6637 display: flex;
6638 height: 1.5rem;
6639 border: 1px solid var(--border);
6640 overflow: hidden;
6641 }
6642
6643 .calc-bar-seg {
6644 height: 100%;
6645 }
6646
6647 .calc-bar-seg--other {
6648 background: var(--warning);
6649 opacity: 0.35;
6650 }
6651
6652 .calc-bar-seg--mnw {
6653 background: var(--success);
6654 opacity: 0.35;
6655 }
6656
6657 /* "You are here" on the sales axis. */
6658 .calc-marker {
6659 position: absolute;
6660 top: 0;
6661 bottom: 0;
6662 width: 2px;
6663 margin-left: -1px;
6664 background: var(--content);
6665 }
6666
6667 .calc-crossover {
6668 position: absolute;
6669 top: 0;
6670 bottom: 0;
6671 width: 1px;
6672 background: var(--content);
6673 opacity: 0.4;
6674 }
6675
6676 .calc-axis {
6677 display: flex;
6678 justify-content: space-between;
6679 gap: var(--gap-bound);
6680 font-family: var(--font-mono);
6681 font-size: var(--text-fine);
6682 opacity: 0.6;
6683 }
6684
6685 .calc-axis-crossover {
6686 opacity: 0.9;
6687 }
6688
6689 .calc-legend {
6690 display: flex;
6691 flex-wrap: wrap;
6692 gap: var(--gap-section);
6693 font-size: var(--text-fine);
6694 opacity: 0.7;
6695 }
6696
6697 .calc-legend-item::before {
6698 content: "";
6699 display: inline-block;
6700 width: 0.75rem;
6701 height: 0.75rem;
6702 margin-right: var(--gap-bound);
6703 vertical-align: baseline;
6704 }
6705
6706 .calc-legend-item--other::before {
6707 background: var(--warning);
6708 opacity: 0.35;
6709 }
6710
6711 .calc-legend-item--mnw::before {
6712 background: var(--success);
6713 opacity: 0.35;
6714 }
6715
6716 .calc-note {
6717 font-family: var(--font-mono);
6718 font-size: var(--text-note);
6719 padding: var(--gap-section);
6720 background: var(--surface-overlay);
6721 border-left: 3px solid var(--border);
6722 line-height: 1.6;
6723 }
6724
6725 /* ===========================================
6726 DOCUMENTATION PAGES
6727 =========================================== */
6728
6729 /* .doc-container takes the shared reading column near the top of this file. */
6730
6731 .doc-breadcrumb {
6732 font-family: var(--font-mono);
6733 font-size: var(--text-note);
6734 margin-bottom: var(--gap-page);
6735 }
6736
6737 .doc-breadcrumb a { color: var(--content); text-decoration: none; }
6738 .doc-breadcrumb a:hover { text-decoration: underline; }
6739
6740 .doc-title {
6741 font-family: var(--font-display);
6742 font-size: var(--text-display);
6743 font-weight: normal;
6744 line-height: 1.2;
6745 margin-bottom: var(--gap-pane);
6746 color: var(--content);
6747 text-align: left;
6748 }
6749
6750 .doc-body {
6751 font-family: var(--font-sans);
6752 font-size: var(--text-lead);
6753 line-height: 1.9;
6754 }
6755
6756 .doc-body p { margin-bottom: var(--gap-pane); }
6757
6758 .doc-body h1 {
6759 font-family: var(--font-display);
6760 font-size: var(--text-title);
6761 font-weight: normal;
6762 margin-top: var(--gap-page);
6763 margin-bottom: var(--gap-section);
6764 color: var(--content);
6765 text-align: left;
6766 }
6767
6768 .doc-body h2 {
6769 font-family: var(--font-mono);
6770 font-size: var(--text-head);
6771 font-weight: normal;
6772 margin-top: var(--gap-page);
6773 margin-bottom: var(--gap-section);
6774 color: var(--content);
6775 }
6776
6777 .doc-body h3 {
6778 font-family: var(--font-mono);
6779 font-size: var(--text-subhead);
6780 font-weight: normal;
6781 margin-top: var(--gap-page);
6782 margin-bottom: var(--gap-section);
6783 color: var(--content);
6784 }
6785
6786 .doc-body blockquote {
6787 border-left: 3px solid var(--action);
6788 padding-left: var(--gap-pane);
6789 margin: var(--gap-page) 0;
6790 font-style: italic;
6791 color: var(--content-muted);
6792 }
6793
6794 .doc-body ul, .doc-body ol { margin-bottom: var(--gap-pane); padding-left: var(--gap-pane); }
6795 .doc-body li { margin-bottom: var(--gap-peer); }
6796
6797 .doc-body a {
6798 color: var(--action);
6799 text-decoration: underline;
6800 text-decoration-color: color-mix(in oklch, var(--action) 40%, transparent);
6801 text-underline-offset: 2px;
6802 }
6803
6804 .doc-body a:hover { text-decoration-color: var(--action); }
6805 .doc-body strong { font-weight: bold; }
6806 .doc-body em { font-style: italic; }
6807
6808 .doc-body code {
6809 font-family: var(--font-mono);
6810 background: var(--surface-sunken);
6811 padding: var(--gap-bound) var(--gap-peer);
6812 font-size: var(--text-body);
6813 }
6814
6815 .doc-body pre {
6816 background: var(--surface-sunken);
6817 padding: var(--gap-section);
6818 overflow-x: auto;
6819 margin-bottom: var(--gap-pane);
6820 }
6821
6822 .doc-body pre code { background: none; padding: 0; }
6823
6824 .doc-body hr { border: none; text-align: center; margin: var(--gap-page) 0; }
6825 .doc-body hr::before { content: "* * *"; color: var(--content-muted); letter-spacing: 1rem; }
6826
6827 .doc-body table { width: 100%; border-collapse: collapse; margin-bottom: var(--gap-pane); }
6828 .doc-body th, .doc-body td { padding: var(--gap-section); text-align: left; border-bottom: 1px solid var(--border); }
6829 .doc-body th { font-family: var(--font-mono); font-weight: 600; }
6830
6831 .doc-backlinks {
6832 margin-top: var(--gap-page);
6833 padding-top: var(--gap-pane);
6834 border-top: 1px solid var(--border);
6835 }
6836
6837 .doc-backlinks-title {
6838 font-family: var(--font-mono);
6839 font-size: var(--text-note);
6840 font-weight: 600;
6841 text-transform: uppercase;
6842 letter-spacing: 0.05em;
6843 color: var(--content-muted);
6844 margin-bottom: var(--gap-section);
6845 }
6846
6847 .doc-backlinks-list {
6848 list-style: none;
6849 margin: 0;
6850 padding: 0;
6851 }
6852
6853 .doc-backlinks-list li { margin-bottom: var(--gap-peer); }
6854 .doc-backlinks-list a { color: var(--content); text-decoration: none; }
6855 .doc-backlinks-list a:hover { text-decoration: underline; }
6856
6857 /* Docs index page */
6858 /* .docs-index takes the shared reading column near the top of this file. */
6859
6860 .docs-index-title {
6861 font-family: var(--font-display);
6862 font-size: var(--text-display);
6863 font-weight: normal;
6864 line-height: 1.2;
6865 margin-bottom: var(--gap-page);
6866 color: var(--content);
6867 }
6868
6869 .docs-section {
6870 margin-bottom: var(--gap-page);
6871 }
6872
6873 .docs-section h2 {
6874 font-family: var(--font-mono);
6875 font-size: var(--text-subhead);
6876 font-weight: normal;
6877 color: var(--content-muted);
6878 margin-bottom: var(--gap-section);
6879 }
6880
6881 .docs-section ul {
6882 list-style: none;
6883 padding: 0;
6884 margin: 0;
6885 }
6886
6887 .docs-section li {
6888 margin-bottom: var(--gap-peer);
6889 }
6890
6891 .docs-section a {
6892 font-family: var(--font-sans);
6893 font-size: var(--text-lead);
6894 color: var(--content);
6895 text-decoration: none;
6896 }
6897
6898 .docs-section a:hover {
6899 color: var(--action);
6900 text-decoration: underline;
6901 }
6902
6903 .docs-section details {
6904 margin-bottom: var(--gap-section);
6905 }
6906
6907 .docs-section summary {
6908 font-family: var(--font-mono);
6909 font-size: var(--text-body);
6910 color: var(--content-muted);
6911 cursor: pointer;
6912 padding: var(--gap-bound) 0;
6913 list-style: disclosure-closed;
6914 }
6915
6916 .docs-section details[open] > summary {
6917 list-style: disclosure-open;
6918 margin-bottom: var(--gap-bound);
6919 }
6920
6921 .docs-section summary:hover {
6922 color: var(--content);
6923 }
6924
6925 .docs-section details ul {
6926 padding-left: var(--gap-section);
6927 }
6928
6929 /* Docs search */
6930 .docs-search-container {
6931 position: relative;
6932 margin-bottom: var(--gap-page);
6933 }
6934 .docs-search-inline {
6935 display: inline-block;
6936 margin-bottom: 0;
6937 margin-left: auto;
6938 float: right;
6939 }
6940 .docs-search-input {
6941 width: 100%;
6942 max-width: 320px;
6943 padding: var(--gap-peer) var(--gap-section);
6944 font-family: var(--font-sans);
6945 font-size: var(--text-body);
6946 background: var(--surface-sunken);
6947 border: 1px solid var(--border);
6948 color: var(--content);
6949 }
6950 .docs-search-input:focus {
6951 outline: 2px solid var(--action);
6952 border-color: var(--action);
6953 }
6954 .docs-search-input::placeholder { opacity: 0.5; }
6955 .docs-search-results {
6956 position: absolute;
6957 top: 100%;
6958 left: 0;
6959 right: 0;
6960 max-width: 320px;
6961 background: var(--surface-page);
6962 border: 1px solid var(--border);
6963 border-top: none;
6964 max-height: 300px;
6965 overflow-y: auto;
6966 z-index: var(--z-dropdown);
6967 box-shadow: var(--elevation-overlay);
6968 }
6969 .docs-search-result {
6970 display: flex;
6971 justify-content: space-between;
6972 align-items: baseline;
6973 padding: var(--gap-peer) var(--gap-section);
6974 text-decoration: none;
6975 color: var(--content);
6976 font-size: var(--text-note);
6977 border-bottom: 1px solid var(--border);
6978 }
6979 .docs-search-result:last-child { border-bottom: none; }
6980 .docs-search-result:hover { background: var(--surface-sunken); }
6981 .docs-search-result-title { font-family: var(--font-sans); }
6982 .docs-search-result-section { font-family: var(--font-mono); font-size: var(--text-fine); opacity: 0.5; }.alert .alert-title {
6983 font-family: var(--font-mono);
6984 font-size: var(--text-note);
6985 font-weight: bold;
6986 text-transform: uppercase;
6987 letter-spacing: 0.04em;
6988 margin-bottom: var(--gap-bound);
6989 }
6990 .alert p:last-child { margin-bottom: 0; }
6991 .alert-note { border-left-color: var(--focus-ring); }
6992 .alert-note .alert-title { color: var(--focus-ring); }
6993 .alert-tip { border-left-color: var(--success); }
6994 .alert-tip .alert-title { color: var(--success); }
6995 .alert-important { border-left-color: var(--action); }
6996 .alert-important .alert-title { color: var(--action); }
6997 .alert-warning { border-left-color: var(--warning); }
6998 .alert-warning .alert-title { color: var(--warning); }
6999 .alert-caution { border-left-color: var(--danger); }
7000 .alert-caution .alert-title { color: var(--danger); }
7001
7002 /* ===========================================
7003 USE CASES PAGE
7004 =========================================== */
7005
7006 .use-cases-page {
7007 max-width: 900px;
7008 margin: 0 auto;
7009 }
7010
7011 .use-cases-intro {
7012 font-size: var(--text-lead);
7013 margin-bottom: var(--gap-page);
7014 line-height: 1.6;
7015 }
7016
7017 .use-case-grid {
7018 display: grid;
7019 grid-template-columns: 1fr 1fr 1fr;
7020 gap: var(--gap-section);
7021 text-align: left;
7022 }
7023
7024 .use-case-grid-2col {
7025 grid-template-columns: 1fr 1fr;
7026 }
7027
7028 .use-case-title {
7029 font-family: var(--font-display);
7030 font-size: var(--text-lead);
7031 margin-bottom: var(--gap-bound);
7032 }
7033
7034 .use-case-who {
7035 font-family: var(--font-mono);
7036 font-size: var(--text-fine);
7037 opacity: 0.6;
7038 margin-bottom: var(--gap-peer);
7039 }
7040
7041 .use-case-desc {
7042 font-size: var(--text-note);
7043 line-height: 1.5;
7044 margin-bottom: var(--gap-peer);
7045 }
7046
7047 .use-case-features {
7048 list-style: none;
7049 margin: 0 0 var(--gap-section);
7050 padding: 0;
7051 }
7052
7053 .use-case-features li {
7054 font-size: var(--text-note);
7055 padding-left: var(--gap-section);
7056 position: relative;
7057 margin-bottom: var(--gap-bound);
7058 }
7059
7060 .use-case-features li::before {
7061 content: "-";
7062 position: absolute;
7063 left: 0;
7064 opacity: 0.5;
7065 }
7066
7067 .use-case-tier {
7068 font-family: var(--font-mono);
7069 font-size: var(--text-fine);
7070 opacity: 0.5;
7071 }
7072
7073 /* ===========================================
7074 RESPONSIVE, 768px (tablet)
7075 =========================================== */
7076
7077 /* ==========================================================================
7078 Git Source Browser
7079 ========================================================================== */
7080
7081 .git-repo-header { margin-bottom: var(--gap-pane); }
7082 .git-repo-name { font-size: var(--text-head); margin: 0 0 var(--gap-bound); }
7083 .git-repo-name a { color: var(--content); text-decoration: none; }
7084 .git-repo-name a:hover { opacity: 0.6; }
7085 .git-repo-name .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
7086 .git-repo-desc { opacity: 0.6; margin: 0 0 var(--gap-section); font-size: var(--text-note); }
7087 .git-clone-url {
7088 font-family: var(--font-mono);
7089 font-size: var(--text-fine);
7090 padding: var(--gap-peer) var(--gap-group);
7091 background: var(--surface-overlay);
7092 border: 1px solid var(--border);
7093 border-radius: var(--radius-control);
7094 display: inline-block;
7095 user-select: all;
7096 margin-bottom: var(--gap-section);
7097 }
7098 .git-linked-project { margin-top: var(--gap-peer); font-size: var(--text-note); }
7099 .git-linked-project a { color: var(--content); }
7100
7101 /* Ref bar (branch/tag selector + nav links) */
7102 .git-ref-bar {
7103 display: flex;
7104 align-items: center;
7105 gap: var(--gap-section);
7106 margin-bottom: var(--gap-section);
7107 font-size: var(--text-note);
7108 }
7109 .git-ref-select {
7110 font-family: var(--font-mono);
7111 font-size: var(--text-fine);
7112 padding: var(--gap-bound) var(--gap-peer);
7113 background: var(--surface-overlay);
7114 border: 1px solid var(--border);
7115 border-radius: var(--radius-control);
7116 }
7117 .git-nav-links { display: flex; gap: var(--gap-section); }
7118 .git-nav-links a { text-decoration: none; color: var(--content); opacity: 0.6; }
7119 .git-nav-links a:hover,
7120 .git-nav-links a.is-selected { opacity: 1; }
7121
7122 /* Breadcrumb navigation */
7123 .git-breadcrumb {
7124 font-family: var(--font-mono);
7125 font-size: var(--text-note);
7126 margin-bottom: var(--gap-section);
7127 }
7128 .git-breadcrumb a { color: var(--content); text-decoration: none; }
7129 .git-breadcrumb a:hover { text-decoration: underline; }
7130 .git-breadcrumb .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
7131
7132 /* File tree table */
7133 .git-tree { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
7134 .git-tree th {
7135 text-align: left;
7136 padding: var(--gap-peer) var(--gap-group);
7137 border-bottom: 2px solid var(--border);
7138 font-family: var(--font-mono);
7139 font-size: var(--text-fine);
7140 opacity: 0.5;
7141 text-transform: uppercase;
7142 }
7143 .git-tree td { padding: var(--gap-peer) var(--gap-group); border-bottom: 1px solid var(--border); }
7144 .git-tree tr:last-child td { border-bottom: none; }
7145 .git-tree .icon { width: 1.5rem; text-align: center; opacity: 0.4; font-family: var(--font-mono); }
7146 .tree-icon-dir { font-weight: 700; color: var(--content); }
7147 .tree-icon-file { font-size: var(--text-note); }
7148 .git-tree .name a { text-decoration: none; color: var(--content); }
7149 .git-tree .name a:hover { text-decoration: underline; }
7150 .git-tree .size {
7151 text-align: right;
7152 opacity: 0.5;
7153 font-family: var(--font-mono);
7154 font-size: var(--text-fine);
7155 }
7156
7157 /* README rendering */
7158 .git-readme { margin-top: var(--gap-page); padding-top: var(--gap-pane); border-top: 1px solid var(--border); }
7159 .git-readme h2 { font-size: var(--text-body); opacity: 0.5; margin-bottom: var(--gap-section); }
7160 .git-readme-body { line-height: 1.6; }
7161 .git-readme-body h1,
7162 .git-readme-body h2,
7163 .git-readme-body h3 { margin-top: var(--gap-pane); margin-bottom: var(--gap-peer); }
7164 .git-readme-body pre {
7165 background: var(--surface-overlay);
7166 padding: var(--gap-section);
7167 border-radius: var(--radius-control);
7168 overflow-x: auto;
7169 font-family: var(--font-mono);
7170 font-size: var(--text-fine);
7171 }
7172 .git-readme-body code {
7173 font-family: var(--font-mono);
7174 font-size: var(--text-note);
7175 background: var(--surface-overlay);
7176 padding: var(--gap-bound);
7177 border-radius: var(--radius-fine);
7178 }
7179 .git-readme-body pre code { background: none; padding: 0; }
7180 .git-readme-body table { border-collapse: collapse; margin: var(--gap-section) 0; }
7181 .git-readme-body th,
7182 .git-readme-body td { border: 1px solid var(--border); padding: var(--gap-peer) var(--gap-group); }
7183
7184 /* File viewer */
7185 .git-file-header {
7186 display: flex;
7187 justify-content: space-between;
7188 align-items: center;
7189 padding: var(--gap-peer) var(--gap-group);
7190 background: var(--surface-overlay);
7191 border: 1px solid var(--border);
7192 border-bottom: none;
7193 border-radius: var(--radius-control) var(--radius-control) 0 0;
7194 font-size: var(--text-fine);
7195 }
7196 .git-file-meta {
7197 font-family: var(--font-mono);
7198 opacity: 0.6;
7199 }
7200 .git-file-actions a {
7201 font-family: var(--font-mono);
7202 text-decoration: none;
7203 color: var(--content);
7204 opacity: 0.6;
7205 font-size: var(--text-fine);
7206 }
7207 .git-file-actions a:hover { opacity: 1; }
7208 .git-file-content {
7209 border: 1px solid var(--border);
7210 border-radius: 0 0 var(--radius-control) var(--radius-control);
7211 overflow-x: auto;
7212 }
7213 .git-file-content table { border-collapse: collapse; width: 100%; }
7214 .git-file-content .line-number {
7215 width: 1px;
7216 white-space: nowrap;
7217 padding: 0 var(--gap-section);
7218 text-align: right;
7219 user-select: none;
7220 opacity: 0.3;
7221 font-family: var(--font-mono);
7222 font-size: var(--text-fine);
7223 border-right: 1px solid var(--border);
7224 vertical-align: top;
7225 }
7226 .git-file-content .line-code {
7227 padding: 0 var(--gap-section);
7228 white-space: pre;
7229 font-family: var(--font-mono);
7230 font-size: var(--text-fine);
7231 line-height: 1.5;
7232 }
7233 .git-file-content .line-code span {
7234 font-family: var(--font-mono);
7235 }
7236 .git-binary-notice {
7237 padding: var(--gap-page);
7238 text-align: center;
7239 opacity: 0.5;
7240 font-family: var(--font-mono);
7241 font-size: var(--text-note);
7242 border: 1px solid var(--border);
7243 border-radius: 0 0 var(--radius-control) var(--radius-control);
7244 }
7245
7246 /* Syntax highlighting (syntect class-based) */
7247 .git-file-content .source { color: var(--content); }
7248 .git-file-content .keyword,
7249 .git-file-content .storage { color: var(--syntax-keyword); }
7250 .git-file-content .string { color: var(--syntax-string); }
7251 .git-file-content .comment { color: var(--syntax-comment); font-style: italic; }
7252 .git-file-content .constant,
7253 .git-file-content .constant.numeric { color: var(--syntax-constant); }
7254 .git-file-content .entity.name { color: var(--syntax-entity); }
7255 .git-file-content .variable { color: var(--syntax-variable); }
7256 .git-file-content .support { color: var(--syntax-support); }
7257 .git-file-content .meta.preprocessor { color: var(--syntax-keyword); }
7258
7259 /* Commit list */
7260 .git-commit-list { list-style: none; }
7261 .git-commit { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7262 .git-commit:last-child { border-bottom: none; }
7263 .git-commit-message { font-size: var(--text-note); margin: 0 0 var(--gap-bound); }
7264 .git-commit-message .summary { font-weight: 600; }
7265 .git-commit-meta {
7266 font-family: var(--font-mono);
7267 font-size: var(--text-fine);
7268 opacity: 0.5;
7269 display: flex;
7270 gap: var(--gap-section);
7271 flex-wrap: wrap;
7272 }
7273 .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); }
7274 .git-commit-oid a { color: var(--content); text-decoration: none; }
7275 .git-commit-oid a:hover { text-decoration: underline; }
7276 .git-pagination {
7277 display: flex;
7278 justify-content: center;
7279 gap: var(--gap-section);
7280 margin-top: var(--gap-pane);
7281 padding-top: var(--gap-section);
7282 font-size: var(--text-note);
7283 }
7284 .git-pagination a { color: var(--content); text-decoration: none; }
7285 .git-pagination a:hover { text-decoration: underline; }
7286
7287 /* Release items on repo page */
7288 .git-release { margin-bottom: var(--gap-pane); padding: var(--gap-section); background: var(--surface-overlay); }
7289 .git-release-title { margin: 0 0 var(--gap-bound); font-size: var(--text-lead); }
7290 .git-release-title a { color: var(--content); text-decoration: none; }
7291 .git-release-meta { font-size: var(--text-note); opacity: 0.6; margin-bottom: var(--gap-peer); }
7292 .git-release-desc { font-size: var(--text-note); opacity: 0.8; margin-bottom: var(--gap-section); }
7293 .git-release-versions { font-size: var(--text-fine); }
7294 .git-release-version {
7295 display: flex;
7296 gap: var(--gap-section);
7297 align-items: center;
7298 padding: var(--gap-bound) 0;
7299 border-top: 1px solid var(--border);
7300 }
7301 .git-release-version .version-number { font-family: var(--font-mono); }
7302 .git-release-version .version-badge {
7303 font-size: var(--text-fine);
7304 padding: var(--gap-bound) var(--gap-peer);
7305 background: var(--content);
7306 color: var(--surface-page);
7307 border-radius: var(--radius-fine);
7308 }
7309 .git-release-version .version-meta { opacity: 0.5; }
7310
7311 /* Git commit detail + diff */
7312 .git-commit-detail { margin-bottom: var(--gap-pane); }
7313 .git-commit-full-message {
7314 font-family: var(--font-mono);
7315 font-size: var(--text-note);
7316 white-space: pre-wrap;
7317 line-height: 1.5;
7318 padding: var(--gap-section);
7319 background: var(--surface-overlay);
7320 border: 1px solid var(--border);
7321 margin-bottom: var(--gap-section);
7322 }
7323 .git-commit-detail-meta { font-size: var(--text-note); opacity: 0.7; line-height: 1.8; }
7324 .git-commit-detail-meta .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); word-break: break-all; }
7325 .git-commit-parents a { font-family: var(--font-mono); color: var(--content); text-decoration: none; }
7326 .git-commit-parents a:hover { text-decoration: underline; }
7327 /* Commit trailers, lifted out of the message body and shown as what they are */
7328 .git-commit-trailers {
7329 display: grid;
7330 grid-template-columns: max-content 1fr;
7331 gap: var(--gap-bound) var(--gap-section);
7332 font-size: var(--text-note);
7333 margin-bottom: var(--gap-section);
7334 }
7335 .git-commit-trailers dt { opacity: 0.6; }
7336 .git-commit-trailers dd { margin: 0; font-family: var(--font-mono); font-size: var(--text-fine); }
7337 /* Tags, with the annotation an annotated tag carries */
7338 .git-tag-list { list-style: none; }
7339 .git-tag { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7340 .git-tag:last-child { border-bottom: none; }
7341 .git-tag-name { font-size: var(--text-note); font-weight: 600; margin: 0 0 var(--gap-bound); }
7342 .git-tag-name a { color: var(--content); text-decoration: none; }
7343 .git-tag-name a:hover { text-decoration: underline; }
7344 .git-tag-lightweight { font-weight: 400; font-size: var(--text-fine); opacity: 0.5; }
7345 .git-tag-message {
7346 font-family: var(--font-mono);
7347 font-size: var(--text-note);
7348 white-space: pre-wrap;
7349 line-height: 1.5;
7350 padding: var(--gap-peer) var(--gap-section);
7351 border-left: 2px solid var(--border);
7352 margin-bottom: var(--gap-bound);
7353 }
7354 .git-tag-meta {
7355 font-family: var(--font-mono);
7356 font-size: var(--text-fine);
7357 opacity: 0.5;
7358 display: flex;
7359 gap: var(--gap-section);
7360 flex-wrap: wrap;
7361 }
7362 .git-tag-meta a { color: var(--content); text-decoration: none; }
7363 .git-tag-meta a:hover { text-decoration: underline; }
7364 /* Signature state. Neutral by default: only a bad signature gets a colour. */
7365 .git-signature { font-size: var(--text-fine); opacity: 0.7; }
7366 .git-signature a { color: var(--content); }
7367 .git-signature.is-verified { opacity: 1; }
7368 .git-signature.is-bad { color: var(--danger); opacity: 1; }
7369 /* refs/replace/*: object substitutions, listed but not applied while browsing */
7370 .git-replace-intro { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7371 .git-replace-intro p { margin: 0; }
7372 .git-replace-notice {
7373 font-size: var(--text-note);
7374 border-left: 2px solid var(--border);
7375 padding: var(--gap-peer) var(--gap-section);
7376 margin: 0 0 var(--gap-section);
7377 }
7378 .git-replace-table { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
7379 .git-replace-table th {
7380 text-align: left;
7381 font-weight: 400;
7382 opacity: 0.5;
7383 font-size: var(--text-fine);
7384 padding: var(--gap-bound) var(--gap-section) var(--gap-bound) 0;
7385 border-bottom: 1px solid var(--border);
7386 }
7387 .git-replace-table td { padding: var(--gap-peer) var(--gap-section) var(--gap-peer) 0; border-bottom: 1px solid var(--border); }
7388 .git-replace-table a { color: var(--content); text-decoration: none; }
7389 .git-replace-table a:hover { text-decoration: underline; }
7390 /* Git notes (refs/notes/*) shown against the object they annotate */
7391 .git-notes { margin-bottom: var(--gap-pane); }
7392 .git-note {
7393 border: 1px solid var(--border);
7394 border-left: 2px solid var(--content);
7395 margin-bottom: var(--gap-section);
7396 }
7397 .git-note-header {
7398 display: flex;
7399 align-items: baseline;
7400 justify-content: space-between;
7401 gap: var(--gap-group);
7402 padding: var(--gap-peer) var(--gap-section);
7403 border-bottom: 1px solid var(--border);
7404 font-size: var(--text-fine);
7405 }
7406 .git-note-namespace { font-family: var(--font-mono); }
7407 /* Sits beside the namespace rather than being spread across the header: the
7408 auto margin absorbs the free space `space-between` would otherwise put
7409 between the two, leaving the attribution on the right where it was. */
7410 .git-note-owned { margin-right: auto; opacity: 0.6; }
7411 .git-note-attribution { opacity: 0.6; }
7412 .git-note-namespaces {
7413 display: flex;
7414 flex-wrap: wrap;
7415 gap: var(--gap-group);
7416 padding: var(--gap-section) 0;
7417 border-bottom: 1px solid var(--border);
7418 margin-bottom: var(--gap-section);
7419 font-size: var(--text-note);
7420 }
7421 .git-note-namespaces a { color: var(--content); text-decoration: none; opacity: 0.6; }
7422 .git-note-namespaces a:hover { opacity: 1; }
7423 .git-note-namespaces a.is-selected { opacity: 1; text-decoration: underline; }
7424 .git-note-target-kind { opacity: 0.6; }
7425 .git-explore-intro { margin: 0 0 var(--gap-section); font-size: var(--text-note); opacity: 0.8; }
7426 .git-notes-fetch { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7427 .git-notes-fetch p { margin: 0 0 var(--gap-peer); }
7428 .git-note-feed-cap { font-size: var(--text-fine); opacity: 0.6; padding-top: var(--gap-section); }
7429 .git-note-search {
7430 display: flex;
7431 flex-wrap: wrap;
7432 align-items: center;
7433 gap: var(--gap-group);
7434 padding-bottom: var(--gap-section);
7435 font-size: var(--text-note);
7436 }
7437 .git-note-search input[type="search"] { flex: 1 1 16rem; }
7438 .git-note-search label { display: flex; align-items: center; gap: var(--gap-bound); opacity: 0.8; }
7439 .git-note-excerpt {
7440 margin: var(--gap-bound) 0 0;
7441 font-size: var(--text-note);
7442 opacity: 0.7;
7443 }
7444 .git-note-paging {
7445 display: flex;
7446 gap: var(--gap-group);
7447 align-items: center;
7448 padding-top: var(--gap-section);
7449 font-size: var(--text-note);
7450 }
7451 .git-note-paging span { font-size: var(--text-fine); opacity: 0.6; }
7452 .git-note-feed-link { padding-top: var(--gap-peer); font-size: var(--text-fine); }
7453 .git-note-badge {
7454 font-size: var(--text-fine);
7455 padding: var(--gap-bound) var(--gap-peer);
7456 border: 1px solid var(--border);
7457 border-radius: var(--radius-fine);
7458 color: var(--content);
7459 text-decoration: none;
7460 opacity: 0.7;
7461 }
7462 .git-note-badge:hover { opacity: 1; }
7463 .git-note-body { padding: var(--gap-section); font-size: var(--text-note); }
7464 .git-note-body > :first-child { margin-top: 0; }
7465 .git-note-body > :last-child { margin-bottom: 0; }
7466 /* Writing a note. Only rendered for a reader who can push. */
7467 .git-notes-edit {
7468 border-top: 1px solid var(--border);
7469 padding-top: var(--gap-section);
7470 margin-bottom: var(--gap-pane);
7471 }
7472 .git-notes-edit textarea { width: 100%; font-family: var(--font-mono); font-size: var(--text-note); }
7473 .git-note-form { margin-bottom: var(--gap-section); }
7474 .git-note-delete { margin-bottom: var(--gap-pane); }
7475 .git-note-merged {
7476 border-left: 2px solid var(--content);
7477 padding: var(--gap-peer) var(--gap-section);
7478 margin-bottom: var(--gap-section);
7479 font-size: var(--text-note);
7480 }
7481 .git-diff-stats {
7482 font-size: var(--text-note);
7483 padding: var(--gap-section) 0;
7484 margin-bottom: var(--gap-section);
7485 border-bottom: 1px solid var(--border);
7486 }
7487 .git-diff-stats .additions { color: var(--diff-add); }
7488 .git-diff-stats .deletions { color: var(--diff-del); }
7489 .git-diff-file { margin-bottom: var(--gap-pane); border: 1px solid var(--border); }
7490 .git-diff-file-header {
7491 display: flex;
7492 align-items: center;
7493 gap: var(--gap-peer);
7494 padding: var(--gap-peer) var(--gap-section);
7495 background: var(--surface-overlay);
7496 border-bottom: 1px solid var(--border);
7497 font-size: var(--text-note);
7498 flex-wrap: wrap;
7499 }
7500 .git-diff-file-header a { color: var(--content); text-decoration: none; }
7501 .git-diff-file-header a:hover { text-decoration: underline; }
7502 .git-diff-file-stats { margin-left: auto; font-size: var(--text-fine); }
7503 .git-diff-file-stats .additions { color: var(--diff-add); }
7504 .git-diff-file-stats .deletions { color: var(--diff-del); margin-left: var(--gap-bound); }
7505 .diff-status-added, .diff-status-deleted, .diff-status-modified, .diff-status-renamed {
7506 display: inline-block;
7507 width: 1.2rem;
7508 text-align: center;
7509 font-size: var(--text-fine);
7510 font-weight: 700;
7511 font-family: var(--font-mono);
7512 border-radius: var(--radius-fine);
7513 line-height: 1.4;
7514 }
7515 .diff-status-added { background: color-mix(in oklch, var(--success) 14%, var(--surface-page)); color: var(--success); }
7516 .diff-status-deleted { background: color-mix(in oklch, var(--danger) 14%, var(--surface-page)); color: var(--danger); }
7517 .diff-status-modified { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
7518 .diff-status-renamed { background: color-mix(in oklch, var(--info) 14%, var(--surface-page)); color: var(--info); }
7519 .git-diff-table {
7520 width: 100%;
7521 border-collapse: collapse;
7522 font-family: var(--font-mono);
7523 font-size: var(--text-fine);
7524 table-layout: fixed;
7525 }
7526 .git-diff-hunk-header td {
7527 padding: var(--gap-bound) var(--gap-peer);
7528 background: var(--highlight-faint);
7529 color: var(--content);
7530 opacity: 0.6;
7531 font-size: var(--text-fine);
7532 }
7533 .git-diff-lineno {
7534 width: 3.5rem;
7535 padding: 0 var(--gap-peer);
7536 text-align: right;
7537 color: var(--content);
7538 opacity: 0.35;
7539 user-select: none;
7540 vertical-align: top;
7541 }
7542 .git-diff-origin {
7543 width: 1rem;
7544 text-align: center;
7545 user-select: none;
7546 vertical-align: top;
7547 }
7548 .git-diff-content {
7549 padding: 0 var(--gap-peer);
7550 white-space: pre-wrap;
7551 word-break: break-all;
7552 }
7553 .git-diff-line-add { background: var(--diff-add-bg); }
7554 .git-diff-line-add .git-diff-origin { color: var(--diff-add); }
7555 .git-diff-line-del { background: var(--diff-del-bg); }
7556 .git-diff-line-del .git-diff-origin { color: var(--diff-del); }
7557 .git-diff-line-ctx { background: transparent; }
7558 .git-diff-truncated { padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.5; font-style: italic; }
7559
7560 /* Git file view line linking */
7561 .git-file-content .line-number a {
7562 color: inherit;
7563 text-decoration: none;
7564 display: block;
7565 }
7566 .git-file-content .line-number a:hover { opacity: 0.7; }
7567 .git-file-content tr:target td,
7568 .git-file-content tr.highlighted td { background: var(--highlight-faint); }
7569
7570 /* Git blame view */
7571 .git-blame-content { overflow-x: auto; border: 1px solid var(--border); }
7572 .git-blame-content table { border-collapse: collapse; width: 100%; font-size: var(--text-fine); }
7573 .git-blame-info {
7574 padding: 0 var(--gap-peer);
7575 font-family: var(--font-mono);
7576 white-space: nowrap;
7577 width: 4.5rem;
7578 }
7579 .git-blame-info a { color: var(--content); text-decoration: none; }
7580 .git-blame-info a:hover { text-decoration: underline; }
7581 .git-blame-author {
7582 padding: 0 var(--gap-bound);
7583 font-size: var(--text-fine);
7584 opacity: 0.5;
7585 white-space: nowrap;
7586 max-width: 8rem;
7587 overflow: hidden;
7588 text-overflow: ellipsis;
7589 }
7590 .git-blame-date {
7591 padding: 0 var(--gap-bound);
7592 font-size: var(--text-fine);
7593 opacity: 0.35;
7594 white-space: nowrap;
7595 width: 5.5rem;
7596 }
7597 .git-blame-content .line-number {
7598 padding: 0 var(--gap-peer);
7599 text-align: right;
7600 opacity: 0.35;
7601 user-select: none;
7602 width: 3rem;
7603 font-family: var(--font-mono);
7604 }
7605 .git-blame-content .line-code {
7606 padding: 0 var(--gap-peer);
7607 white-space: pre;
7608 font-family: var(--font-mono);
7609 }
7610 .git-blame-boundary td { border-top: 1px solid var(--border); }
7611
7612 /* Git user repos listing */
7613 .git-repos-list { list-style: none; }
7614 .git-repos-item { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7615 .git-repos-item:last-child { border-bottom: none; }
7616 .git-repos-item-header { display: flex; align-items: center; gap: var(--gap-peer); }
7617 .git-repos-item-name {
7618 font-size: var(--text-lead);
7619 font-weight: 600;
7620 color: var(--content);
7621 text-decoration: none;
7622 }
7623 .git-repos-item-name:hover { text-decoration: underline; }
7624 .git-repos-visibility {
7625 font-size: var(--text-fine);
7626 padding: var(--gap-bound) var(--gap-peer);
7627 border: 1px solid var(--border);
7628 border-radius: var(--radius-fine);
7629 opacity: 0.5;
7630 }
7631 .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); }
7632 .git-repos-owner { opacity: 0.6; }
7633 .git-repos-item-name .sep { opacity: 0.4; }
7634 .git-file-log-label {
7635 font-family: var(--font-mono);
7636 font-size: var(--text-fine);
7637 opacity: 0.5;
7638 margin-right: var(--gap-bound);
7639 }
7640
7641 /* Site-wide footer */
7642 .site-footer {
7643 margin-top: var(--gap-page);
7644 padding: var(--gap-page) 0;
7645 text-align: center;
7646 font-family: var(--font-mono);
7647 font-size: var(--text-fine);
7648 opacity: 0.5;
7649 }
7650 .site-footer-links { margin-bottom: var(--gap-peer); }
7651 .site-footer-links a { margin: 0 var(--gap-section); color: inherit; text-decoration: none; }
7652 .site-footer-links a:hover { text-decoration: underline; }
7653
7654 @media (max-width: 839px) {
7655 /* Git browser mobile */
7656 .git-ref-bar { flex-wrap: wrap; gap: var(--gap-peer); }
7657 .git-repo-name { font-size: var(--text-subhead); }
7658 .git-clone-url { font-size: var(--text-fine); word-break: break-all; }
7659 .git-tree .icon { display: none; }
7660 .git-tree td, .git-tree th { padding: var(--gap-bound) var(--gap-peer); }
7661 .git-file-header { flex-direction: column; align-items: flex-start; gap: var(--gap-bound); }
7662 .git-file-content .line-number { padding: 0 var(--gap-peer); }
7663 .git-file-content .line-code { padding: 0 var(--gap-peer); font-size: var(--text-fine); }
7664 .git-commit-meta { flex-direction: column; gap: var(--gap-bound); }
7665 .git-release-version { flex-wrap: wrap; gap: var(--gap-peer); }
7666 .git-diff-file-header { font-size: var(--text-fine); }
7667 .git-diff-table { font-size: var(--text-fine); }
7668 .git-diff-lineno { width: 2.5rem; font-size: var(--text-fine); }
7669 .git-blame-author { display: none; }
7670 .git-blame-date { display: none; }
7671
7672 .container {
7673 padding: var(--gap-section);
7674 }
7675
7676 .padded-page {
7677 padding: var(--gap-section);
7678 }
7679
7680 .form-row {
7681 grid-template-columns: 1fr;
7682 }
7683
7684 .tab {
7685 padding: var(--gap-group) var(--gap-section);
7686 font-size: var(--text-note);
7687 }
7688
7689 .tab-content {
7690 padding: var(--gap-section);
7691 }
7692
7693 .data-table th,
7694 .data-table td {
7695 padding: var(--gap-peer) var(--gap-group);
7696 font-size: var(--text-note);
7697 }
7698
7699 .modal {
7700 max-width: 95%;
7701 }
7702
7703 .stats-grid {
7704 grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
7705 }
7706
7707 .content-section {
7708 padding: var(--gap-section);
7709 }
7710
7711 .action-buttons {
7712 flex-direction: column;
7713 }
7714
7715 .action-buttons button {
7716 width: 100%;
7717 }
7718
7719 .analytics-grid {
7720 grid-template-columns: 1fr;
7721 }
7722
7723 /* Hamburger menu */
7724 .nav-toggle-label {
7725 display: block;
7726 }
7727
7728 .header-search {
7729 display: none;
7730 flex: none;
7731 margin: 0;
7732 width: 100%;
7733 }
7734
7735 .nav-toggle-checkbox:checked ~ .header-search {
7736 display: block;
7737 position: absolute;
7738 top: 100%;
7739 left: 0;
7740 right: 0;
7741 padding: var(--gap-section) var(--gap-pane);
7742 background: var(--surface-page);
7743 border-bottom: 1px solid var(--border);
7744 z-index: var(--z-header);
7745 }
7746
7747 .site-header nav {
7748 display: none;
7749 position: absolute;
7750 top: 100%;
7751 left: 0;
7752 right: 0;
7753 background: var(--surface-page);
7754 border-bottom: 1px solid var(--border);
7755 padding: var(--gap-section) var(--gap-pane);
7756 z-index: var(--z-nav);
7757 }
7758
7759 .site-header {
7760 position: relative;
7761 }
7762
7763 .nav-toggle-checkbox:checked ~ nav {
7764 display: block;
7765 }
7766
7767 .site-header .nav-links {
7768 flex-direction: column;
7769 gap: var(--gap-section);
7770 }
7771
7772 /* Animate hamburger to X when open */
7773 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(1) {
7774 transform: rotate(45deg) translate(5px, 5px);
7775 }
7776
7777 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(2) {
7778 opacity: 0;
7779 }
7780
7781 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(3) {
7782 transform: rotate(-45deg) translate(5px, -5px);
7783 }
7784 }
7785
7786 /* Doc pages, mobile */
7787 @media (max-width: 599px) {
7788 .doc-title { font-size: var(--text-title); }
7789 .doc-body { font-size: var(--text-body); }
7790 .docs-index-title { font-size: var(--text-title); }
7791 }
7792
7793 /* ===========================================
7794 RESPONSIVE, 480px (phone)
7795 =========================================== */
7796
7797 @media (max-width: 599px) {
7798 .container {
7799 padding: var(--gap-section);
7800 }
7801
7802 .padded-page {
7803 padding: var(--gap-section);
7804 }
7805
7806 h1 {
7807 font-size: var(--text-title);
7808 }
7809
7810 .centered-page h1 {
7811 font-size: var(--text-hero);
7812 }
7813
7814 .login-container,
7815 .form-container {
7816 max-width: 100%;
7817 padding: var(--gap-section);
7818 }
7819
7820 .big-button {
7821 width: 100%;
7822 }
7823
7824 .stats-grid {
7825 grid-template-columns: repeat(2, 1fr);
7826 }
7827
7828 .tab {
7829 padding: var(--gap-peer) var(--gap-section);
7830 font-size: var(--text-note);
7831 }
7832
7833 .tab-content {
7834 padding: var(--gap-section);
7835 }
7836
7837 .content-section {
7838 padding: var(--gap-section);
7839 }
7840
7841 .section-header {
7842 flex-direction: column;
7843 align-items: flex-start;
7844 gap: var(--gap-section);
7845 }
7846
7847 /* Landing page (merged from 500px) */
7848 .landing-fork,
7849 .tier-selector,
7850 .feature-grid,
7851 .use-case-grid,
7852 .use-case-grid-2col {
7853 grid-template-columns: 1fr;
7854 }
7855
7856 .explore-links {
7857 flex-direction: column;
7858 align-items: center;
7859 gap: var(--gap-section);
7860 }
7861
7862 .pricing-input {
7863 font-size: var(--text-title);
7864 }
7865
7866 .pricing-currency {
7867 font-size: var(--text-title);
7868 }
7869
7870 .calc-verdict {
7871 font-size: var(--text-body);
7872 }
7873
7874 .calc-dials {
7875 grid-template-columns: 1fr;
7876 }
7877
7878 .secondary-links {
7879 flex-direction: column;
7880 align-items: center;
7881 gap: var(--gap-section);
7882 }
7883
7884 .landing-cta {
7885 flex-direction: column;
7886 gap: var(--gap-section);
7887 }
7888
7889 .toast-container {
7890 left: 0.75rem;
7891 right: 0.75rem;
7892 bottom: 0.75rem;
7893 }
7894
7895 .toast {
7896 max-width: 100%;
7897 }
7898 }
7899
7900 /* ===========================================
7901 DISCOVER PAGE
7902 =========================================== */
7903
7904 .discover-layout { display: grid; grid-template-columns: 200px 1fr; gap: var(--gap-pane); }
7905 .discover-layout.no-sidebar { grid-template-columns: 1fr; }
7906 .discover-layout.no-sidebar .discover-sidebar { display: none; }
7907 .discover-sidebar { font-size: var(--text-note); }
7908 .filter-section { margin-bottom: var(--gap-section); }
7909 .filter-title { font-family: var(--font-display); font-weight: bold; font-size: var(--text-note); margin-bottom: var(--gap-peer); color: var(--content); }
7910 .filter-list { list-style: none; }
7911 /* The row carries selection (`.is-selected` recipe) and hover; the filter itself
7912 is a real <button> child so Enter/Space work without scripting. */
7913 .filter-item { display: flex; align-items: center; gap: var(--gap-bound); transition: opacity 0.1s ease; }
7914 .filter-item:hover { opacity: 0.6; }
7915 .filter-item.is-selected { font-weight: bold; }
7916 .filter-item .count { opacity: 0.4; font-size: var(--text-fine); }
7917 .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); }
7918 .tag-follow-btn:hover { opacity: 1; }
7919 .tag-follow-btn.is-selected { opacity: 0.85; border-color: var(--focus-ring); background: var(--highlight-faint); }
7920 .filter-checkbox { display: flex; align-items: center; gap: var(--gap-peer); padding: var(--gap-bound) 0; cursor: pointer; }
7921 .filter-checkbox input { width: auto; margin: 0; }
7922 .price-inputs { display: flex; gap: var(--gap-bound); align-items: center; margin-bottom: var(--gap-peer); }
7923 .price-inputs input { width: 60px; padding: var(--gap-bound); font-size: var(--text-fine); background: var(--surface-sunken); border: none; }
7924 .price-inputs span { opacity: 0.4; }
7925 .discover-main { min-width: 0; }
7926
7927 /* ---- Discover sidebar: tag spine ----------------------------------------
7928 Tags are the sidebar's structure, not one facet among several, so the spine
7929 reads as the primary surface and .filter-refine below it is subordinate.
7930 Everything here composes existing primitives: .filter-item carries the row
7931 and its .is-selected recipe, .search-suggestions carries the dropdown shell,
7932 .breadcrumb carries the crumb idiom. */
7933 .tag-spine { padding-bottom: var(--gap-section); border-bottom: 1px solid var(--border); }
7934
7935 /* Selected tags. Also the active-filter summary, so they use the canonical
7936 selected tint rather than a new colour. */
7937 .tag-chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--gap-bound); margin-bottom: var(--gap-section); }
7938 .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); }
7939 .tag-chip-label { overflow-wrap: anywhere; }
7940 .tag-chip-remove { text-decoration: none; color: var(--content-secondary); font-size: var(--text-body); line-height: 1; padding: 0 var(--gap-bound); }
7941 .tag-chip-remove:hover { color: var(--content); }
7942 .tag-chip-clear { background: none; border: 1px dashed var(--border-strong); }
7943 .tag-chip-clear a { text-decoration: none; color: var(--content-secondary); font-size: var(--text-fine); }
7944 .tag-chip-clear a:hover { color: var(--content); }
7945
7946 /* Typeahead. Shares the .search-suggestions shell; it toggles with the
7947 `hidden` attribute rather than an inline display, so the shared rule's
7948 display:none has to be overridden when open. */
7949 .tag-combobox { position: relative; margin-bottom: var(--gap-section); }
7950
7951 /* Drill-down. A rung either selects (assignable leaf) or navigates (category);
7952 rows with both put the chevron after the label. */
7953 .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); }
7954 .tag-crumbs li + li::before { content: "/"; opacity: 0.4; margin: 0 var(--gap-bound); }
7955 .tag-crumbs a { color: var(--content); text-decoration: none; }
7956 .tag-crumbs a:hover { text-decoration: underline; }
7957 .tag-drill-select { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7958 .tag-drill-select input { width: auto; margin: 0; }
7959 .tag-drill-select span:first-of-type { flex: 1; }
7960 .tag-drill-into { display: flex; align-items: center; gap: var(--gap-peer); flex: 1; text-decoration: none; color: inherit; padding: var(--gap-bound) 0; }
7961 .tag-drill-into span:first-child { flex: 1; }
7962 .tag-drill-chevron { opacity: 0.4; }
7963
7964 /* ---- Discover sidebar: refine block -------------------------------------
7965 Subordinate to the spine: same controls, quieter. */
7966 .filter-refine { margin-top: var(--gap-section); }
7967 .filter-fieldset { border: none; padding: 0; margin: 0; min-width: 0; }
7968 .filter-check { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7969 .filter-check input { width: auto; margin: 0; }
7970 .filter-check span:first-of-type { flex: 1; }
7971
7972 /* A zero-count facet stays visible but inert, so the shape of the catalog
7973 stays legible as filters narrow. No existing rule covers a disabled input
7974 inside a label, so this is the checkbox counterpart of the button rule.
7975 `.is-empty` covers the drill rungs, which are links and carry no input at
7976 all; they were rendering identically to rungs with items. */
7977 .filter-item:has(input:disabled), .filter-item.is-empty { opacity: 0.5; }
7978 .filter-item:has(input:disabled) .filter-check { cursor: not-allowed; }
7979
7980 /* Price buckets are links carrying a range, not form state. */
7981 .price-buckets { margin-top: var(--gap-peer); }
7982 .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; }
7983
7984 .mode-toggle { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
7985 .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; }
7986 .toggle-btn:hover { opacity: 0.7; }
7987 .toggle-btn.is-selected { background: var(--primary-dark); color: var(--primary-light); }
7988 .table-controls { background: var(--surface-sunken); display: flex; align-items: center; gap: var(--gap-section); padding: var(--gap-peer) var(--gap-section); }
7989 .search-field { flex: 1 1 0; background: var(--surface-page); border: none; padding: var(--gap-peer) var(--gap-section); font-family: inherit; font-size: var(--text-note); color: var(--content); min-width: 0; }
7990 .search-field:focus { outline: 2px solid var(--action); }
7991 .search-field::placeholder { opacity: 0.5; }
7992 .table-meta { font-size: var(--text-fine); opacity: 0.6; white-space: nowrap; flex-shrink: 0; }
7993 .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; }
7994 .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; }
7995 .table-header.projects-header { grid-template-columns: 1fr 100px 80px 70px; }
7996 /* Column headers describe the list layout only; hidden when the grid view is active. */
7997 .table-header.is-hidden { display: none; }
7998 .col-right { text-align: right; }
7999 .results-table { border: 1px solid var(--border); border-top: none; }
8000 .table-row { /* respec-ok: a five-column grid template. The generated `.table-row` sets a display
8001 but has no vocabulary for column tracks, so the tracks and the display that carries
8002 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; }
8003 .table-row-item { grid-template-columns: 1fr 30px; padding: 0; gap: 0; }
8004 .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; }
8005 .table-row-item .row-save { display: flex; align-items: center; justify-content: center; }
8006 .table-row.project-row { grid-template-columns: 1fr 100px 80px 70px; }
8007 .table-row:last-child { border-bottom: none; }
8008 .table-row:hover { background: var(--surface-sunken); text-decoration: none; }
8009 .row-type { font-size: var(--text-fine); background: var(--surface-sunken); padding: var(--gap-bound) var(--gap-peer); text-align: center; opacity: 0.7; }
8010 .row-info { min-width: 0; display: flex; flex-direction: column; gap: var(--gap-bound); }
8011 .row-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
8012 .row-creator { font-size: var(--text-fine); opacity: 0.5; }.row-category { font-size: var(--text-fine); opacity: 0.6; }
8013 .row-price { text-align: right; }
8014 .row-items { text-align: right; opacity: 0.6; font-size: var(--text-fine); }
8015 .row-date { text-align: right; opacity: 0.5; font-size: var(--text-fine); }.pagination { display: flex; gap: var(--gap-bound); }
8016 .pagination button { background: var(--surface-page); border: none; padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); cursor: pointer; }
8017 .pagination button:disabled { opacity: 0.3; cursor: not-allowed; }
8018 .pagination button.is-selected { background: var(--primary-dark); color: var(--primary-light); }
8019
8020 /* Issue tabs (open / closed) inside the git browser */
8021 .issue-tabs { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
8022 .issue-tab {
8023 padding: var(--gap-peer) var(--gap-section);
8024 border: 1px solid var(--border);
8025 background: var(--surface-overlay);
8026 font-family: var(--font-mono);
8027 font-size: var(--text-note);
8028 color: var(--content);
8029 text-decoration: none;
8030 opacity: 0.7;
8031 }
8032 .issue-tab:hover { opacity: 1; }
8033 .issue-tab.is-selected {
8034 opacity: 1;
8035 background: var(--highlight-faint);
8036 border-color: var(--focus-ring);
8037 }
8038 .page-info { opacity: 0.6; }
8039 .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); }
8040 .padded-page .page-header h1 { font-size: var(--text-head); text-align: center; position: absolute; left: 50%; transform: translateX(-50%); }
8041 .padded-page .page-header nav { margin: 0; }
8042 .padded-page .page-header .nav-links { gap: var(--gap-pane); }
8043 .discover-filter-toggle {
8044 display: none;
8045 font-family: var(--font-mono);
8046 font-size: var(--text-note);
8047 padding: var(--gap-peer) var(--gap-section);
8048 background: var(--surface-overlay);
8049 border: 1px solid var(--border);
8050 cursor: pointer;
8051 margin-bottom: var(--gap-section);
8052 }
8053
8054 .discover-filter-toggle.active { background: var(--surface-sunken); border-color: var(--border-strong); }
8055
8056 .discover-filter-toggle .filter-count {
8057 background: var(--action);
8058 color: var(--primary-light);
8059 font-size: var(--text-fine);
8060 padding: var(--gap-bound) var(--gap-peer);
8061 border-radius: var(--radius-control);
8062 margin-left: var(--gap-bound);
8063 }
8064
8065 @media (max-width: 839px) {
8066 .discover-filter-toggle { display: inline-block; }
8067 .discover-layout { grid-template-columns: 1fr; }
8068 .discover-sidebar { display: none; }
8069 /* Open state lives on the layout, not the sidebar: the sidebar is replaced
8070 by an out-of-band swap on every filter change and would lose the class. */
8071 .discover-layout.filters-open .discover-sidebar { display: block; margin-bottom: var(--gap-section); }
8072 .table-header, .table-row { grid-template-columns: 40px 1fr 60px; }
8073 .table-row-item { grid-template-columns: 1fr 30px; }
8074 .table-row-item .table-row-link { grid-template-columns: 40px 1fr 60px; }
8075 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr 70px; }
8076 .row-date, .row-items, .row-category { display: none; }
8077 .table-header span:nth-child(3), .table-header span:nth-child(4), .table-header span:nth-child(5) { display: none; }
8078 .table-header.projects-header span:nth-child(3), .table-header.projects-header span:nth-child(4) { display: none; }
8079 }
8080
8081 @media (max-width: 599px) {
8082 .table-header, .table-row { grid-template-columns: 1fr 60px; }
8083 .table-row-item { grid-template-columns: 1fr 30px; }
8084 .table-row-item .table-row-link { grid-template-columns: 1fr 60px; }
8085 .row-type { display: none; }
8086 .table-header span:first-child { display: none; }
8087 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr; }
8088 .table-header.projects-header span:nth-child(2) { display: none; }
8089 .row-price { font-size: var(--text-fine); }
8090 .search-field { min-width: 0; width: 100%; }
8091 .table-controls { flex-wrap: wrap; }
8092 .mode-toggle { flex-wrap: wrap; }
8093 .table-footer { flex-direction: column; gap: var(--gap-peer); align-items: flex-start; }
8094 }
8095
8096 /* ── Feature Cards & Suggestion Input (shared by wizard + settings) ── */
8097
8098 .type-grid {
8099 display: grid;
8100 grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
8101 gap: var(--gap-section);
8102 margin-bottom: var(--gap-section);
8103 }
8104
8105 /* Canonical selectable-card primitive. One recipe; per-context size modifiers.
8106 Used by wizard type pickers, pricing-model pickers, content-choice pickers,
8107 monetization-mode pickers, anywhere a radio-like choice is rendered as a card. */
8108 .card--selectable {
8109 cursor: pointer;
8110 display: block;
8111 }
8112 .card--selectable input[type="radio"],
8113 .card--selectable input[type="checkbox"] {
8114 position: absolute;
8115 opacity: 0;
8116 pointer-events: none;
8117 }
8118
8119 .card--selectable-inner {
8120 display: flex;
8121 flex-direction: column;
8122 align-items: center;
8123 justify-content: center;
8124 padding: var(--gap-section);
8125 border: 2px solid var(--border);
8126 border-radius: var(--radius-square);
8127 background: var(--surface-page);
8128 text-align: center;
8129 transition: border-color 0.15s, background 0.15s;
8130 min-height: 60px;
8131 }
8132 /* Variant: column-aligned content (text-heavy pricing cards). */
8133 .card--selectable.is-text-heavy .card--selectable-inner {
8134 align-items: stretch;
8135 text-align: left;
8136 padding: var(--gap-section);
8137 }
8138
8139 .card--selectable input:checked + .card--selectable-inner,
8140 .card--selectable.is-selected .card--selectable-inner {
8141 border-color: var(--action);
8142 background: var(--highlight-faint);
8143 }
8144
8145 .card--selectable:hover .card--selectable-inner {
8146 background: var(--surface-sunken);
8147 }
8148
8149 /* Focus. The card is a <label> wrapping a visually hidden radio, so the focus
8150 lands on the input and the ring has to be drawn on the inner surface. The
8151 <a> form of the card takes focus itself. */
8152 .card--selectable:focus-visible,
8153 .card--selectable input:focus-visible + .card--selectable-inner {
8154 outline: 2px solid var(--focus-ring);
8155 outline-offset: 2px;
8156 }
8157
8158 .card--selectable-label {
8159 font-weight: bold;
8160 font-size: var(--text-note);
8161 }
8162
8163 .card--selectable-desc {
8164 font-size: var(--text-fine);
8165 color: var(--content-muted);
8166 margin-top: var(--gap-bound);
8167 }
8168
8169 /* Disabled selectable card, visually dimmed, ignores pointer events. */
8170 .card--selectable.is-disabled {
8171 opacity: 0.7;
8172 pointer-events: none;
8173 }
8174
8175 .suggestion-input {
8176 position: relative;
8177 }
8178
8179 .suggestion-dropdown {
8180 display: none;
8181 position: absolute;
8182 top: 100%;
8183 left: 0;
8184 right: 0;
8185 background: var(--surface-page);
8186 border: 1px solid var(--border);
8187 border-top: none;
8188 border-radius: 0 0 var(--radius-square) var(--radius-square);
8189 max-height: 200px;
8190 overflow-y: auto;
8191 z-index: var(--z-dropdown);
8192 box-shadow: var(--elevation-overlay);
8193 }
8194
8195 .suggestion-dropdown.open {
8196 display: block;
8197 }
8198
8199 .suggestion-item {
8200 padding: var(--gap-peer) var(--gap-section);
8201 cursor: pointer;
8202 font-size: var(--text-note);
8203 }
8204
8205 .suggestion-item:hover {
8206 background: var(--surface-overlay);
8207 }
8208
8209 .suggestion-create {
8210 font-style: italic;
8211 opacity: 0.8;
8212 border-top: 1px solid var(--border);
8213 }
8214
8215 /* ── Tips ── */
8216
8217 .tip-section {
8218 margin-top: var(--gap-section);
8219 text-align: center;
8220 }
8221
8222 .tip-toggle {
8223 font-family: var(--font-mono);
8224 font-size: var(--text-note);
8225 padding: var(--gap-peer) var(--gap-pane);
8226 background: none;
8227 border: 1px solid var(--content);
8228 color: var(--content);
8229 cursor: pointer;
8230 text-decoration: none;
8231 display: inline-block;
8232 }
8233
8234 .tip-toggle:hover {
8235 background: var(--content);
8236 color: var(--surface-page);
8237 }
8238
8239 .tip-form {
8240 margin-top: var(--gap-section);
8241 display: flex;
8242 flex-direction: column;
8243 gap: var(--gap-section);
8244 max-width: 300px;
8245 margin-left: auto;
8246 margin-right: auto;
8247 text-align: left;
8248 }
8249
8250 .tip-form.hidden {
8251 display: none;
8252 }
8253
8254 .tip-amount-row {
8255 display: flex;
8256 align-items: center;
8257 gap: var(--gap-bound);
8258 }
8259
8260 .tip-amount-row .pricing-currency {
8261 font-size: var(--text-subhead);
8262 opacity: 0.7;
8263 }
8264
8265 .tip-amount-input {
8266 width: 80px;
8267 font-size: var(--text-subhead);
8268 padding: var(--gap-peer);
8269 border: 1px solid var(--border);
8270 background: var(--surface-overlay);
8271 font-family: var(--font-mono);
8272 }
8273
8274 .tip-message-input {
8275 width: 100%;
8276 font-size: var(--text-note);
8277 padding: var(--gap-peer);
8278 border: 1px solid var(--border);
8279 background: var(--surface-overlay);
8280 font-family: var(--font-sans);
8281 resize: vertical;
8282 }
8283
8284 .tip-submit {
8285 font-family: var(--font-mono);
8286 font-size: var(--text-note);
8287 padding: var(--gap-peer) var(--gap-section);
8288 background: var(--content);
8289 color: var(--surface-page);
8290 border: none;
8291 cursor: pointer;
8292 }
8293
8294 .tip-submit:hover {
8295 opacity: 0.85;
8296 }
8297
8298 /* ===========================================
8299 COLLECTION PICKER (shared dropdown)
8300 =========================================== */
8301
8302 .collection-picker-anchor { position: relative; }
8303 .collection-picker {
8304 position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
8305 background: var(--surface-page); border: 1px solid var(--border);
8306 box-shadow: var(--elevation-overlay); min-width: 220px;
8307 }
8308 .collection-picker-list { max-height: 200px; overflow-y: auto; padding: var(--gap-bound) 0; }
8309 .collection-picker-item {
8310 display: flex; align-items: center; gap: var(--gap-peer);
8311 padding: var(--gap-peer) var(--gap-section); cursor: pointer; font-size: var(--text-note);
8312 }
8313 .collection-picker-item:hover { background: var(--surface-sunken); }
8314 .collection-picker-loading,
8315 .collection-picker-create { border-top: 1px solid var(--border); padding: var(--gap-peer) var(--gap-section); }
8316 .collection-picker-create form { display: flex; gap: var(--gap-peer); }
8317 .collection-picker-create input { flex: 1; padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-note); min-width: 0; }
8318 .collection-picker-create button { font-size: var(--text-note); white-space: nowrap; }
8319
8320 /* Save button on discover cards */
8321 .row-save, .grid-card-save {
8322 background: none; border: none; cursor: pointer;
8323 font-size: var(--text-note); padding: var(--gap-bound) var(--gap-peer); opacity: 0.4;
8324 color: var(--content); transition: opacity 0.15s;
8325 }
8326 .row-save:hover, .grid-card-save:hover { opacity: 1; }
8327 .grid-card-save {
8328 position: absolute; top: 0.4rem; right: 0.4rem; z-index: var(--z-raised);
8329 background: var(--surface-page); border-radius: var(--radius-control); padding: var(--gap-bound) var(--gap-peer);
8330 box-shadow: var(--elevation-overlay); opacity: 0;
8331 }
8332 .grid-card:hover .grid-card-save { opacity: 0.7; }
8333 .grid-card-save:hover { opacity: 1; }
8334
8335 /* Item page save button saved state */
8336 button.saved { border-color: var(--action); color: var(--action); }
8337
8338 /* ===========================================
8339 SEARCH SUGGESTIONS
8340 =========================================== */
8341
8342 .search-wrapper { position: relative; flex: 1; min-width: 0; }
8343 .search-suggestions {
8344 display: none; position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
8345 background: var(--surface-page); border: 1px solid var(--border); border-top: none;
8346 box-shadow: var(--elevation-overlay); max-height: 280px; overflow-y: auto;
8347 }
8348 .suggestion-item {
8349 display: flex; justify-content: space-between; align-items: center;
8350 padding: var(--gap-peer) var(--gap-section); text-decoration: none; color: var(--content);
8351 font-size: var(--text-note); cursor: pointer;
8352 }
8353 .suggestion-item:hover, .suggestion-item.highlighted { background: var(--surface-sunken); }
8354 .suggestion-category { font-size: var(--text-fine); opacity: 0.5; text-transform: uppercase; letter-spacing: 0.05em; }
8355
8356 /* ===========================================
8357 DOC UI EXAMPLES (embedded live UI in docs)
8358 =========================================== */
8359
8360 .doc-ui {
8361 margin: var(--gap-pane) 0; border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden;
8362 }
8363 .doc-ui-frame {
8364 padding: var(--gap-section); background: var(--surface-overlay); pointer-events: none; user-select: none;
8365 }
8366 .doc-ui figcaption {
8367 padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.6;
8368 border-top: 1px solid var(--border); background: var(--surface-overlay);
8369 font-family: var(--font-mono);
8370 }
8371 .doc-ui-missing {
8372 padding: var(--gap-section); opacity: 0.4; font-style: italic;
8373 }
8374
8375 /* Library "New" badge for items with undownloaded versions. Not a status, so
8376 it takes no tone; it is the plain badge with the accent hue and a little
8377 more weight, since "New" is the one badge meant to catch the eye. */
8378 .badge-new {
8379 color: var(--action); font-weight: bold;
8380 margin-left: var(--gap-peer); vertical-align: middle;
8381 }
8382
8383 /* ===========================================
8384 BENEFIT LIST (bulleted feature lists, e.g. Stripe Connect prompt)
8385 =========================================== */
8386 .benefit-list {
8387 list-style: none;
8388 padding: 0;
8389 margin: 0 auto var(--gap-pane);
8390 max-width: 280px;
8391 text-align: left;
8392 display: flex;
8393 flex-direction: column;
8394 gap: var(--gap-peer);
8395 }
8396 .benefit-list li {
8397 position: relative;
8398 padding-left: var(--gap-section);
8399 font-size: var(--text-note);
8400 }
8401 .benefit-list li::before {
8402 content: "";
8403 position: absolute;
8404 left: 0.15rem;
8405 top: 0.55rem;
8406 width: 6px;
8407 height: 6px;
8408 border-radius: var(--radius-round);
8409 background: var(--success);
8410 }
8411
8412 /* ===========================================
8413 CHECKLIST (setup / onboarding step lists)
8414 =========================================== */
8415 .checklist {
8416 margin-bottom: var(--gap-pane);
8417 padding: var(--gap-section) var(--gap-pane);
8418 background: var(--surface-overlay);
8419 border: 1px solid var(--border);
8420 }
8421 .checklist-header {
8422 display: flex;
8423 justify-content: space-between;
8424 align-items: center;
8425 margin-bottom: var(--gap-section);
8426 }
8427 .checklist-title {
8428 margin: 0;
8429 font-family: var(--font-display);
8430 font-weight: bold;
8431 }
8432 .checklist-mark {
8433 width: 1.1rem;
8434 height: 1.1rem;
8435 border-radius: var(--radius-round);
8436 border: 2px solid var(--border);
8437 flex-shrink: 0;
8438 box-sizing: border-box;
8439 }
8440 .checklist-mark--done {
8441 border-color: var(--success);
8442 background: var(--success);
8443 }
8444 .checklist-label {
8445 flex: 1;
8446 font-size: var(--text-note);
8447 }
8448 .checklist-label--done {
8449 opacity: 0.5;
8450 text-decoration: line-through;
8451 }
8452
8453 /* ===========================================
8454 PAYMENTS TAB (replaces inline styles in user_payments.html)
8455 =========================================== */
8456 /* Canonical "stack row", title-on-left + actions-on-right header bar.
8457 One primitive; per-context modifiers handle gap, wrap, alignment, margins.
8458 Aliases below cover existing per-tab class names (kept as JS hooks). */
8459 .stack-row,
8460 .content-header,
8461 .user-media-header,
8462 .transactions-header,
8463 .analytics-range-bar,
8464 .cart-multi-bar {
8465 display: flex;
8466 justify-content: space-between;
8467 align-items: center;
8468 flex-wrap: wrap;
8469 gap: var(--gap-section);
8470 }
8471 .stack-row--tight { gap: var(--gap-section); flex-wrap: nowrap; }
8472 .stack-row--top { align-items: flex-start; }
8473 .stack-row--bordered,
8474 .transactions-header {
8475 margin-bottom: var(--gap-section);
8476 padding-bottom: var(--gap-section);
8477 border-bottom: 1px solid var(--border);
8478 }
8479 .content-header,
8480 .user-media-header,
8481 .analytics-range-bar { margin-bottom: var(--gap-section); }
8482 .cart-multi-bar {
8483 margin-bottom: var(--gap-pane);
8484 padding: var(--gap-section);
8485 background: var(--surface-sunken);
8486 border: 1px solid var(--border);
8487 }
8488
8489 /* Canonical list row (flex, gap, padding, bottom border). Aliases below cover
8490 the per-tab variants. They exist as JS hooks but share the same look. */
8491 .list-row,
8492 .psection-row,
8493 .section-mgmt-row,
8494 .bundle-picker-row,
8495 .checklist-row {
8496 display: flex;
8497 align-items: center;
8498 gap: var(--gap-section);
8499 padding: var(--gap-group) 0;
8500 border-bottom: 1px solid var(--border);
8501 }
8502 .list-row:last-child,
8503 .psection-row:last-child,
8504 .section-mgmt-row:last-child,
8505 .bundle-picker-row:last-child,
8506 .checklist-row:last-child { border-bottom: none; }
8507 .warn-glyph {
8508 font-size: var(--text-subhead);
8509 color: var(--warning);
8510 }
8511
8512 .card-h {
8513 margin: 0 0 var(--gap-section) 0;
8514 font-size: var(--text-body);
8515 }
8516
8517 .amount-big {
8518 font-size: var(--text-subhead);
8519 font-weight: bold;
8520 }
8521
8522 .account-details {
8523 margin-top: var(--gap-section);
8524 padding-top: var(--gap-section);
8525 border-top: 1px solid var(--border);
8526 }
8527 .account-details summary { cursor: pointer; }
8528 .account-details-id {
8529 font-size: var(--text-fine);
8530 opacity: 0.5;
8531 font-family: var(--font-mono);
8532 margin-top: var(--gap-bound);
8533 }
8534
8535 .stripe-actions {
8536 display: flex;
8537 gap: var(--gap-section);
8538 flex-wrap: wrap;
8539 }
8540 .stripe-actions-disconnect { margin-left: auto; }
8541
8542 .payout-stats-grid {
8543 display: grid;
8544 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
8545 gap: var(--gap-section);
8546 margin-top: var(--gap-section);
8547 padding-top: var(--gap-section);
8548 border-top: 1px solid var(--border);
8549 }
8550
8551 .payouts-disabled-notice {
8552 margin-top: var(--gap-section);
8553 padding: var(--gap-section);
8554 background: var(--warning);
8555 color: var(--primary-light);
8556 font-size: var(--text-note);
8557 }
8558
8559 .fee-breakdown {
8560 display: grid;
8561 grid-template-columns: 1fr 1fr;
8562 gap: var(--gap-peer);
8563 font-size: var(--text-note);
8564 }
8565 .fee-row {
8566 display: flex;
8567 justify-content: space-between;
8568 padding: var(--gap-peer) 0;
8569 border-bottom: 1px solid var(--border);
8570 }
8571
8572 .checkbox-row {
8573 display: flex;
8574 align-items: flex-start;
8575 gap: var(--gap-peer);
8576 cursor: pointer;
8577 font-size: var(--text-note);
8578 }
8579 .check-inline {
8580 width: auto;
8581 margin-top: var(--gap-bound);
8582 }
8583
8584 .stripe-connect-prompt { text-align: center; padding: var(--gap-pane); }
8585 .stripe-logo { opacity: 0.8; }
8586 .stripe-connect-btn {
8587 background: var(--stripe);
8588 padding: var(--gap-section) var(--gap-page);
8589 }
8590
8591 .export-row {
8592 display: flex;
8593 justify-content: flex-end;
8594 margin-bottom: var(--gap-section);
8595 }
8596
8597 .splits-grid {
8598 display: grid;
8599 grid-template-columns: 1fr 1fr;
8600 gap: var(--gap-section);
8601 margin-bottom: var(--gap-section);
8602 }
8603
8604 .tip-message {
8605 max-width: 300px;
8606 overflow: hidden;
8607 text-overflow: ellipsis;
8608 }
8609
8610 .transactions-title { font-size: var(--text-subhead); }
8611
8612 /* ===========================================
8613 CREATOR TAB (replaces inline styles in user_creator.html)
8614 =========================================== */
8615 .creator-tab-section {
8616 padding: var(--gap-page);
8617 }
8618 .creator-tab-section--centered { text-align: center; }
8619 .creator-tab-section--no-top { padding-top: 0; }
8620
8621 .creator-h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
8622 .creator-h2--sm { font-size: var(--text-subhead); }
8623
8624 .creator-section-label {
8625 margin-left: var(--gap-page);
8626 margin-right: var(--gap-page);
8627 }
8628 .creator-form-section { padding: 0 var(--gap-page); }
8629
8630 .creator-plan-box {
8631 background: var(--surface-overlay);
8632 padding: var(--gap-section) var(--gap-pane);
8633 }
8634 .creator-plan-head {
8635 display: flex;
8636 align-items: center;
8637 gap: var(--gap-section);
8638 margin-bottom: var(--gap-peer);
8639 flex-wrap: wrap;
8640 }
8641 .creator-plan-name {
8642 font-family: var(--font-display);
8643 font-weight: bold;
8644 font-size: var(--text-lead);
8645 }
8646
8647 /* Founder pricing, two states, and neither is a status tone: locked-for-life
8648 is emphasis and pending-lock-in is an aside. They were a --content fill and
8649 a --surface-sunken fill back when every badge was filled; as coloured text
8650 the same distinction is full-strength ink against muted. */
8651 .badge--founder-locked {
8652 color: var(--content);
8653 font-weight: bold;
8654 }
8655 .badge--founder-pending {
8656 color: var(--content-muted);
8657 }
8658
8659 .founder-callout {
8660 background: var(--surface-overlay);
8661 border-left: 3px solid var(--content);
8662 padding: var(--gap-section);
8663 }
8664
8665 .creator-tier-grid {
8666 display: grid;
8667 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
8668 gap: var(--gap-section);
8669 }
8670 .creator-tier-card {
8671 background: var(--surface-overlay);
8672 padding: var(--gap-section);
8673 text-align: center;
8674 }
8675 .creator-tier-name {
8676 font-family: var(--font-display);
8677 font-weight: bold;
8678 font-size: var(--text-body);
8679 margin-bottom: var(--gap-bound);
8680 }
8681 .creator-tier-link {
8682 color: inherit;
8683 text-decoration: none;
8684 border-bottom: 1px solid var(--border);
8685 }
8686 .creator-tier-sub {
8687 font-size: var(--text-fine);
8688 opacity: 0.7;
8689 }
8690 .creator-tier-storage { font-size: var(--text-fine); }
8691 .creator-tier-buttons {
8692 display: flex;
8693 flex-direction: column;
8694 gap: var(--gap-peer);
8695 }
8696 .creator-tier-btn {
8697 font-size: var(--text-note);
8698 width: 100%;
8699 }
8700
8701 .strike-dim {
8702 opacity: 0.5;
8703 text-decoration: line-through;
8704 }
8705
8706 .storage-box {
8707 background: var(--surface-overlay);
8708 padding: var(--gap-section) var(--gap-pane);
8709 }
8710 .storage-row {
8711 display: flex;
8712 justify-content: space-between;
8713 margin-bottom: var(--gap-peer);
8714 font-size: var(--text-note);
8715 }
8716 .storage-breakdown {
8717 display: grid;
8718 grid-template-columns: 1fr 1fr;
8719 gap: var(--gap-bound) var(--gap-pane);
8720 margin-top: var(--gap-section);
8721 font-size: var(--text-note);
8722 }
8723
8724 .broadcast-head {
8725 display: flex;
8726 justify-content: space-between;
8727 align-items: flex-start;
8728 }
8729 .broadcast-desc { flex: 1; }
8730 .broadcast-export { margin-left: var(--gap-section); }
8731
8732 .creator-divider {
8733 margin: 0 var(--gap-page);
8734 border-color: var(--border);
8735 }
8736
8737 .creator-intro { line-height: 1.6; }
8738 .creator-pitch-box {
8739 background: var(--surface-overlay);
8740 padding: var(--gap-pane);
8741 }
8742
8743 .mono-sm {
8744 font-family: var(--font-mono);
8745 font-size: var(--text-note);
8746 }
8747
8748 /* ===========================================
8749 PROJECT CONTENT TAB (replaces inline styles in project_content.html)
8750 =========================================== */
8751
8752 .empty-state--lg { padding: var(--gap-page) var(--gap-section); }
8753
8754 .content-filters {
8755 display: flex;
8756 gap: var(--gap-section);
8757 margin-bottom: var(--gap-section);
8758 align-items: center;
8759 flex-wrap: wrap;
8760 }
8761 .content-filter-search {
8762 flex: 1;
8763 min-width: 180px;
8764 padding: var(--gap-peer) var(--gap-group);
8765 font-size: var(--text-note);
8766 }
8767 .content-filter-select {
8768 padding: var(--gap-peer);
8769 font-size: var(--text-note);
8770 }
8771
8772 .bulk-action-bar {
8773 padding: var(--gap-section);
8774 margin-bottom: var(--gap-section);
8775 background: var(--surface-raised);
8776 border: 1px solid var(--border);
8777 border-radius: var(--radius-square);
8778 display: flex;
8779 align-items: center;
8780 gap: var(--gap-peer);
8781 flex-wrap: wrap;
8782 opacity: 0.4;
8783 }
8784 .bulk-action-bar--active { opacity: 1; }
8785 .bulk-count { font-weight: bold; min-width: 5rem; }
8786 .bulk-deselect { margin-left: auto; }
8787 .danger-text { color: var(--danger); }
8788
8789 .bulk-form {
8790 margin-bottom: var(--gap-section);
8791 padding: var(--gap-section);
8792 background: var(--surface-sunken);
8793 border: 1px solid var(--border);
8794 border-radius: var(--radius-square);
8795 }
8796 .bulk-form-row {
8797 display: flex;
8798 gap: var(--gap-section);
8799 align-items: end;
8800 }
8801 .bulk-form-label {
8802 font-size: var(--text-note);
8803 display: block;
8804 margin-bottom: var(--gap-bound);
8805 }
8806
8807 /* Sortable table column header
8808
8809 The cursor and the caret are NOT here. `.table-heading[data-sortable]` and
8810 `.table-heading[aria-sort="..."]::after` are generated into static/layout.css
8811 by makeover-webview out of `Column::sortable` and `Column::sorted`. This file
8812 carried two hand-written copies of the same fact -- `.sortable-th` here and
8813 `.data-table th.sortable` up at the data-table block -- and a third channel in
8814 tab-project-content.js, which wrote the glyph as text and toggled
8815 `.sort-arrow--active`. Three vocabularies, one state, and the generated pair
8816 matched nothing because no heading carried `.table-heading`.
8817
8818 The focus ring stays: it is this app's focus treatment, not a restatement of
8819 the caret, and makeover emits no focus rule for a heading. */
8820 .table-heading[data-sortable] {
8821 user-select: none;
8822 transition: background 0.2s ease;
8823 }
8824
8825 .table-heading[data-sortable]:hover {
8826 background: var(--border);
8827 }
8828
8829 .table-heading[data-sortable]:focus-visible {
8830 outline: 2px solid var(--focus-ring);
8831 outline-offset: 2px;
8832 }
8833
8834 /* Data-table column-width utilities (numeric width hints,
8835 kept as classes to avoid inline `style="width: NN%"`) */
8836 .col-3 { width: 3%; }
8837 .col-5 { width: 5%; }
8838 .col-8 { width: 8%; }
8839 .col-10 { width: 10%; }
8840 .col-12 { width: 12%; }
8841 .col-15 { width: 15%; }
8842 .col-20 { width: 20%; }
8843 .col-32 { width: 32%; }
8844 .col-45 { width: 45%; }
8845
8846 /* Pixel-width utilities for inputs and narrow fixed-width controls.
8847 Use on form inputs that can't be sized by their parent column. */
8848 .w-80 { width: 80px; }
8849 .w-100 { width: 100px; }
8850 .w-120 { width: 120px; }
8851 .w-180 { width: 180px; }
8852 .w-220 { width: 220px; }
8853 .w-260 { width: 260px; }
8854 .w-full { width: 100%; }
8855 .maxw-320 { max-width: 320px; }
8856
8857 /* Canonical input shape modifiers. One base form-input look (defined globally
8858 at line ~365 input[type], textarea, select) plus these size + feature variants.
8859 Apply alongside the per-tab class which acts as a JS hook. */
8860 .input--xs {
8861 padding: var(--gap-bound) var(--gap-peer);
8862 font-size: var(--text-note);
8863 }
8864 .input--sm {
8865 padding: var(--gap-bound) var(--gap-peer);
8866 font-size: var(--text-note);
8867 }
8868 .input--mono { font-family: var(--font-mono); }
8869 .input--upper { text-transform: uppercase; }
8870 .input--numeric { text-align: right; }
8871
8872 .order-arrow-btn {
8873 padding: var(--gap-bound);
8874 font-size: var(--text-fine);
8875 line-height: 1;
8876 }
8877
8878 .bundle-toggle {
8879 background: none;
8880 border: none;
8881 cursor: pointer;
8882 padding: 0 var(--gap-bound) 0 0;
8883 font-size: var(--text-fine);
8884 color: var(--content-muted);
8885 }
8886 .bundle-child-cell { padding-left: var(--gap-page); }
8887 .bundle-child-title { opacity: 0.85; }
8888 .bundle-child-arrow {
8889 color: var(--content-muted);
8890 margin-right: var(--gap-bound);
8891 }
8892
8893 .item-actions {
8894 display: flex;
8895 gap: var(--gap-peer);
8896 }
8897
8898 .badge--inline-tiny {
8899 font-size: var(--text-fine);
8900 margin-left: var(--gap-bound);
8901 opacity: 0.7;
8902 }
8903
8904 .deleted-items-details { margin-top: var(--gap-page); }
8905 .deleted-items-summary {
8906 cursor: pointer;
8907 font-size: var(--text-body);
8908 opacity: 0.7;
8909 }
8910
8911 .section-divider {
8912 margin: var(--gap-page) 0;
8913 border: none;
8914 border-top: 1px solid var(--border);
8915 }
8916
8917 .inline-rename-input {
8918 font-weight: bold;
8919 width: 100%;
8920 padding: var(--gap-bound) var(--gap-peer);
8921 font-size: inherit;
8922 }
8923
8924 /* ===========================================
8925 ITEM DETAILS TAB (replaces inline styles in item_details.html)
8926 =========================================== */
8927
8928 /* Compact button utility (small icon-side actions) */
8929 .btn-compact { padding: var(--gap-peer) var(--gap-section); }
8930
8931 .ml-2 { margin-left: var(--gap-peer); }
8932 .cursor-pointer { cursor: pointer; }
8933 .inline { display: inline; }
8934
8935 .tag-search-wrap { position: relative; }
8936 .tag-suggestions {
8937 position: absolute;
8938 z-index: var(--z-dropdown);
8939 background: var(--surface-sunken);
8940 border: 1px solid var(--border);
8941 width: 100%;
8942 max-height: 200px;
8943 overflow-y: auto;
8944 }
8945
8946 .advanced-details { margin-top: var(--gap-pane); }
8947 .advanced-summary {
8948 cursor: pointer;
8949 font-size: var(--text-body);
8950 opacity: 0.8;
8951 }
8952
8953 /* Bundle table inside item-details */
8954 .bundle-table {
8955 width: 100%;
8956 border-collapse: collapse;
8957 }
8958 .bundle-table-head {
8959 text-align: left;
8960 font-size: var(--text-fine);
8961 opacity: 0.7;
8962 text-transform: uppercase;
8963 letter-spacing: 0.03em;
8964 }
8965 .bundle-row { border-bottom: 1px solid var(--border); }
8966 .bundle-cell { padding: var(--gap-peer); }
8967 .bundle-cell--first { padding-left: 0; }
8968 .bundle-cell--actions { width: 60px; }
8969 .bundle-cell--desc { font-size: var(--text-note); opacity: 0.8; }
8970 .bundle-cell--file { font-size: var(--text-note); }
8971
8972 .bundle-remove-btn {
8973 padding: var(--gap-bound) var(--gap-peer);
8974 font-size: var(--text-fine);
8975 }
8976
8977 .bundle-add-row {
8978 margin-top: var(--gap-section);
8979 display: flex;
8980 gap: var(--gap-section);
8981 align-items: center;
8982 }
8983
8984 .bundle-existing-details { margin-top: var(--gap-section); }
8985 .bundle-existing-summary {
8986 cursor: pointer;
8987 font-size: var(--text-note);
8988 opacity: 0.7;
8989 }
8990 .bundle-existing-row {
8991 margin-top: var(--gap-peer);
8992 display: flex;
8993 gap: var(--gap-peer);
8994 align-items: center;
8995 }
8996 .bundle-existing-select {
8997 flex: 1;
8998 padding: var(--gap-peer);
8999 }
9000
9001 .sections-intro {
9002 font-size: var(--text-note);
9003 opacity: 0.7;
9004 margin: var(--gap-section) 0 var(--gap-section);
9005 }
9006
9007 .section-mgmt-title {
9008 flex: 1;
9009 font-weight: bold;
9010 }
9011 .section-edit-btn,
9012 .section-del-btn {
9013 padding: var(--gap-bound) var(--gap-group);
9014 font-size: var(--text-fine);
9015 }
9016
9017 .section-add-details { margin-top: var(--gap-section); }
9018 .section-add-summary {
9019 cursor: pointer;
9020 font-size: var(--text-body);
9021 }
9022
9023 .section-edit-modal {
9024 margin-top: var(--gap-section);
9025 padding: var(--gap-section);
9026 background: var(--surface-sunken);
9027 }
9028 .section-edit-actions {
9029 display: flex;
9030 gap: var(--gap-peer);
9031 }
9032
9033 .publish-note {
9034 margin: var(--gap-section) 0;
9035 opacity: 0.7;
9036 text-align: left;
9037 }
9038
9039 /* ===========================================
9040 CONTENT WIZARD STEP (replaces inline styles in wizards/steps/item/content.html)
9041 =========================================== */
9042
9043 /* (Wizard upload-status + slim progress moved into canonical .upload-status*
9044 / .progress-bar-container--slim primitives near top of file.) */
9045
9046 /* Bundle items picker, server-rendered rows and JS-appended rows share these. */
9047 .bundle-picker-row-title { flex: 1; }
9048 .bundle-picker-row-type { font-size: var(--text-fine); opacity: 0.6; }
9049 .bundle-picker-row-unlisted {
9050 font-size: var(--text-fine);
9051 display: flex;
9052 align-items: center;
9053 gap: var(--gap-bound);
9054 }
9055
9056 /* Batch upload queue table. */
9057 .batch-queue-table {
9058 width: 100%;
9059 border-collapse: collapse;
9060 }
9061 .batch-queue-table thead tr {
9062 border-bottom: 2px solid var(--border);
9063 text-align: left;
9064 }
9065 .batch-queue-table th,
9066 .batch-queue-table td {
9067 padding: var(--gap-peer);
9068 font-size: var(--text-note);
9069 }
9070 .batch-queue-table th:first-child,
9071 .batch-queue-table td:first-child { padding-left: 0; }
9072 .batch-queue-table th:last-child,
9073 .batch-queue-table td:last-child { padding-right: 0; text-align: right; }
9074 .batch-queue-table tbody tr { border-bottom: 1px solid var(--border); }
9075
9076 .batch-queue-name {
9077 max-width: 150px;
9078 overflow: hidden;
9079 text-overflow: ellipsis;
9080 white-space: nowrap;
9081 }
9082
9083 /* ===========================================
9084 ACCOUNT TAB (replaces inline styles in partials/tabs/user_account.html)
9085 =========================================== */
9086
9087 /* Canonical solid-tint callout (one of these in the codebase).
9088 Distinct from .alert (left-border) and .banner (full-bleed page notice).
9089 Variants:
9090 - .callout--danger (light bg, danger border, moderation, errors)
9091 - .callout--warning (light bg, warning border, DNS, email-not-verified)
9092 - .callout--solid-warning (full saturation warning bg, dark text, partial checkout) */
9093 .callout {
9094 padding: var(--gap-section);
9095 margin-bottom: var(--gap-section);
9096 font-size: var(--text-note);
9097 border: 1px solid;
9098 }
9099 .callout--danger {
9100 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
9101 border-color: var(--danger);
9102 }
9103 .callout--warning {
9104 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
9105 border-color: var(--warning);
9106 }
9107 .callout--solid-warning {
9108 background: var(--warning);
9109 color: var(--primary-dark);
9110 border-color: transparent;
9111 padding: var(--gap-section);
9112 }
9113 .callout-title { text-transform: capitalize; }
9114 .callout-meta {
9115 opacity: 0.6;
9116 margin-left: var(--gap-peer);
9117 }
9118 .callout-body { margin: var(--gap-peer) 0 0 0; }
9119 .callout-hint {
9120 font-size: var(--text-note);
9121 margin-top: var(--gap-peer);
9122 }
9123
9124 .account-mod-history { margin-top: var(--gap-section); }
9125 .account-mod-history summary {
9126 cursor: pointer;
9127 font-size: var(--text-note);
9128 opacity: 0.7;
9129 }
9130 .account-mod-history-list { margin-top: var(--gap-peer); }
9131 .account-mod-history-item {
9132 padding: var(--gap-peer) 0;
9133 border-bottom: 1px solid var(--border);
9134 font-size: var(--text-note);
9135 }
9136 .account-mod-history-reason {
9137 margin: var(--gap-bound) 0 0 0;
9138 opacity: 0.8;
9139 }
9140
9141 .account-tip-card-title {
9142 font-weight: bold;
9143 margin-bottom: var(--gap-peer);
9144 }
9145 .account-tip-list {
9146 display: flex;
9147 flex-direction: column;
9148 gap: var(--gap-bound);
9149 opacity: 0.8;
9150 }
9151
9152 /* Resend-verification button needs left spacing from preceding strong. */
9153 .account-resend-btn { margin-left: var(--gap-peer); }
9154
9155 /* Cluster of small inline forms (manage-billing / cancel / resume). */
9156 .inline-form { display: inline; }
9157
9158 /* Notification preferences, bare fieldset + monospace legend. */
9159 .pref-fieldset {
9160 border: none;
9161 padding: 0;
9162 margin: 0 0 var(--gap-section) 0;
9163 }
9164 .pref-fieldset--last { margin-bottom: var(--gap-peer); }
9165 .pref-legend {
9166 font-family: var(--font-mono);
9167 font-size: var(--text-note);
9168 font-weight: bold;
9169 margin-bottom: var(--gap-peer);
9170 }
9171
9172 /* The cannot-opt-out list, rendered from email::OperationalKind. Reads as
9173 reference material rather than as more settings: no controls, and the
9174 justification sits under its label rather than beside it. */
9175 .operational-mail {
9176 margin: var(--gap-peer) 0 0 0;
9177 }
9178 .operational-mail dt {
9179 font-weight: bold;
9180 font-size: var(--text-note);
9181 margin-top: var(--gap-peer);
9182 }
9183 .operational-mail dd {
9184 margin: 0;
9185 opacity: 0.8;
9186 font-size: var(--text-note);
9187 }
9188
9189 /* ===========================================
9190 PROJECT CODE TAB (replaces inline styles in partials/tabs/project_code.html)
9191 =========================================== */
9192
9193 .proj-code-docs-link { margin-left: var(--gap-section); }
9194
9195 .proj-code-linked-list { margin-bottom: var(--gap-pane); }
9196 .proj-code-linked-list > label { margin-bottom: var(--gap-peer); display: block; }
9197
9198 .proj-code-repo {
9199 margin-bottom: var(--gap-section);
9200 padding: var(--gap-section);
9201 background: var(--surface-overlay);
9202 }
9203 .proj-code-repo-row {
9204 display: flex;
9205 align-items: center;
9206 gap: var(--gap-section);
9207 }
9208 .proj-code-repo-link { flex: 1; }
9209 .proj-code-repo .danger { padding: var(--gap-bound) var(--gap-section); font-size: var(--text-note); }
9210
9211 .proj-code-collab-block {
9212 margin-top: var(--gap-peer);
9213 padding-top: var(--gap-peer);
9214 border-top: 1px solid var(--border);
9215 }
9216 .proj-code-collab-label { font-size: var(--text-note); margin-bottom: var(--gap-bound); display: block; }
9217 .proj-code-collab-row {
9218 display: flex;
9219 align-items: center;
9220 gap: var(--gap-peer);
9221 margin-bottom: var(--gap-bound);
9222 font-size: var(--text-note);
9223 }
9224 .proj-code-collab-name { flex: 1; }
9225 .proj-code-collab-readonly { opacity: 0.5; }
9226
9227 .proj-code-add-collab-row {
9228 display: flex;
9229 gap: var(--gap-peer);
9230 align-items: center;
9231 }
9232 .proj-code-add-collab-row .collab-username-input {
9233 flex: 1;
9234 font-size: var(--text-note);
9235 padding: var(--gap-bound) var(--gap-peer);
9236 }
9237 .proj-code-add-collab-row .collab-add-btn { white-space: nowrap; }
9238
9239 .proj-code-link-row {
9240 display: flex;
9241 gap: var(--gap-peer);
9242 align-items: center;
9243 }
9244 .proj-code-link-row #link-repo-select { flex: 1; }
9245 .proj-code-link-row #link-repo-btn { white-space: nowrap; }
9246
9247 .proj-code-create-section {
9248 margin-top: var(--gap-pane);
9249 padding-top: var(--gap-section);
9250 border-top: 1px solid var(--border);
9251 }
9252 .proj-code-create-section #create-repo-name { flex: 1; }
9253 .proj-code-create-section #create-repo-btn { white-space: nowrap; }
9254 .proj-code-create-status { margin-top: var(--gap-bound); }
9255 .proj-code-create-status.is-error { color: var(--danger); }
9256
9257 .proj-code-disabled {
9258 padding: var(--gap-page);
9259 text-align: center;
9260 opacity: 0.7;
9261 }
9262
9263 /* ===========================================
9264 ADMIN REPORT ENTRIES (replaces inline styles in partials/admin_report_entries.html)
9265 =========================================== */
9266
9267 .admin-report-table {
9268 width: 100%;
9269 border-collapse: collapse;
9270 font-size: var(--text-note);
9271 }
9272 .admin-report-table thead tr {
9273 border-bottom: 2px solid var(--border);
9274 text-align: left;
9275 }
9276 .admin-report-table tbody tr {
9277 border-bottom: 1px solid var(--border);
9278 }
9279 .admin-report-table th,
9280 .admin-report-table td {
9281 padding: var(--gap-peer);
9282 }
9283 .admin-report-table td.admin-report-reason {
9284 max-width: 200px;
9285 overflow: hidden;
9286 text-overflow: ellipsis;
9287 white-space: nowrap;
9288 }
9289 .admin-report-table td.admin-report-date {
9290 white-space: nowrap;
9291 }
9292
9293 .admin-report-target-type {
9294 opacity: 0.5;
9295 font-size: var(--text-fine);
9296 }
9297
9298 .admin-report-status-open {
9299 color: var(--action);
9300 font-weight: 600;
9301 }
9302 .admin-report-status-closed { opacity: 0.6; }
9303
9304 .admin-report-resolve-form {
9305 display: inline-flex;
9306 gap: var(--gap-bound);
9307 align-items: center;
9308 }
9309 .admin-report-resolve-form input[type="text"] {
9310 width: 120px;
9311 padding: var(--gap-bound) var(--gap-peer);
9312 font-size: var(--text-fine);
9313 }
9314 .admin-report-dismiss-form { display: inline; }
9315
9316 .admin-report-admin-notes {
9317 font-size: var(--text-fine);
9318 opacity: 0.6;
9319 }
9320
9321 /* ===========================================
9322 PROJECT PROMOTIONS TAB (replaces inline styles in partials/tabs/project_promotions.html)
9323 =========================================== */
9324
9325 .proj-promo-form {
9326 margin-bottom: var(--gap-page);
9327 }
9328
9329 .proj-promo-grid {
9330 display: grid;
9331 grid-template-columns: 1fr 1fr;
9332 gap: var(--gap-section) var(--gap-pane);
9333 max-width: 500px;
9334 }
9335
9336 .proj-promo-grid--bordered {
9337 margin-top: var(--gap-section);
9338 padding-top: var(--gap-section);
9339 border-top: 1px solid var(--border);
9340 }
9341
9342 .proj-promo-trial-fields {
9343 display: none;
9344 max-width: 500px;
9345 margin-top: var(--gap-section);
9346 }
9347
9348 .proj-promo-trial-fields.is-visible {
9349 display: block;
9350 }
9351
9352 .proj-promo-grid.is-hidden,
9353 .proj-promo-trial-fields.is-hidden {
9354 display: none;
9355 }
9356
9357 .proj-promo-form .form-group {
9358 margin-bottom: 0;
9359 }
9360
9361 .proj-promo-form .form-group label {
9362 font-size: var(--text-note);
9363 }
9364
9365 .proj-promo-trial-group {
9366 max-width: 240px;
9367 }
9368
9369 .proj-promo-scope {
9370 grid-column: 1 / -1;
9371 }
9372
9373 .proj-promo-actions {
9374 margin-top: var(--gap-section);
9375 }
9376
9377 .proj-promo-hint {
9378 margin-top: var(--gap-peer);
9379 }
9380
9381 /* ===========================================
9382 PROJECT OVERVIEW TAB (replaces inline styles in partials/tabs/project_overview.html)
9383 =========================================== */
9384
9385 .proj-overview-setup {
9386 margin-bottom: var(--gap-section);
9387 padding: var(--gap-section);
9388 background: var(--surface-overlay);
9389 border: 1px solid var(--border);
9390 }
9391 .proj-overview-setup-title {
9392 margin: 0 0 var(--gap-section) 0;
9393 font-family: var(--font-display);
9394 font-weight: bold;
9395 }
9396 .proj-overview-setup-row {
9397 display: flex;
9398 align-items: center;
9399 gap: var(--gap-section);
9400 padding: var(--gap-peer) 0;
9401 border-bottom: 1px solid var(--border);
9402 }
9403 .proj-overview-setup-row:last-child { border-bottom: none; }
9404 .proj-overview-setup-check {
9405 color: var(--success);
9406 font-family: var(--font-mono);
9407 font-size: var(--text-fine);
9408 text-transform: uppercase;
9409 letter-spacing: 0.04em;
9410 flex-shrink: 0;
9411 }
9412 .proj-overview-setup-bullet {
9413 width: 1.1rem;
9414 height: 1.1rem;
9415 border: 2px solid var(--border);
9416 border-radius: var(--radius-round);
9417 flex-shrink: 0;
9418 }
9419 .proj-overview-setup-label {
9420 flex: 1;
9421 font-size: var(--text-note);
9422 }
9423 .proj-overview-setup-label--done {
9424 opacity: 0.5;
9425 text-decoration: line-through;
9426 }
9427 .proj-overview-setup-btn {
9428 font-size: var(--text-fine);
9429 padding: var(--gap-bound) var(--gap-group);
9430 }
9431
9432 .proj-overview-tools { margin-top: var(--gap-page); }
9433 .proj-overview-tools-summary {
9434 cursor: pointer;
9435 font-size: var(--text-body);
9436 font-family: var(--font-display);
9437 font-weight: bold;
9438 }
9439 .proj-overview-tools-grid {
9440 display: grid;
9441 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
9442 gap: var(--gap-section);
9443 margin-top: var(--gap-section);
9444 }
9445 .proj-overview-tool {
9446 background: var(--surface-sunken);
9447 padding: var(--gap-section);
9448 }
9449 .proj-overview-tool-name {
9450 font-weight: bold;
9451 font-size: var(--text-body);
9452 margin-bottom: var(--gap-bound);
9453 }
9454 .proj-overview-tool-desc {
9455 font-size: var(--text-note);
9456 opacity: 0.7;
9457 margin: 0;
9458 }
9459
9460 /* ===========================================
9461 PROJECT SYNCKIT TAB (replaces inline styles in partials/tabs/project_synckit.html)
9462 =========================================== */
9463
9464 .proj-synckit-intro { margin-bottom: var(--gap-section); }
9465 .proj-synckit-intro-secondary { margin-bottom: var(--gap-pane); }
9466 .proj-synckit-create { margin-bottom: var(--gap-pane); }
9467 .proj-synckit-create-body {
9468 padding: var(--gap-section); background: var(--surface-overlay); border: 1px solid var(--border);
9469 }
9470 .proj-synckit-create-form {
9471 display: flex; flex-direction: row; gap: var(--gap-section); align-items: flex-end;
9472 }
9473 .proj-synckit-label {
9474 display: block; font-size: var(--text-note); margin-bottom: var(--gap-bound);
9475 }
9476 .proj-synckit-code { font-size: var(--text-fine); }
9477 .proj-synckit-code--wrap { font-size: var(--text-fine); word-break: break-all; }
9478 .proj-synckit-code--selectable { font-size: var(--text-note); word-break: break-all; user-select: all; }
9479 .proj-synckit-btn-tight {
9480 margin-left: var(--gap-peer); padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9481 }
9482 .proj-synckit-btn-row {
9483 padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9484 }
9485 .proj-synckit-btn-row + .proj-synckit-btn-row { margin-left: var(--gap-bound); }
9486 .proj-synckit-hint-inline {
9487 font-size: var(--text-fine); margin-left: var(--gap-bound);
9488 }
9489 .proj-synckit-hint-warn {
9490 font-size: var(--text-fine); color: var(--danger);
9491 }
9492 .proj-synckit-status-active { color: var(--success); }
9493 .proj-synckit-status-inactive { color: var(--danger); }
9494 .proj-synckit-new-key {
9495 padding: var(--gap-section); background: var(--surface-overlay);
9496 border: 1px solid var(--border); margin-top: var(--gap-peer);
9497 }
9498 .proj-synckit-new-key-heading {
9499 margin: 0 0 var(--gap-peer) 0; font-weight: 500; color: var(--danger);
9500 }
9501 .proj-synckit-create-status--error { color: var(--danger); margin-top: var(--gap-peer); }
9502 .proj-synckit-create-status--reset { margin-top: var(--gap-peer); }
9503
9504 /* ===========================================
9505 ITEM PAGE (replaces inline styles in pages/item.html)
9506 =========================================== */
9507
9508 .item-page .breadcrumb {
9509 display: flex;
9510 justify-content: space-between;
9511 align-items: center;
9512 }
9513
9514 .item-page .breadcrumb-edit-link {
9515 font-size: var(--text-note);
9516 color: var(--content);
9517 }
9518
9519 .item-page .video-player {
9520 width: 100%;
9521 max-height: 600px;
9522 background: var(--primary-dark);
9523 }
9524
9525 .item-page .item-cover-img {
9526 width: 100%;
9527 aspect-ratio: 1 / 1;
9528 object-fit: cover;
9529 display: block;
9530 }
9531
9532
9533 .item-page .bundle-notice-inline {
9534 background: var(--surface-sunken);
9535 padding: var(--gap-section);
9536 }
9537
9538 .item-page .bundle-notice-title {
9539 font-weight: bold;
9540 margin-bottom: var(--gap-peer);
9541 }
9542
9543 .item-page .bundle-notice-desc {
9544 opacity: 0.8;
9545 font-size: var(--text-note);
9546 }
9547
9548 .item-page .bundle-notice-entry {
9549 margin-top: var(--gap-section);
9550 }
9551
9552 .item-page .bundle-notice-entry a {
9553 font-weight: bold;
9554 }
9555
9556 .item-page .bundle-notice-price {
9557 opacity: 0.7;
9558 }
9559
9560 .item-page .bundle-notice-empty {
9561 margin-top: var(--gap-peer);
9562 opacity: 0.7;
9563 font-size: var(--text-note);
9564 }
9565
9566 .item-page .promo-details {
9567 margin-bottom: var(--gap-section);
9568 font-size: var(--text-note);
9569 }
9570
9571 .item-page .promo-summary {
9572 cursor: pointer;
9573 opacity: 0.7;
9574 }
9575
9576 .item-page .promo-form {
9577 display: flex;
9578 gap: var(--gap-peer);
9579 align-items: center;
9580 margin-top: var(--gap-peer);
9581 }
9582
9583 .item-page .promo-input {
9584 flex: 1;
9585 padding: var(--gap-peer) var(--gap-group);
9586 font-size: var(--text-note);
9587 }
9588
9589 .item-page .action-row {
9590 margin-top: var(--gap-section);
9591 }
9592
9593 .item-page .action-row-tight {
9594 margin-top: var(--gap-peer);
9595 }
9596
9597 .item-page .full-width-btn {
9598 width: 100%;
9599 font-size: var(--text-note);
9600 }
9601
9602 .item-page .license-preset-name {
9603 margin-bottom: var(--gap-section);
9604 }
9605
9606 .item-page .license-summary {
9607 cursor: pointer;
9608 opacity: 0.7;
9609 font-size: var(--text-note);
9610 }
9611
9612 .item-page .license-text {
9613 white-space: pre-wrap;
9614 font-family: var(--font-sans);
9615 font-size: var(--text-note);
9616 margin-top: var(--gap-section);
9617 padding: var(--gap-section);
9618 background: var(--surface-sunken);
9619 }
9620
9621 .item-page .license-download {
9622 margin-top: var(--gap-section);
9623 }
9624
9625 .item-page .license-download a {
9626 font-size: var(--text-note);
9627 color: var(--content);
9628 }
9629
9630 .item-page .bundle-list-entry {
9631 margin-top: var(--gap-peer);
9632 }
9633
9634 .item-page .footer-links {
9635 margin-top: var(--gap-peer);
9636 }
9637
9638 /* ===========================================
9639 JOIN COMPLETE STEP (replaces inline styles in wizards/steps/join/complete.html)
9640 =========================================== */
9641 .join-complete-intro { margin-bottom: var(--gap-pane); }
9642 .join-complete-choices {
9643 display: grid; grid-template-columns: 1fr 1fr;
9644 gap: var(--gap-section); max-width: 500px;
9645 }
9646 .join-complete-choice { text-decoration: none; color: inherit; display: block; }
9647 .join-complete-card {
9648 background: var(--surface-sunken); padding: var(--gap-section); text-align: center;
9649 border: 2px solid transparent; transition: border-color 0.15s;
9650 }
9651 .join-complete-choice:hover .join-complete-card,
9652 .join-complete-card--active { border-color: var(--content); }
9653 .join-complete-card-title {
9654 font-family: var(--font-display); font-weight: bold;
9655 font-size: var(--text-lead); margin-bottom: var(--gap-peer);
9656 }
9657 .join-complete-card-desc {
9658 font-size: var(--text-note); opacity: 0.7; margin: 0;
9659 }
9660 .join-complete-footnote {
9661 font-size: var(--text-note); opacity: 0.5;
9662 margin-top: var(--gap-section); text-align: center;
9663 }
9664
9665 /* ===========================================
9666 PROMO CODES LIST (replaces inline styles in partials/promo_codes_list.html)
9667 =========================================== */
9668 .promo-codes-row--expired { opacity: 0.5; }
9669 .promo-codes-row--pending { opacity: 0.7; }
9670 .promo-codes-schedule { font-size: var(--text-note); }
9671 .promo-codes-schedule-pending { color: var(--warning); }
9672 .promo-codes-schedule-expired { color: var(--danger); }
9673 .promo-codes-schedule-none { opacity: 0.5; }
9674 .promo-codes-actions { white-space: nowrap; }
9675 .promo-codes-edit-btn { font-size: var(--text-fine); }
9676 .promo-codes-edit-row { background: var(--surface-sunken); }
9677 .promo-codes-edit-cell { padding: var(--gap-section); }
9678 .promo-codes-edit-form {
9679 display: flex; gap: var(--gap-section); align-items: end; flex-wrap: wrap;
9680 }
9681 .promo-codes-edit-label {
9682 font-size: var(--text-fine); display: block; margin-bottom: var(--gap-bound);
9683 }
9684 .promo-codes-edit-save { font-size: var(--text-note); }
9685 .promo-codes-bulk-row { margin-top: var(--gap-section); }
9686 .promo-codes-bulk-btn { font-size: var(--text-note); opacity: 0.7; }
9687
9688 /* ===========================================
9689 USER MEDIA TAB (replaces inline styles in partials/tabs/user_media.html)
9690 =========================================== */
9691
9692 .user-media-intro {
9693 margin-bottom: var(--gap-section);
9694 }
9695
9696 .user-media-upload {
9697 border: 2px dashed var(--border);
9698 padding: var(--gap-pane);
9699 margin-bottom: var(--gap-pane);
9700 text-align: center;
9701 background: var(--surface-overlay);
9702 }
9703
9704 .user-media-upload--dragover {
9705 border-color: var(--action);
9706 }
9707
9708 .user-media-upload-row {
9709 margin-bottom: var(--gap-section);
9710 }
9711
9712 .user-media-upload-label {
9713 display: inline-block;
9714 font-size: var(--text-note);
9715 margin-right: var(--gap-section);
9716 }
9717
9718 .user-media-upload-folder-input {
9719 padding: var(--gap-bound);
9720 font-size: var(--text-note);
9721 width: 140px;
9722 margin-left: var(--gap-bound);
9723 }
9724
9725 .user-media-upload-progress {
9726 margin-top: var(--gap-section);
9727 }
9728
9729 .user-media-upload-status {
9730 font-size: var(--text-note);
9731 margin-bottom: var(--gap-bound);
9732 }
9733
9734 .user-media-upload-status--ok {
9735 color: var(--success);
9736 }
9737
9738 .user-media-upload-status--err {
9739 color: var(--danger);
9740 }
9741
9742 .user-media-folders {
9743 margin-bottom: var(--gap-section);
9744 display: flex;
9745 gap: var(--gap-peer);
9746 flex-wrap: wrap;
9747 align-items: center;
9748 }
9749
9750 .user-media-folders-label {
9751 font-size: var(--text-note);
9752 opacity: 0.7;
9753 }
9754
9755 .user-media-folders-btn {
9756 font-size: var(--text-fine);
9757 }
9758
9759 .user-media-grid {
9760 display: grid;
9761 grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
9762 gap: var(--gap-section);
9763 }
9764
9765 .user-media-card {
9766 background: var(--surface-overlay);
9767 border: 1px solid var(--border);
9768 padding: var(--gap-section);
9769 position: relative;
9770 }
9771
9772 .user-media-card-thumb {
9773 width: 100%;
9774 height: 120px;
9775 overflow: hidden;
9776 margin-bottom: var(--gap-peer);
9777 display: flex;
9778 align-items: center;
9779 justify-content: center;
9780 background: var(--surface-sunken);
9781 }
9782
9783 .user-media-card-thumb--video {
9784 font-size: var(--text-title);
9785 opacity: 0.4;
9786 }
9787
9788 .user-media-card-img {
9789 max-width: 100%;
9790 max-height: 120px;
9791 object-fit: contain;
9792 }
9793
9794 .user-media-card-name {
9795 font-size: var(--text-fine);
9796 word-break: break-all;
9797 margin-bottom: var(--gap-bound);
9798 }
9799
9800 .user-media-card-meta {
9801 font-size: var(--text-fine);
9802 opacity: 0.6;
9803 margin-bottom: var(--gap-peer);
9804 }
9805
9806 .user-media-card-actions {
9807 display: flex;
9808 gap: var(--gap-bound);
9809 }
9810
9811 .user-media-card-btn {
9812 font-size: var(--text-fine);
9813 padding: var(--gap-bound) var(--gap-peer);
9814 }
9815
9816 /* ===========================================
9817 ITEM VERSION UPLOAD (replaces inline styles in partials/item_version_upload.html)
9818 =========================================== */
9819
9820 .item-version-upload-cell-sm {
9821 font-size: var(--text-note);
9822 }
9823
9824 .item-version-upload-no-file {
9825 opacity: 0.5;
9826 }
9827
9828 .item-version-upload-btn {
9829 padding: var(--gap-peer) var(--gap-section);
9830 font-size: var(--text-note);
9831 }
9832
9833 .item-version-upload-add-btn {
9834 padding: var(--gap-peer) var(--gap-section);
9835 }
9836
9837 .item-version-upload-grid {
9838 display: grid;
9839 grid-template-columns: 1fr 1fr;
9840 gap: var(--gap-section);
9841 }
9842
9843 .item-version-upload-hint {
9844 margin-bottom: var(--gap-peer);
9845 }
9846
9847 .item-version-upload-file-table {
9848 width: 100%;
9849 border-collapse: collapse;
9850 }
9851
9852 .item-version-upload-file-table thead tr {
9853 text-align: left;
9854 font-size: var(--text-fine);
9855 opacity: 0.7;
9856 text-transform: uppercase;
9857 letter-spacing: 0.03em;
9858 }
9859
9860 .item-version-upload-file-table th:first-child {
9861 padding: var(--gap-peer) var(--gap-peer) var(--gap-peer) 0;
9862 }
9863
9864 .item-version-upload-file-table th + th {
9865 padding: var(--gap-peer);
9866 }
9867
9868 .item-version-upload-file-table th:last-child {
9869 width: 40px;
9870 }
9871
9872 .item-version-upload-add-row {
9873 margin-top: var(--gap-peer);
9874 }
9875
9876 .item-version-upload-queue {
9877 margin-bottom: var(--gap-section);
9878 }
9879
9880 .item-version-upload-speed {
9881 font-size: var(--text-fine);
9882 opacity: 0.6;
9883 margin-top: var(--gap-bound);
9884 }
9885
9886 /* ===========================================
9887 PROJECT PAGE (replaces inline styles in pages/project.html)
9888 =========================================== */
9889
9890 .project-page .store-cover {
9891 width: 120px;
9892 height: 120px;
9893 border-radius: var(--radius-panel);
9894 object-fit: cover;
9895 margin-bottom: var(--gap-pane);
9896 }
9897 .project-page .follow-btn.is-selected {
9898 opacity: 0.7;
9899 }
9900 .project-page .follower-count-muted {
9901 font-size: var(--text-note);
9902 opacity: 0.7;
9903 padding: var(--gap-peer) 0;
9904 }
9905 .project-page .promo-details {
9906 font-size: var(--text-note);
9907 margin-bottom: var(--gap-peer);
9908 }
9909 .project-page .promo-details summary {
9910 cursor: pointer;
9911 opacity: 0.7;
9912 }
9913 .project-page .promo-input {
9914 width: 100%;
9915 margin-top: var(--gap-peer);
9916 padding: var(--gap-bound) var(--gap-peer);
9917 font-size: var(--text-note);
9918 text-transform: uppercase;
9919 }
9920 .project-page .store-footer-links {
9921 margin-top: var(--gap-peer);
9922 }
9923
9924 /* ===========================================
9925 INSERTION LIST (replaces inline styles in partials/insertion_list.html)
9926 =========================================== */
9927
9928 .insertion-list-heading {
9929 font-family: var(--font-mono);
9930 font-size: var(--text-body);
9931 margin-bottom: var(--gap-section);
9932 }
9933 .insertion-list-toolbar {
9934 margin-bottom: var(--gap-section);
9935 }
9936 .insertion-list-upload {
9937 font-family: var(--font-mono);
9938 }
9939 .insertion-list-table {
9940 width: 100%;
9941 font-family: var(--font-sans);
9942 }
9943 .insertion-list-table th {
9944 text-align: left;
9945 font-family: var(--font-mono);
9946 font-size: var(--text-note);
9947 }
9948 .insertion-list-table th.insertion-list-th-actions {
9949 text-align: right;
9950 }
9951 .insertion-list-table .insertion-list-actions {
9952 text-align: right;
9953 }
9954 .insertion-list-table .insertion-list-action-btn {
9955 font-family: var(--font-mono);
9956 }
9957
9958 /* ===========================================
9959 PROJECT MEMBERS TAB (replaces inline styles in partials/tabs/project_members.html)
9960 =========================================== */
9961
9962 .proj-members-summary {
9963 background: var(--surface-sunken);
9964 padding: var(--gap-section);
9965 margin-bottom: var(--gap-section);
9966 }
9967
9968 .proj-members-summary-row {
9969 display: flex;
9970 justify-content: space-between;
9971 align-items: center;
9972 }
9973
9974 .proj-members-stat {
9975 font-size: var(--text-head);
9976 font-weight: bold;
9977 }
9978
9979 .proj-members-add {
9980 margin-bottom: var(--gap-page);
9981 }
9982
9983 .proj-members-add-grid {
9984 display: grid;
9985 grid-template-columns: 1fr auto auto;
9986 gap: var(--gap-section);
9987 align-items: end;
9988 }
9989
9990 .proj-members-field-label {
9991 font-size: var(--text-note);
9992 display: block;
9993 margin-bottom: var(--gap-bound);
9994 }
9995
9996 .proj-members-add-result {
9997 margin-top: var(--gap-peer);
9998 }
9999
10000 .proj-members-name-link {
10001 color: inherit;
10002 }
10003
10004 .proj-members-split-cell {
10005 font-weight: bold;
10006 }
10007
10008 /* ===========================================
10009 PLACEMENT LIST (replaces inline styles in partials/placement_list.html)
10010 =========================================== */
10011
10012 .placement-list-heading {
10013 font-family: var(--font-mono);
10014 font-size: var(--text-body);
10015 margin-bottom: var(--gap-section);
10016 }
10017 .placement-list-table {
10018 width: 100%;
10019 font-family: var(--font-sans);
10020 margin-bottom: var(--gap-section);
10021 }
10022 .placement-list-table th {
10023 text-align: left;
10024 font-family: var(--font-mono);
10025 font-size: var(--text-note);
10026 }
10027 .placement-list-table th.placement-list-th-actions {
10028 text-align: right;
10029 }
10030 .placement-list-table .placement-list-actions {
10031 text-align: right;
10032 }
10033 .placement-list-table .placement-list-remove {
10034 font-family: var(--font-mono);
10035 }
10036 .placement-list-form {
10037 display: flex;
10038 gap: var(--gap-peer);
10039 align-items: flex-end;
10040 flex-wrap: wrap;
10041 }
10042 .placement-list-label {
10043 display: block;
10044 font-family: var(--font-mono);
10045 font-size: var(--text-fine);
10046 margin-bottom: var(--gap-bound);
10047 }
10048 .placement-list-add {
10049 font-family: var(--font-mono);
10050 }
10051
10052 /* ===========================================
10053 USER SYNCKIT TAB (replaces inline styles in partials/tabs/user_synckit.html)
10054 =========================================== */
10055
10056 .user-synckit-create { margin-bottom: var(--gap-pane); }
10057 .user-synckit-create-body {
10058 padding: var(--gap-section);
10059 background: var(--surface-overlay);
10060 border: 1px solid var(--border);
10061 }
10062 .user-synckit-create-form {
10063 display: flex;
10064 flex-direction: row;
10065 gap: var(--gap-section);
10066 align-items: flex-end;
10067 }
10068 .user-synckit-create-form label {
10069 display: block;
10070 font-size: var(--text-note);
10071 margin-bottom: var(--gap-bound);
10072 }
10073 .user-synckit-create-form input[type="text"],
10074 .user-synckit-create-form select {
10075 padding: var(--gap-peer);
10076 font-size: var(--text-note);
10077 }
10078 .user-synckit-create-form input[type="text"] { width: 220px; }
10079 .user-synckit-create-form select { width: 180px; }
10080
10081 .user-synckit-code { font-size: var(--text-fine); }
10082 .user-synckit-hint {
10083 font-size: var(--text-fine);
10084 margin-left: var(--gap-bound);
10085 }
10086
10087 .user-synckit-inline-btn {
10088 margin-left: var(--gap-peer);
10089 padding: var(--gap-bound) var(--gap-peer);
10090 font-size: var(--text-fine);
10091 }
10092 .user-synckit-row-btn {
10093 padding: var(--gap-bound) var(--gap-peer);
10094 font-size: var(--text-fine);
10095 }
10096 .user-synckit-row-btn + .user-synckit-row-btn {
10097 margin-left: var(--gap-bound);
10098 }
10099
10100 .user-synckit-status-active { color: var(--success); }
10101 .user-synckit-status-inactive { color: var(--danger); }
10102
10103 .user-synckit-edit-btn {
10104 padding: var(--gap-bound) var(--gap-peer);
10105 font-size: var(--text-fine);
10106 }
10107
10108 .user-synckit-create-error {
10109 color: var(--danger);
10110 margin-top: var(--gap-peer);
10111 }
10112
10113 /* ===========================================
10114 TOTP (replaces inline styles in partials/totp_setup.html and totp_status.html)
10115 =========================================== */
10116
10117 /* Section wrapper between TOTP subsections (QR + backup codes + confirm,
10118 or regenerate + disable). */
10119 .totp-section { margin-bottom: var(--gap-pane); }
10120
10121 /* Confirm-setup section is separated from the backup codes by a top border. */
10122 .totp-confirm {
10123 border-top: 1px solid var(--border);
10124 padding-top: var(--gap-section);
10125 }
10126
10127 /* QR code container, centered above the manual-entry details. */
10128 .totp-qr {
10129 text-align: center;
10130 margin-bottom: var(--gap-section);
10131 }
10132 .totp-qr img {
10133 max-width: 200px;
10134 image-rendering: pixelated;
10135 }
10136
10137 /* Collapsible "Manual entry key" group. */
10138 .totp-manual { margin-bottom: var(--gap-section); }
10139 .totp-manual summary {
10140 cursor: pointer;
10141 opacity: 0.7;
10142 font-size: var(--text-note);
10143 }
10144 .totp-manual-secret {
10145 display: block;
10146 margin-top: var(--gap-peer);
10147 word-break: break-all;
10148 font-size: var(--text-note);
10149 padding: var(--gap-peer);
10150 background: var(--surface-sunken);
10151 }
10152
10153 /* Backup codes grid (rendered list of one-time codes). */
10154 .backup-codes-grid {
10155 display: grid;
10156 grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
10157 gap: var(--gap-peer);
10158 }
10159 .backup-codes-grid code {
10160 padding: var(--gap-peer);
10161 background: var(--surface-sunken);
10162 text-align: center;
10163 font-size: var(--text-note);
10164 }
10165
10166 /* Status row for the "Enabled" badge above the management forms. */
10167 .totp-status-row {
10168 display: flex;
10169 align-items: center;
10170 gap: var(--gap-peer);
10171 margin-bottom: var(--gap-section);
10172 }
10173
10174 /* Container where regenerated backup codes are swapped in via HTMX. */
10175 .totp-regen-output { margin-top: var(--gap-section); }
10176
10177 /* ===========================================
10178 DASHBOARD-USER SHELL (replaces inline styles in dashboards/dashboard-user.html)
10179 =========================================== */
10180
10181 .sandbox-banner {
10182 background: var(--action);
10183 color: var(--primary-light);
10184 padding: var(--gap-section) var(--gap-pane);
10185 text-align: center;
10186 font-family: var(--font-mono);
10187 font-size: var(--text-note);
10188 }
10189
10190 .sandbox-banner-link {
10191 color: var(--primary-light);
10192 text-decoration: underline;
10193 margin-left: var(--gap-peer);
10194 }
10195 .account-status-actions {
10196 display: flex;
10197 gap: var(--gap-section);
10198 flex-wrap: wrap;
10199 margin-top: var(--gap-section);
10200 }
10201
10202 .suspension-wrap {
10203 margin-bottom: var(--gap-section);
10204 }
10205
10206 .password-warning-banner {
10207 margin-bottom: var(--gap-section);
10208 padding: var(--gap-section);
10209 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
10210 border: 1px solid var(--warning);
10211 font-size: var(--text-body);
10212 }
10213
10214 .dash-user-header-links {
10215 margin-bottom: var(--gap-peer);
10216 display: flex;
10217 gap: var(--gap-pane);
10218 }
10219
10220 .dash-user-header-link {
10221 font-size: var(--text-note);
10222 opacity: 0.7;
10223 }
10224
10225 .checklist-recovery {
10226 padding: var(--gap-section) 0;
10227 text-align: right;
10228 }
10229
10230 .checklist-recovery-link {
10231 font-size: var(--text-note);
10232 opacity: 0.7;
10233 }
10234
10235 .tab-spinner {
10236 margin-left: var(--gap-section);
10237 }
10238
10239 /* ===========================================
10240 USER PROFILE PAGE (replaces inline styles in pages/user.html)
10241 =========================================== */
10242
10243 .user-page .creator-paused-notice {
10244 margin-bottom: var(--gap-pane);
10245 padding: var(--gap-section);
10246 background: var(--surface-sunken);
10247 border: 1px solid var(--border);
10248 text-align: center;
10249 font-size: var(--text-body);
10250 }
10251
10252 .user-page .profile-action-row {
10253 margin-top: var(--gap-section);
10254 }
10255
10256 .user-page .profile-edit-btn {
10257 display: inline-block;
10258 padding: var(--gap-peer) var(--gap-section);
10259 text-decoration: none;
10260 font-size: var(--text-note);
10261 }
10262
10263 .user-page .follow-btn.is-selected {
10264 opacity: 0.7;
10265 }
10266
10267 .user-page .follower-count {
10268 margin-top: var(--gap-section);
10269 font-size: var(--text-note);
10270 opacity: 0.7;
10271 }
10272
10273 .user-page .profile-share-row {
10274 margin-top: var(--gap-section);
10275 display: flex;
10276 gap: var(--gap-section);
10277 justify-content: center;
10278 font-size: var(--text-note);
10279 }
10280
10281 .user-page .profile-share-link {
10282 opacity: 0.7;
10283 }
10284
10285 .user-page .collections-section {
10286 margin-bottom: var(--gap-page);
10287 }
10288
10289 /* ===========================================
10290 SUSPENSION BANNER (replaces inline styles in partials/suspension_banner.html)
10291 =========================================== */
10292
10293 .suspension-banner {
10294 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
10295 border: 1px solid var(--danger);
10296 padding: var(--gap-pane);
10297 margin-bottom: var(--gap-pane);
10298 }
10299
10300 .suspension-banner-title {
10301 margin: 0 0 var(--gap-peer) 0;
10302 color: var(--danger);
10303 }
10304
10305 .suspension-banner-reason {
10306 font-size: var(--text-note);
10307 margin: 0 0 var(--gap-section) 0;
10308 }
10309
10310 .suspension-banner-denial {
10311 background: var(--surface-overlay);
10312 padding: var(--gap-section);
10313 margin-bottom: var(--gap-section);
10314 font-size: var(--text-note);
10315 }
10316
10317 .suspension-banner-denial-response {
10318 margin: var(--gap-bound) 0 0 0;
10319 }
10320
10321 .suspension-banner-denial-hint {
10322 margin: var(--gap-peer) 0 0 0;
10323 opacity: 0.7;
10324 }
10325
10326 .suspension-banner-pending {
10327 font-size: var(--text-note);
10328 opacity: 0.7;
10329 margin: 0;
10330 }
10331
10332 .suspension-banner-appeal {
10333 margin-top: var(--gap-peer);
10334 }
10335
10336 .suspension-banner-appeal-summary {
10337 cursor: pointer;
10338 font-size: var(--text-note);
10339 }
10340
10341 .suspension-banner-appeal-form {
10342 margin-top: var(--gap-section);
10343 }
10344
10345 .suspension-banner-appeal-text {
10346 width: 100%;
10347 margin-bottom: var(--gap-peer);
10348 font-size: var(--text-note);
10349 }
10350
10351 .suspension-banner-appeal-submit {
10352 font-size: var(--text-note);
10353 }
10354
10355 .suspension-banner-export {
10356 margin-top: var(--gap-section);
10357 }
10358
10359 .suspension-banner-export-link {
10360 font-size: var(--text-note);
10361 }
10362
10363 /* ===========================================
10364 REPORT MODAL (replaces inline styles in partials/report_modal.html)
10365 =========================================== */
10366
10367 .report-modal-content {
10368 background: var(--surface-page);
10369 padding: var(--gap-page);
10370 width: 90%;
10371 max-width: 480px;
10372 max-height: 90vh;
10373 overflow-y: auto;
10374 }
10375
10376 .report-modal-title {
10377 font-size: var(--text-subhead);
10378 margin: 0;
10379 }
10380
10381 .report-modal-fieldset {
10382 border: none;
10383 padding: 0;
10384 margin: 0 0 var(--gap-section) 0;
10385 }
10386
10387 .report-modal-legend {
10388 font-size: var(--text-note);
10389 font-weight: 600;
10390 margin-bottom: var(--gap-peer);
10391 }
10392
10393 .report-modal-option {
10394 display: block;
10395 margin-bottom: var(--gap-peer);
10396 cursor: pointer;
10397 }
10398
10399 .report-modal-details {
10400 margin-bottom: var(--gap-section);
10401 }
10402
10403 .report-modal-details-label {
10404 display: block;
10405 font-size: var(--text-note);
10406 margin-bottom: var(--gap-bound);
10407 }
10408
10409 .report-modal-textarea {
10410 width: 100%;
10411 font-size: var(--text-note);
10412 }
10413
10414 .report-modal-actions {
10415 display: flex;
10416 gap: var(--gap-peer);
10417 justify-content: flex-end;
10418 }
10419
10420 /* ===========================================
10421 LIBRARY COLLECTIONS TAB (partials/tabs/library_collections.html)
10422 =========================================== */
10423
10424 .badge.is-faded { opacity: 0.5; }
10425 .lib-coll-new { margin-top: var(--gap-pane); }
10426 .lib-coll-new-summary { cursor: pointer; font-weight: bold; }
10427 .lib-coll-new-form { margin-top: var(--gap-section); }
10428 .lib-coll-wishlist-heading {
10429 font-size: var(--text-lead);
10430 margin-top: var(--gap-page);
10431 margin-bottom: var(--gap-section);
10432 opacity: 0.8;
10433 }
10434 .lib-coll-wishlist-wrap {
10435 overflow-x: auto;
10436 -webkit-overflow-scrolling: touch;
10437 }
10438 .lib-coll-wishlist-table { min-width: 500px; }
10439 .lib-coll-row-actions { white-space: nowrap; }
10440 .lib-coll-row-actions .lib-coll-cart-btn { margin-right: var(--gap-bound); }
10441
10442 /* ===========================================
10443 USER ANALYTICS TAB (partials/tabs/user_analytics.html)
10444 =========================================== */
10445
10446 .analytics-export-link { margin-left: var(--gap-section); }
10447 .analytics-range-heading {
10448 font-size: var(--text-lead);
10449 margin: 0;
10450 }
10451 .analytics-section--comparison { margin-bottom: var(--gap-page); }
10452 .analytics-section--comparison h2 { margin-bottom: var(--gap-section); }
10453 .analytics-table-wrap {
10454 overflow-x: auto;
10455 -webkit-overflow-scrolling: touch;
10456 }
10457 .analytics-table { min-width: 500px; }
10458 .analytics-revenue-cell {
10459 display: flex;
10460 align-items: center;
10461 gap: var(--gap-peer);
10462 }
10463 .analytics-revenue-bar {
10464 width: var(--fill, 0%);
10465 height: 10px;
10466 min-width: 2px;
10467 max-width: 80px;
10468 background: var(--action);
10469 }
10470
10471 /* ===========================================
10472 ITEM EMBED TAB (templates/partials/tabs/item_embed.html)
10473 =========================================== */
10474 .embed-intro {
10475 opacity: 0.7;
10476 font-size: var(--text-note);
10477 margin-bottom: var(--gap-pane);
10478 }
10479 .embed-option {
10480 margin-bottom: var(--gap-pane);
10481 padding: var(--gap-section);
10482 border: 1px solid var(--border);
10483 border-radius: var(--radius-panel);
10484 }
10485 .embed-option h4 {
10486 margin-bottom: var(--gap-bound);
10487 font-size: var(--text-body);
10488 }
10489 .embed-option-desc {
10490 font-size: var(--text-note);
10491 opacity: 0.7;
10492 margin-bottom: var(--gap-peer);
10493 }
10494 .embed-preview {
10495 margin: var(--gap-section) 0;
10496 overflow: hidden;
10497 }
10498 .embed-iframe {
10499 border: none;
10500 border-radius: var(--radius-control);
10501 }
10502 .embed-layout-toggle {
10503 font-size: var(--text-note);
10504 margin-bottom: var(--gap-peer);
10505 display: flex;
10506 gap: var(--gap-section);
10507 }
10508 .embed-layout-toggle label {
10509 cursor: pointer;
10510 display: flex;
10511 align-items: center;
10512 gap: var(--gap-bound);
10513 }
10514 .embed-code-row {
10515 display: flex;
10516 align-items: center;
10517 gap: var(--gap-peer);
10518 }
10519 .embed-snippet {
10520 flex: 1;
10521 padding: var(--gap-peer) var(--gap-section);
10522 background: var(--surface-overlay);
10523 border-radius: var(--radius-control);
10524 font-size: var(--text-fine);
10525 overflow-x: auto;
10526 white-space: nowrap;
10527 font-family: var(--font-mono);
10528 border: 1px solid var(--border);
10529 }
10530 .copy-btn {
10531 padding: 6px 12px;
10532 font-size: var(--text-fine);
10533 background: var(--action);
10534 color: var(--primary-light);
10535 border: none;
10536 border-radius: var(--radius-control);
10537 cursor: pointer;
10538 white-space: nowrap;
10539 }
10540 .copy-btn:hover {
10541 opacity: 0.9;
10542 }
10543
10544 /* ===========================================
10545 ADMIN NAV (templates/partials/admin_nav.html)
10546 =========================================== */
10547 .admin-nav {
10548 display: flex;
10549 gap: var(--gap-peer);
10550 margin-bottom: var(--gap-pane);
10551 flex-wrap: wrap;
10552 font-family: var(--font-mono);
10553 font-size: var(--text-note);
10554 }
10555 .admin-nav a {
10556 padding: var(--gap-bound) var(--gap-section);
10557 text-decoration: none;
10558 }
10559
10560 /* Admin nav items and filter-bar toggles are .btn-primary / .btn-secondary,
10561 toggled by the templates to mark the current page or active filter. Colour
10562 and depth come from the button recipe; only the tighter admin metrics and
10563 the active weight are stated here. */
10564 .admin-nav a.btn-primary,
10565 .filter-bar button.btn-primary {
10566 font-weight: 700;
10567 }
10568 .admin-nav a.btn-secondary:hover,
10569 .filter-bar button.btn-secondary:hover {
10570 color: var(--content);
10571 }
10572
10573 /* ===========================================
10574 VIDEO PLAYER PAGE (templates/pages/video_player.html)
10575 =========================================== */
10576 .video-cover-img {
10577 width: 100%;
10578 display: block;
10579 background: var(--primary-dark);
10580 }
10581 .video-cover-placeholder {
10582 width: 100%;
10583 aspect-ratio: 16 / 9;
10584 background: var(--primary-dark);
10585 display: flex;
10586 align-items: center;
10587 justify-content: center;
10588 color: var(--content-muted);
10589 }
10590 .video-store-cta {
10591 margin: var(--gap-page) 0;
10592 padding: var(--gap-pane);
10593 background: var(--surface-sunken);
10594 text-align: center;
10595 }
10596 .video-cta-lead {
10597 font-size: var(--text-lead);
10598 margin-bottom: var(--gap-section);
10599 }
10600 .video-cta-price {
10601 font-size: var(--text-head);
10602 margin-bottom: var(--gap-section);
10603 }
10604 .video-cta-fineprint {
10605 font-size: var(--text-note);
10606 opacity: 0.7;
10607 margin-top: var(--gap-section);
10608 }
10609 .video-library-status {
10610 font-family: var(--font-mono);
10611 color: var(--content-muted);
10612 }
10613
10614 /* ===========================================
10615 LIBRARY LOCKED PAGE (templates/pages/library_locked.html)
10616 =========================================== */
10617 .library-locked-byline {
10618 opacity: 0.7;
10619 }
10620 .library-locked-cover {
10621 max-width: 320px;
10622 margin: var(--gap-section) 0;
10623 /* Reserve the space before the image loads (audit Run 17 Frontend). */
10624 aspect-ratio: 1 / 1;
10625 object-fit: cover;
10626 }
10627 .library-locked-purchase {
10628 margin-top: var(--gap-page);
10629 }
10630 .library-locked-lead {
10631 opacity: 0.85;
10632 }
10633 .library-locked-actions {
10634 margin-top: var(--gap-pane);
10635 display: flex;
10636 gap: var(--gap-section);
10637 flex-wrap: wrap;
10638 }
10639 .library-locked-bundles {
10640 margin-top: var(--gap-pane);
10641 }
10642 .library-locked-bundles-title {
10643 font-weight: bold;
10644 }
10645 .library-locked-bundle {
10646 margin-top: var(--gap-peer);
10647 }
10648 .library-locked-bundle-price {
10649 opacity: 0.7;
10650 }
10651
10652 /* ===========================================
10653 BLOG (replaces inline styles in project_blog.html
10654 and dashboard-blog-editor.html)
10655 =========================================== */
10656
10657 .blog-empty-state { padding: var(--gap-page) 0; }
10658 .blog-empty-hint {
10659 opacity: 0.7;
10660 font-size: var(--text-note);
10661 margin-top: var(--gap-peer);
10662 }
10663 .blog-post-title-link { font-weight: bold; }
10664 .blog-post-actions {
10665 display: flex;
10666 gap: var(--gap-peer);
10667 }
10668
10669 /* Blog editor dashboard (templates/dashboards/dashboard-blog-editor.html). */
10670 .blog-editor h1 {
10671 font-size: var(--text-title);
10672 margin-bottom: var(--gap-section);
10673 text-align: left;
10674 }
10675 .blog-editor .editor-form { max-width: 800px; }
10676 .blog-editor .editor-form .form-group { margin-bottom: var(--gap-section); }
10677 .blog-editor .editor-form textarea {
10678 font-family: var(--font-mono);
10679 font-size: var(--text-note);
10680 min-height: 400px;
10681 resize: vertical;
10682 }
10683 .blog-editor-slug-spinner { font-size: var(--text-note); }
10684 .blog-editor-actions {
10685 display: flex;
10686 gap: var(--gap-section);
10687 align-items: center;
10688 }
10689 .blog-editor-status { margin-top: var(--gap-peer); }
10690
10691 /* ===========================================
10692 AUDIO PLAYER (replaces inline styles in pages/audio_player.html)
10693 =========================================== */
10694
10695 .audio-store-cta {
10696 margin: var(--gap-page) 0;
10697 padding: var(--gap-pane);
10698 background: var(--surface-sunken);
10699 text-align: center;
10700 }
10701 .audio-store-cta .audio-access-msg {
10702 font-size: var(--text-lead);
10703 margin-bottom: var(--gap-section);
10704 }
10705 .audio-store-cta .audio-price {
10706 font-size: var(--text-head);
10707 margin-bottom: var(--gap-section);
10708 }
10709 .audio-store-cta .audio-fee-note {
10710 font-size: var(--text-note);
10711 opacity: 0.7;
10712 margin-top: var(--gap-section);
10713 }
10714 .audio-library-status {
10715 font-family: var(--font-mono);
10716 color: var(--content-muted);
10717 }
10718
10719 /* ===========================================
10720 SECTIONS WIZARD STEP (templates/wizards/steps/item/sections.html)
10721 Parallels .psection-row used by item_details + project-sections.js,
10722 but isolated so wizard markup can evolve independently.
10723 =========================================== */
10724
10725 .section-row {
10726 display: flex;
10727 align-items: center;
10728 gap: var(--gap-section);
10729 padding: var(--gap-group) 0;
10730 border-bottom: 1px solid var(--border);
10731 }
10732 .section-row-title { flex: 1; font-weight: bold; }
10733 .section-row-length { font-size: var(--text-fine); opacity: 0.6; }
10734 .section-row .section-delete-btn {
10735 padding: var(--gap-bound) var(--gap-group);
10736 font-size: var(--text-fine);
10737 }
10738
10739 .section-add-body { margin-top: var(--gap-section); }
10740
10741 .wizard-actions { margin-top: var(--gap-pane); }
10742
10743 /* ===========================================
10744 PROJECT SUBSCRIPTIONS TAB (templates/partials/tabs/project_subscriptions.html)
10745 =========================================== */
10746
10747 .subs-connect-info {
10748 margin-bottom: var(--gap-pane);
10749 padding: var(--gap-section);
10750 }
10751 .subs-new-tier { margin-bottom: var(--gap-page); }
10752
10753 .tier-row-desc {
10754 font-size: var(--text-note);
10755 opacity: 0.7;
10756 margin-top: var(--gap-bound);
10757 }
10758 .tier-action-btn {
10759 font-size: var(--text-note);
10760 padding: var(--gap-bound) var(--gap-group);
10761 }
10762
10763 .subs-members-bar {
10764 margin-top: var(--gap-page);
10765 display: flex;
10766 justify-content: space-between;
10767 align-items: center;
10768 }
10769
10770 /* ===========================================
10771 LIBRARY CONSUMPTION PAGES
10772 ===========================================
10773 Shared styles for the library-side consumption surfaces:
10774 text_reader.html, library_text.html (.article-page),
10775 library_downloads.html (.library-page),
10776 library_audio.html / library_video.html (.media-container + helpers).
10777 The .article-page and .library-page sections higher up in this file
10778 are activated by body classes set in the templates. */
10779
10780 /* Faded excerpt text on the public store-page text reader. */
10781 .article-page .article-body--excerpt { opacity: 0.85; }
10782
10783 /* Store-page call-to-action block (text_reader.html). */
10784 .article-page .store-cta {
10785 margin: var(--gap-page) 0;
10786 padding: var(--gap-pane);
10787 background: var(--surface-sunken);
10788 text-align: center;
10789 }
10790 .article-page .store-cta-lead {
10791 font-size: var(--text-lead);
10792 margin-bottom: var(--gap-section);
10793 }
10794 .article-page .store-cta-price {
10795 font-size: var(--text-head);
10796 margin-bottom: var(--gap-section);
10797 }
10798 .article-page .store-cta-note {
10799 font-size: var(--text-note);
10800 opacity: 0.7;
10801 margin-top: var(--gap-section);
10802 }
10803 .article-page .store-cta .library-status {
10804 font-family: var(--font-mono);
10805 color: var(--content-muted);
10806 }
10807
10808 /* Library back-link breadcrumb shared by the media library pages. */
10809 .media-container .library-back {
10810 font-size: var(--text-note);
10811 opacity: 0.7;
10812 margin: var(--gap-section) 0;
10813 }
10814 .media-container .library-back a { color: var(--content); }
10815
10816 /* Source-files download list under audio/video library pages.
10817 The .library-page version exists higher up; this block scopes the
10818 same look under .media-container for audio/video. */
10819 .media-container .library-downloads {
10820 background: var(--surface-overlay);
10821 padding: var(--gap-pane);
10822 margin-top: var(--gap-page);
10823 }
10824 .media-container .library-downloads h3 {
10825 font-size: var(--text-lead);
10826 margin-bottom: var(--gap-section);
10827 }
10828 .media-container .download-row {
10829 display: flex;
10830 align-items: center;
10831 gap: var(--gap-section);
10832 padding: var(--gap-peer) 0;
10833 border-bottom: 1px solid var(--border);
10834 }
10835 .media-container .download-row:last-child { border-bottom: none; }
10836 .media-container .download-version {
10837 min-width: 4em;
10838 font-family: var(--font-mono);
10839 font-size: var(--text-note);
10840 }
10841 .media-container .download-label { flex: 1; }
10842 .media-container .download-label--empty { flex: 1; opacity: 0.5; }
10843 .media-container .download-size {
10844 font-size: var(--text-note);
10845 opacity: 0.6;
10846 min-width: 5em;
10847 text-align: right;
10848 }
10849
10850 /* Compact download button used in both library_downloads.html and
10851 media library pages. */
10852 .btn-download-compact {
10853 padding: var(--gap-peer) var(--gap-section);
10854 font-size: var(--text-note);
10855 }
10856
10857 /* Faded "no label" placeholder on library_downloads.html download rows. */
10858 .library-page .download-label--empty { opacity: 0.5; }
10859
10860 /* Library-downloads description <details> summary + body. */
10861 .library-page .lib-summary {
10862 cursor: pointer;
10863 font-family: var(--font-display);
10864 font-size: var(--text-subhead);
10865 }
10866 .library-page .lib-summary-body { margin-top: var(--gap-section); }
10867
10868 /* License section on library_downloads.html. */
10869 .library-page .lib-license-name { margin-bottom: var(--gap-section); }
10870 .library-page .lib-license-download {
10871 font-size: var(--text-note);
10872 color: var(--content);
10873 }
10874
10875 /* Footer below the item-footer (was inline on library_downloads.html). */
10876 .library-page .item-footer-sub { margin-top: var(--gap-peer); }
10877
10878 /* library_text.html footer: a terse in-page footer for the library's own
10879 text view, unrelated to base.html's site-footer. */
10880 .article-page .lib-text-footer {
10881 text-align: center;
10882 padding: var(--gap-page);
10883 font-size: var(--text-note);
10884 opacity: 0.6;
10885 border-top: 1px solid var(--border);
10886 }
10887 .article-page .lib-text-footer a { color: var(--content); }
10888
10889 /* Media player play/pause icon initial state. The JS in media-player.js
10890 toggles via element.style.display directly, so we must NOT use
10891 !important here, element.style would still beat a normal-priority
10892 declaration, but !important in the stylesheet would defeat that. */
10893 .media-icon-hidden { display: none; }
10894
10895 /* Video element fills its .video-display parent. .video-display already
10896 provides a black background. */
10897 .video-display video { width: 100%; }
10898
10899 /* Cover image inside .cover-art on library_audio.html, the parent
10900 .cover-art already sets aspect ratio and centering; the inline
10901 width/aspect-ratio/object-fit was redundant with .cover-art img. */
10902
10903 /* ===========================================
10904 USER DASHBOARD TABS (support / sessions / buyer contacts)
10905 =========================================== */
10906
10907 /* Support tab (templates/partials/tabs/user_support.html). */
10908 .support-tab .support-intro-meta {
10909 margin-bottom: var(--gap-pane);
10910 font-size: var(--text-note);
10911 opacity: 0.8;
10912 }
10913 .support-tab .support-form { max-width: 600px; }
10914 .support-tab .response-times {
10915 margin-top: var(--gap-page);
10916 font-size: var(--text-note);
10917 opacity: 0.7;
10918 }
10919 .support-tab .response-times ul {
10920 margin: var(--gap-bound) 0 0 var(--gap-pane);
10921 }
10922
10923 /* Sessions tab (templates/partials/tabs/user_sessions.html). */
10924 .sessions-tab .sessions-table { width: 100%; }
10925 .sessions-tab .device-cell {
10926 max-width: 300px;
10927 overflow: hidden;
10928 text-overflow: ellipsis;
10929 white-space: nowrap;
10930 }
10931
10932 /* Buyer contacts tab (templates/partials/tabs/buyer_contacts.html). */
10933 .buyer-contacts .contacts-table { min-width: 500px; }
10934
10935 /* Creators page extras (templates/pages/creators.html). */
10936 .creators-page .pricing-fineprint {
10937 font-size: var(--text-note);
10938 opacity: 0.8;
10939 margin-top: var(--gap-peer);
10940 }
10941 .creators-page .trial-callout {
10942 font-size: var(--text-note);
10943 margin-top: var(--gap-pane);
10944 }
10945
10946 /* ===========================================
10947 LIBRARY TABS
10948 Shared classes for the library-dashboard tab partials:
10949 library_wishlists, library_contacts, library_purchases,
10950 library_subscriptions. Migrated from inline styles.
10951 =========================================== */
10952
10953 /* Tables in library tabs need a minimum width so columns stay readable
10954 while the outer container scrolls horizontally on narrow viewports. */
10955
10956 /* Empty-state hint paragraph (smaller, slightly faded). */
10957 .library-tab-empty-hint {
10958 font-size: var(--text-note);
10959 opacity: 0.7;
10960 margin-bottom: var(--gap-section);
10961 }
10962
10963 /* Compact row-action button (wishlist Add to Cart / Remove, etc.). */
10964
10965 .library-filter-wrap { margin-bottom: var(--gap-section); }
10966
10967 /* Purchases table "Open" action link + context menu trigger group. */
10968 .library-row-actions {
10969 display: flex;
10970 align-items: center;
10971 gap: var(--gap-peer);
10972 }
10973 .library-row-open {
10974 font-size: var(--text-fine);
10975 padding: var(--gap-bound) var(--gap-section);
10976 text-decoration: none;
10977 white-space: nowrap;
10978 }
10979
10980 /* Section sub-heading inside purchases tab (e.g. "Subscriptions"). */
10981 .library-tab-subheading {
10982 font-size: var(--text-lead);
10983 margin-top: var(--gap-page);
10984 margin-bottom: var(--gap-section);
10985 opacity: 0.8;
10986 }
10987
10988 /* Inline-block button styled as a link target on empty-state CTAs. */
10989 .library-tab-cta {
10990 display: inline-block;
10991 text-decoration: none;
10992 }
10993
10994 /* Context menu (dropdown) on each purchase row. */
10995 .context-menu-wrapper { position: relative; }
10996 .context-menu-btn {
10997 background: none;
10998 border: none;
10999 cursor: pointer;
11000 padding: var(--gap-peer);
11001 font-size: var(--text-subhead);
11002 line-height: 1;
11003 color: var(--content-muted);
11004 border-radius: var(--radius-control);
11005 }
11006 .context-menu-btn:hover {
11007 background: var(--surface-sunken);
11008 color: var(--content);
11009 }
11010 .context-menu {
11011 display: none;
11012 position: absolute;
11013 right: 0;
11014 top: 100%;
11015 background: var(--surface-overlay);
11016 border: 1px solid var(--border);
11017 box-shadow: var(--elevation-overlay);
11018 min-width: 160px;
11019 z-index: var(--z-context);
11020 }
11021 .context-menu.open { display: block; }
11022 .context-menu-item {
11023 display: block;
11024 width: 100%;
11025 padding: var(--gap-section);
11026 text-align: left;
11027 background: none;
11028 border: none;
11029 font-family: var(--font-sans);
11030 font-size: var(--text-note);
11031 color: var(--content);
11032 cursor: pointer;
11033 text-decoration: none;
11034 }
11035 .context-menu-item:hover { background: var(--surface-sunken); }
11036 .context-menu-item.danger { color: var(--danger); }
11037
11038 /* Inline license-key code chip shown beneath a purchase date. */
11039 .license-key-inline { margin-top: var(--gap-bound); }
11040 .key-code-small {
11041 font-size: var(--text-fine);
11042 padding: var(--gap-bound) var(--gap-peer);
11043 background: var(--surface-sunken);
11044 border-radius: var(--radius-control);
11045 cursor: pointer;
11046 user-select: all;
11047 }
11048 .key-code-small:hover { background: var(--border); }
11049
11050 /* Library row title link reads as plain text until hovered. */
11051 .library-title-link:hover { text-decoration: underline; }
11052 /* ===========================================
11053 ADMIN ENTRIES (replaces inline styles in partials/admin_user_entries.html,
11054 partials/admin_waitlist_entries.html,
11055 partials/admin_appeal_entries.html)
11056 =========================================== */
11057
11058 /* (`.badge-active` / `.badge-trusted` removed in wave 2 tier B2. They were a
11059 second, ad-hoc spelling of what data-tone="success" already says, and the
11060 admin entries take the tone now.) */
11061
11062 /* "Trust" toggle button sits next to a Trusted badge. */
11063 .admin-entries-trust-btn {
11064 margin-left: var(--gap-bound);
11065 }
11066
11067 /* Waitlist pitch cell, narrow body text. */
11068 .admin-entries-pitch {
11069 font-size: var(--text-note);
11070 max-width: 300px;
11071 }
11072 .admin-entries-pitch-empty {
11073 opacity: 0.5;
11074 font-style: italic;
11075 }
11076 .admin-entries-pitch-inviter {
11077 margin-top: var(--gap-bound);
11078 }
11079
11080 /* Appeal text columns. */
11081 .admin-entries-reason {
11082 font-size: var(--text-note);
11083 max-width: 200px;
11084 }
11085 .admin-entries-appeal {
11086 font-size: var(--text-note);
11087 max-width: 300px;
11088 }
11089
11090 /* Decide form textarea + stacked action buttons. */
11091 .admin-entries-decide-textarea {
11092 width: 200px;
11093 }
11094 .admin-entries-decide-actions {
11095 display: flex;
11096 flex-direction: column;
11097 gap: var(--gap-bound);
11098 }
11099
11100 /* Pagination row at the bottom of admin entry tables. */
11101 .admin-entries-pagination {
11102 display: flex;
11103 justify-content: center;
11104 align-items: center;
11105 gap: var(--gap-section);
11106 padding: var(--gap-section) 0;
11107 }
11108
11109 /* ===========================================
11110 ITEM TABS (item_settings, item_sales, item_overview partials)
11111 =========================================== */
11112
11113 /* Schedule form is hidden until "Schedule" button is clicked. JS toggles
11114 the `.hidden` utility class. */
11115 .item-settings-schedule-form {
11116 margin-top: var(--gap-section);
11117 }
11118
11119 .item-sales-tab-header {
11120 display: flex;
11121 justify-content: space-between;
11122 align-items: center;
11123 margin-bottom: var(--gap-section);
11124 }
11125 .item-sales-tab-header h2 { margin: 0; }
11126 .item-overview-embed {
11127 margin-top: var(--gap-pane);
11128 }
11129 .item-overview-embed > summary {
11130 cursor: pointer;
11131 font-weight: bold;
11132 font-size: var(--text-lead);
11133 }
11134
11135 /* ===========================================
11136 ITEM TEXT EDITOR (partials/item_text_editor.html)
11137 =========================================== */
11138
11139 .text-editor-actions {
11140 margin-top: var(--gap-section);
11141 }
11142
11143 /* ===========================================
11144 PROJECT ANALYTICS TAB (partials/tabs/project_analytics.html)
11145 =========================================== */
11146
11147 .tab-docs-extra {
11148 margin-left: var(--gap-section);
11149 }
11150
11151 .analytics-tab-header {
11152 display: flex;
11153 justify-content: space-between;
11154 align-items: center;
11155 margin-bottom: var(--gap-section);
11156 }
11157 .analytics-tab-header h2 {
11158 font-size: var(--text-lead);
11159 margin: 0;
11160 }
11161
11162 .analytics-empty-block {
11163 text-align: center;
11164 padding: var(--gap-page) var(--gap-section);
11165 opacity: 0.6;
11166 }
11167
11168 /* ===========================================
11169 RECEIPT PAGE INLINE FRAGMENTS (pages/receipt.html)
11170 =========================================== */
11171
11172 .receipt-page .receipt-brand {
11173 opacity: 0.6;
11174 font-size: var(--text-note);
11175 }
11176 .receipt-page .receipt-meta {
11177 text-align: right;
11178 }
11179 .receipt-page .receipt-meta-line {
11180 font-family: var(--font-mono);
11181 font-size: var(--text-note);
11182 opacity: 0.6;
11183 }
11184 .receipt-page .receipt-meta-sub {
11185 font-family: var(--font-mono);
11186 font-size: var(--text-fine);
11187 opacity: 0.4;
11188 margin-top: var(--gap-bound);
11189 }
11190 .receipt-page .print-btn {
11191 margin-top: var(--gap-section);
11192 font-size: var(--text-note);
11193 }
11194
11195 /* ===========================================
11196 IMPORT PAGE INLINE FRAGMENTS (dashboards/dashboard-import.html)
11197 =========================================== */
11198
11199 .import-page .import-preview-label,
11200 .import-page .import-mapping-label,
11201 .import-page .import-progress-label {
11202 font-family: var(--font-mono);
11203 font-size: var(--text-note);
11204 margin-bottom: var(--gap-peer);
11205 }
11206 .import-page .import-mapping-label {
11207 margin-bottom: var(--gap-section);
11208 }
11209 .import-page .import-progress-label {
11210 font-size: var(--text-note);
11211 }
11212 .import-page .import-start-btn {
11213 margin-top: var(--gap-section);
11214 }
11215 .import-page .import-history-heading {
11216 font-size: var(--text-subhead);
11217 margin-bottom: var(--gap-section);
11218 }
11219
11220 /* ===========================================
11221 SLUG STATUS PARTIAL (partials/slug_status.html)
11222 =========================================== */
11223
11224 .slug-status {
11225 font-size: var(--text-note);
11226 }
11227 .slug-status.available { color: var(--success); }
11228 .slug-status.taken { color: var(--danger); }
11229
11230 .slug-suggestions {
11231 font-size: var(--text-note);
11232 display: block;
11233 margin-top: var(--gap-bound);
11234 }
11235 .slug-suggestion-link {
11236 margin-right: var(--gap-peer);
11237 }
11238
11239 /* ===========================================
11240 STRIPE DISCLAIMER PAGE (pages/stripe_disclaimer.html)
11241 =========================================== */
11242
11243 .stripe-disclaimer-page .container {
11244 max-width: 700px;
11245 margin: var(--gap-pane) auto;
11246 padding: 0 var(--gap-page);
11247 }
11248 .stripe-disclaimer-page .stripe-disclaimer-title {
11249 text-align: center;
11250 }
11251 .stripe-disclaimer-page .tagline {
11252 font-size: var(--text-note);
11253 opacity: 0.7;
11254 margin-bottom: var(--gap-page);
11255 text-align: center;
11256 }
11257 .stripe-disclaimer-page h2 {
11258 font-size: var(--text-head);
11259 margin-bottom: var(--gap-pane);
11260 }
11261 .stripe-disclaimer-page .stripe-disclaimer-lead {
11262 margin-bottom: var(--gap-pane);
11263 opacity: 0.8;
11264 }
11265 .stripe-disclaimer-page .section { margin-bottom: var(--gap-page); }
11266 .stripe-disclaimer-page .section h3 {
11267 font-size: var(--text-lead);
11268 margin-bottom: var(--gap-section);
11269 font-family: var(--font-mono);
11270 }
11271 .stripe-disclaimer-page .section ul {
11272 margin: 0;
11273 padding-left: var(--gap-section);
11274 }
11275 .stripe-disclaimer-page .section li {
11276 margin-bottom: var(--gap-peer);
11277 line-height: 1.5;
11278 }
11279 .stripe-disclaimer-page .stripe-disclaimer-fee-lead {
11280 margin-bottom: var(--gap-section);
11281 }
11282 .stripe-disclaimer-page .fee-example {
11283 background: var(--surface-page);
11284 padding: var(--gap-section);
11285 margin: var(--gap-section) 0;
11286 font-family: var(--font-mono);
11287 font-size: var(--text-note);
11288 }
11289 .stripe-disclaimer-page .fee-row {
11290 display: flex;
11291 justify-content: space-between;
11292 padding: var(--gap-bound) 0;
11293 }
11294 .stripe-disclaimer-page .fee-row.highlight {
11295 font-weight: bold;
11296 border-top: 1px solid var(--border);
11297 margin-top: var(--gap-peer);
11298 padding-top: var(--gap-peer);
11299 }
11300 .stripe-disclaimer-page .fee-row .zero { color: var(--content); }
11301 .stripe-disclaimer-page .stripe-disclaimer-note {
11302 font-size: var(--text-note);
11303 opacity: 0.8;
11304 }
11305 .stripe-disclaimer-page .acceptance {
11306 background: var(--surface-page);
11307 padding: var(--gap-section);
11308 margin: var(--gap-pane) 0;
11309 }
11310 .stripe-disclaimer-page .checkbox-group {
11311 display: flex;
11312 align-items: flex-start;
11313 gap: var(--gap-section);
11314 }
11315 .stripe-disclaimer-page .checkbox-group input[type="checkbox"] {
11316 margin-top: var(--gap-bound);
11317 flex-shrink: 0;
11318 }
11319 .stripe-disclaimer-page .checkbox-group label {
11320 line-height: 1.5;
11321 cursor: pointer;
11322 }
11323 .stripe-disclaimer-page .button-row {
11324 display: flex;
11325 gap: var(--gap-section);
11326 margin-top: var(--gap-pane);
11327 }
11328 .stripe-disclaimer-page .button-row button { flex: 1; }
11329 .stripe-disclaimer-page button:disabled {
11330 opacity: 0.5;
11331 cursor: not-allowed;
11332 }
11333 .stripe-disclaimer-page .note {
11334 font-size: var(--text-note);
11335 opacity: 0.6;
11336 margin-top: var(--gap-section);
11337 text-align: center;
11338 }
11339
11340 /* ===========================================
11341 LONG-TAIL CLEANUP, shared utilities promoted from the final inline-style sweep.
11342 Each rule replaces ≥2 inline-style instances across templates.
11343 =========================================== */
11344
11345 /* Tab-spinner loading indicator (used by 3+ dashboards). */
11346
11347 /* Inline-block secondary action link styled as a button. */
11348 .btn-link {
11349 display: inline-block;
11350 padding: var(--gap-peer) var(--gap-section);
11351 text-decoration: none;
11352 }
11353 .btn-link--sm { padding: var(--gap-peer) var(--gap-section); }
11354
11355 /* Username-availability spinner (wizards + standalone account form). */
11356 .username-spinner { font-size: var(--text-note); }
11357
11358 /* Centered foot-link beneath wizard cards. */
11359 .foot-link--centered { margin-top: var(--gap-section); text-align: center; }
11360
11361 /* Health-page status dots. */
11362 .health-indicator-dot { width: 8px; height: 8px; margin-right: var(--gap-peer); }
11363 .health-error-text { color: var(--danger); }
11364
11365 /* Inline error fragments (login_error, error_fragment) toggle this on. */
11366 .error-message.is-active { display: block; }
11367
11368 /* Cart count badge in nav. */
11369 .cart-badge-count { font-size: var(--text-fine); }
11370
11371 /* Empty-row inside a data table. */
11372 .table-empty-row { text-align: center; opacity: 0.6; }
11373
11374 /* Discover grid: card wrapper + body anchor. */
11375 .discover-grid-card { position: relative; }
11376 .discover-card-link {
11377 text-decoration: none;
11378 color: inherit;
11379 display: flex;
11380 flex-direction: column;
11381 flex: 1;
11382 }
11383 .discover-filter-checkbox {
11384 display: flex;
11385 align-items: center;
11386 gap: var(--gap-peer);
11387 cursor: pointer;
11388 font-size: var(--text-note);
11389 }
11390
11391 /* Tag suggestion list. */
11392 .tag-suggest-label {
11393 font-size: var(--text-fine);
11394 opacity: 0.7;
11395 margin-bottom: var(--gap-peer);
11396 overflow-wrap: anywhere;
11397 }
11398 .tag-suggest-pill {
11399 background: var(--surface-sunken);
11400 border: 1px solid var(--border);
11401 padding: var(--gap-bound) var(--gap-peer);
11402 font-size: var(--text-fine);
11403 cursor: pointer;
11404 margin: var(--gap-bound);
11405 }
11406
11407 /* Dashboard-export header layout + Export-all CTA. */
11408 .dashboard-export-header {
11409 display: flex;
11410 justify-content: space-between;
11411 align-items: flex-start;
11412 }
11413 .export-all-btn {
11414 white-space: nowrap;
11415 margin-top: var(--gap-peer);
11416 }
11417
11418 /* Tiny inline action button (admin-metrics, dashboard-item share). */
11419 .dashboard-item-share-btn {
11420 font-size: var(--text-fine);
11421 padding: var(--gap-bound) var(--gap-peer);
11422 opacity: 0.6;
11423 }
11424
11425 /* Monospaced page sub-headers. */
11426 .section-mono-meta {
11427 font-family: var(--font-mono);
11428 font-size: var(--text-note);
11429 opacity: 0.6;
11430 margin-bottom: var(--gap-pane);
11431 }
11432 /* Email-result single-card page. */
11433 .email-result-wrap {
11434 max-width: 600px;
11435 margin: 50px auto;
11436 padding: 20px;
11437 }
11438
11439 /* Admin lottery form (admin-waitlist). */
11440 .lottery-form {
11441 display: flex;
11442 gap: var(--gap-section);
11443 align-items: flex-end;
11444 }
11445
11446 /* Login screen: "or" divider + passkey button + error slot. */
11447 .login-divider {
11448 text-align: center;
11449 margin: var(--gap-pane) 0 var(--gap-section);
11450 opacity: 0.5;
11451 font-size: var(--text-note);
11452 }
11453 .login-passkey-btn { width: 100%; }
11454 .login-passkey-error { margin-top: var(--gap-peer); }
11455
11456 /* Upload-progress sibling text (item_audio_upload). */
11457 .upload-speed-text {
11458 font-size: var(--text-fine);
11459 opacity: 0.6;
11460 margin-top: var(--gap-bound);
11461 }
11462
11463 /* Faded "Following" follow-button state. */
11464 .follow-btn.is-selected { opacity: 0.7; }
11465
11466 /* Project blog footer. */
11467 .project-blog-footer {
11468 margin-top: var(--gap-page);
11469 padding-top: var(--gap-page);
11470 border-top: 1px solid var(--border);
11471 opacity: 0.6;
11472 }
11473 .project-blog-footer a { color: var(--content); }
11474
11475 /* Compact alert / paragraph margin variants used 2+ times. */
11476
11477 /* Paywall trust line. */
11478 .paywall-trust {
11479 font-size: var(--text-note);
11480 opacity: 0.7;
11481 margin-top: var(--gap-section);
11482 line-height: 1.4;
11483 }
11484
11485 /* Confirm-delete: button background already covered by .danger class.
11486 The wizard step uses `.primary` and inlines the danger background; we just
11487 change the class instead. No CSS rule needed here. */
11488
11489 /* Paywall-style centered narrow form-group used inside project_paywall. */
11490 .form-group--centered-narrow {
11491 max-width: 240px;
11492 margin: var(--gap-section) auto;
11493 }
11494
11495 /* Inline-form margin-left for adjacent pills (collection.html private badge). */
11496 .badge--inline-l { margin-left: var(--gap-peer); }
11497
11498 /* ===== Team page ===== */
11499
11500 .team-page { max-width: 56rem; margin: 0 auto; }
11501 .team-intro {
11502 font-size: var(--text-body);
11503 line-height: 1.55;
11504 margin: 0 0 var(--gap-page);
11505 opacity: 0.85;
11506 }
11507 .team-section { margin-top: var(--gap-page); }
11508 .team-section + .team-section { margin-top: var(--gap-page); }
11509 .team-card-experience {
11510 margin: var(--gap-section) 0 var(--gap-peer);
11511 line-height: 1.5;
11512 }
11513 .team-card-links {
11514 margin: var(--gap-peer) 0;
11515 font-size: var(--text-body);
11516 }
11517 .team-card-link { font-weight: 500; }
11518 .team-card-bio {
11519 margin-top: var(--gap-section);
11520 border-top: 1px solid var(--border);
11521 padding-top: var(--gap-section);
11522 }
11523 .team-card-bio > summary {
11524 cursor: pointer;
11525 font-weight: 500;
11526 font-size: var(--text-body);
11527 list-style: none;
11528 }
11529 .team-card-bio > summary::-webkit-details-marker { display: none; }
11530 .team-card-bio > summary::before {
11531 content: "+ ";
11532 display: inline-block;
11533 width: 1.2em;
11534 opacity: 0.6;
11535 }
11536 .team-card-bio[open] > summary::before { content: ""; }
11537 .team-card-bio > p {
11538 margin-top: var(--gap-section);
11539 line-height: 1.6;
11540 }
11541
11542 /* ── SyncKit billing panel ──
11543 Per-app accordion shown inside the SyncKit dashboard tab (both user and
11544 project levels). The .synckit-billing root carries pricing constants as
11545 data-attrs that static/synckit-billing.js reads to mirror monthly_price_cents.
11546 */
11547 .synckit-billing {
11548 background: var(--surface-raised);
11549 border: 1px solid var(--border);
11550 border-radius: var(--radius-control);
11551 padding: var(--gap-peer) var(--gap-section);
11552 min-width: 220px;
11553 }
11554 .synckit-billing-summary {
11555 cursor: pointer;
11556 display: flex;
11557 align-items: center;
11558 gap: var(--gap-section);
11559 font-size: var(--text-note);
11560 }
11561 .synckit-billing-toggle { margin-left: auto; color: var(--content-muted); }
11562 .synckit-billing-status {
11563 display: inline-block;
11564 padding: 2px var(--gap-peer);
11565 border-radius: var(--radius-fine);
11566 font-size: var(--text-fine);
11567 font-weight: 600;
11568 }
11569 .synckit-billing-status--draft { background: var(--surface-sunken); }
11570 .synckit-billing-status--active { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); color: var(--success); }
11571 .synckit-billing-status--past-due { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
11572 .synckit-billing-status--canceled { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); color: var(--danger); }
11573 .synckit-billing-price { font-weight: 600; }
11574 .synckit-billing-body {
11575 padding-top: var(--gap-section);
11576 display: flex;
11577 flex-direction: column;
11578 gap: var(--gap-section);
11579 }
11580 .synckit-billing-section { display: flex; flex-direction: column; gap: var(--gap-peer); }
11581 .synckit-billing-subheading { font-size: var(--text-note); margin: 0; font-weight: 600; }
11582 .synckit-knob-row {
11583 display: flex;
11584 align-items: center;
11585 gap: var(--gap-peer);
11586 flex-wrap: wrap;
11587 }
11588 .synckit-knob-row label { min-width: 110px; }
11589 .synckit-knob-row input[type="range"] { flex: 1 1 160px; min-width: 120px; }
11590 .synckit-radio { display: inline-flex; align-items: center; gap: var(--gap-bound); min-width: 0; }
11591 .synckit-billing-summary-line {
11592 display: flex;
11593 align-items: baseline;
11594 gap: var(--gap-peer);
11595 padding-top: var(--gap-peer);
11596 border-top: 1px solid var(--border);
11597 }
11598 .synckit-price-preview { font-size: var(--text-lead); font-weight: 600; }
11599 .synckit-billing-actions { display: flex; gap: var(--gap-peer); }
11600 .synckit-billing-manage { flex-direction: row; gap: var(--gap-peer); }
11601 .synckit-billing-status-msg { font-size: var(--text-note); min-height: 1.25em; }
11602 .synckit-billing-status-msg--info { color: var(--content-muted); }
11603 .synckit-billing-status-msg--error { color: var(--danger); }
11604 .synckit-billing-period { margin: 0; }
11605 .synckit-gauge { display: flex; flex-direction: column; gap: 2px; }
11606 .synckit-gauge-label {
11607 display: flex;
11608 justify-content: space-between;
11609 font-size: var(--text-fine);
11610 }
11611 .synckit-gauge-value { color: var(--content-muted); }
11612
11613 /* ============================================================================
11614 Carousel: the widget tier's first consumer.
11615
11616 The markup is no longer written in a template. It comes from a description --
11617 `crate::quasi::widgets::carousel` says "an ordered set of pictures, called a
11618 carousel" -- and quasi-webview turns that into a region carrying
11619 `data-widget="carousel"` with one `.picture` or `.picture-img` per frame.
11620
11621 So these rules key on the widget name rather than on a hand-written class.
11622 That is what "a renderer that recognises the name draws it its own way"
11623 means on this host: the name is the hook, and the stylesheet plus the island
11624 are the browser's idea of a carousel.
11625
11626 THE FALLBACK RUNS THE OTHER WAY NOW. This used to render frame one and hide
11627 the rest, because the controls that reach them are the scripted part -- so
11628 with JS off, two of three screenshots were unreachable. Now the unenhanced
11629 page is the whole gallery, stacked in order.
11630
11631 THE CHROME IS DESCRIBED TOO, as of makeover-layout 0.23.0. The description
11632 says `Showing::One` -- an ordered set of pictures with one showing -- and
11633 quasi-webview derives prev/position/next from that, once, for every widget
11634 there will ever be. Nothing here or in any renderer matches on the name
11635 "carousel" to get it. The island stopped injecting chrome and now only binds
11636 what is already on the page; the dot strip and the overlaid arrows are gone.
11637
11638 The controls are still not shipped live to a reader with no script: makeover
11639 keeps `.showing` hidden until something sets [data-ready], so the markup no
11640 longer has to be absent to avoid lying.
11641 ========================================================================= */
11642
11643 /* Public gallery carousel sections (item + project pages). */
11644 .item-gallery,
11645 .project-gallery {
11646 margin: var(--gap-pane) 0;
11647 }
11648
11649 /* <mnw-carousel> wraps the described region rather than being it: a custom
11650 element is what the browser upgrades automatically after an htmx swap, and
11651 the description emits a plain div because it does not know this host's tag
11652 prefix. Default display for an unknown element is inline, so say otherwise. */
11653 mnw-carousel {
11654 display: block;
11655 }
11656
11657 /* The default is ONE frame, which is what a visitor with JavaScript sees, so
11658 the page is settled from first paint and nothing rearranges on load. Showing
11659 all three and collapsing them was a 141px -> 58px jump on every load that
11660 everyone with JS paid; `no-js.css`, loaded from a <noscript>, is what opens
11661 the stack out for a visitor without scripting.
11662
11663 This is the one half of the carousel that stays local, and it is host policy
11664 rather than a gap. makeover's generated sheet collapses the stack on
11665 [data-ready], which is the honest default for a renderer that cannot know
11666 whether this page has script: it ships every child and takes them away once
11667 something binds them. Trading a load-time jump for that is a decision about
11668 this site's landing page, and <noscript> is a mechanism a generated
11669 stylesheet has no way to reach.
11670
11671 No padding-bottom and no position: relative any more. Both were for chrome
11672 that is gone -- the strip that had to be reserved and the arrows that were
11673 absolutely positioned over the frame. */
11674 [data-widget="carousel"] > .showing-frame:not(.current) {
11675 /* respec-ok: the generated rule is the same claim gated on [data-ready], and
11676 what it cannot express is <noscript>. A renderer that cannot know whether a
11677 page has script has to ship every child and take them away once something
11678 binds them; this page collapses from first paint instead and lets no-js.css
11679 open it back, which is a trade about this landing page rather than about
11680 the vocabulary. */
11681 display: none;
11682 }
11683
11684 [data-widget="carousel"] .picture {
11685 margin: 0;
11686 }
11687
11688 /* Only the corner. `display`, `width`, `height` and the raised fill and bevel
11689 all come from makeover's own `.picture-img` rule, which layout.css puts on
11690 every page through crate::shell::head. Restating any of them here is a
11691 components-layer declaration quietly beating the design system, which is what the
11692 drift check in build.rs exists to catch -- and did, on the first attempt at
11693 this file. */
11694 [data-widget="carousel"] .picture-img {
11695 border-radius: var(--radius-control);
11696 }
11697
11698 /* No colour: makeover's `.picture-caption` already sets it to
11699 --content-muted, and this said the same thing one layer louder. What is
11700 local is the centring and the mono face, which are this widget's look and
11701 not a claim about what a caption is. */
11702 [data-widget="carousel"] .picture-caption {
11703 margin-top: var(--gap-group);
11704 font-family: var(--font-mono);
11705 font-size: var(--text-note);
11706 text-align: center;
11707 }
11708
11709 /* Landing showcase only: close the top edge.
11710
11711 The three landing frames are screenshots of this site, so the parchment they
11712 were shot on is the parchment they sit on, and a 1px border between two
11713 identical grounds is not an edge anyone sees.
11714
11715 THE ELEVATION IS GONE, and that is the point of the port. This carried
11716 `box-shadow: 0 4px 10px var(--elevation), 0 14px 36px var(--elevation)`,
11717 which is the defect `c0b63ea9` names: in-flow depth is a bevel and elevation
11718 is for what sits *over* the page. It survived this long because it was
11719 invisible -- a declaration in a later layer on the same class makeover
11720 sets, so it beat the design system's bevel silently. The drift check in build.rs found it
11721 the moment the frame became a described `.picture-img`.
11722
11723 So the frame is raised by makeover's bevel now, from `Depth::Raised`, and
11724 what is left here is the one thing that was never about depth: a stronger
11725 border colour, because a shadow falling downward does nothing for the top
11726 edge and the top edge is where a parchment screenshot dissolves into a
11727 parchment page. */
11728 .landing-showcase .picture-img {
11729 border-color: var(--border-strong);
11730 }
11731
11732 /* ---------------------------------------------------------------------------
11733 Enhanced. Everything below needs the island, and the island sets
11734 [data-ready] once it has upgraded and bound the controls.
11735 --------------------------------------------------------------------------- */
11736
11737 [data-widget="carousel"][data-ready]:focus-visible {
11738 outline: 2px solid var(--focus-ring);
11739 outline-offset: 3px;
11740 }
11741
11742 /* The control row is makeover's `.showing`, rendered by the description rather
11743 than injected here, and it needs no rules: it is two `.button`s and a muted
11744 readout, all three of which the generated sheet already styles. What used to
11745 sit here was `.carousel-nav`, `.carousel-prev`, `.carousel-next`,
11746 `.carousel-dots` and `.carousel-dot` -- 60 lines for chrome this host drew by
11747 hand because nothing could describe it.
11748
11749 The arrows were absolutely positioned over the frame, which Max called
11750 cluttered and which a terminal cannot honestly do at all; the dots had no
11751 form past a handful of frames, and two of the three galleries here are
11752 creator uploads of arbitrary length. Both halves of that were the same half. */
11753
11754
11755 /* ---------------------------------------------------------------------------
11756 Custom page editor (dashboard/custom_page_editor.html)
11757 Moved out of an inline <style> block per the extract-shared-CSS rule.
11758 --------------------------------------------------------------------------- */
11759 .cp-editor { max-width: 1200px; margin: 0 auto; padding: var(--gap-section); }
11760 .cp-editor-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--gap-section); flex-wrap: wrap; }
11761 .cp-editor-actions { display: flex; gap: var(--gap-section); }
11762 .cp-intro { color: var(--content-muted); margin: var(--gap-bound) 0 var(--gap-section); }
11763 /* Form beside preview. A code textarea below about 380px is not editable, so
11764 that number is the pane's, not the viewport's, and the pair stacks itself. */
11765 .cp-editor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: var(--gap-section); align-items: start; }
11766 .cp-editor-form { display: flex; flex-direction: column; gap: var(--gap-peer); }
11767 .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; }
11768 .cp-editor-buttons { display: flex; align-items: center; gap: var(--gap-section); margin-top: var(--gap-peer); }
11769 .cp-preview-pane { border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden; }
11770 .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); }
11771 .cp-preview { width: 100%; height: 520px; border: 0; background: var(--primary-light); display: block; }
11772 .cp-blocked { margin-top: var(--gap-section); }
11773 .cp-blocked-title { font-weight: 600; margin-bottom: var(--gap-peer); }
11774 .cp-blocked-empty { color: var(--content-muted); }
11775 .cp-blocked-list { list-style: none; padding: 0; margin: 0; }
11776 .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; }
11777 .cp-blocked-kind { font-weight: 600; }
11778 .cp-blocked-loc { color: var(--content-muted); }
11779 .cp-blocked-val { background: var(--surface-sunken); padding: 0 var(--gap-bound); }
11780 .cp-status.cp-ok { color: var(--success); }
11781 .cp-status.cp-err { color: var(--warning); }
11782 .cp-reset { margin-top: var(--gap-pane); }
11783 .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)); }
11784 /* Layout-shift fix (audit Run 17 Frontend): reserve space for a cover image
11785 whose class didn't already set an aspect-ratio / fixed size. */
11786 .proj-image-preview img { width: 120px; height: 120px; object-fit: cover; }
11787
11788 /* ---- Discover search: how well a result matched -------------------------
11789 Two surfaces, doing two different jobs (db/discover.rs, migration 179).
11790
11791 .results-tier-break is the single heading, and it sits exactly where the
11792 claim it makes turns true: above it every row holds at least one word the
11793 searcher typed, below it none of them do. .row-match-note is the per-row
11794 half, "Matched 3 of 5 words", because coverage varies row to row and one
11795 page can hold a 4-of-5 beside a 1-of-5.
11796
11797 Both are deliberately quiet. They explain a result the reader is already
11798 looking at; competing with the title would make search feel like it is
11799 apologising. Muted colour and small type, no accent, no border of their own
11800 beyond the rule that separates the tiers. */
11801 .results-tier-break {
11802 display: flex;
11803 align-items: baseline;
11804 gap: var(--gap-peer);
11805 flex-wrap: wrap;
11806 padding: var(--gap-section) var(--gap-section) var(--gap-peer);
11807 border-top: 1px solid var(--border);
11808 background: var(--surface-page);
11809 }
11810
11811 /* .results-table sets border-top: none and lets .table-row draw the rules, so
11812 the break supplies its own top border. Mid-list that sits against the
11813 preceding row's border-bottom and reads as one heavier 2px rule, which is
11814 wanted here: it is a section change, not another row. At the top of an
11815 all-fuzzy page there is no preceding row, and the same border supplies the
11816 table's missing top edge. */
11817 .results-tier-label {
11818 font-family: var(--font-display);
11819 font-weight: bold;
11820 font-size: var(--text-fine);
11821 text-transform: uppercase;
11822 letter-spacing: 0.03em;
11823 color: var(--content);
11824 }
11825
11826 .results-tier-hint {
11827 font-size: var(--text-fine);
11828 color: var(--content-muted);
11829 }
11830
11831 /* The grid is `repeat(auto-fill, minmax(280px, 1fr))`, so without this the
11832 heading takes one card's slot and the tiers interleave visually. */
11833 .results-container.results-grid .results-tier-break {
11834 grid-column: 1 / -1;
11835 padding-left: 0;
11836 padding-right: 0;
11837 background: none;
11838 }
11839
11840 /* In list view this rides inside .row-info, under the creator line, not as
11841 another child of .table-row-link: that is a fixed five-column grid and a
11842 sixth child would invent a column and shift every row that carried one. */
11843 .row-match-note {
11844 font-family: var(--font-mono);
11845 font-size: var(--text-fine);
11846 color: var(--content-muted);
11847 white-space: nowrap;
11848 overflow: hidden;
11849 text-overflow: ellipsis;
11850 }
11851
11852 /* Grid cards have room to breathe; give the note its own line under the meta. */
11853 .grid-card .row-match-note {
11854 display: block;
11855 margin-top: var(--gap-bound);
11856 }
11857
11858 /* The narrow list layouts drop columns rather than wrap, and .row-info is the
11859 column that survives. Keeping the note there would crowd the title on a
11860 phone, where the tier heading above still carries the important half. */
11861 @media (max-width: 599px) {
11862 .table-row .row-match-note { /* respec-ok: hiding a cell at a breakpoint. The
11863 generated sheet describes one `.table-row`; which of its children survive a
11864 narrow viewport is this page's call, not the design system's. */ display: none; }
11865 }
11866
11867
11868 /* ============================================================================
11869 LAYER HANDOFFS
11870
11871 Everything below gives a property back to @layer makeover rather than
11872 setting one. This file is in @layer components and the order statement puts
11873 it after `makeover`, so every rule here wins over the design system whatever
11874 the specificity -- including the bare `button`, `a` and `input` rules further
11875 up, which carry no class and so reach every described component the site
11876 renders. That is the whole reason this section exists: without it a described
11877 act takes this file's chrome instead of the one makeover drew for it, and the
11878 difference is invisible in the source of either file.
11879
11880 `revert-layer` is the deferral. It names no value, so the design system can
11881 change what a chip looks like without this file learning about it.
11882
11883 Two rules govern how these are written, and both are load-bearing:
11884
11885 - Hand a property back on the arms makeover actually paints, not on the
11886 plain class. Makeover gives a chip a colour only when it is disabled, so
11887 `.chip { color: revert-layer }` would roll a live chip back past this
11888 file's `button { color }` to the UA default rather than to anything
11889 makeover chose.
11890 - Outrank the rule being deferred past. Both rules are this file's and both
11891 are in the same layer, so that contest is decided by specificity and then
11892 by source order. `.field` does not beat `input[type="text"]`, which is
11893 why the field handoffs are element-qualified, and this section sits at
11894 the end of the file so an equal-specificity handoff still wins.
11895
11896 makeover-build's check_vocabulary reads both halves and fails the build on a
11897 pair that has neither a handoff nor a reviewed exception, so this section
11898 cannot fall behind the design system without somebody being told.
11899 ============================================================================ */
11900
11901 /* The surfaces a bare `button` was taking. `.card`, `.tab` and the two facet
11902 controls are all rendered as buttons by the described screens. */
11903 .card,
11904 .tab,
11905 .facet-take,
11906 .facet-prune {
11907 /* respec-ok: a layer handoff, not a value. */
11908 background: revert-layer;
11909 box-shadow: revert-layer;
11910 }
11911
11912 .card:hover,
11913 .tab:hover,
11914 .facet-take:hover,
11915 .facet-prune:hover {
11916 /* respec-ok: a layer handoff, not a value. */
11917 background: revert-layer;
11918 }
11919
11920 .card:active,
11921 .tab:active,
11922 .facet-take:active,
11923 .facet-prune:active {
11924 /* respec-ok: a layer handoff, not a value. */
11925 background: revert-layer;
11926 box-shadow: revert-layer;
11927 }
11928
11929 /* A facet control's colour is makeover's in every state: muted when disabled,
11930 secondary when the value it names is not standing, inherited otherwise.
11931 There is no arm of it this file has an opinion about. */
11932 .facet-take,
11933 .facet-prune {
11934 /* respec-ok: a layer handoff, not a value. */
11935 color: revert-layer;
11936 }
11937
11938 /* The disabled arm. Makeover paints all four properties here -- muted text, the
11939 not-allowed cursor, and the surface it keeps rather than dropping -- and this
11940 file's `button:disabled` was taking every one of them. */
11941 .button:disabled,
11942 .button[aria-disabled="true"],
11943 .card:disabled,
11944 .card[aria-disabled="true"],
11945 .chip:disabled,
11946 .chip[aria-disabled="true"],
11947 .tab:disabled,
11948 .tab[aria-disabled="true"],
11949 .segment:disabled,
11950 .segment[aria-disabled="true"],
11951 .toggle:disabled,
11952 .toggle[aria-disabled="true"],
11953 .field:disabled,
11954 .field[aria-disabled="true"],
11955 .facet-take:disabled,
11956 .facet-take[aria-disabled="true"],
11957 .facet-prune:disabled,
11958 .facet-prune[aria-disabled="true"] {
11959 /* respec-ok: a layer handoff, not a value. */
11960 background: revert-layer;
11961 box-shadow: revert-layer;
11962 color: revert-layer;
11963 cursor: revert-layer;
11964 }
11965
11966 /* The focus ring. Makeover insets a field's so it does not collide with the
11967 bevel and leaves every other one outside, and this file's `:focus-visible`
11968 rules were flattening the distinction on every described control. */
11969 .button:focus-visible,
11970 .card:focus-visible,
11971 .chip:focus-visible,
11972 .link:focus-visible,
11973 .tab:focus-visible,
11974 .segment:focus-visible,
11975 .toggle:focus-visible,
11976 .facet-take:focus-visible,
11977 .facet-prune:focus-visible {
11978 /* respec-ok: a layer handoff, not a value. */
11979 outline: revert-layer;
11980 outline-offset: revert-layer;
11981 }
11982
11983 /* A described field is an `<input>`, a `<select>` or a `<textarea>`, and the
11984 rules for those three carry an attribute, so the handoff has to as well: a
11985 bare `.field` is the weaker selector and loses inside this layer. The `:focus`
11986 arm is here because this file sets `outline: none` on it, which removes the
11987 ring makeover draws on `:focus-visible` rather than replacing it. */
11988 input.field,
11989 select.field,
11990 textarea.field,
11991 input.field:hover,
11992 select.field:hover,
11993 textarea.field:hover,
11994 input.field:active,
11995 select.field:active,
11996 textarea.field:active {
11997 /* respec-ok: a layer handoff, not a value. */
11998 background: revert-layer;
11999 box-shadow: revert-layer;
12000 }
12001
12002 input.field:focus,
12003 select.field:focus,
12004 textarea.field:focus,
12005 input.field:focus-visible,
12006 select.field:focus-visible,
12007 textarea.field:focus-visible {
12008 /* respec-ok: a layer handoff, not a value. */
12009 outline: revert-layer;
12010 outline-offset: revert-layer;
12011 }
12012
12013 /* A described toggle is an `<input>` too. The hover and active arms are here
12014 for the same reason as the field's: this file dresses `input[type="submit"]`
12015 as a raised button in every state, and a toggle is an input. */
12016 input.toggle,
12017 input.toggle:hover,
12018 input.toggle:active,
12019 input.toggle:focus,
12020 input.toggle:focus-visible {
12021 /* respec-ok: a layer handoff, not a value. */
12022 background: revert-layer;
12023 box-shadow: revert-layer;
12024 outline: revert-layer;
12025 outline-offset: revert-layer;
12026 }
12027
12028 /* A described link on hover. `.link` above hands the resting colour back; this
12029 file's `a:hover` is the more specific of the two and was taking the hover
12030 colour and the underline with it. */
12031 .link:hover {
12032 /* respec-ok: a layer handoff, not a value. */
12033 color: revert-layer;
12034 text-decoration: revert-layer;
12035 }
12036
12037 /* Text makeover mutes: a facet's name, and the line a placeholder shows when
12038 there is nothing to list. This file's bare `p` rule was giving both the body
12039 colour. */
12040 .facet-name,
12041 .placeholder-text {
12042 /* respec-ok: a layer handoff, not a value. */
12043 color: revert-layer;
12044 }
12045 }
12046
12047 /* ===========================================
12048 DESCRIBED MODAL (quasi RegionKind::Modal)
12049 ===========================================
12050 The first described modal on this host is the media picker; these two rules
12051 are what "modal" means here, and every one after it gets them for free. The
12052 description says the region IS a modal and says nothing about where it sits,
12053 which is the division the vocabulary draws: `role="dialog" aria-modal="true"`
12054 comes off the renderer, and a dialog that lays out in flow would make that
12055 attribute a lie.
12056
12057 Two boxes because a scrim and a panel are two boxes: the modal region covers
12058 the viewport and takes the presses the page behind it must not get, and the
12059 region inside it is the panel. */
12060 .region.modal {
12061 position: fixed;
12062 inset: 0;
12063 z-index: var(--z-overlay);
12064 display: flex;
12065 align-items: center;
12066 justify-content: center;
12067 background: var(--overlay);
12068 }
12069
12070 .region.modal > .region {
12071 position: relative;
12072 z-index: var(--z-modal);
12073 width: 90%;
12074 max-width: 700px;
12075 max-height: 80vh;
12076 overflow-y: auto;
12077 padding: var(--gap-section);
12078 background: var(--surface-overlay);
12079 box-shadow: var(--elevation-overlay);
12080 }
12081
12082 /* ===========================================
12083 MEDIA PICKER (crate::quasi::media_picker)
12084 ===========================================
12085 Addressed by the widget names the description carries, not by ids: a document
12086 holds one picker per markdown editor and their ids differ by destination,
12087 while what they are drawn like is the same assembly every time. */
12088 [data-widget="media-picker-grid"] {
12089 display: grid;
12090 grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
12091 gap: var(--gap-peer);
12092 margin-top: var(--gap-section);
12093 }
12094
12095 [data-widget="media-tile"] {
12096 display: flex;
12097 flex-direction: column;
12098 gap: var(--gap-peer);
12099 padding: var(--gap-peer);
12100 border: 1px solid var(--border);
12101 background: var(--surface-raised);
12102 min-width: 0;
12103 }
12104
12105 /* A cap, not a height, and never a bare `img`. The design system already sets
12106 the picture's width, height and background, and an app rule taking any of
12107 those wins over `@layer makeover` silently — which is what `makeover-build`'s
12108 drift check refuses, and it is right to. `max-height` is not one of them, and
12109 it is all this needs: `Fit::Contain` is in the description, so a capped
12110 picture letterboxes inside the cap rather than being cropped or stretched. */
12111 [data-widget="media-tile"] .picture-img {
12112 max-height: 80px;
12113 }
12114
12115 /* The file name is the control, so it wraps rather than pushing the tile wide.
12116 `Act` is a label and nothing in the vocabulary says a control reads as a
12117 picture, so the thumbnail above it is a sibling and only this half answers a
12118 press. Filed rather than faked; see the module header. */
12119 [data-widget="media-tile"] .button {
12120 overflow-wrap: anywhere;
12121 text-align: left;
12122 font-size: var(--text-note);
12123 }
12124