Skip to main content

max / makenotwork

322.4 KB · 11741 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 .creators-page .container { max-width: 900px; margin: 0 auto; }
410
411 /* READING COLUMN, the 680px measure every long-form surface uses. Narrower
412 than .container and with tighter horizontal padding, so it is its own
413 primitive rather than a .container width tier.
414
415 These four wrappers were four identical rules until 2026-08-16. They are
416 grouped rather than replaced by one class because the charter treats a new
417 class as a smell, and because grouping keeps each selector's own
418 specificity intact. A fifth long-form surface joins this list; it does not
419 copy the recipe.
420
421 .media-container's rule used to live in media-player.css. It is here now so
422 the measure has one definition; that sheet keeps its descendant rules. */
423 .article-page .article-container,
424 .doc-container,
425 .docs-index,
426 .media-container {
427 max-width: 680px;
428 margin: 0 auto;
429 padding: var(--gap-page) var(--gap-pane) var(--gap-page);
430 }
431
432 /* ===========================================
433 BUTTONS
434 =========================================== */
435
436 button {
437 color: var(--content);
438 background: var(--surface-overlay);
439 padding: 6px 12px;
440 border: 1px solid var(--content);
441 font-family: inherit;
442 cursor: pointer;
443 box-shadow: var(--shadow-raised);
444 transition: box-shadow 0.1s ease, background 0.2s ease;
445 }
446
447 button:hover {
448 background: var(--hover-surface);
449 }
450
451 button:active {
452 box-shadow: var(--shadow-inset);
453 }
454
455 /* A described act carries the generated `.button` class, and the element rule
456 above sets the same surface, bevel and tone the generated class does. This
457 file is in @layer components, which the order statement puts after
458 `makeover`, so the element rule wins whatever the specificity. Without the
459 handoff below a described act renders in --surface-overlay with
460 --shadow-raised and, worse, in --content rather than its tone: a
461 destructive act looks identical to an ordinary one. Same mechanism and same
462 remedy as `.tab.chosen` further down; read the comment there.
463
464 The split is deliberate. The app keeps padding, border and font from the
465 element rule, makeover keeps surface, bevel and tone. That is the
466 arrangement that already makes `.field` work, where the app's input rule
467 happens to set only geometry and typography.
468
469 Tone is handed back on `[data-tone]` rather than on `.button`, because an
470 untoned button has no colour in the layer to roll back to and would land on
471 the UA default instead of --content. */
472 .button,
473 .chip,
474 .segment,
475 .toggle {
476 /* respec-ok: a layer handoff, not a value. */
477 background: revert-layer;
478 box-shadow: revert-layer;
479 }
480
481 .button:hover,
482 .chip:hover,
483 .segment:hover,
484 .toggle:hover {
485 /* respec-ok: a layer handoff, not a value. */
486 background: revert-layer;
487 }
488
489 .button:active,
490 .chip:active,
491 .segment:active,
492 .toggle:active {
493 /* respec-ok: a layer handoff, not a value. */
494 background: revert-layer;
495 box-shadow: revert-layer;
496 }
497
498 .button[data-tone] {
499 /* respec-ok: a layer handoff, not a value. */
500 color: revert-layer;
501 }
502
503 /* A described link, same handoff on the `a` and `button` element rules.
504 `a { color: var(--content); text-decoration: none }` renders a described
505 link as body text -- the reader cannot tell it is a link at all, which is
506 the same defect as the lost tone above and worse for being silent.
507
508 `button.link` is the second arm: a link that posts is a button in the
509 markup, and makeover already flattens it (`background: none`, no border, no
510 padding, `font: inherit`). The element rule's chrome beat all four, so it
511 rendered as an ordinary raised button. Only the properties makeover sets on
512 this class are handed back; `font` is handed back on the button arm alone,
513 because an `a.link` has no lower-layer font to roll back to and would land
514 on the UA default face. */
515 .link {
516 /* respec-ok: a layer handoff, not a value. */
517 color: revert-layer;
518 text-decoration: revert-layer;
519 }
520
521 button.link {
522 /* respec-ok: a layer handoff, not a value. */
523 background: revert-layer;
524 border: revert-layer;
525 padding: revert-layer;
526 font: revert-layer;
527 box-shadow: revert-layer;
528 cursor: revert-layer;
529 }
530
531 /* A described facet value, the third arm of the same handoff. makeover 0.67.0
532 names the element-chrome reset, and a facet control withdraws the button's
533 fill, stroke and bevel with it: a value reads flat until it is hovered,
534 pressed or in force. The element rule above supplies all three, so without
535 this a described facet renders as an ordinary raised button and
536 `[aria-pressed="true"]` is its only correct state -- which is the whole of
537 what the panel had to say and the one thing it could not.
538
539 Wider than the `.button` group above by one property. That group keeps the
540 app's border deliberately, because a described act is still a button. Here
541 the flat rest look IS the withdrawal of the border, so it is handed back
542 with the other two. */
543 .facet-take,
544 .facet-prune {
545 /* respec-ok: a layer handoff, not a value. */
546 background: revert-layer;
547 border: revert-layer;
548 box-shadow: revert-layer;
549 }
550
551 .facet-take:hover,
552 .facet-prune:hover {
553 /* respec-ok: a layer handoff, not a value. */
554 background: revert-layer;
555 }
556
557 .facet-take:active,
558 .facet-prune:active,
559 .facet-take[aria-pressed="true"],
560 .facet-prune[aria-pressed="true"] {
561 /* respec-ok: a layer handoff, not a value. */
562 background: revert-layer;
563 box-shadow: revert-layer;
564 }
565
566 /* A described field on a `select` or a `textarea`. The input rule further down
567 sets padding and font only, which is why the shipped `.field` precedents
568 survived; these two also set `background` and `box-shadow`, so a described
569 field on either renders in --surface-sunken rather than the well. The focus
570 ring is handed back with them: makeover insets it (`calc(-1 * 2px)`) so it
571 does not collide with the bevel, and the app's outer 2px was winning. */
572 .field {
573 /* respec-ok: a layer handoff, not a value. */
574 background: revert-layer;
575 box-shadow: revert-layer;
576 }
577
578 .field:focus-visible {
579 /* respec-ok: a layer handoff, not a value. */
580 outline-offset: revert-layer;
581 }
582
583 .btn-primary {
584 background: var(--primary-dark);
585 color: var(--primary-light);
586 border: none;
587 padding: var(--gap-section) var(--gap-pane);
588 font-family: inherit;
589 font-size: var(--text-body);
590 cursor: pointer;
591 box-shadow: var(--shadow-raised);
592 transition: box-shadow 0.1s ease, background 0.2s ease;
593 display: inline-block;
594 text-decoration: none;
595 }
596
597 /* A solid-fill button shifts its own fill rather than taking --hover-surface.
598 The charter's hover rule is "one step lighter than the resting fill", and on
599 a neutral control that step is --hover-surface; on this one it is not, since
600 the label is --primary-light and a parchment fill under it is unreadable. */
601 .btn-primary:hover {
602 background: color-mix(in oklch, var(--primary-dark) 85%, var(--primary-light));
603 text-decoration: none;
604 }
605
606 .btn-primary:active {
607 box-shadow: var(--shadow-inset);
608 }
609
610 .btn-secondary {
611 background: var(--surface-sunken);
612 color: var(--content);
613 border: none;
614 padding: var(--gap-section) var(--gap-pane);
615 font-family: var(--font-mono);
616 font-size: var(--text-body);
617 cursor: pointer;
618 box-shadow: var(--shadow-raised);
619 transition: box-shadow 0.1s ease, background 0.2s ease;
620 display: inline-block;
621 text-decoration: none;
622 }
623
624 .btn-secondary:hover {
625 background: var(--hover-surface);
626 text-decoration: none;
627 }
628
629 .btn-secondary:active {
630 box-shadow: var(--shadow-inset);
631 }
632
633 .btn-danger {
634 background: var(--danger);
635 color: var(--primary-light);
636 border: none;
637 padding: var(--gap-section) var(--gap-pane);
638 font-family: var(--font-mono);
639 font-size: var(--text-body);
640 cursor: pointer;
641 box-shadow: var(--shadow-raised);
642 transition: box-shadow 0.1s ease, background 0.2s ease;
643 display: inline-block;
644 text-decoration: none;
645 }
646
647 /* Solid fill, so the same exception as .btn-primary applies. */
648 .btn-danger:hover {
649 background: color-mix(in oklch, var(--danger) 85%, var(--primary-light));
650 text-decoration: none;
651 }
652
653 .btn-danger:active {
654 box-shadow: var(--shadow-inset);
655 }
656
657 /* Disabled state for all button variants (charter rule).
658 Applies whether disabled via attribute or aria-disabled. */
659 button:disabled,
660 button[aria-disabled="true"],
661 .btn-primary:disabled,
662 .btn-secondary:disabled,
663 .btn-danger:disabled {
664 opacity: 0.5;
665 cursor: not-allowed;
666 box-shadow: none;
667 transform: none;
668 }
669
670 /* Button size + style modifiers (charter: docs/design-system.md).
671 Use these on top of .btn-primary / -secondary / -danger.
672
673 .btn--large: large CTA padding bump.
674 .btn--icon: square icon-only / micro button (small padding,
675 tight line-height).
676 .btn--link: visually a link, semantically a button (no bg/
677 border, opacity-fade hover, mono).
678
679 Some surfaces have tuned button variants that are NOT compositions
680 of these modifiers and keep their own classes:
681 .big-button: Young Serif 200×60 anchor on splash pages.
682 .order-btn: list reorder up/down (with active flash).
683 .play-button: circular media-player play control.
684 .speed-button: segment in media-player speed group.
685 .shortcuts-help-btn: 1.5rem square help glyph in toolbars.
686 .toast-retry-btn: inline bordered button inside a toast,
687 uses currentColor to inherit toast tone.
688 These are documented variants, not deprecation targets. */
689 .btn--large {
690 padding: var(--gap-section) var(--gap-page);
691 font-size: var(--text-lead);
692 }
693
694 .btn--icon {
695 padding: var(--gap-bound) var(--gap-peer);
696 font-size: var(--text-note);
697 line-height: 1;
698 min-width: 1.75rem;
699 }
700
701 .btn--link {
702 background: none;
703 color: var(--content);
704 border: none;
705 padding: 0;
706 font-family: var(--font-mono);
707 font-size: var(--text-body);
708 cursor: pointer;
709 /* Link semantics, flat, no depth. Overrides the shadow that would
710 otherwise cascade from `button` / `form button` when a .btn--link
711 happens to be a <button> inside a <form> (e.g. logout). */
712 box-shadow: none;
713 transition: opacity 0.2s ease;
714 }
715
716 .btn--link:hover {
717 opacity: 0.6;
718 box-shadow: none;
719 }
720
721 .btn--link:active {
722 box-shadow: var(--shadow-inset);
723 }
724
725 /* Menu / disclosure openers, buttons whose job is to reveal a menu,
726 filter panel, expander, or popover. They sit flat rather than raised.
727 Add new opener selectors here as they appear. */
728 .context-menu-btn,
729 .discover-filter-toggle,
730 .tip-toggle,
731 .bundle-toggle {
732 box-shadow: none;
733 }
734
735 .context-menu-btn:hover,
736 .discover-filter-toggle:hover,
737 .tip-toggle:hover,
738 .bundle-toggle:hover {
739 box-shadow: none;
740 }
741
742 .context-menu-btn:active,
743 .discover-filter-toggle:active,
744 .tip-toggle:active,
745 .bundle-toggle:active {
746 box-shadow: var(--shadow-inset);
747 }
748
749 /* ===========================================
750 FORMS
751 =========================================== */
752
753 form {
754 display: flex;
755 flex-direction: column;
756 gap: var(--gap-section);
757 width: 100%;
758 }
759
760 .form-container {
761 max-width: 400px;
762 background: var(--surface-overlay);
763 padding: var(--gap-page);
764 }
765
766 label {
767 font-family: var(--font-mono);
768 color: var(--content);
769 font-size: var(--text-note);
770 }
771
772 input[type="text"],
773 input[type="email"],
774 input[type="password"],
775 input[type="number"],
776 input[type="date"],
777 textarea,
778 select {
779 width: 100%;
780 background: var(--surface-sunken);
781 border: 1px solid var(--border);
782 padding: var(--gap-section);
783 font-family: var(--font-mono);
784 font-size: var(--text-note);
785 color: var(--content);
786 box-shadow: var(--shadow-inset);
787 }
788
789 input::placeholder,
790 textarea::placeholder {
791 opacity: 0.5;
792 }
793
794 input[type="text"]:focus,
795 input[type="email"]:focus,
796 input[type="password"]:focus,
797 input[type="number"]:focus,
798 textarea:focus,
799 select:focus {
800 outline: none;
801 border: 1px solid var(--action);
802 }
803
804 textarea {
805 min-height: 100px;
806 resize: vertical;
807 }
808
809 select {
810 cursor: pointer;
811 }
812
813 input[type="submit"],
814 form button {
815 font-family: var(--font-display);
816 font-size: var(--text-body);
817 color: var(--content);
818 background: var(--surface-overlay);
819 padding: 12px 24px;
820 border: 1px solid var(--content);
821 cursor: pointer;
822 box-shadow: var(--shadow-raised);
823 transition: box-shadow 0.1s ease, background 0.2s ease;
824 }
825
826 input[type="submit"]:hover,
827 form button:hover {
828 background: var(--hover-surface);
829 }
830
831 input[type="submit"]:active,
832 form button:active {
833 box-shadow: var(--shadow-inset);
834 }
835
836 .form-group {
837 margin-bottom: var(--gap-pane);
838 }
839
840 .form-group label {
841 display: block;
842 margin-bottom: var(--gap-peer);
843 font-size: var(--text-note);
844 }
845
846 .form-group input,
847 .form-group textarea,
848 .form-group select {
849 width: 100%;
850 }
851
852 .form-row {
853 display: grid;
854 grid-template-columns: 1fr 1fr;
855 gap: var(--gap-section);
856 }
857
858 .hint {
859 font-size: var(--text-note);
860 opacity: 0.6;
861 margin-top: var(--gap-peer);
862 }
863
864 /* Field-level validation error, paired with .form-group--error.
865 Canonical: `partials/_ui.html` ui::form_field macro. */
866 .form-group--error label {
867 color: var(--danger);
868 }
869
870 .form-group--error input,
871 .form-group--error textarea,
872 .form-group--error select {
873 border-color: var(--danger);
874 }
875
876 .field-error {
877 font-size: var(--text-note);
878 color: var(--danger);
879 margin-top: var(--gap-peer);
880 }
881
882 /* Checkbox group */
883 .checkbox-group {
884 display: flex;
885 align-items: start;
886 gap: var(--gap-section);
887 cursor: pointer;
888 transition: background 0.2s ease;
889 margin-bottom: var(--gap-peer);
890 }
891
892 .checkbox-group input[type="checkbox"] {
893 width: auto;
894 margin-top: var(--gap-bound);
895 margin-bottom: var(--gap-bound);
896 cursor: pointer;
897 }
898 /* Radio group */
899 .radio-group {
900 display: flex;
901 flex-direction: column;
902 gap: var(--gap-section);
903 margin-top: var(--gap-peer);
904 }
905 /* ===========================================
906 TABS
907 =========================================== */
908
909 .tabs {
910 display: flex;
911 flex-wrap: nowrap;
912 gap: 0;
913 margin-bottom: 0;
914 }
915
916 /* Geometry and type only. The surface is layout.css's: `.tab` is sunken,
917 `:hover` steps to --hover-surface on a fine pointer, `:active` insets. This
918 rule used to restate the sunken background verbatim and then carry a second,
919 redundant selection signal on top of it — opacity 0.6 unselected, 1 selected
920 — so a tab said "not chosen" twice and the generated surface step did
921 nothing. The opacity idiom is gone and the surface contrast is the signal. */
922 .tab {
923 color: var(--content);
924 border: none;
925 padding: var(--gap-section) var(--gap-page);
926 font-family: var(--font-mono);
927 font-size: var(--text-body);
928 cursor: pointer;
929 /* Tabs navigate, they don't commit, stay flat. Overrides the base
930 `button` shadow if a tab is a <button>. */
931 box-shadow: none;
932 transition: background 0.2s ease;
933 white-space: nowrap;
934 flex-shrink: 0;
935 }
936
937 /* A tab's selected state is `.tab.chosen` in layout.css and nowhere else now.
938 This file used to carry a `.tab.is-selected` rule setting the same two
939 declarations byte for byte, on the argument that `.is-selected` is the
940 site's own idiom across git nav links, editor tabs, section tabs, issue tabs
941 and filter buttons, so renaming the tabs alone would split it. That is true
942 and it is still the wrong trade: a second name for a state the design system
943 already names is invisible only while both say the same thing, and stops
944 being invisible the moment makeover changes what a chosen tab looks like.
945 The other components keep `.is-selected`, because makeover has no primitive
946 for a git nav link; the tab has one.
947
948 The chosen arm's box-shadow comes back through the layer rather than being
949 restated. `.tab` below zeroes the base `button` shadow, and this file is in
950 @layer components, which the order statement puts after `makeover`, so it
951 outranks it whatever the specificity: without the revert, the generated
952 bevel never applies and a chosen tab reads flat.
953 `revert-layer` hands the property back instead of naming a value, which is
954 the difference between deferring to the design system and copying it. */
955 .tab.chosen {
956 /* respec-ok: a layer handoff, not a value. See the comment above. */
957 box-shadow: revert-layer;
958 }
959
960 .tab-more-wrap {
961 position: relative;
962 flex-shrink: 0;
963 }
964
965 .tab-overflow-menu {
966 position: absolute;
967 top: 100%;
968 right: 0;
969 z-index: var(--z-dropdown);
970 background: var(--surface-page);
971 border: 1px solid var(--border);
972 min-width: 180px;
973 box-shadow: var(--elevation-overlay);
974 }
975
976 /* The overflow menu restates the tab as a menu row: block, full width, left
977 aligned, tighter. It carried the same opacity pair the strip did and it goes
978 for the same reason; hover and selection are the generated surface steps. */
979 .tab-overflow-menu .tab {
980 display: block;
981 width: 100%;
982 text-align: left;
983 padding: var(--gap-group) var(--gap-section);
984 }
985
986
987 .tab-content {
988 display: none;
989 background: var(--surface-overlay);
990 padding: var(--gap-page);
991 }
992
993 .tab-content.active {
994 display: block;
995 }
996
997 /* ===========================================
998 TABLES
999 =========================================== */
1000
1001 .data-table {
1002 width: 100%;
1003 border-collapse: collapse;
1004 font-size: var(--text-note);
1005 font-family: var(--font-sans);
1006 margin-bottom: var(--gap-section);
1007 }
1008
1009 .data-table thead {
1010 background: var(--surface-sunken);
1011 }
1012
1013 .data-table th {
1014 text-align: left;
1015 padding: var(--gap-section);
1016 font-weight: normal;
1017 }
1018
1019 /* `.data-table th.sortable` and its four rules were here and had no consumer:
1020 nothing in templates/ ever put `sortable` on a `th`. The one sortable table
1021 in the app is templates/partials/tabs/project_content.html, which used
1022 `.sortable-th` further down this file. Both are gone; the affordance and the
1023 caret are `.table-heading[data-sortable]` in the generated static/layout.css.
1024 See the block that replaced `.sortable-th` for what stayed behind. */
1025
1026 .data-table tbody tr {
1027 transition: background 0.1s ease;
1028 }
1029
1030 /* No zebra. There were two rules here, :nth-child(odd) and (even), and both
1031 set --surface-overlay — the same value, so the stripe never existed and the
1032 effect was every row painted the panel's own colour. The table is a .well
1033 now and the rows sit on it unpainted, which is what the banding was
1034 reaching for. makeover derives no row-stripe intent, so if one is ever
1035 wanted it is a makeover-side question, not a literal here. */
1036
1037 .data-table tbody tr:hover {
1038 background: var(--surface-sunken);
1039 }
1040
1041 .data-table td {
1042 padding: var(--gap-section);
1043 border-top: 1px solid var(--border);
1044 }
1045
1046 /* Marks the row a table is about, used across the dashboard and library
1047 tables. Lived in the pricing block until the fee calculator replaced it. */
1048 .data-table .highlight {
1049 background: var(--surface-overlay);
1050 border-left: 3px solid var(--action);
1051 }
1052
1053 /* ===========================================
1054 COMPACT TABLES (dashboard/admin, lighter than .data-table)
1055 =========================================== */
1056
1057 .compact-table {
1058 width: 100%;
1059 border-collapse: collapse;
1060 }
1061
1062 .compact-table thead tr {
1063 font-size: var(--text-note);
1064 opacity: 0.7;
1065 }
1066
1067 .compact-table th {
1068 padding: var(--gap-peer) var(--gap-section);
1069 text-align: left;
1070 }
1071
1072 .compact-table td {
1073 padding: var(--gap-section);
1074 }
1075
1076 .compact-table tbody tr {
1077 border-bottom: 1px solid var(--border);
1078 }
1079
1080 /* ===========================================
1081 UTILITIES
1082 =========================================== */
1083
1084 .text-sm { font-size: var(--text-note); }
1085 .text-xs { font-size: var(--text-fine); }
1086 .muted { opacity: 0.7; }
1087 .dimmed { opacity: 0.6; }
1088 .dimmer { opacity: 0.5; }
1089 .meta { font-size: var(--text-note); opacity: 0.7; }
1090 .nowrap { white-space: nowrap; }
1091 .scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
1092 .text-center { text-align: center; }
1093 .text-right { text-align: right; }
1094 .fw-bold { font-weight: bold; }
1095 .unstyled-link { text-decoration: none; color: inherit; }
1096 .square-cover { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
1097
1098 /* Spacing utilities.
1099 mt = margin-top, mb = margin-bottom, my = vertical, py = vertical padding.
1100 Named for the relationship, same vocabulary as the --gap-* tokens they
1101 read. Use sparingly; prefer parent layout primitives. */
1102 .m-0 { margin: 0; }
1103 .mt-0 { margin-top: 0; }
1104 .mb-0 { margin-bottom: 0; }
1105 .mt-peer { margin-top: var(--gap-peer); }
1106 .mt-section { margin-top: var(--gap-section); }
1107 .mt-pane { margin-top: var(--gap-pane); }
1108 .mt-page { margin-top: var(--gap-page); }
1109 .mb-peer { margin-bottom: var(--gap-peer); }
1110 .mb-section { margin-bottom: var(--gap-section); }
1111 .mb-pane { margin-bottom: var(--gap-pane); }
1112 .my-peer { margin-block: var(--gap-peer); }
1113 .my-section { margin-block: var(--gap-section); }
1114 .py-section { padding-block: var(--gap-section); }
1115
1116 /* Min-width utilities for tables inside .scroll-x wrappers. */
1117 .minw-300 { min-width: 300px; }
1118 .minw-400 { min-width: 400px; }
1119 .minw-500 { min-width: 500px; }
1120 .minw-600 { min-width: 600px; }
1121 .minw-700 { min-width: 700px; }
1122 .minw-800 { min-width: 800px; }
1123
1124 /* Compact button sizes. Canonical: .small (broad use, 0.25rem 0.6rem 0.8rem).
1125 The tiny / row-btn / cart-row-btn aliases are visually equivalent, within
1126 sub-pixel of each other in the original templates, and folded into .small. */
1127 .small { padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); }
1128
1129 /* Canonical empty-state primitive. Use modifiers for tight/sized contexts.
1130 Markup: <div class="empty-state">…</div> (optionally with .empty-state--compact
1131 inside dropdowns or .empty-state--chart inside a fixed-height chart slot). */
1132 .empty-state { text-align: center; padding: var(--gap-page); opacity: 0.6; }
1133 .empty-state--compact { padding: var(--gap-section); font-size: var(--text-note); }
1134 .empty-state--chart { height: 200px; padding: 0; display: flex; align-items: center; justify-content: center; opacity: 0.5; }
1135 .empty-state-hint { font-family: var(--font-mono); font-size: var(--text-fine); margin-top: var(--gap-peer); opacity: 0.6; }
1136
1137 .tab-docs {
1138 display: flex;
1139 justify-content: flex-end;
1140 margin-bottom: var(--gap-section);
1141 }
1142
1143 .tab-docs a {
1144 font-family: var(--font-mono);
1145 font-size: var(--text-fine);
1146 opacity: 0.5;
1147 transition: opacity 0.2s ease;
1148 }
1149
1150 .tab-docs a:hover {
1151 opacity: 1;
1152 }
1153
1154 /* ===========================================
1155 BADGES
1156 =========================================== */
1157
1158 /* Geometry and type only. The colour is layout.css's: a badge is coloured
1159 text on the page, and its status colour comes from `data-tone` — see
1160 src/types/badge.rs, which maps every status enum onto a tone name.
1161
1162 This used to be a filled pill (near-black ground, --primary-light label)
1163 with a local `.badge--live/pending/failed` set painting the fill per
1164 status. All of it went in wave 2 tier B2: the fill was carrying exactly
1165 what the generated `color` carries, so it was the same thing said in a
1166 different channel, and keeping it meant the tone attribute could never do
1167 anything. */
1168 .badge {
1169 display: inline-block;
1170 padding: var(--gap-bound) var(--gap-peer);
1171 font-size: var(--text-fine);
1172 font-family: var(--font-mono);
1173 }
1174
1175 /* AI disclosure tier badges. See site-docs/public/about/generative-ai.md.
1176 Colours track the brand palette: violet for the cleanest case, warm-tan for
1177 disclosed AI use, charcoal for primarily generated.
1178
1179 respec-ok: this is a taxonomy makeover has no vocabulary for. Its four
1180 tones are info / success / warning / danger, which all read as alarm, and
1181 disclosure is not alarm — a handmade item is not a "success" and a
1182 generated one is not a "danger". So the three hues stay local. They set
1183 `color` rather than a background, because the badge form is the generated
1184 one now and only the hue is ours. */
1185 .badge.ai-tier {
1186 letter-spacing: 0.02em;
1187 }
1188 .badge.ai-tier-handmade {
1189 color: var(--action);
1190 }
1191 .badge.ai-tier-assisted {
1192 color: var(--warning);
1193 }
1194 .badge.ai-tier-generated {
1195 color: var(--content);
1196 }
1197
1198 .item-ai-tier {
1199 margin: var(--gap-bound) 0 var(--gap-section);
1200 }
1201
1202 .ai-disclosure {
1203 margin: var(--gap-section) 0;
1204 padding: var(--gap-section);
1205 background: var(--surface-sunken);
1206 border-left: 3px solid var(--warning);
1207 }
1208 .ai-disclosure-label {
1209 font-family: var(--font-mono);
1210 font-size: var(--text-fine);
1211 text-transform: uppercase;
1212 letter-spacing: 0.05em;
1213 color: var(--content-muted);
1214 margin-bottom: var(--gap-bound);
1215 }
1216 .ai-disclosure-text {
1217 font-size: var(--text-note);
1218 color: var(--content);
1219 }
1220
1221 /* Discover row: plain text mention per design decision 2026-06-03:
1222 no pill in the row, so fans skim the listing without disclosure noise
1223 and use the explicit filter when they care. */
1224 .discover-row-ai-tier {
1225 font-family: var(--font-mono);
1226 font-size: var(--text-fine);
1227 color: var(--content-muted);
1228 }
1229
1230 /* ===========================================
1231 TAGS
1232 =========================================== */
1233
1234 .tag {
1235 display: inline-block;
1236 background: var(--primary-dark);
1237 color: var(--primary-light);
1238 padding: var(--gap-bound) var(--gap-peer);
1239 font-size: var(--text-fine);
1240 margin-right: var(--gap-bound);
1241 }
1242
1243 .tag-input {
1244 background: var(--surface-sunken);
1245 padding: var(--gap-peer);
1246 margin-bottom: var(--gap-peer);
1247 display: flex;
1248 flex-wrap: wrap;
1249 gap: var(--gap-peer);
1250 min-height: 40px;
1251 }
1252
1253 .tag-input .tag {
1254 display: flex;
1255 align-items: center;
1256 gap: var(--gap-peer);
1257 padding: var(--gap-bound) var(--gap-peer);
1258 font-size: var(--text-note);
1259 }
1260
1261 .tag-input .tag button {
1262 background: none;
1263 border: none;
1264 color: var(--primary-light);
1265 cursor: pointer;
1266 padding: 0;
1267 font-size: var(--text-body);
1268 }
1269
1270 /* ===========================================
1271 CARDS
1272 =========================================== */
1273
1274 /* Canonical card primitive (charter: docs/design-system.md).
1275 Variants:
1276 .card: filled, hover-step (default content card).
1277 .card.card-muted: surface-muted fill, no hover (dashboard stat / analytics blocks).
1278 .card.card--bordered: bordered, padded, no fill (marketing cards: feature/tier/use-case).
1279 .card.card--selectable: radio-card pattern; pair with .is-selected.
1280 .card.card--grid: grid-cell card (discover grid).
1281 No aliases. A template names the modifier it wants and adds its own
1282 component class beside it; a recipe reached only through a per-page name is
1283 a recipe nobody can find from the charter. */
1284 /* Fill and depth come from layout.css, generated by makeover-webview: the card
1285 is `--surface-raised` plus `--bevel-raised`, and `:hover` goes to
1286 `--hover-surface` on a fine pointer only. Declaring either here would take
1287 them back, since style.css is in @layer components, which the order
1288 statement puts after `makeover`, whatever the specificity. The box-shadow this used to carry was
1289 `--shadow-card`, which already aliases `--bevel-raised`, so only the fill
1290 actually moved: `--surface-overlay` to `--surface-raised`. Those are equal on
1291 the platform default and a theme may set them apart. */
1292 .card {
1293 padding: var(--gap-pane);
1294 margin-bottom: var(--gap-section);
1295 text-decoration: none;
1296 color: var(--content);
1297 display: block;
1298 border: 1px solid var(--border);
1299 transition: background 0.2s ease;
1300 }
1301
1302 /* Muted card primitive. */
1303 .card-muted {
1304 background: var(--surface-sunken);
1305 padding: var(--gap-pane);
1306 }
1307 /* Bordered card. Templates put this modifier on the element alongside any
1308 component class of their own (.tier-card, .use-case-card, .fork-card), so
1309 the name the charter teaches is the name in the markup. */
1310 .card--bordered {
1311 background: transparent;
1312 padding: var(--gap-section);
1313 border: 1px solid var(--border);
1314 margin-bottom: 0;
1315 }
1316 .use-case-card { padding: var(--gap-section); }
1317 .fork-card { padding: var(--gap-pane); display: flex; flex-direction: column; }
1318
1319 .card-title {
1320 font-size: var(--text-subhead);
1321 margin-bottom: var(--gap-peer);
1322 font-family: var(--font-display);
1323 }
1324
1325 .card-meta {
1326 font-size: var(--text-note);
1327 color: var(--content-muted);
1328 margin-bottom: var(--gap-peer);
1329 font-family: var(--font-mono);
1330 }
1331
1332 .card-description {
1333 font-size: var(--text-note);
1334 opacity: 0.8;
1335 font-family: var(--font-sans);
1336 }
1337
1338 /* ===========================================
1339 STATS
1340 =========================================== */
1341
1342 .stats-grid {
1343 display: grid;
1344 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1345 gap: var(--gap-section);
1346 margin-bottom: var(--gap-pane);
1347 }
1348
1349 .stats-row {
1350 display: flex;
1351 gap: var(--gap-pane);
1352 margin: var(--gap-pane) 0;
1353 }
1354
1355 .stat-box {
1356 background: var(--surface-overlay);
1357 padding: var(--gap-section) var(--gap-pane);
1358 flex: 1;
1359 text-align: center;
1360 }
1361
1362 .stat-box .number {
1363 font-family: var(--font-display);
1364 font-weight: bold;
1365 font-size: var(--text-head);
1366 }
1367
1368 .stat-box .label {
1369 font-size: var(--text-fine);
1370 opacity: 0.7;
1371 }
1372
1373 /* .stat-card base recipe defined above with .card-muted */
1374
1375 .stat-label {
1376 font-size: var(--text-note);
1377 opacity: 0.7;
1378 margin-bottom: var(--gap-bound);
1379 }
1380
1381 .stat-value {
1382 font-size: var(--text-head);
1383 }
1384
1385 .stat-change {
1386 font-size: var(--text-note);
1387 opacity: 0.6;
1388 }
1389
1390 .stat-change.positive {
1391 color: var(--success);
1392 }
1393
1394 .stat-change.negative {
1395 color: var(--danger);
1396 }
1397
1398 /* ===========================================
1399 FILTER BAR
1400 =========================================== */
1401
1402 .filter-bar {
1403 display: flex;
1404 gap: var(--gap-peer);
1405 margin: var(--gap-pane) 0;
1406 flex-wrap: wrap;
1407 align-items: center;
1408 }
1409
1410 .filter-bar > .label {
1411 font-size: var(--text-note);
1412 opacity: 0.7;
1413 margin-right: var(--gap-peer);
1414 }
1415
1416 .filter-bar button {
1417 padding: var(--gap-bound) var(--gap-section);
1418 font-size: var(--text-note);
1419 }
1420
1421 /* ===========================================
1422 DISCUSSION SECTION
1423 =========================================== */
1424
1425 /* Sits inside the parent container; previously had max-width: 720px and
1426 margin: 2rem auto, which looked indented on wide pages (.library-page,
1427 .item-page) where every neighbouring card filled the container. Let the
1428 parent dictate width, article-page already constrains via
1429 .article-container; player pages have their own narrow layout. */
1430 .discussion-section {
1431 margin: var(--gap-page) 0;
1432 padding: var(--gap-pane);
1433 border: 1px solid var(--border);
1434 font-family: var(--font-mono);
1435 }
1436
1437 .discussion-section h3 {
1438 font-family: var(--font-display);
1439 font-size: var(--text-subhead);
1440 margin: 0 0 var(--gap-peer);
1441 }
1442
1443 .discussion-section p {
1444 margin: 0 0 var(--gap-section);
1445 color: var(--content-muted);
1446 font-size: var(--text-note);
1447 }
1448
1449 .discussion-section a {
1450 color: var(--action);
1451 text-decoration: none;
1452 font-size: var(--text-note);
1453 }
1454
1455 /* ===========================================
1456 NOTIFICATION SURFACES (info-box, warning-box, error-message)
1457 =========================================== */
1458
1459 /* Notification surface roles (charter: docs/design-system.md).
1460 Five distinct components. Keep them distinct, don't merge:
1461 .toast: transient, JS-dismissible, bottom-right corner.
1462 .banner: full-bleed page-top notice (sandbox, restart,
1463 founder pricing). One short sentence + optional link.
1464 .alert: inline directive callout with uppercase mono title
1465 (NOTE / TIP / IMPORTANT / WARNING / CAUTION). Used in
1466 docs and longer-form bodies.
1467 .info-box: informational headed block with h3 + bullet list
1468 inside forms / wizards (distinct register from .alert).
1469 .warning-box: loud yellow-on-yellow attention-grabber for
1470 page-level warnings (delete confirms, account
1471 warnings). Louder than .alert-warning intentionally.
1472 .error-message: inline form-field error; hidden by default, shown
1473 with .is-active. Distinct from page-level .alert. */
1474 .info-box {
1475 background: var(--surface-sunken);
1476 padding: var(--gap-section);
1477 margin-bottom: var(--gap-section);
1478 font-size: var(--text-note);
1479 font-family: var(--font-sans);
1480 }
1481
1482 .info-box h3 {
1483 font-size: var(--text-body);
1484 font-weight: normal;
1485 margin-bottom: var(--gap-peer);
1486 }
1487
1488 .info-box ul {
1489 list-style: none;
1490 margin-top: var(--gap-peer);
1491 }
1492
1493 .info-box li {
1494 padding: var(--gap-bound) 0;
1495 }
1496
1497 .info-box li::before {
1498 content: "- ";
1499 opacity: 0.5;
1500 }
1501
1502 .warning-box {
1503 background: var(--warning);
1504 color: var(--primary-light);
1505 padding: var(--gap-section);
1506 margin-bottom: var(--gap-section);
1507 font-size: var(--text-note);
1508 font-family: var(--font-sans);
1509 }
1510
1511 .error-message {
1512 font-family: var(--font-mono);
1513 color: var(--danger);
1514 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
1515 padding: 12px;
1516 border: 1px solid var(--danger);
1517 border-radius: var(--radius-fine);
1518 text-align: center;
1519 font-size: var(--text-note);
1520 display: none;
1521 }
1522
1523 /* ===========================================
1524 ERROR PAGE
1525 =========================================== */
1526
1527 /* Full-page error layout (templates/pages/error.html). Distinct from
1528 the inline `.error-message` form-error class above; named to avoid
1529 collision. */
1530 .error-page {
1531 min-height: 100vh;
1532 display: flex;
1533 align-items: center;
1534 justify-content: center;
1535 padding: var(--gap-page);
1536 background: var(--surface-page);
1537 }
1538
1539 .error-container {
1540 text-align: center;
1541 max-width: 560px;
1542 }
1543
1544 .error-status {
1545 font-size: var(--text-note);
1546 font-weight: 600;
1547 letter-spacing: 0.08em;
1548 text-transform: uppercase;
1549 color: var(--content-muted);
1550 margin-bottom: var(--gap-section);
1551 }
1552
1553 .error-page-message {
1554 font-size: var(--text-head);
1555 line-height: 1.4;
1556 color: var(--content);
1557 margin-bottom: var(--gap-page);
1558 }
1559
1560 .error-actions {
1561 display: flex;
1562 gap: var(--gap-section);
1563 justify-content: center;
1564 }
1565
1566
1567 /* ===========================================
1568 BUY PAGE (standalone direct purchase)
1569 =========================================== */
1570
1571 /* Minimal direct-purchase page (templates/pages/buy.html). Standalone
1572 page used for link-in-bio sharing, no site chrome, just a card.
1573 Scoped under .buy-page so the rules don't bleed into the rest of
1574 the app. Tokenized; previously lived as a page-isolated <style>. */
1575 .buy-page {
1576 min-height: 100vh;
1577 display: flex;
1578 align-items: center;
1579 justify-content: center;
1580 padding: var(--gap-page);
1581 }
1582
1583 /* The one former --shadow-3 site that is not a float. The card is the whole
1584 page's content rather than something laid over it, so it takes .raised
1585 from layout.css for its surface and its edge, and keeps only the geometry
1586 the generated primitive does not carry. */
1587 .buy-page .buy-card {
1588 border-radius: var(--radius-panel);
1589 padding: var(--gap-page);
1590 max-width: 420px;
1591 width: 100%;
1592 }
1593
1594 .buy-page .cover,
1595 .buy-page .no-cover {
1596 width: 100%;
1597 aspect-ratio: 1;
1598 max-height: 280px;
1599 border-radius: var(--radius-panel);
1600 margin-bottom: var(--gap-pane);
1601 background: var(--surface-raised);
1602 }
1603
1604 .buy-page .cover {
1605 object-fit: cover;
1606 }
1607
1608 .buy-page .no-cover {
1609 display: flex;
1610 align-items: center;
1611 justify-content: center;
1612 font-size: var(--text-hero);
1613 opacity: 0.2;
1614 }
1615
1616 .buy-page h1 {
1617 text-align: left;
1618 font-size: var(--text-head);
1619 margin-bottom: var(--gap-bound);
1620 }
1621
1622 .buy-page .creator {
1623 font-family: var(--font-mono);
1624 font-size: var(--text-note);
1625 opacity: 0.6;
1626 margin-bottom: var(--gap-pane);
1627 }
1628
1629 .buy-page .creator a {
1630 color: inherit;
1631 }
1632
1633 .buy-page .price {
1634 font-family: var(--font-mono);
1635 font-size: var(--text-subhead);
1636 margin-bottom: var(--gap-pane);
1637 }
1638
1639 .buy-page .description {
1640 font-size: var(--text-note);
1641 line-height: 1.5;
1642 opacity: 0.8;
1643 margin-bottom: var(--gap-pane);
1644 max-height: 4.5em;
1645 overflow: hidden;
1646 }
1647
1648 .buy-page .buy-btn {
1649 display: block;
1650 width: 100%;
1651 padding: 14px;
1652 background: var(--action);
1653 color: var(--primary-light);
1654 border: none;
1655 border-radius: var(--radius-panel);
1656 font-size: var(--text-body);
1657 font-weight: 600;
1658 cursor: pointer;
1659 text-align: center;
1660 text-decoration: none;
1661 transition: opacity 0.2s ease;
1662 }
1663
1664 .buy-page .buy-btn:hover {
1665 opacity: 0.85;
1666 }
1667
1668 .buy-page .buy-btn:disabled {
1669 opacity: 0.6;
1670 cursor: wait;
1671 }
1672
1673 .buy-page .pwyw-input {
1674 display: flex;
1675 align-items: center;
1676 gap: var(--gap-bound);
1677 margin-bottom: var(--gap-section);
1678 }
1679
1680 .buy-page .pwyw-input input {
1681 width: 100px;
1682 padding: 8px;
1683 border: 1px solid var(--border);
1684 border-radius: var(--radius-control);
1685 font-size: var(--text-body);
1686 }
1687
1688 .buy-page .note {
1689 font-size: var(--text-fine);
1690 opacity: 0.5;
1691 text-align: center;
1692 margin-top: var(--gap-section);
1693 }
1694
1695 .buy-page .note a {
1696 color: var(--action);
1697 }
1698
1699 .buy-page .footer {
1700 text-align: center;
1701 margin-top: var(--gap-pane);
1702 padding-top: var(--gap-section);
1703 border-top: 1px solid var(--border);
1704 font-size: var(--text-fine);
1705 opacity: 0.5;
1706 }
1707
1708 .buy-page .footer a {
1709 color: inherit;
1710 }
1711
1712 /* ===========================================
1713 SECTIONED CONTENT (tabs + panels, shared by three pages)
1714 =========================================== */
1715
1716 /* Sectioned content (templates/pages/item.html, project.html,
1717 library_downloads.html). Tab+panel pattern shared by all three
1718 pages, kept global, NOT scoped under .item-page. */
1719 .section-tabs {
1720 display: flex;
1721 gap: 0;
1722 border-bottom: 1px solid var(--border);
1723 margin-bottom: var(--gap-pane);
1724 }
1725
1726 .section-tab {
1727 background: none;
1728 border: none;
1729 padding: var(--gap-group) var(--gap-section);
1730 cursor: pointer;
1731 font-size: var(--text-body);
1732 opacity: 0.6;
1733 border-bottom: 2px solid transparent;
1734 font-family: var(--font-sans);
1735 color: var(--content);
1736 }
1737
1738 .section-tab.is-selected {
1739 opacity: 1;
1740 border-bottom-color: var(--content);
1741 }
1742
1743 .section-tab:hover { opacity: 0.9; }
1744
1745 /* .section-panel.active is a visibility toggle (display: none / block),
1746 not a selection, kept as .active. */
1747 .section-panel { display: none; }
1748 .section-panel.active { display: block; }
1749 .section-panel p { margin-bottom: var(--gap-section); }
1750 .section-panel ul,
1751 .section-panel ol {
1752 margin-left: var(--gap-pane);
1753 margin-bottom: var(--gap-section);
1754 }
1755 .section-panel li { margin-bottom: var(--gap-peer); }
1756 .section-panel h1,
1757 .section-panel h2,
1758 .section-panel h3 {
1759 margin-top: var(--gap-section);
1760 margin-bottom: var(--gap-peer);
1761 }
1762 .section-panel pre {
1763 background: var(--surface-sunken);
1764 padding: var(--gap-section);
1765 overflow-x: auto;
1766 margin-bottom: var(--gap-section);
1767 }
1768 .section-panel code { font-size: var(--text-note); }
1769
1770 /* ===========================================
1771 ITEM PAGE
1772 =========================================== */
1773
1774 /* Item detail page (templates/pages/item.html). Scoped under .item-page
1775 body class so the .item-title / .item-meta / .item-price / .item-footer
1776 names don't collide with the store-front item-card on project.html. */
1777 .item-page .item-layout {
1778 display: grid;
1779 grid-template-columns: 400px 1fr;
1780 gap: var(--gap-page);
1781 margin-bottom: var(--gap-page);
1782 }
1783
1784 .item-page .item-layout.no-media { grid-template-columns: 1fr; }
1785
1786 .item-page .item-title {
1787 font-size: var(--text-title);
1788 margin-bottom: var(--gap-peer);
1789 }
1790
1791 .item-page .item-creator {
1792 font-size: var(--text-body);
1793 opacity: 0.7;
1794 margin-bottom: var(--gap-pane);
1795 }
1796
1797 .item-page .item-creator a { color: var(--content); }
1798
1799 .item-page .item-price {
1800 font-size: var(--text-display);
1801 margin-bottom: var(--gap-pane);
1802 }
1803
1804 .item-page .item-meta {
1805 display: grid;
1806 grid-template-columns: 1fr 1fr;
1807 gap: var(--gap-section);
1808 margin-bottom: var(--gap-pane);
1809 padding-bottom: var(--gap-pane);
1810 border-bottom: 1px solid var(--border);
1811 }
1812
1813 .item-page .meta-item { font-size: var(--text-note); }
1814 .item-page .meta-label { opacity: 0.7; margin-bottom: var(--gap-bound); }
1815 .item-page .meta-value { font-weight: bold; }
1816
1817 .item-page .item-tags { margin-bottom: var(--gap-pane); }
1818
1819 /* purchase-box uses .card-muted for the box; only its h3/ul/li specifics remain. */
1820 .purchase-box h3 {
1821 font-size: var(--text-body);
1822 font-weight: normal;
1823 margin-bottom: var(--gap-section);
1824 }
1825 .purchase-box ul { list-style: none; margin-bottom: var(--gap-section); }
1826 .purchase-box li { padding: var(--gap-bound) 0; }
1827 .purchase-box li::before {
1828 content: "";
1829 opacity: 0.5;
1830 }
1831
1832 .item-page .btn-primary {
1833 width: 100%;
1834 padding: var(--gap-section) var(--gap-page);
1835 font-size: var(--text-lead);
1836 }
1837
1838 .item-page .btn-secondary {
1839 width: 100%;
1840 margin-top: var(--gap-peer);
1841 }
1842
1843 .item-page .payment-note {
1844 font-size: var(--text-note);
1845 opacity: 0.6;
1846 text-align: center;
1847 margin-top: var(--gap-section);
1848 }
1849
1850 /* bundle-child uses .list-row co-class; only the gap-4 override remains. */
1851 .bundle-child { gap: var(--gap-section); padding: var(--gap-section) 0; }
1852
1853 .item-page .bundle-child-type {
1854 font-size: var(--text-fine);
1855 padding: var(--gap-bound) var(--gap-group);
1856 background: var(--surface-sunken);
1857 white-space: nowrap;
1858 }
1859
1860 .item-page .bundle-child-title { flex: 1; }
1861 .item-page .bundle-child-title a { color: var(--content); }
1862 .item-page .bundle-child-price { font-size: var(--text-note); opacity: 0.7; }
1863
1864 .item-page .bundle-notice {
1865 background: var(--surface-sunken);
1866 padding: var(--gap-pane);
1867 margin-bottom: var(--gap-pane);
1868 text-align: center;
1869 }
1870
1871 .item-page .bundle-notice a { color: var(--content); }
1872
1873 .item-page .item-description p { margin-bottom: var(--gap-section); text-align: left; }
1874 .item-page .item-description ul { margin-left: var(--gap-pane); margin-bottom: var(--gap-section); }
1875 .item-page .item-description li { margin-bottom: var(--gap-peer); }
1876
1877 .item-page .features-grid {
1878 display: grid;
1879 grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
1880 gap: var(--gap-pane);
1881 }
1882 .item-page .version-number { font-size: var(--text-lead); }
1883 .item-page .item-footer {
1884 margin-top: var(--gap-page);
1885 padding-top: var(--gap-page);
1886 border-top: 1px solid var(--border);
1887 text-align: center;
1888 opacity: 0.6;
1889 font-size: var(--text-note);
1890 }
1891
1892 .item-page .item-footer a { color: var(--content); }
1893
1894 @media (max-width: 839px) {
1895 .item-page .item-layout { grid-template-columns: 1fr; }
1896 }
1897
1898 @media (max-width: 599px) {
1899 .item-page .item-title { font-size: var(--text-head); }
1900 .item-page .item-price { font-size: var(--text-title); }
1901 .item-page .item-meta { grid-template-columns: 1fr; gap: var(--gap-peer); }
1902 .item-page .item-media,
1903 .item-page .item-details { padding: var(--gap-section); }
1904 .item-page .item-description { padding: var(--gap-section); }
1905 .item-page .item-description h2 { font-size: var(--text-subhead); }
1906 .item-page .purchase-box { padding: var(--gap-section); }
1907 }
1908
1909 /* ===========================================
1910 PROJECT PAGE (store front)
1911 =========================================== */
1912
1913 /* Project store-front page (templates/pages/project.html). Scoped under
1914 .project-page body class, `.item-card`, `.item-title`, `.item-meta`,
1915 `.item-price`, etc. here mean store-front item cards, distinct from
1916 the item-detail-page versions scoped under .item-page above. */
1917 .project-page .store-header { margin-bottom: var(--gap-page); }
1918 .project-page .store-title { font-size: var(--text-hero); margin-bottom: var(--gap-peer); }
1919 .project-page .store-meta {
1920 font-size: var(--text-note);
1921 opacity: 0.7;
1922 margin-bottom: var(--gap-pane);
1923 }
1924 .project-page .store-meta a { color: var(--content); }
1925 .project-page .store-description {
1926 font-size: var(--text-lead);
1927 max-width: 800px;
1928 margin-bottom: var(--gap-page);
1929 text-align: left;
1930 }
1931 .project-page .store-actions {
1932 display: flex;
1933 gap: var(--gap-section);
1934 flex-wrap: wrap;
1935 }
1936
1937 .project-page .items-section { margin-bottom: var(--gap-page); }
1938
1939 .project-page .items-grid {
1940 display: grid;
1941 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1942 gap: var(--gap-page);
1943 }
1944
1945 .project-page .item-card {
1946 background: var(--surface-overlay);
1947 transition: background 0.2s ease;
1948 cursor: pointer;
1949 display: flex;
1950 flex-direction: column;
1951 height: 100%;
1952 }
1953
1954 .project-page .item-card:hover { background: var(--surface-sunken); }
1955
1956 .project-page a.item-thumbnail {
1957 width: 100%;
1958 height: 300px;
1959 background: var(--border);
1960 display: flex;
1961 align-items: center;
1962 justify-content: center;
1963 font-size: var(--text-hero);
1964 text-decoration: none;
1965 overflow: hidden;
1966 }
1967
1968 .project-page a.item-thumbnail img {
1969 width: 100%;
1970 height: 100%;
1971 object-fit: cover;
1972 display: block;
1973 }
1974
1975 /* The dimming belongs to the placeholder, not to the slot. It was on the slot,
1976 so the day covers started rendering here they would have rendered at 30%. */
1977 .project-page a.item-thumbnail-empty { opacity: 0.3; }
1978 .project-page a.item-thumbnail-empty:hover { opacity: 0.5; }
1979
1980 .project-page .item-content {
1981 padding: var(--gap-pane);
1982 display: flex;
1983 flex-direction: column;
1984 flex-grow: 1;
1985 }
1986
1987 .project-page .item-title {
1988 font-size: var(--text-subhead);
1989 margin-bottom: var(--gap-peer);
1990 font-family: var(--font-display);
1991 font-weight: bold;
1992 }
1993
1994 .project-page .item-title a:hover { text-decoration: underline; }
1995
1996 .project-page .item-meta {
1997 font-size: var(--text-note);
1998 opacity: 0.7;
1999 margin-bottom: var(--gap-section);
2000 }
2001
2002 .project-page .item-tags { margin-bottom: var(--gap-section); }
2003
2004 .project-page .item-description {
2005 font-size: var(--text-note);
2006 margin-bottom: var(--gap-section);
2007 opacity: 0.8;
2008 text-align: left;
2009 flex-grow: 1;
2010 }
2011
2012 .project-page .item-footer {
2013 display: flex;
2014 justify-content: space-between;
2015 align-items: center;
2016 margin-top: auto;
2017 }
2018
2019 .project-page .item-price { font-size: var(--text-subhead); }
2020 .project-page .item-stats { font-size: var(--text-note); opacity: 0.6; }
2021
2022 .project-page .store-footer {
2023 margin-top: var(--gap-page);
2024 padding-top: var(--gap-page);
2025 border-top: 1px solid var(--border);
2026 opacity: 0.6;
2027 }
2028
2029 .project-page .store-footer a { color: var(--content); }
2030
2031 .project-page .item-content .btn-primary,
2032 .project-page .item-content .btn-secondary {
2033 width: 100%;
2034 margin-top: var(--gap-section);
2035 }
2036
2037 /* project.html allows .section-tabs to wrap (other pages don't). */
2038 .project-page .section-tabs { flex-wrap: wrap; }
2039
2040 .project-page .tiers-section { margin-bottom: var(--gap-page); }
2041
2042 .project-page .tiers-grid {
2043 display: grid;
2044 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
2045 gap: var(--gap-pane);
2046 }
2047
2048 .project-page .tier-card {
2049 background: var(--surface-overlay);
2050 padding: var(--gap-page);
2051 display: flex;
2052 flex-direction: column;
2053 }
2054
2055 .project-page .tier-name {
2056 font-family: var(--font-display);
2057 font-weight: bold;
2058 font-size: var(--text-subhead);
2059 margin-bottom: var(--gap-peer);
2060 }
2061
2062 .project-page .tier-price {
2063 font-size: var(--text-head);
2064 margin-bottom: var(--gap-section);
2065 }
2066
2067 .project-page .tier-description {
2068 font-size: var(--text-note);
2069 opacity: 0.8;
2070 margin-bottom: var(--gap-pane);
2071 flex-grow: 1;
2072 text-align: left;
2073 }
2074
2075 .project-page .tier-card form { margin-top: auto; }
2076 .project-page .tier-card button,
2077 .project-page .tier-card .btn-primary { width: 100%; }
2078
2079 .project-page .tier-active-badge {
2080 font-size: var(--text-note);
2081 padding: var(--gap-peer) var(--gap-section);
2082 background: var(--surface-sunken);
2083 text-align: center;
2084 opacity: 0.7;
2085 }
2086
2087 @media (max-width: 599px) {
2088 .project-page .store-title { font-size: var(--text-title); }
2089 .project-page a.item-thumbnail { height: 200px; }
2090 .project-page .store-description { font-size: var(--text-body); }
2091 }
2092
2093 /* ===========================================
2094 LIBRARY DOWNLOADS PAGE
2095 =========================================== */
2096
2097 /* Library downloads page (templates/pages/library_downloads.html).
2098 Scoped under .library-page body class. Class names like .item-footer,
2099 .bundle-child, etc. overlap with .item-page; the scope keeps them
2100 distinct. */
2101 .library-page .library-header {
2102 display: grid;
2103 grid-template-columns: 200px 1fr;
2104 gap: var(--gap-pane);
2105 margin-bottom: var(--gap-page);
2106 align-items: center;
2107 }
2108
2109 .library-page .library-header.no-cover { grid-template-columns: 1fr; }
2110
2111 .library-page .library-cover img {
2112 width: 100%;
2113 aspect-ratio: 1 / 1;
2114 object-fit: cover;
2115 display: block;
2116 }
2117
2118 .library-page .library-title {
2119 font-size: var(--text-title);
2120 margin-bottom: var(--gap-bound);
2121 }
2122
2123 /* Byline sits under the centered H1 (global `h1 { text-align: center }`),
2124 so center it too, otherwise it floats flush-left while the title is
2125 centered above. Same for the back-link breadcrumb. When a cover is
2126 present the grid scope below restores left-alignment in the text column. */
2127 .library-page .library-creator {
2128 font-size: var(--text-body);
2129 opacity: 0.7;
2130 margin-bottom: var(--gap-section);
2131 text-align: center;
2132 }
2133
2134 .library-page .library-creator a { color: var(--content); }
2135
2136 .library-page .library-back {
2137 font-size: var(--text-note);
2138 opacity: 0.7;
2139 text-align: center;
2140 }
2141 .library-page .library-back a { color: var(--content); }
2142
2143 /* When a cover is present, the title block sits in the right grid column;
2144 left-align in that context so the column reads as a coherent block next
2145 to the cover instead of a centered island. */
2146 .library-page .library-header:not(.no-cover) .library-title,
2147 .library-page .library-header:not(.no-cover) .library-creator {
2148 text-align: left;
2149 }
2150
2151 .library-page .downloads-hero h2 {
2152 font-size: var(--text-head);
2153 margin-bottom: var(--gap-section);
2154 padding-bottom: var(--gap-peer);
2155 border-bottom: 1px solid var(--border);
2156 }
2157
2158 .library-page .download-row {
2159 display: flex;
2160 align-items: center;
2161 gap: var(--gap-section);
2162 padding: var(--gap-group) 0;
2163 border-bottom: 1px solid var(--border);
2164 }
2165
2166 .library-page .download-row:last-child { border-bottom: none; }
2167
2168 .library-page .download-version {
2169 min-width: 4em;
2170 font-family: var(--font-mono);
2171 font-size: var(--text-note);
2172 }
2173
2174 .library-page .download-label { flex: 1; }
2175
2176 .library-page .download-size {
2177 font-size: var(--text-note);
2178 opacity: 0.6;
2179 min-width: 5em;
2180 text-align: right;
2181 }
2182
2183 .library-page .download-notice {
2184 background: var(--surface-sunken);
2185 padding: var(--gap-pane);
2186 margin-bottom: var(--gap-page);
2187 font-size: var(--text-note);
2188 opacity: 0.8;
2189 }
2190
2191 .library-page .download-notice strong { font-family: var(--font-mono); }
2192
2193 /* Inline download list on library_audio.html / library_video.html
2194 (below the media player). */
2195
2196 .library-page .library-downloads h3 {
2197 font-size: var(--text-lead);
2198 margin-bottom: var(--gap-section);
2199 }
2200
2201 .library-page .library-section h2 {
2202 font-size: var(--text-subhead);
2203 margin-bottom: var(--gap-section);
2204 padding-bottom: var(--gap-peer);
2205 border-bottom: 1px solid var(--border);
2206 }
2207
2208 .library-page .bundle-child {
2209 display: flex;
2210 align-items: center;
2211 gap: var(--gap-section);
2212 padding: var(--gap-section) 0;
2213 border-bottom: 1px solid var(--border);
2214 }
2215
2216 .library-page .bundle-child:last-child { border-bottom: none; }
2217
2218 .library-page .bundle-child-type {
2219 font-size: var(--text-fine);
2220 padding: var(--gap-bound) var(--gap-group);
2221 background: var(--surface-sunken);
2222 white-space: nowrap;
2223 }
2224
2225 .library-page .bundle-child-title { flex: 1; }
2226 .library-page .bundle-child-title a { color: var(--content); }
2227
2228 .library-page .item-footer {
2229 margin-top: var(--gap-page);
2230 padding-top: var(--gap-page);
2231 border-top: 1px solid var(--border);
2232 text-align: center;
2233 opacity: 0.6;
2234 font-size: var(--text-note);
2235 }
2236
2237 .library-page .item-footer a { color: var(--content); }
2238
2239 @media (max-width: 599px) {
2240 .library-page .library-header { grid-template-columns: 1fr; }
2241 .library-page .library-cover img { max-width: 200px; }
2242 }
2243
2244 /* ===========================================
2245 ARTICLE & BLOG READER
2246 =========================================== */
2247
2248 /* Article reader pages (templates/pages/text_reader.html, blog_post.html,
2249 library_text.html). Long-form article layout, centered narrow column,
2250 author header, large heading, magazine-style body type. Scoped under
2251 .article-page body class. The container itself takes the shared reading
2252 column near the top of this file. */
2253
2254 .article-page .author-header {
2255 display: flex;
2256 align-items: center;
2257 gap: var(--gap-section);
2258 margin-bottom: var(--gap-page);
2259 }
2260
2261 .article-page .author-avatar {
2262 width: 48px;
2263 height: 48px;
2264 background: var(--surface-sunken);
2265 border-radius: var(--radius-round);
2266 display: flex;
2267 align-items: center;
2268 justify-content: center;
2269 font-family: var(--font-display);
2270 font-size: var(--text-subhead);
2271 color: var(--content);
2272 }
2273
2274 .article-page .author-info { flex: 1; }
2275
2276 .article-page .author-name {
2277 font-family: var(--font-mono);
2278 font-size: var(--text-body);
2279 color: var(--content);
2280 }
2281
2282
2283 .article-page .author-name a:hover { text-decoration: underline; }
2284
2285 .article-page .article-meta {
2286 font-family: var(--font-mono);
2287 font-size: var(--text-note);
2288 color: var(--content-muted);
2289 }
2290
2291 .article-page .article-title {
2292 font-family: var(--font-display);
2293 font-size: var(--text-display);
2294 font-weight: normal;
2295 line-height: 1.2;
2296 margin-bottom: var(--gap-pane);
2297 color: var(--content);
2298 text-align: left;
2299 }
2300
2301 .article-page .article-deck {
2302 font-family: var(--font-mono);
2303 font-size: var(--text-subhead);
2304 color: var(--content-muted);
2305 margin-bottom: var(--gap-page);
2306 line-height: 1.6;
2307 }
2308
2309 .article-page .article-body {
2310 font-family: var(--font-sans);
2311 font-size: var(--text-lead);
2312 line-height: 1.9;
2313 }
2314
2315 .article-page .article-body p { margin-bottom: var(--gap-pane); }
2316
2317 .article-page .article-body h1 {
2318 font-family: var(--font-display);
2319 font-size: var(--text-title);
2320 font-weight: normal;
2321 margin-top: var(--gap-page);
2322 margin-bottom: var(--gap-section);
2323 color: var(--content);
2324 text-align: left;
2325 }
2326
2327 .article-page .article-body h2 {
2328 font-family: var(--font-mono);
2329 font-size: var(--text-head);
2330 font-weight: normal;
2331 margin-top: var(--gap-page);
2332 margin-bottom: var(--gap-section);
2333 color: var(--content);
2334 }
2335
2336 .article-page .article-body h3 {
2337 font-family: var(--font-mono);
2338 font-size: var(--text-subhead);
2339 font-weight: normal;
2340 margin-top: var(--gap-page);
2341 margin-bottom: var(--gap-section);
2342 color: var(--content);
2343 }
2344
2345 .article-page .article-body blockquote {
2346 border-left: 3px solid var(--action);
2347 padding-left: var(--gap-pane);
2348 margin: var(--gap-page) 0;
2349 font-style: italic;
2350 color: var(--content-muted);
2351 }
2352
2353 .article-page .article-body ul,
2354 .article-page .article-body ol {
2355 margin-bottom: var(--gap-pane);
2356 padding-left: var(--gap-pane);
2357 }
2358
2359 .article-page .article-body li { margin-bottom: var(--gap-peer); }
2360
2361 .article-page .article-body a {
2362 color: var(--action);
2363 text-decoration: underline;
2364 text-decoration-color: color-mix(in oklch, var(--action) 40%, transparent);
2365 text-underline-offset: 2px;
2366 }
2367
2368 .article-page .article-body a:hover {
2369 text-decoration-color: var(--action);
2370 }
2371
2372 .article-page .article-body strong { font-weight: bold; }
2373 .article-page .article-body em { font-style: italic; }
2374
2375 .article-page .article-body code {
2376 font-family: var(--font-mono);
2377 background: var(--surface-sunken);
2378 padding: var(--gap-bound) var(--gap-peer);
2379 font-size: var(--text-body);
2380 }
2381
2382 .article-page .article-body pre {
2383 background: var(--surface-sunken);
2384 padding: var(--gap-section);
2385 overflow-x: auto;
2386 margin-bottom: var(--gap-pane);
2387 }
2388
2389 .article-page .article-body pre code {
2390 background: none;
2391 padding: 0;
2392 }
2393
2394 .article-page .article-body hr {
2395 border: none;
2396 text-align: center;
2397 margin: var(--gap-page) 0;
2398 }
2399
2400 .article-page .article-body hr::before {
2401 content: "* * *";
2402 color: var(--content-muted);
2403 letter-spacing: 1rem;
2404 }
2405
2406 .article-page .article-footer {
2407 margin-top: var(--gap-page);
2408 padding-top: var(--gap-page);
2409 border-top: 1px solid var(--border);
2410 }
2411
2412 .article-page .author-bio {
2413 display: flex;
2414 gap: var(--gap-section);
2415 padding: var(--gap-pane);
2416 background: var(--surface-overlay);
2417 }
2418
2419 .article-page .author-bio .author-avatar {
2420 width: 64px;
2421 height: 64px;
2422 font-size: var(--text-head);
2423 flex-shrink: 0;
2424 }
2425
2426 .article-page .author-bio-content { flex: 1; }
2427
2428 .article-page .author-bio-name {
2429 font-family: var(--font-mono);
2430 font-size: var(--text-lead);
2431 margin-bottom: var(--gap-peer);
2432 }
2433
2434
2435 .article-page .author-bio-name a:hover { text-decoration: underline; }
2436
2437 .article-page .article-tags {
2438 margin-top: var(--gap-page);
2439 display: flex;
2440 gap: var(--gap-peer);
2441 flex-wrap: wrap;
2442 }
2443
2444 .article-page .article-tag {
2445 font-family: var(--font-mono);
2446 background: var(--surface-sunken);
2447 color: var(--content);
2448 padding: var(--gap-peer) var(--gap-section);
2449 font-size: var(--text-fine);
2450 text-decoration: none;
2451 }
2452
2453 /* Share controls at the end of the article, inside .article-footer beside
2454 the tags or the blog crumb. Was a page-bottom footer below the comments
2455 until the site-footer consolidation; the control belongs with the piece
2456 it copies, not under the discussion. */
2457 .article-page .reader-actions {
2458 font-family: var(--font-mono);
2459 font-size: var(--text-note);
2460 margin-top: var(--gap-section);
2461 }
2462
2463 .article-page .reader-actions a { color: var(--content); text-decoration: none; }
2464 .article-page .reader-actions a:hover { text-decoration: underline; }
2465
2466 /* Blog-specific: nav crumb above the article. */
2467 .article-page .blog-nav {
2468 font-family: var(--font-mono);
2469 font-size: var(--text-note);
2470 margin-bottom: var(--gap-page);
2471 }
2472
2473 .article-page .blog-nav a { color: var(--content); text-decoration: none; }
2474 .article-page .blog-nav a:hover { text-decoration: underline; }
2475
2476 @media (max-width: 599px) {
2477 .article-page .article-title { font-size: var(--text-title); }
2478 .article-page .article-deck { font-size: var(--text-lead); }
2479 .article-page .article-body { font-size: var(--text-body); }
2480 }
2481
2482 /* ===========================================
2483 ADMIN & HEALTH DASHBOARDS
2484 =========================================== */
2485
2486 /* Admin dashboards (templates/dashboards/admin-*.html). Every admin
2487 page had a duplicate `h1 { font-size: 2rem; margin-bottom: 0.5rem; }`
2488 inline, consolidated here. */
2489
2490 /* System health dashboard (templates/pages/health.html). */
2491 .health-page {
2492 padding: var(--gap-page);
2493 }
2494
2495 .health-page .health-container {
2496 max-width: 900px;
2497 margin: 0 auto;
2498 }
2499
2500 .health-page .health-grid {
2501 display: grid;
2502 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
2503 gap: var(--gap-pane);
2504 margin-bottom: var(--gap-page);
2505 }
2506
2507 .health-page .health-card {
2508 background: var(--surface-sunken);
2509 padding: var(--gap-pane);
2510 }
2511
2512 .health-page .health-card h3 {
2513 font-family: var(--font-mono);
2514 font-size: var(--text-note);
2515 margin-bottom: var(--gap-section);
2516 display: flex;
2517 align-items: center;
2518 gap: var(--gap-peer);
2519 }
2520
2521 .health-page .status-indicator {
2522 width: 10px;
2523 height: 10px;
2524 border-radius: var(--radius-round);
2525 display: inline-block;
2526 }
2527
2528 .health-page .status-ok { background: var(--success); }
2529 .health-page .status-warn { background: var(--warning); }
2530 .health-page .status-error { background: var(--danger); }
2531 .health-page .status-unknown { background: var(--content-muted); }
2532
2533 .health-page .health-card dl {
2534 display: grid;
2535 grid-template-columns: auto 1fr;
2536 gap: var(--gap-peer) var(--gap-section);
2537 font-size: var(--text-note);
2538 }
2539
2540 .health-page .health-card dt { opacity: 0.7; }
2541 .health-page .health-card dd {
2542 font-family: var(--font-mono);
2543 text-align: right;
2544 }
2545
2546 .health-page .section-title {
2547 font-family: var(--font-mono);
2548 font-size: var(--text-body);
2549 margin: var(--gap-page) 0 var(--gap-section);
2550 padding-bottom: var(--gap-peer);
2551 border-bottom: 1px solid var(--border);
2552 }
2553 .health-page .test-list {
2554 list-style: none;
2555 padding: 0;
2556 margin: 0;
2557 }
2558
2559 .health-page .test-list li {
2560 display: flex;
2561 justify-content: space-between;
2562 padding: var(--gap-peer) 0;
2563 border-bottom: 1px solid var(--border);
2564 font-size: var(--text-note);
2565 }
2566
2567 .health-page .test-list li:last-child { border-bottom: none; }
2568
2569 .health-page .test-name { font-family: var(--font-mono); }
2570
2571 .health-page .test-pass { color: var(--success); }
2572 .health-page .test-fail { color: var(--danger); }
2573 .health-page .summary-bar {
2574 display: flex;
2575 gap: var(--gap-page);
2576 padding: var(--gap-section);
2577 background: var(--surface-sunken);
2578 margin-bottom: var(--gap-page);
2579 font-family: var(--font-mono);
2580 font-size: var(--text-note);
2581 }
2582
2583 .health-page .summary-item {
2584 display: flex;
2585 align-items: center;
2586 gap: var(--gap-peer);
2587 }
2588
2589 .health-page .timestamp {
2590 font-size: var(--text-fine);
2591 opacity: 0.6;
2592 text-align: right;
2593 margin-top: var(--gap-page);
2594 }
2595
2596 .health-page details.check-list {
2597 background: var(--surface-overlay);
2598 padding: var(--gap-pane);
2599 margin-bottom: var(--gap-page);
2600 }
2601
2602 .health-page details.check-list summary {
2603 font-family: var(--font-mono);
2604 font-size: var(--text-note);
2605 cursor: pointer;
2606 list-style: none;
2607 display: flex;
2608 align-items: center;
2609 gap: var(--gap-peer);
2610 }
2611
2612 .health-page details.check-list summary::-webkit-details-marker { display: none; }
2613
2614 .health-page details.check-list summary::before {
2615 content: "";
2616 font-size: var(--text-fine);
2617 transition: transform 0.15s;
2618 }
2619
2620 .health-page details.check-list[open] summary::before {
2621 transform: rotate(90deg);
2622 }
2623
2624 .health-page details.check-list .test-list {
2625 margin-top: var(--gap-section);
2626 }
2627 /* ===========================================
2628 IMPORT WIZARD
2629 =========================================== */
2630
2631 /* Import data wizard (templates/dashboards/dashboard-import.html).
2632 Page-specific upload + column-mapping + progress flow. Migration also
2633 fixed token bypasses: --border-color → --border, --accent → --highlight,
2634 --success-background → --success-bg, --error-background → --error-bg. */
2635
2636 .import-page .back-link {
2637 display: inline-block;
2638 margin-bottom: var(--gap-pane);
2639 font-size: var(--text-note);
2640 opacity: 0.7;
2641 }
2642
2643 .import-page .back-link:hover { opacity: 1; }
2644
2645 .import-page .form-group { margin-bottom: var(--gap-pane); }
2646
2647 .import-page .form-group label {
2648 display: block;
2649 font-family: var(--font-mono);
2650 font-size: var(--text-note);
2651 font-weight: bold;
2652 margin-bottom: var(--gap-peer);
2653 }
2654
2655 .import-page .form-group select,
2656 .import-page .form-group input[type="file"] {
2657 width: 100%;
2658 padding: var(--gap-peer);
2659 font-size: var(--text-note);
2660 border: 1px solid var(--border);
2661 background: var(--surface-page);
2662 }
2663
2664 .import-page .csv-preview {
2665 margin-top: var(--gap-section);
2666 overflow-x: auto;
2667 }
2668
2669 .import-page .csv-preview table {
2670 width: 100%;
2671 border-collapse: collapse;
2672 font-size: var(--text-note);
2673 }
2674
2675 .import-page .csv-preview th,
2676 .import-page .csv-preview td {
2677 padding: var(--gap-peer) var(--gap-group);
2678 border: 1px solid var(--border);
2679 text-align: left;
2680 }
2681
2682 .import-page .csv-preview th {
2683 background: var(--surface-sunken);
2684 font-family: var(--font-mono);
2685 }
2686
2687 .import-page .column-mapping { margin-top: var(--gap-section); }
2688
2689 .import-page .mapping-row {
2690 display: flex;
2691 align-items: center;
2692 gap: var(--gap-section);
2693 margin-bottom: var(--gap-peer);
2694 }
2695
2696 .import-page .mapping-row label {
2697 min-width: 100px;
2698 font-size: var(--text-note);
2699 font-family: var(--font-mono);
2700 margin-bottom: 0;
2701 }
2702
2703 .import-page .mapping-row select {
2704 flex: 1;
2705 padding: var(--gap-bound);
2706 font-size: var(--text-note);
2707 }
2708
2709 .import-page .progress-stats {
2710 display: flex;
2711 gap: var(--gap-page);
2712 font-size: var(--text-note);
2713 font-family: var(--font-mono);
2714 }
2715
2716 .import-page .progress-stats .stat-value { font-weight: bold; }
2717
2718 .import-page .import-result {
2719 padding: var(--gap-section);
2720 margin-bottom: var(--gap-section);
2721 font-size: var(--text-note);
2722 }
2723
2724 .import-page .import-result.success { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); }
2725 .import-page .import-result.error { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); }
2726
2727 .import-page .error-log {
2728 margin-top: var(--gap-section);
2729 padding: var(--gap-peer);
2730 background: var(--surface-sunken);
2731 font-family: var(--font-mono);
2732 font-size: var(--text-fine);
2733 max-height: 200px;
2734 overflow-y: auto;
2735 white-space: pre-wrap;
2736 }
2737
2738 .import-page .import-history { margin-top: var(--gap-page); }
2739
2740 .import-page .history-table {
2741 width: 100%;
2742 border-collapse: collapse;
2743 font-size: var(--text-note);
2744 }
2745
2746 .import-page .history-table th,
2747 .import-page .history-table td {
2748 padding: var(--gap-peer);
2749 border-bottom: 1px solid var(--border);
2750 text-align: left;
2751 }
2752
2753 .import-page .history-table th {
2754 font-family: var(--font-mono);
2755 font-weight: bold;
2756 }
2757
2758 .import-page .import-note {
2759 background: var(--surface-sunken);
2760 padding: var(--gap-section);
2761 margin-top: var(--gap-page);
2762 font-size: var(--text-note);
2763 }
2764
2765 .import-page .import-note h3 {
2766 font-family: var(--font-mono);
2767 font-size: var(--text-note);
2768 margin-bottom: var(--gap-peer);
2769 }
2770
2771 /* ===========================================
2772 DELETE ACCOUNT PAGE
2773 =========================================== */
2774
2775 /* Delete account page (templates/dashboards/dashboard-delete-account.html).
2776 Overrides .warning-box to use --danger (delete is danger, not warning)
2777 and the .form-status partial styling. Scoped to keep the global versions
2778 intact for other pages. */
2779
2780 .delete-account-page .warning-box {
2781 background: var(--danger);
2782 color: var(--primary-light);
2783 padding: var(--gap-pane);
2784 margin-bottom: var(--gap-page);
2785 }
2786
2787 .delete-account-page .warning-box h3 {
2788 font-family: var(--font-mono);
2789 font-size: var(--text-body);
2790 margin-bottom: var(--gap-peer);
2791 }
2792
2793 .delete-account-page .warning-box ul {
2794 margin: var(--gap-peer) 0 0 var(--gap-pane);
2795 font-size: var(--text-note);
2796 }
2797
2798 .delete-account-page .warning-box li { margin-bottom: var(--gap-bound); }
2799
2800 .delete-account-page .export-prompt {
2801 background: var(--surface-sunken);
2802 padding: var(--gap-pane);
2803 margin-bottom: var(--gap-page);
2804 }
2805
2806 .delete-account-page .export-prompt h3 {
2807 font-family: var(--font-mono);
2808 font-size: var(--text-body);
2809 margin-bottom: var(--gap-peer);
2810 }
2811
2812 .delete-account-page .export-prompt p {
2813 font-size: var(--text-note);
2814 margin-bottom: var(--gap-section);
2815 }
2816
2817 .delete-account-page .delete-form {
2818 background: var(--surface-overlay);
2819 padding: var(--gap-pane);
2820 }
2821
2822 .delete-account-page .delete-form .form-group { margin-bottom: var(--gap-pane); }
2823
2824 .delete-account-page .delete-form label {
2825 display: block;
2826 margin-bottom: var(--gap-peer);
2827 font-family: var(--font-mono);
2828 font-size: var(--text-note);
2829 }
2830
2831 .delete-account-page .delete-form .hint {
2832 font-size: var(--text-note);
2833 opacity: 0.7;
2834 margin-top: var(--gap-bound);
2835 }
2836
2837 .delete-account-page .delete-form input[type="text"] {
2838 width: 100%;
2839 padding: var(--gap-section);
2840 font-size: var(--text-body);
2841 border: 2px solid var(--border);
2842 background: var(--surface-sunken);
2843 }
2844
2845 .delete-account-page .delete-form input[type="text"]:focus {
2846 border-color: var(--danger);
2847 outline: none;
2848 }
2849
2850 .delete-account-page .back-link {
2851 display: inline-block;
2852 margin-bottom: var(--gap-pane);
2853 font-size: var(--text-note);
2854 opacity: 0.7;
2855 }
2856
2857 .delete-account-page .back-link:hover { opacity: 1; }
2858
2859 .delete-account-page .form-actions {
2860 display: flex;
2861 gap: var(--gap-section);
2862 align-items: center;
2863 }
2864
2865 .delete-account-page .form-status { font-size: var(--text-note); }
2866 .delete-account-page .form-status.error { color: var(--danger); }
2867 .delete-account-page .form-status.success { color: var(--success); }
2868
2869 /* ===========================================
2870 EXPORT PAGE
2871 =========================================== */
2872
2873 /* Export data page (templates/dashboards/dashboard-export.html).
2874 `.export-card` and its inner classes (`-info`, `-title`, `-desc`, `-meta`)
2875 are intentionally page-scoped: this is a row-layout card (info on left,
2876 action button on right, flex justify-between) that doesn't fit `.card-muted`
2877 cleanly. Background uses `--light-background` rather than `--surface-muted`
2878 for visual distinction from the surrounding dashboard surface. */
2879
2880 .export-page .export-cards {
2881 display: grid;
2882 gap: var(--gap-section);
2883 }
2884
2885 .export-page .export-card {
2886 background: var(--surface-overlay);
2887 padding: var(--gap-pane);
2888 display: flex;
2889 justify-content: space-between;
2890 align-items: flex-start;
2891 gap: var(--gap-section);
2892 }
2893
2894 .export-page .export-card-info { flex: 1; }
2895
2896 .export-page .export-card-title {
2897 font-family: var(--font-mono);
2898 font-weight: bold;
2899 font-size: var(--text-lead);
2900 margin-bottom: var(--gap-peer);
2901 }
2902
2903 .export-page .export-card-desc {
2904 font-size: var(--text-note);
2905 opacity: 0.8;
2906 margin-bottom: var(--gap-peer);
2907 }
2908
2909 .export-page .export-card-meta {
2910 font-size: var(--text-fine);
2911 opacity: 0.6;
2912 }
2913
2914 .export-page .export-card button { white-space: nowrap; }
2915
2916 .export-page .export-note {
2917 background: var(--surface-sunken);
2918 padding: var(--gap-section);
2919 margin-top: var(--gap-page);
2920 font-size: var(--text-note);
2921 }
2922
2923 .export-page .export-note h3 {
2924 font-family: var(--font-mono);
2925 font-size: var(--text-note);
2926 margin-bottom: var(--gap-peer);
2927 }
2928
2929 .export-page .back-link {
2930 display: inline-block;
2931 margin-bottom: var(--gap-pane);
2932 font-size: var(--text-note);
2933 opacity: 0.7;
2934 }
2935
2936 .export-page .back-link:hover { opacity: 1; }
2937
2938 .export-page .export-status {
2939 margin-top: var(--gap-peer);
2940 font-size: var(--text-note);
2941 }
2942
2943 .export-page .export-status.success { color: var(--success); }
2944 .export-page .export-status.error { color: var(--danger); }
2945
2946 /* ===========================================
2947 RECEIPT PAGE
2948 =========================================== */
2949
2950 /* Receipt page (templates/pages/receipt.html). */
2951
2952 .receipt-page .receipt-header {
2953 display: flex;
2954 justify-content: space-between;
2955 align-items: flex-start;
2956 margin-bottom: var(--gap-page);
2957 padding-bottom: var(--gap-pane);
2958 border-bottom: 1px solid var(--border);
2959 }
2960
2961 .receipt-page .receipt-row {
2962 display: flex;
2963 justify-content: space-between;
2964 padding: var(--gap-peer) 0;
2965 font-size: var(--text-body);
2966 }
2967
2968 .receipt-page .receipt-row.total {
2969 font-weight: bold;
2970 font-size: var(--text-lead);
2971 border-top: 1px solid var(--border);
2972 padding-top: var(--gap-section);
2973 margin-top: var(--gap-peer);
2974 }
2975
2976 .receipt-page .receipt-label { opacity: 0.7; }
2977
2978 .receipt-page .receipt-footer {
2979 margin-top: var(--gap-page);
2980 padding-top: var(--gap-pane);
2981 border-top: 1px solid var(--border);
2982 font-size: var(--text-note);
2983 opacity: 0.6;
2984 }
2985
2986 .receipt-page .back-link {
2987 display: inline-block;
2988 margin-bottom: var(--gap-pane);
2989 font-size: var(--text-note);
2990 opacity: 0.7;
2991 }
2992
2993 .receipt-page .back-link:hover { opacity: 1; }
2994
2995 @media print {
2996 .receipt-page .back-link,
2997 .receipt-page .site-header,
2998 .receipt-page .print-btn { display: none !important; }
2999 .receipt-page .receipt-box { border: 1px solid var(--border); }
3000 }
3001
3002 /* ===========================================
3003 FAN+ PAGE
3004 =========================================== */
3005
3006 /* Fan+ subscription page (templates/pages/fan_plus.html). */
3007 .fan-plus-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
3008 .fan-plus-page h2 { font-size: var(--text-head); margin-top: var(--gap-page); margin-bottom: var(--gap-section); }
3009
3010 .fan-plus-page .fan-plus-section { margin-bottom: var(--gap-page); line-height: 1.7; }
3011 .fan-plus-page .fan-plus-section ul { padding-left: var(--gap-pane); margin: var(--gap-section) 0; }
3012 .fan-plus-page .fan-plus-section li { margin-bottom: var(--gap-peer); }
3013
3014 .fan-plus-page .price-callout {
3015 font-size: var(--text-subhead);
3016 font-family: var(--font-mono);
3017 margin: var(--gap-pane) 0;
3018 }
3019
3020 .fan-plus-page .subscribe-form { margin: var(--gap-page) 0; }
3021
3022 .fan-plus-page .subscribe-btn {
3023 display: inline-block;
3024 padding: var(--gap-section) var(--gap-page);
3025 background: var(--action);
3026 color: var(--primary-light);
3027 border: none;
3028 font-size: var(--text-lead);
3029 cursor: pointer;
3030 font-family: var(--font-mono);
3031 }
3032
3033 .fan-plus-page .subscribe-btn:hover { opacity: 0.9; }
3034
3035 .fan-plus-page .status-box {
3036 padding: var(--gap-pane);
3037 background: var(--surface-overlay);
3038 margin: var(--gap-pane) 0;
3039 }
3040
3041 .fan-plus-page .status-box p { margin: var(--gap-peer) 0; }
3042
3043 .fan-plus-page .success-banner {
3044 padding: var(--gap-section) var(--gap-pane);
3045 background: color-mix(in oklch, var(--success) 12%, var(--surface-page));
3046 margin-bottom: var(--gap-pane);
3047 font-family: var(--font-mono);
3048 }
3049
3050 .fan-plus-page .login-link { font-family: var(--font-mono); }
3051
3052 /* ===========================================
3053 PURCHASE PAGE
3054 =========================================== */
3055
3056 /* Purchase confirmation page (templates/pages/purchase.html). */
3057 .purchase-page .container {
3058 max-width: 800px;
3059 margin: var(--gap-page) auto;
3060 padding: 0 var(--gap-page);
3061 }
3062
3063 .purchase-page .tagline {
3064 font-size: var(--text-note);
3065 opacity: 0.7;
3066 margin-bottom: var(--gap-page);
3067 text-align: center;
3068 }
3069
3070 .purchase-page .checkout-box { margin-bottom: var(--gap-pane); }
3071
3072 .purchase-page h2 { font-size: var(--text-title); margin-bottom: var(--gap-peer); }
3073
3074 .purchase-page .step-indicator {
3075 font-size: var(--text-note);
3076 opacity: 0.6;
3077 margin-bottom: var(--gap-pane);
3078 }
3079
3080 .purchase-page .item-summary {
3081 background: var(--surface-page);
3082 padding: var(--gap-pane);
3083 margin-bottom: var(--gap-pane);
3084 display: flex;
3085 gap: var(--gap-pane);
3086 }
3087
3088 .purchase-page .item-thumbnail {
3089 width: 120px;
3090 height: 120px;
3091 background: var(--surface-sunken);
3092 flex-shrink: 0;
3093 display: flex;
3094 align-items: center;
3095 justify-content: center;
3096 font-size: var(--text-hero);
3097 overflow: hidden;
3098 }
3099
3100 .purchase-page .item-thumbnail img {
3101 width: 100%;
3102 height: 100%;
3103 object-fit: cover;
3104 display: block;
3105 }
3106
3107 /* See the storefront card: the dimming is the placeholder's, not the slot's. */
3108 .purchase-page .item-thumbnail-empty { opacity: 0.3; }
3109
3110 .purchase-page .item-details { flex: 1; }
3111
3112 .purchase-page .item-title {
3113 font-size: var(--text-subhead);
3114 margin-bottom: var(--gap-peer);
3115 font-family: var(--font-display);
3116 font-weight: bold;
3117 }
3118
3119 .purchase-page .item-creator {
3120 font-size: var(--text-note);
3121 opacity: 0.7;
3122 margin-bottom: var(--gap-section);
3123 }
3124
3125 .purchase-page .item-creator a { color: var(--content); }
3126 .purchase-page .item-description { font-size: var(--text-note); opacity: 0.8; }
3127
3128 .purchase-page .price-breakdown {
3129 background: var(--surface-page);
3130 padding: var(--gap-pane);
3131 margin-bottom: var(--gap-pane);
3132 }
3133
3134 .purchase-page .price-breakdown h2 { font-size: var(--text-lead); margin-bottom: var(--gap-section); }
3135
3136 .purchase-page .price-row {
3137 display: flex;
3138 justify-content: space-between;
3139 padding: var(--gap-peer) 0;
3140 border-bottom: 1px solid var(--border);
3141 }
3142
3143 .purchase-page .price-row:last-child {
3144 border-bottom: none;
3145 margin-top: var(--gap-peer);
3146 font-size: var(--text-subhead);
3147 padding-top: var(--gap-section);
3148 border-top: 2px solid var(--border);
3149 }
3150
3151 .purchase-page .price-row.total { font-weight: bold; }
3152
3153 .purchase-page .payment-section { margin-bottom: var(--gap-pane); }
3154 .purchase-page .payment-section h2 { font-size: var(--text-lead); margin-bottom: var(--gap-section); }
3155
3156 .purchase-page .btn-primary { width: 100%; }
3157 .purchase-page .btn-secondary { width: 100%; margin-top: var(--gap-section); }
3158
3159 .purchase-page .security-note {
3160 font-size: var(--text-note);
3161 opacity: 0.6;
3162 text-align: center;
3163 margin-top: var(--gap-section);
3164 }
3165
3166 /* Replaces inline styles in pages/purchase.html. */
3167 .purchase-page h1 { text-align: center; }
3168
3169 .purchase-page .price-breakdown-note {
3170 margin-top: var(--gap-peer);
3171 font-size: var(--text-fine);
3172 opacity: 0.6;
3173 }
3174
3175 .purchase-page .creator-breakdown {
3176 background: var(--surface-page);
3177 padding: var(--gap-section) var(--gap-pane);
3178 margin-bottom: var(--gap-pane);
3179 font-size: var(--text-note);
3180 }
3181 .purchase-page .creator-breakdown h2 {
3182 font-size: var(--text-body);
3183 margin-bottom: var(--gap-section);
3184 opacity: 0.7;
3185 }
3186 .purchase-page .creator-breakdown .price-row { opacity: 0.8; }
3187 .purchase-page .creator-breakdown .price-row.is-total {
3188 opacity: 1;
3189 font-weight: bold;
3190 border-top: 1px solid var(--border);
3191 margin-top: var(--gap-peer);
3192 padding-top: var(--gap-peer);
3193 /* override .price-row:last-child upsize */
3194 font-size: inherit;
3195 border-bottom: none;
3196 }
3197 .purchase-page .creator-breakdown .platform-fee-amount { color: var(--content); }
3198 .purchase-page .creator-breakdown-note {
3199 margin-top: var(--gap-section);
3200 font-size: var(--text-note);
3201 opacity: 0.6;
3202 }
3203
3204 .purchase-page .payment-section-lead {
3205 margin-bottom: var(--gap-pane);
3206 opacity: 0.8;
3207 }
3208
3209 .purchase-page .pending-checkout {
3210 margin-bottom: var(--gap-pane);
3211 background: var(--surface-sunken);
3212 padding: var(--gap-section);
3213 }
3214 .purchase-page .pending-checkout p { margin: 0 0 var(--gap-section); }
3215 .purchase-page .pending-checkout p.is-sub {
3216 font-size: var(--text-note);
3217 opacity: 0.8;
3218 }
3219 .purchase-page .pending-checkout form { margin: 0; }
3220
3221 /* Checkout form field wrappers. */
3222 .purchase-page .checkout-field { margin-bottom: var(--gap-section); }
3223 .purchase-page .checkout-field-label {
3224 font-size: var(--text-note);
3225 display: block;
3226 margin-bottom: var(--gap-bound);
3227 }
3228 .purchase-page .pwyw-row {
3229 display: flex;
3230 align-items: center;
3231 gap: var(--gap-bound);
3232 }
3233 .purchase-page .pwyw-symbol { font-size: var(--text-lead); }
3234 .purchase-page .pwyw-input { width: 120px; }
3235 .purchase-page .pwyw-min-note {
3236 font-size: var(--text-fine);
3237 opacity: 0.6;
3238 margin-top: var(--gap-bound);
3239 }
3240 .purchase-page .promo-input {
3241 width: 200px;
3242 text-transform: uppercase;
3243 }
3244 .purchase-page .share-contact-label {
3245 font-size: var(--text-note);
3246 display: flex;
3247 align-items: center;
3248 gap: var(--gap-peer);
3249 cursor: pointer;
3250 }
3251 .purchase-page .share-contact-checkbox { width: auto; }
3252
3253 .purchase-page .cart-alt-link {
3254 text-align: center;
3255 margin-top: var(--gap-section);
3256 font-size: var(--text-note);
3257 opacity: 0.7;
3258 }
3259 .purchase-page .guest-hint {
3260 font-size: var(--text-fine);
3261 opacity: 0.6;
3262 margin-top: var(--gap-section);
3263 text-align: center;
3264 }
3265 .purchase-page .guest-login-prompt {
3266 font-size: var(--text-fine);
3267 margin-top: var(--gap-peer);
3268 text-align: center;
3269 }
3270
3271 /* ===========================================
3272 PROJECT SETTINGS TAB
3273 (replaces inline styles in partials/tabs/project_settings.html and
3274 related innerHTML in static/project-sections.js)
3275 =========================================== */
3276
3277 .proj-image-hint {
3278 font-size: var(--text-note);
3279 opacity: 0.7;
3280 margin: 0 0 var(--gap-peer);
3281 }
3282
3283 /* Canonical inline form-status text following a Save / Change button.
3284 Two class names because templates evolved with both. They're aliases.
3285 States via .success / .error / .saving modifier. */
3286 .field-status,
3287 .save-status {
3288 margin-left: var(--gap-peer);
3289 font-size: var(--text-note);
3290 }
3291 .field-status.success, .save-status.success { color: var(--success); }
3292 .field-status.error, .save-status.error { color: var(--danger); }
3293 .field-status.saving, .save-status.saving { opacity: 0.6; }
3294
3295 /* Muted lead paragraph above a form button (monetization / features / delete). */
3296 .section-lead {
3297 margin-bottom: var(--gap-section);
3298 opacity: 0.7;
3299 text-align: left;
3300 }
3301
3302 /* Large intro paragraph under a page h1 (creators, policy, changelog, fan_plus). */
3303 .page-intro {
3304 font-size: var(--text-lead);
3305 margin-bottom: var(--gap-page);
3306 line-height: 1.6;
3307 }
3308
3309 .subscription-note p {
3310 opacity: 0.7;
3311 font-size: var(--text-note);
3312 }
3313
3314 .features-grid-status {
3315 margin-top: var(--gap-peer);
3316 display: inline-block;
3317 }
3318
3319 /* "Pages" disclosure (project-level markdown sections). */
3320 .pages-toggle { margin-top: var(--gap-page); }
3321 .pages-toggle > summary { cursor: pointer; }
3322 .pages-toggle > summary h2 { display: inline; }
3323 .pages-intro {
3324 font-size: var(--text-note);
3325 opacity: 0.7;
3326 margin: var(--gap-section) 0 var(--gap-section);
3327 }
3328
3329 /* psection-* mirrors section-mgmt-* (used by item_details) but psection
3330 class hooks are also queried by static/project-sections.js. */
3331 .psection-row-title { flex: 1; font-weight: bold; }
3332 .psection-row-anchor { font-size: var(--text-fine); opacity: 0.6; }
3333 .psection-row-length { font-size: var(--text-fine); opacity: 0.6; }
3334
3335 .psection-edit-btn,
3336 .psection-del-btn {
3337 padding: var(--gap-bound) var(--gap-group);
3338 font-size: var(--text-fine);
3339 }
3340
3341 .psection-add-details { margin-top: var(--gap-section); }
3342 .psection-add-details > summary {
3343 cursor: pointer;
3344 font-size: var(--text-body);
3345 }
3346 .psection-add-fields { margin-top: var(--gap-section); }
3347
3348 .psection-edit-modal {
3349 margin-top: var(--gap-section);
3350 padding: var(--gap-section);
3351 background: var(--surface-sunken);
3352 }
3353 .psection-edit-actions {
3354 display: flex;
3355 gap: var(--gap-peer);
3356 }
3357
3358 /* ===========================================
3359 CART PAGE (replaces inline styles in pages/cart.html)
3360 =========================================== */
3361
3362 .cart-empty {
3363 text-align: center;
3364 padding: var(--gap-page) var(--gap-section);
3365 }
3366 .cart-empty-hint {
3367 font-size: var(--text-note);
3368 opacity: 0.7;
3369 margin-bottom: var(--gap-pane);
3370 }
3371
3372 /* Multi-creator summary bar (only renders when seller_groups.len() > 1). */
3373 .cart-multi-bar-note {
3374 opacity: 0.7;
3375 margin-left: var(--gap-peer);
3376 }
3377 .cart-multi-bar-form {
3378 display: flex;
3379 align-items: center;
3380 gap: var(--gap-section);
3381 }
3382 .cart-share-label {
3383 font-size: var(--text-note);
3384 display: flex;
3385 align-items: center;
3386 gap: var(--gap-peer);
3387 }
3388
3389 /* Per-seller group card. */
3390 .cart-group { margin-bottom: var(--gap-page); }
3391 .cart-group-title { margin-bottom: var(--gap-bound); }
3392 .cart-group-count {
3393 font-size: var(--text-note);
3394 opacity: 0.6;
3395 margin-bottom: var(--gap-section);
3396 }
3397
3398 /* PWYW editable price cell. */
3399 .cart-pwyw-cell {
3400 display: flex;
3401 align-items: center;
3402 justify-content: flex-end;
3403 gap: var(--gap-bound);
3404 }
3405 .cart-pwyw-symbol { font-size: var(--text-note); }
3406
3407 /* Compact action button used in cart/wishlist tables. */
3408
3409 /* Bottom summary row of each seller group. */
3410 .cart-group-summary {
3411 margin-top: var(--gap-section);
3412 display: flex;
3413 justify-content: space-between;
3414 align-items: center;
3415 flex-wrap: wrap;
3416 gap: var(--gap-section);
3417 }
3418 .cart-subtotal {
3419 font-family: var(--font-display);
3420 font-size: var(--text-lead);
3421 }
3422 .cart-fee-line {
3423 font-size: var(--text-note);
3424 opacity: 0.6;
3425 }
3426 .cart-savings {
3427 font-size: var(--text-note);
3428 color: var(--action);
3429 margin-top: var(--gap-bound);
3430 }
3431 .cart-stripe-not-ready {
3432 font-size: var(--text-note);
3433 opacity: 0.6;
3434 }
3435
3436 /* Checkout form within a seller group. */
3437 .cart-promo-row { margin-bottom: var(--gap-peer); }
3438 .cart-checkout-share {
3439 display: flex;
3440 align-items: center;
3441 gap: var(--gap-peer);
3442 font-size: var(--text-note);
3443 margin-bottom: var(--gap-peer);
3444 }
3445
3446 /* Wishlist suggestions section below the cart. */
3447 .cart-wishlist { margin-top: var(--gap-page); }
3448
3449 /* Generic "faded out" row state, used by Add-to-Cart click handler in cart.html
3450 to indicate the wishlist row has been moved into the cart. */
3451 .is-faded { opacity: 0.4; }
3452
3453 /* ===========================================
3454 PROFILE TAB (replaces inline styles in partials/tabs/user_profile.html)
3455 =========================================== */
3456
3457 /* Inline form row: input(s) + button laid out horizontally, bottom-aligned.
3458 Distinct from .form-row (which is a 1fr 1fr grid). */
3459 .field-row {
3460 display: flex;
3461 gap: var(--gap-section);
3462 align-items: flex-end;
3463 }
3464 .field-row > .form-group { margin-bottom: 0; }
3465 .field-row > .form-group.is-grow { flex: 1; }
3466 .field-row > .form-group.is-grow-2 { flex: 2; }
3467 .field-row > button { margin-bottom: 0; }
3468
3469 /* Custom domain section. */
3470 .profile-domain-header { margin-bottom: var(--gap-section); }
3471 .profile-domain-status {
3472 margin-left: var(--gap-peer);
3473 }
3474 .profile-domain-status.is-verified { color: var(--success); }
3475 .profile-domain-status.is-pending { color: var(--warning); }
3476
3477 .dns-row {
3478 display: flex;
3479 align-items: center;
3480 gap: var(--gap-peer);
3481 margin-bottom: var(--gap-peer);
3482 }
3483 .dns-row:last-of-type { margin-bottom: 0; }
3484 .dns-row-label {
3485 font-size: var(--text-fine);
3486 opacity: 0.7;
3487 min-width: 70px;
3488 }
3489 .dns-row-value {
3490 font-size: var(--text-note);
3491 flex: 1;
3492 }
3493 .dns-row-copy {
3494 padding: var(--gap-bound) var(--gap-peer);
3495 font-size: var(--text-fine);
3496 }
3497 .profile-domain-actions {
3498 display: flex;
3499 gap: var(--gap-section);
3500 }
3501 .profile-domain-result { margin-top: var(--gap-section); }
3502 .profile-domain-lead {
3503 font-size: var(--text-note);
3504 opacity: 0.8;
3505 }
3506 .profile-domain-add-result { margin-top: var(--gap-section); }
3507
3508 /* Personal feed row: URL input + Copy button. */
3509 .profile-feed-row {
3510 display: flex;
3511 gap: var(--gap-peer);
3512 align-items: center;
3513 }
3514 .profile-feed-input {
3515 flex: 1;
3516 font-size: var(--text-note);
3517 font-family: var(--font-mono);
3518 opacity: 0.8;
3519 }
3520 .profile-feed-lead { margin-bottom: var(--gap-section); }
3521
3522 .purchase-page .checkout-footer {
3523 margin-top: var(--gap-page);
3524 padding-top: var(--gap-pane);
3525 border-top: 1px solid var(--border);
3526 opacity: 0.6;
3527 text-align: center;
3528 font-size: var(--text-note);
3529 }
3530
3531 .purchase-page .checkout-footer a { color: var(--content); }
3532
3533 @media (max-width: 599px) {
3534 .purchase-page .container { padding: 0 var(--gap-section); }
3535 .purchase-page .item-summary { flex-direction: column; }
3536 .purchase-page .item-thumbnail { width: 100%; height: 160px; }
3537 .purchase-page .checkout-box { padding: var(--gap-pane); }
3538 .purchase-page h2 { font-size: var(--text-head); }
3539 }
3540
3541 /* Feed page (src/quasi/feeds.rs). Compact tabular layout of items from
3542 followed users / projects / tags.
3543
3544 The page is described rather than templated as of 2026-08-31, so the
3545 selectors below name what quasi and makeover emit. It has no .container:
3546 a described document is <body> > <main> > the screen's region, so the 900px
3547 measure the container carried sits on <main> here. */
3548 .feed-page main { max-width: 900px; margin: 0 auto; padding: var(--gap-page); }
3549
3550 .feed-page main h1 { font-size: var(--text-head); margin-bottom: var(--gap-section); }
3551
3552 /* The "Showing X-Y of N items" line, which the description states as a plain
3553 text node above the table. */
3554 .feed-page .pane > p.text {
3555 font-size: var(--text-fine);
3556 opacity: 0.6;
3557 margin-bottom: var(--gap-section);
3558 }
3559
3560 .feed-page .table-head {
3561 /* respec-ok: the same five-column grid the rows carry, on the heading row
3562 that has to line up with them. */
3563 display: grid;
3564 grid-template-columns: 50px 1fr 100px 70px 70px;
3565 gap: var(--gap-peer);
3566 padding: var(--gap-peer) var(--gap-section);
3567 background: var(--surface-overlay);
3568 font-size: var(--text-fine);
3569 opacity: 0.7;
3570 text-transform: uppercase;
3571 letter-spacing: 0.03em;
3572 }
3573
3574 .feed-page .table {
3575 border: 1px solid var(--border);
3576 }
3577
3578 .feed-page .table-row {
3579 /* respec-ok: a five-column grid template. The generated `.table-row` sets a
3580 display but has no vocabulary for column tracks, so the tracks and the
3581 display that carries them are stated together here. */
3582 display: grid;
3583 grid-template-columns: 50px 1fr 100px 70px 70px;
3584 gap: var(--gap-peer);
3585 padding: var(--gap-peer) var(--gap-section);
3586 align-items: center;
3587 text-decoration: none;
3588 color: var(--content);
3589 font-size: var(--text-note);
3590 border-bottom: 1px solid var(--border);
3591 transition: background 0.1s ease;
3592 }
3593
3594 .feed-page .table-row:last-child { border-bottom: none; }
3595 .feed-page .table-row:hover { background: var(--surface-sunken); }
3596
3597 /* The two-line name cell: `Cell::part` puts the creator under the name in one
3598 cell, and each part is emitted as its own `.cell-value`. */
3599 .feed-page .col-Name {
3600 display: flex;
3601 flex-direction: column;
3602 gap: var(--gap-bound);
3603 min-width: 0;
3604 }
3605
3606 .feed-page .table-row .col-Name .cell-value:first-child {
3607 white-space: nowrap;
3608 overflow: hidden;
3609 text-overflow: ellipsis;
3610 }
3611
3612 .feed-page .table-row .col-Name .cell-value:last-child {
3613 font-size: var(--text-fine);
3614 opacity: 0.5;
3615 }
3616
3617 /* The feed's badges shout a little: caps and tracking. The sunken fill that
3618 used to come with them went with the filled-badge form in B2. */
3619 .feed-page .badge {
3620 text-transform: uppercase;
3621 letter-spacing: 0.03em;
3622 }
3623
3624 /* The empty state and the numbered strip carry no page-specific rules any
3625 more: `Node::empty` and `Rest` emit their own classes, styled once for every
3626 described screen rather than again per page. */
3627
3628 @media (max-width: 599px) {
3629 .feed-page main { padding: var(--gap-section); }
3630 .feed-page .table-head,
3631 .feed-page .table-row { grid-template-columns: 1fr 70px; }
3632 /* Name and Date survive the narrow viewport, which is what the five-column
3633 header dropped to before the conversion. */
3634 .feed-page .col-Type,
3635 .feed-page .col-Tag,
3636 .feed-page .col-Price { display: none; }
3637 }
3638
3639 /* (Stripe Connect disclaimer page rules defined in the canonical
3640 STRIPE DISCLAIMER PAGE section near end of file.) */
3641
3642 /* User profile page (templates/pages/user.html). */
3643 .user-page .profile-header { text-align: center; margin-bottom: var(--gap-page); }
3644
3645 .user-page .profile-avatar {
3646 width: 120px;
3647 height: 120px;
3648 background: var(--surface-overlay);
3649 margin: 0 auto var(--gap-section);
3650 display: flex;
3651 align-items: center;
3652 justify-content: center;
3653 font-size: var(--text-hero);
3654 opacity: 0.5;
3655 }
3656
3657 .user-page .profile-name { font-size: var(--text-title); margin-bottom: var(--gap-peer); }
3658
3659 .user-page .profile-username {
3660 font-size: var(--text-note);
3661 opacity: 0.7;
3662 margin-bottom: var(--gap-section);
3663 }
3664
3665 .user-page .profile-bio {
3666 font-size: var(--text-body);
3667 max-width: 500px;
3668 margin: 0 auto;
3669 text-align: center;
3670 }
3671
3672 .user-page .links-section { margin-bottom: var(--gap-page); }
3673 .user-page .link-item { text-align: center; }
3674
3675 .user-page .link-title {
3676 font-size: var(--text-lead);
3677 margin-bottom: var(--gap-bound);
3678 font-family: var(--font-display);
3679 font-weight: bold;
3680 }
3681
3682 .user-page .link-description { font-size: var(--text-note); opacity: 0.7; }
3683
3684 .user-page .project-title {
3685 font-size: var(--text-lead);
3686 margin-bottom: var(--gap-peer);
3687 font-family: var(--font-display);
3688 font-weight: bold;
3689 }
3690
3691 .user-page .project-meta {
3692 font-size: var(--text-note);
3693 opacity: 0.7;
3694 margin-bottom: var(--gap-peer);
3695 }
3696
3697 .user-page .project-description { font-size: var(--text-note); opacity: 0.8; }
3698
3699 /* Creators / become-a-creator page (templates/pages/creators.html). */
3700 .creators-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
3701 .creators-page h2 { font-size: var(--text-head); margin-top: var(--gap-page); margin-bottom: var(--gap-section); }
3702
3703 .creators-page .how-it-works { margin-bottom: var(--gap-page); }
3704 .creators-page .how-it-works ol { padding-left: var(--gap-pane); line-height: 1.8; }
3705
3706 .creators-page .stats-row { gap: var(--gap-page); margin: var(--gap-page) 0; }
3707 .creators-page .stat-box { padding: var(--gap-pane); }
3708 .creators-page .stat-box .number { font-size: var(--text-title); }
3709 .creators-page .stat-box .label { font-size: var(--text-note); margin-top: var(--gap-bound); }
3710
3711 .creators-page .wave-table {
3712 width: 100%;
3713 border-collapse: collapse;
3714 margin: var(--gap-section) 0;
3715 }
3716
3717 .creators-page .wave-table th,
3718 .creators-page .wave-table td {
3719 padding: var(--gap-section);
3720 text-align: left;
3721 border-bottom: 1px solid var(--border);
3722 }
3723
3724 .creators-page .wave-table th {
3725 font-family: var(--font-mono);
3726 font-size: var(--text-note);
3727 opacity: 0.7;
3728 }
3729
3730 .creators-page .cta {
3731 margin-top: var(--gap-page);
3732 padding: var(--gap-pane);
3733 background: var(--surface-overlay);
3734 text-align: center;
3735 }
3736
3737 .creators-page .cta a { margin: 0 var(--gap-peer); }
3738
3739 /* Tag tree browse page (templates/pages/tag_tree.html). */
3740 .tag-tree-page .breadcrumbs {
3741 font-size: var(--text-note);
3742 margin-bottom: var(--gap-pane);
3743 opacity: 0.7;
3744 }
3745 .tag-tree-page .breadcrumbs a:hover { opacity: 0.6; }
3746 .tag-tree-page .breadcrumbs .sep { margin: 0 var(--gap-bound); opacity: 0.4; }
3747
3748 .tag-tree-page .tag-grid {
3749 display: grid;
3750 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
3751 gap: var(--gap-section);
3752 }
3753
3754 .tag-tree-page .tag-card {
3755 background: var(--surface-sunken);
3756 padding: var(--gap-section);
3757 text-decoration: none;
3758 color: var(--content);
3759 border: 1px solid var(--border);
3760 transition: background 0.1s ease;
3761 }
3762
3763 .tag-tree-page .tag-card:hover {
3764 background: var(--surface-sunken);
3765 text-decoration: none;
3766 }
3767
3768 .tag-tree-page .tag-card-name {
3769 font-family: var(--font-display);
3770 font-weight: bold;
3771 font-size: var(--text-body);
3772 }
3773
3774 .tag-tree-page .tag-card-meta {
3775 font-size: var(--text-fine);
3776 opacity: 0.5;
3777 margin-top: var(--gap-bound);
3778 }
3779
3780 .tag-tree-page .empty-state {
3781 opacity: 0.5;
3782 font-size: var(--text-note);
3783 padding: var(--gap-page) 0;
3784 }
3785
3786 /* Confirm delete prompt (templates/pages/confirm_delete.html). Overrides
3787 the global .warning-box to use --danger-bg (matches the delete-account
3788 semantic). Also tokenized previously-broken fallbacks. */
3789 .confirm-delete-page .warning-box {
3790 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
3791 border: 1px solid var(--danger);
3792 border-radius: var(--radius-panel);
3793 padding: var(--gap-pane);
3794 margin-bottom: var(--gap-pane);
3795 }
3796
3797 .confirm-delete-page .warning-box p {
3798 margin: 0;
3799 color: var(--danger);
3800 }
3801
3802 /* Project paywall page (templates/pages/project_paywall.html). */
3803 .project-paywall-page .paywall-cover { margin-bottom: var(--gap-pane); }
3804 .project-paywall-page .paywall-cover img {
3805 width: 120px;
3806 height: 120px;
3807 border-radius: var(--radius-panel);
3808 object-fit: cover;
3809 }
3810
3811 .project-paywall-page .paywall-title {
3812 font-family: var(--font-display);
3813 font-size: var(--text-hero);
3814 margin-bottom: var(--gap-peer);
3815 }
3816
3817 .project-paywall-page .paywall-creator {
3818 font-size: var(--text-body);
3819 opacity: 0.7;
3820 margin-bottom: var(--gap-pane);
3821 }
3822
3823 .project-paywall-page .paywall-creator a { color: var(--content); }
3824
3825 .project-paywall-page .paywall-description {
3826 font-size: var(--text-lead);
3827 max-width: 800px;
3828 margin: 0 auto var(--gap-page);
3829 text-align: left;
3830 }
3831
3832 .project-paywall-page .paywall-item-count {
3833 font-size: var(--text-body);
3834 opacity: 0.7;
3835 margin-bottom: var(--gap-peer);
3836 }
3837
3838 .project-paywall-page .paywall-tiers {
3839 display: grid;
3840 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
3841 gap: var(--gap-pane);
3842 margin-top: var(--gap-pane);
3843 text-align: left;
3844 }
3845
3846 .project-paywall-page .paywall-tiers .tier-card {
3847 background: var(--surface-overlay);
3848 padding: var(--gap-page);
3849 display: flex;
3850 flex-direction: column;
3851 }
3852
3853 .project-paywall-page .paywall-tiers .tier-card h3 {
3854 font-family: var(--font-display);
3855 font-weight: bold;
3856 font-size: var(--text-subhead);
3857 margin-bottom: var(--gap-peer);
3858 }
3859
3860 .project-paywall-page .paywall-tiers .tier-price {
3861 font-size: var(--text-head);
3862 margin-bottom: var(--gap-section);
3863 }
3864
3865 .project-paywall-page .paywall-tiers .tier-card form,
3866 .project-paywall-page .paywall-tiers .tier-card .button { margin-top: auto; }
3867
3868 .project-paywall-page .paywall-tiers .tier-card button,
3869 .project-paywall-page .paywall-tiers .tier-card .button { width: 100%; }
3870
3871 /* Collection detail page (templates/pages/collection.html). */
3872 .collection-page .container { max-width: 700px; }
3873 .collection-page .collection-header { margin-bottom: var(--gap-page); }
3874 .collection-page .collection-title { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
3875
3876 .collection-page .collection-owner {
3877 font-size: var(--text-note);
3878 opacity: 0.7;
3879 margin-bottom: var(--gap-section);
3880 }
3881
3882 .collection-page .collection-description {
3883 font-size: var(--text-body);
3884 opacity: 0.85;
3885 margin-bottom: var(--gap-section);
3886 }
3887
3888 .collection-page .collection-meta { font-size: var(--text-note); opacity: 0.6; }
3889
3890 .collection-page .collection-item {
3891 display: flex;
3892 justify-content: space-between;
3893 align-items: flex-start;
3894 padding: var(--gap-section);
3895 background: var(--surface-overlay);
3896 margin-bottom: var(--gap-peer);
3897 }
3898
3899 .collection-page .collection-item-info { flex: 1; }
3900
3901 .collection-page .collection-item-title {
3902 font-family: var(--font-display);
3903 font-weight: bold;
3904 font-size: var(--text-body);
3905 margin-bottom: var(--gap-bound);
3906 }
3907
3908 .collection-page .collection-item-title a { text-decoration: none; }
3909
3910 .collection-page .collection-item-meta { font-size: var(--text-note); opacity: 0.7; }
3911
3912 .collection-page .collection-item-price {
3913 font-size: var(--text-note);
3914 white-space: nowrap;
3915 margin-left: var(--gap-section);
3916 }
3917
3918 .collection-page .empty-state {
3919 text-align: center;
3920 padding: var(--gap-page) var(--gap-section);
3921 opacity: 0.6;
3922 }
3923
3924 /* Project blog index page (templates/pages/project_blog.html). */
3925 .project-blog-page .blog-header { margin-bottom: var(--gap-page); }
3926 .project-blog-page .blog-title { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
3927
3928 .project-blog-page .blog-meta {
3929 font-size: var(--text-note);
3930 opacity: 0.7;
3931 margin-bottom: var(--gap-pane);
3932 }
3933
3934 .project-blog-page .blog-meta a { color: var(--content); }
3935
3936 .project-blog-page .blog-actions {
3937 display: flex;
3938 gap: var(--gap-section);
3939 }
3940
3941 .project-blog-page .posts-list { max-width: 800px; }
3942
3943 .project-blog-page .post-entry {
3944 padding: var(--gap-pane) 0;
3945 border-bottom: 1px solid var(--border);
3946 }
3947
3948 .project-blog-page .post-entry:first-child { padding-top: 0; }
3949 .project-blog-page .post-title { font-size: var(--text-head); margin-bottom: var(--gap-peer); }
3950 .project-blog-page .post-title a:hover { text-decoration: underline; }
3951
3952 .project-blog-page .post-date {
3953 font-size: var(--text-note);
3954 opacity: 0.6;
3955 font-family: var(--font-mono);
3956 }
3957
3958 .project-blog-page .empty-state { padding: var(--gap-page) 0; opacity: 0.6; }
3959
3960 /* Canonical "page-level h1", left-aligned, smaller than the global centered
3961 default. Used by dashboards, admin, health, import, delete-account, export,
3962 receipt. Aliased via comma-grouped selectors so each page picks it up via
3963 its body class without redefining. */
3964 .dashboard-page h1,
3965 .admin-page h1,
3966 .health-page h1,
3967 .import-page h1,
3968 .delete-account-page h1,
3969 .export-page h1,
3970 .receipt-page h1 {
3971 font-size: var(--text-title);
3972 margin-bottom: var(--gap-peer);
3973 text-align: left;
3974 }
3975 .delete-account-page h1 { color: var(--danger); }
3976
3977 /* Canonical subtitle under a page h1, muted, with bottom space. */
3978 .health-page .subtitle,
3979 .import-page .subtitle,
3980 .delete-account-page .subtitle,
3981 .export-page .subtitle {
3982 opacity: 0.7;
3983 margin-bottom: var(--gap-page);
3984 text-align: left;
3985 }
3986 .health-page .subtitle { text-align: center; }
3987
3988 /* User Settings tab (templates/partials/tabs/user_settings.html).
3989 Left-rail nav layout for the Settings dashboard tab. Selection uses the
3990 canonical .is-selected modifier. */
3991 .settings-layout { display: flex; gap: var(--gap-page); }
3992 .settings-nav { flex-shrink: 0; min-width: 140px; }
3993 .settings-nav a {
3994 display: block;
3995 padding: var(--gap-peer) var(--gap-section);
3996 text-decoration: none;
3997 color: var(--content);
3998 opacity: 0.6;
3999 font-family: var(--font-mono);
4000 font-size: var(--text-note);
4001 transition: opacity 0.15s ease;
4002 }
4003 .settings-nav a:hover { opacity: 1; }
4004 .settings-nav a.is-selected { opacity: 1; background: var(--surface-sunken); }
4005 .settings-body { flex: 1; min-width: 0; }
4006 @media (max-width: 839px) {
4007 .settings-layout { flex-direction: column; gap: var(--gap-section); }
4008 .settings-nav { display: flex; flex-wrap: wrap; gap: 0; min-width: 0; }
4009 .settings-nav a { padding: var(--gap-peer) var(--gap-group); font-size: var(--text-note); }
4010 }.library-page .feed-table-header {
4011 display: grid;
4012 grid-template-columns: 50px 1fr 100px 70px 70px;
4013 gap: var(--gap-peer);
4014 padding: var(--gap-peer) var(--gap-section);
4015 background: var(--surface-overlay);
4016 font-size: var(--text-fine);
4017 opacity: 0.7;
4018 text-transform: uppercase;
4019 letter-spacing: 0.03em;
4020 }
4021 .library-page .feed-col-right { text-align: right; }
4022 .library-page .feed-results-table { border: 1px solid var(--border); border-top: none; }
4023 .library-page .feed-table-row {
4024 display: grid;
4025 grid-template-columns: 50px 1fr 100px 70px 70px;
4026 gap: var(--gap-peer);
4027 padding: var(--gap-peer) var(--gap-section);
4028 align-items: center;
4029 text-decoration: none;
4030 color: var(--content);
4031 font-size: var(--text-note);
4032 border-bottom: 1px solid var(--border);
4033 transition: background 0.1s ease;
4034 }
4035 .library-page .feed-table-row:last-child { border-bottom: none; }
4036 .library-page .feed-table-row:hover { background: var(--surface-sunken); }
4037 .library-page .feed-item-name-cell {
4038 display: flex;
4039 flex-direction: column;
4040 gap: var(--gap-bound);
4041 min-width: 0;
4042 }
4043 .library-page .feed-item-name {
4044 white-space: nowrap;
4045 overflow: hidden;
4046 text-overflow: ellipsis;
4047 }
4048 .library-page .feed-item-creator { font-size: var(--text-fine); opacity: 0.5; }
4049 .library-page .feed-pagination {
4050 display: flex;
4051 gap: var(--gap-bound);
4052 justify-content: center;
4053 margin-top: var(--gap-section);
4054 }
4055 .library-page .feed-pagination a,
4056 .library-page .feed-pagination span {
4057 padding: var(--gap-peer) var(--gap-section);
4058 font-size: var(--text-note);
4059 text-decoration: none;
4060 color: var(--content);
4061 border: 1px solid var(--border);
4062 }
4063 .library-page .feed-pagination span.current {
4064 background: var(--primary-dark);
4065 color: var(--primary-light);
4066 border-color: var(--primary-dark);
4067 }
4068 .library-page .feed-pagination a:hover { background: var(--surface-sunken); }
4069 @media (max-width: 599px) {
4070 .library-page .feed-table-header,
4071 .library-page .feed-table-row {
4072 grid-template-columns: 1fr 70px;
4073 }
4074 .library-page .feed-table-header span:nth-child(1),
4075 .library-page .feed-table-row .badge:first-child,
4076 .library-page .feed-table-header span:nth-child(3),
4077 .library-page .feed-table-header span:nth-child(4),
4078 .library-page .feed-table-row span:nth-child(3),
4079 .library-page .feed-table-row span:nth-child(4) {
4080 display: none;
4081 }
4082 }
4083
4084 /* User-level dashboard (templates/dashboards/dashboard-user.html).
4085 Scoped under .dashboard-user-page to avoid colliding with .project-card
4086 / .project-title / .project-meta on the public profile (user.html) and
4087 the user_projects tab partial. */
4088 .dashboard-user-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
4089
4090 .dashboard-user-page .project-card {
4091 background: var(--surface-overlay);
4092 padding: var(--gap-pane);
4093 margin-bottom: var(--gap-section);
4094 display: flex;
4095 justify-content: space-between;
4096 align-items: flex-start;
4097 }
4098
4099 .dashboard-user-page .project-info { flex: 1; }
4100
4101 .dashboard-user-page .project-title {
4102 font-family: var(--font-display);
4103 font-weight: bold;
4104 font-size: var(--text-subhead);
4105 margin-bottom: var(--gap-bound);
4106 }
4107
4108 .dashboard-user-page .project-meta {
4109 font-size: var(--text-note);
4110 opacity: 0.7;
4111 margin-bottom: var(--gap-peer);
4112 }
4113
4114 .dashboard-user-page .project-stats { font-size: var(--text-note); }
4115 .dashboard-user-page .project-actions {
4116 display: flex;
4117 gap: var(--gap-peer);
4118 }
4119
4120 .dashboard-user-page .project-actions button {
4121 padding: var(--gap-peer) var(--gap-section);
4122 font-size: var(--text-note);
4123 }
4124 @media (max-width: 839px) {
4125 .dashboard-user-page .project-card { flex-direction: column; }
4126 .dashboard-user-page .project-actions { margin-top: var(--gap-section); }
4127 }
4128
4129 /* Project dashboard (templates/dashboards/dashboard-project.html). */
4130 .dashboard-project-page .project-meta { font-size: var(--text-note); opacity: 0.7; }
4131 .dashboard-project-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
4132
4133 .dashboard-project-page .content-header {
4134 display: flex;
4135 justify-content: space-between;
4136 align-items: center;
4137 margin-bottom: var(--gap-pane);
4138 }
4139
4140 .dashboard-project-page .content-header h2 { font-size: var(--text-subhead); }
4141
4142 .dashboard-project-page .item-thumbnail {
4143 width: 40px;
4144 height: 40px;
4145 background: var(--border);
4146 display: inline-block;
4147 vertical-align: middle;
4148 margin-right: var(--gap-peer);
4149 }
4150
4151 .dashboard-project-page .item-actions {
4152 display: flex;
4153 gap: var(--gap-peer);
4154 }
4155
4156 .dashboard-project-page .item-actions button {
4157 padding: var(--gap-peer) var(--gap-section);
4158 font-size: var(--text-note);
4159 }
4160
4161 .dashboard-project-page .data-section { margin-bottom: var(--gap-page); }
4162
4163 .dashboard-project-page .data-section h2 {
4164 font-size: var(--text-subhead);
4165 margin-bottom: var(--gap-section);
4166 padding-bottom: var(--gap-peer);
4167 border-bottom: 1px solid var(--border);
4168 }
4169
4170 /* Item dashboard (templates/dashboards/dashboard-item.html). */
4171 .dashboard-item-page .item-meta {
4172 font-size: var(--text-note);
4173 opacity: 0.7;
4174 display: flex;
4175 gap: var(--gap-section);
4176 align-items: center;
4177 }
4178
4179 .dashboard-item-page .stat-value { font-size: var(--text-head); }
4180
4181 .dashboard-item-page .section-header {
4182 display: flex;
4183 justify-content: space-between;
4184 align-items: center;
4185 margin-bottom: var(--gap-pane);
4186 padding-bottom: var(--gap-section);
4187 border-bottom: 1px solid var(--border);
4188 }
4189
4190 .dashboard-item-page .section-header h2 { font-size: var(--text-subhead); }
4191
4192 @media (max-width: 839px) {
4193 .dashboard-item-page .section-header {
4194 flex-direction: column;
4195 align-items: flex-start;
4196 gap: var(--gap-section);
4197 }
4198 .dashboard-item-page .section-header .action-buttons { width: 100%; }
4199 }
4200
4201 /* Changelog page (templates/pages/changelog.html). */
4202 .changelog-page .container { max-width: 900px; margin: 0 auto; }
4203 .changelog-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
4204 .changelog-page .changelog-entry {
4205 margin-bottom: var(--gap-page);
4206 padding-bottom: var(--gap-page);
4207 border-bottom: 1px solid var(--border);
4208 }
4209 .changelog-page .changelog-entry:last-child { border-bottom: none; }
4210 .changelog-page .changelog-date {
4211 font-family: var(--font-mono);
4212 font-size: var(--text-note);
4213 opacity: 0.6;
4214 margin-bottom: var(--gap-peer);
4215 }
4216 .changelog-page .changelog-entry h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
4217 .changelog-page .changelog-entry ul {
4218 padding-left: var(--gap-pane);
4219 margin: var(--gap-peer) 0;
4220 line-height: 1.7;
4221 }
4222 .changelog-page .changelog-entry li { margin-bottom: var(--gap-bound); }
4223
4224 /* Policy page (templates/pages/policy.html). */
4225 .policy-page .container { max-width: 900px; margin: 0 auto; }
4226 .policy-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
4227 .policy-page h2 {
4228 font-size: var(--text-head);
4229 margin-top: var(--gap-page);
4230 margin-bottom: var(--gap-section);
4231 }
4232 .policy-page .policy-section { margin-bottom: var(--gap-page); line-height: 1.7; }
4233 .policy-page .policy-section ul {
4234 padding-left: var(--gap-pane);
4235 margin: var(--gap-section) 0;
4236 }
4237 .policy-page .policy-section li { margin-bottom: var(--gap-bound); }
4238 .policy-page .contact {
4239 margin-top: var(--gap-page);
4240 padding: var(--gap-pane);
4241 background: var(--surface-overlay);
4242 }
4243
4244 /* Admin page extras (in addition to global .admin-page h1). */
4245 .admin-page .metrics-grid {
4246 display: grid;
4247 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
4248 gap: var(--gap-section);
4249 margin-bottom: var(--gap-page);
4250 }
4251
4252 .admin-page .metric-card {
4253 padding: var(--gap-section);
4254 border: 1px solid var(--border);
4255 border-radius: var(--radius-square);
4256 }
4257
4258 .admin-page .metric-card .value {
4259 font-size: var(--text-title);
4260 font-family: var(--font-mono);
4261 font-weight: 600;
4262 }
4263
4264 .admin-page .metric-card .label {
4265 font-size: var(--text-note);
4266 color: var(--content-muted);
4267 margin-top: var(--gap-bound);
4268 }
4269
4270 .admin-page .metric-card.warn .value { color: var(--warning); }
4271 .admin-page .metric-card.error .value { color: var(--danger); }
4272
4273 .admin-page .section-heading {
4274 font-size: var(--text-subhead);
4275 margin: var(--gap-pane) 0 var(--gap-section);
4276 font-family: var(--font-mono);
4277 }
4278
4279 /* Scan pipeline dashboard. See docs/scan-pipeline-audit.md § 5. */
4280
4281 .admin-scan-section {
4282 margin-top: var(--gap-page);
4283 }
4284
4285 .admin-section-header {
4286 display: flex;
4287 justify-content: space-between;
4288 align-items: baseline;
4289 margin-bottom: var(--gap-section);
4290 gap: var(--gap-section);
4291 }
4292
4293 /* Typeface comes from .subsection-title on the heading itself; this sets only
4294 what the flex row needs. */
4295 .admin-section-header h2 {
4296 font-size: var(--text-lead);
4297 margin: 0;
4298 }
4299
4300 .layer-health-grid {
4301 display: grid;
4302 grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
4303 gap: var(--gap-section);
4304 }
4305
4306 .layer-health-card {
4307 padding: var(--gap-section);
4308 border: 1px solid var(--border);
4309 border-radius: var(--radius-square);
4310 border-left-width: 4px;
4311 font-size: var(--text-note);
4312 }
4313
4314 .layer-health-card .layer-name {
4315 font-family: var(--font-mono);
4316 font-weight: 600;
4317 font-size: var(--text-body);
4318 margin-bottom: var(--gap-peer);
4319 }
4320
4321 .layer-health-card .layer-stat {
4322 display: flex;
4323 justify-content: space-between;
4324 margin-bottom: 2px;
4325 }
4326
4327 .layer-health-card .layer-stat .dimmed {
4328 color: var(--content-muted);
4329 }
4330
4331 .layer-health-card.layer-ok { border-left-color: var(--success); }
4332 .layer-health-card.layer-degraded { border-left-color: var(--warning); }
4333 .layer-health-card.layer-down { border-left-color: var(--danger); }
4334
4335 .layer-chips {
4336 display: flex;
4337 flex-wrap: wrap;
4338 gap: 4px;
4339 max-width: 360px;
4340 }
4341
4342 .layer-chip {
4343 display: inline-block;
4344 font-family: var(--font-mono);
4345 font-size: var(--text-fine);
4346 padding: 1px 6px;
4347 border-radius: var(--radius-control);
4348 border: 1px solid transparent;
4349 cursor: help;
4350 line-height: 1.4;
4351 }
4352
4353 .layer-chip-pass { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); border-color: var(--success); color: var(--success); }
4354 .layer-chip-skip { background: var(--surface-page); border-color: var(--border); color: var(--content-muted); }
4355 .layer-chip-fail { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); border-color: var(--danger); color: var(--danger); font-weight: 600; }
4356 .layer-chip-error { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); border-color: var(--warning); color: var(--warning); font-weight: 600; }
4357
4358 .scan-history-grid table { font-size: var(--text-note); }
4359
4360 .scan-history-grid summary {
4361 cursor: pointer;
4362 font-family: var(--font-mono);
4363 padding: var(--gap-peer) 0;
4364 user-select: none;
4365 }
4366
4367 .scan-row-meta {
4368 font-size: var(--text-fine);
4369 color: var(--content-muted);
4370 margin-top: 2px;
4371 }
4372
4373 .audit-filter-form {
4374 display: flex;
4375 flex-wrap: wrap;
4376 align-items: flex-end;
4377 gap: var(--gap-section);
4378 padding: var(--gap-section) 0;
4379 margin-bottom: var(--gap-section);
4380 border-bottom: 1px solid var(--border);
4381 }
4382
4383 .audit-filter-form label {
4384 display: flex;
4385 flex-direction: column;
4386 gap: 2px;
4387 }
4388
4389 .audit-filter-form input,
4390 .audit-filter-form select {
4391 font-size: var(--text-note);
4392 }
4393
4394 .layer-chips-cell { vertical-align: top; }
4395
4396 .layer-detail-disclosure {
4397 margin-top: var(--gap-peer);
4398 }
4399
4400 .layer-detail-disclosure summary {
4401 cursor: pointer;
4402 user-select: none;
4403 padding: 2px 0;
4404 }
4405
4406 .layer-detail-list {
4407 margin: var(--gap-peer) 0 0;
4408 display: grid;
4409 grid-template-columns: max-content 1fr;
4410 gap: 2px var(--gap-section);
4411 font-size: var(--text-fine);
4412 }
4413
4414 .layer-detail-list dt {
4415 font-family: var(--font-mono);
4416 }
4417
4418 .layer-detail-list dd {
4419 margin: 0;
4420 color: var(--content-muted);
4421 word-break: break-word;
4422 }
4423
4424 .admin-page table {
4425 width: 100%;
4426 border-collapse: collapse;
4427 font-size: var(--text-note);
4428 }
4429
4430 .admin-page th,
4431 .admin-page td {
4432 text-align: left;
4433 padding: var(--gap-peer) var(--gap-group);
4434 border-bottom: 1px solid var(--border);
4435 }
4436
4437 .admin-page th {
4438 font-family: var(--font-mono);
4439 font-size: var(--text-fine);
4440 text-transform: uppercase;
4441 color: var(--content-muted);
4442 }
4443
4444 .admin-page td.mono { font-family: var(--font-mono); }
4445
4446 .admin-page .refresh-note {
4447 font-size: var(--text-fine);
4448 color: var(--content-muted);
4449 margin-bottom: var(--gap-section);
4450 font-family: var(--font-mono);
4451 }
4452
4453 .admin-page .appeal-count {
4454 font-size: var(--text-note);
4455 opacity: 0.7;
4456 margin-bottom: var(--gap-pane);
4457 }
4458
4459 .admin-page .decide-form {
4460 display: flex;
4461 gap: var(--gap-peer);
4462 align-items: flex-end;
4463 flex-wrap: wrap;
4464 }
4465
4466 .admin-page .decide-form textarea {
4467 min-width: 200px;
4468 font-size: var(--text-note);
4469 }
4470
4471 .admin-page .suspend-form {
4472 display: flex;
4473 gap: var(--gap-peer);
4474 align-items: flex-end;
4475 }
4476
4477 .admin-page .suspend-form input[type="text"] {
4478 min-width: 200px;
4479 font-size: var(--text-note);
4480 }
4481
4482 .admin-page .lottery-form {
4483 background: var(--surface-overlay);
4484 padding: var(--gap-pane);
4485 margin: var(--gap-pane) 0;
4486 display: flex;
4487 gap: var(--gap-section);
4488 align-items: flex-end;
4489 }
4490
4491 .admin-page .lottery-form .form-group { margin-bottom: 0; }
4492
4493 .admin-page .lottery-form label {
4494 font-size: var(--text-note);
4495 display: block;
4496 margin-bottom: var(--gap-bound);
4497 }
4498
4499 .admin-page .lottery-form input[type="number"] { width: 80px; }
4500
4501 /* Page-top notice (charter primitive, replaces #restart-banner and
4502 the inline sandbox banner in dashboard-project). Full-bleed across
4503 the viewport, monospace, one short sentence + optional action.
4504 Hero callouts on the landing page use `.alert` instead (multi-paragraph). */
4505 .banner {
4506 padding: var(--gap-section) var(--gap-pane);
4507 font-family: var(--font-mono);
4508 font-size: var(--text-note);
4509 text-align: center;
4510 color: var(--content);
4511 }
4512
4513 .banner--info {
4514 background: var(--highlight-faint);
4515 border-bottom: 2px solid var(--focus-ring);
4516 }
4517
4518 .banner--warning {
4519 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
4520 border-bottom: 2px solid var(--warning);
4521 }
4522
4523 .banner a {
4524 color: inherit;
4525 text-decoration: underline;
4526 }
4527
4528 /* ===========================================
4529 SECTIONS
4530 =========================================== */
4531
4532 .section-header {
4533 font-size: var(--text-head);
4534 font-weight: normal;
4535 margin-bottom: var(--gap-section);
4536 padding-bottom: var(--gap-peer);
4537 border-bottom: 1px solid var(--border);
4538 font-family: var(--font-mono);
4539 }
4540
4541 /* Canonical "page section box", light-background card with padding + bottom
4542 space. One of these in the codebase. Aliased to the per-page class names
4543 that were just relabels of this same shape. Templates can use the alias
4544 name OR add `.content-section` as a co-class. */
4545 /* The canonical panel. Every one of these carries `.raised` in the markup
4546 now, so the fill and the bevel come from layout.css and this rule is the
4547 spacing only. The background it used to declare was --surface-overlay,
4548 which .raised spells --surface-raised; equal on the platform default and a
4549 theme may set them apart, the same move .card and .buy-card made. */
4550 .content-section,
4551 .import-page .import-form,
4552 .import-page .import-progress,
4553 .library-page .library-section,
4554 .library-page .library-downloads,
4555 .library-page .downloads-hero,
4556 .receipt-page .receipt-box,
4557 .stripe-disclaimer-page .disclaimer-box,
4558 .project-page .project-sections {
4559 padding: var(--gap-page);
4560 margin-bottom: var(--gap-page);
4561 }
4562
4563 /* Variants of the canonical shape that want top-spacing instead of bottom,
4564 or a tighter --gap-pane padding. */
4565 .library-page .library-downloads,
4566 .receipt-page .receipt-box { margin-bottom: 0; margin-top: var(--gap-pane); }
4567 .import-page .import-progress { padding: var(--gap-pane); margin-bottom: var(--gap-section); }
4568 .stripe-disclaimer-page .disclaimer-box { margin-bottom: var(--gap-pane); }
4569
4570 .form-section {
4571 margin-bottom: var(--gap-page);
4572 }
4573
4574 .section-group-label {
4575 font-family: var(--font-mono);
4576 font-size: var(--text-fine);
4577 font-weight: bold;
4578 text-transform: uppercase;
4579 letter-spacing: 0.05em;
4580 color: var(--content-muted);
4581 margin: var(--gap-page) 0 var(--gap-section) 0;
4582 padding-top: var(--gap-pane);
4583 border-top: 2px solid var(--border);
4584 }
4585
4586 .form-section h2 {
4587 font-size: var(--text-subhead);
4588 font-weight: normal;
4589 margin-bottom: var(--gap-section);
4590 padding-bottom: var(--gap-peer);
4591 border-bottom: 1px solid var(--border);
4592 }
4593
4594 details.form-section > summary {
4595 cursor: pointer;
4596 list-style: disclosure-closed;
4597 }
4598
4599 details.form-section[open] > summary {
4600 list-style: disclosure-open;
4601 margin-bottom: var(--gap-section);
4602 }
4603
4604 details.form-section > summary > h2 {
4605 display: inline;
4606 }
4607
4608 details.form-section > summary:hover > h2 {
4609 color: var(--content);
4610 }
4611
4612 /* ===========================================
4613 NAVIGATION
4614 =========================================== */
4615
4616 nav {
4617 margin-bottom: var(--gap-page);
4618 }
4619
4620 .nav-links {
4621 display: flex;
4622 gap: var(--gap-page);
4623 flex-wrap: wrap;
4624 }
4625
4626 .nav-links a {
4627 color: var(--content);
4628 text-decoration: none;
4629 font-family: var(--font-mono);
4630 transition: opacity 0.2s ease;
4631 }
4632
4633 .nav-links a:hover {
4634 opacity: 0.6;
4635 }
4636
4637 .shortcuts-help-btn {
4638 font-family: var(--font-mono);
4639 color: var(--content-muted);
4640 font-size: var(--text-note);
4641 width: 1.5rem;
4642 height: 1.5rem;
4643 border: 1px solid var(--border);
4644 border-radius: var(--radius-control);
4645 display: inline-flex;
4646 align-items: center;
4647 justify-content: center;
4648 cursor: pointer;
4649 transition: opacity 0.2s ease;
4650 padding: 0;
4651 background: none;
4652 }
4653
4654 .shortcuts-help-btn:hover {
4655 opacity: 0.6;
4656 }
4657
4658 .breadcrumb {
4659 font-size: var(--text-note);
4660 color: var(--content-muted);
4661 margin-bottom: var(--gap-peer);
4662 font-family: var(--font-mono);
4663 }
4664
4665 .breadcrumb a {
4666 color: var(--content);
4667 text-decoration: none;
4668 }
4669
4670 .breadcrumb a:hover {
4671 text-decoration: underline;
4672 }
4673
4674 /* ===========================================
4675 HEADER & FOOTER
4676 =========================================== */
4677
4678 header {
4679 background: transparent;
4680 border-bottom: none;
4681 padding: 0;
4682 margin-bottom: var(--gap-page);
4683 }
4684
4685 footer {
4686 text-align: center;
4687 font-family: var(--font-mono);
4688 color: var(--content-muted);
4689 }
4690
4691 .profile-footer {
4692 margin-top: var(--gap-page);
4693 padding-top: var(--gap-pane);
4694 border-top: 1px solid var(--border);
4695 text-align: center;
4696 opacity: 0.6;
4697 font-size: var(--text-note);
4698 }
4699
4700 .profile-footer a {
4701 color: var(--content);
4702 text-decoration: none;
4703 }
4704
4705 .profile-footer a:hover {
4706 text-decoration: underline;
4707 }
4708
4709 /* Site-wide header */
4710 .site-header {
4711 display: flex;
4712 justify-content: space-between;
4713 align-items: center;
4714 padding: var(--gap-section) var(--gap-pane);
4715 margin-bottom: var(--gap-pane);
4716 }
4717
4718 .site-header nav {
4719 margin: 0;
4720 }
4721
4722 .site-header .nav-links {
4723 gap: var(--gap-pane);
4724 }
4725
4726 .site-logo {
4727 font-family: var(--font-display);
4728 font-size: var(--text-subhead);
4729 text-decoration: none;
4730 color: var(--content);
4731 }
4732
4733 .site-logo:hover {
4734 opacity: 0.8;
4735 }
4736
4737 .nav-form {
4738 display: contents;
4739 }
4740
4741 /* Header search */
4742 .header-search {
4743 flex: 0 1 220px;
4744 margin: 0 var(--gap-section);
4745 }
4746
4747 .header-search input {
4748 width: 100%;
4749 padding: var(--gap-peer) var(--gap-group);
4750 font-size: var(--text-note);
4751 font-family: var(--font-mono);
4752 border: 1px solid var(--border);
4753 background: var(--surface-page);
4754 color: var(--content);
4755 border-radius: var(--radius-control);
4756 }
4757
4758 .header-search input:focus {
4759 outline: 2px solid var(--focus-ring);
4760 outline-offset: -1px;
4761 }
4762
4763 /* Hamburger toggle, hidden by default, shown at 768px */
4764 .nav-toggle-checkbox {
4765 display: none;
4766 }
4767
4768 .nav-toggle-label {
4769 display: none;
4770 cursor: pointer;
4771 padding: var(--gap-peer);
4772 z-index: var(--z-header);
4773 }
4774
4775 .nav-toggle-label span {
4776 display: block;
4777 width: 22px;
4778 height: 2px;
4779 background: var(--content);
4780 margin: 5px 0;
4781 transition: transform 0.2s ease, opacity 0.2s ease;
4782 }
4783
4784 /* ===========================================
4785 LAYOUT UTILITIES
4786 =========================================== */
4787
4788 .action-buttons {
4789 display: flex;
4790 gap: var(--gap-section);
4791 flex-wrap: wrap;
4792 }
4793 /* ===========================================
4794 SPECIFIC COMPONENTS
4795 =========================================== */
4796
4797 /* Login container */
4798 .login-container {
4799 display: flex;
4800 flex-direction: column;
4801 max-width: 400px;
4802 width: 100%;
4803 margin: 0 auto;
4804 background: var(--surface-overlay);
4805 padding: var(--gap-page);
4806 }
4807 /* Logo */
4808 .logo {
4809 font-family: var(--font-display);
4810 color: var(--content);
4811 font-size: var(--text-head);
4812 }
4813
4814 .tagline {
4815 font-family: var(--font-mono);
4816 color: var(--content-muted);
4817 text-align: center;
4818 }
4819
4820 .centered-page .tagline {
4821 margin-bottom: var(--gap-bound);
4822 }
4823 /* Skip link */
4824 .skip-link {
4825 text-align: center;
4826 margin-top: var(--gap-section);
4827 }
4828
4829 .skip-link a {
4830 color: var(--primary-dark);
4831 text-decoration: none;
4832 font-size: var(--text-note);
4833 opacity: 0.7;
4834 }
4835
4836 .skip-link a:hover {
4837 text-decoration: underline;
4838 }
4839
4840 /* Footer link */
4841 .foot-link {
4842 margin-top: 0.5em;
4843 }
4844
4845 /* Status indicators */
4846 .status-indicator {
4847 display: inline-block;
4848 width: 8px;
4849 height: 8px;
4850 background: var(--primary-dark);
4851 margin-right: var(--gap-peer);
4852 }
4853
4854 .status-indicator.active {
4855 background: var(--success);
4856 }
4857
4858 /* Amount styling */
4859 .amount-in {
4860 color: var(--success);
4861 }
4862
4863 .amount-out {
4864 opacity: 0.7;
4865 }
4866
4867 /* File upload */
4868 .file-upload-area {
4869 background: var(--surface-sunken);
4870 padding: var(--gap-page);
4871 text-align: center;
4872 cursor: pointer;
4873 transition: background 0.2s ease;
4874 margin-bottom: var(--gap-section);
4875 }
4876
4877 .file-upload-area:hover,
4878 .file-upload-area.dragover {
4879 background: var(--border);
4880 }
4881 /* Chart placeholder */
4882 .chart-container {
4883 background: var(--surface-sunken);
4884 padding: var(--gap-pane);
4885 margin-bottom: var(--gap-pane);
4886 }
4887
4888 .chart-header {
4889 display: flex;
4890 justify-content: space-between;
4891 align-items: center;
4892 margin-bottom: var(--gap-section);
4893 }
4894
4895 .chart-header h2 {
4896 font-size: var(--text-subhead);
4897 font-weight: normal;
4898 }
4899
4900 .chart-bars {
4901 display: flex;
4902 align-items: flex-end;
4903 gap: 2px;
4904 height: 200px;
4905 padding: 0 var(--gap-peer);
4906 }
4907
4908 .chart-bar-col {
4909 flex: 1;
4910 display: flex;
4911 flex-direction: column;
4912 align-items: center;
4913 min-width: 0;
4914 cursor: default;
4915 position: relative;
4916 }
4917
4918 .chart-bar-col[data-tooltip]:hover::before {
4919 content: attr(data-tooltip);
4920 position: absolute;
4921 bottom: 100%;
4922 left: 50%;
4923 transform: translateX(-50%);
4924 background: var(--primary-dark);
4925 color: var(--primary-light);
4926 font-size: var(--text-fine);
4927 font-family: var(--font-mono);
4928 padding: var(--gap-bound) var(--gap-peer);
4929 white-space: nowrap;
4930 z-index: var(--z-dropdown);
4931 pointer-events: none;
4932 margin-bottom: var(--gap-bound);
4933 }
4934
4935 .chart-bar {
4936 width: 100%;
4937 height: var(--fill, 0%);
4938 background: var(--primary-dark);
4939 min-height: 2px;
4940 transition: opacity 0.15s;
4941 }
4942
4943 .chart-bar-col:hover .chart-bar {
4944 opacity: 0.8;
4945 }
4946
4947 .chart-bar-label {
4948 font-size: var(--text-fine);
4949 opacity: 0.5;
4950 margin-top: var(--gap-bound);
4951 white-space: nowrap;
4952 overflow: hidden;
4953 text-overflow: ellipsis;
4954 max-width: 100%;
4955 }
4956
4957 /* Time selector */
4958 .time-selector {
4959 display: flex;
4960 gap: var(--gap-peer);
4961 }
4962
4963 /* The range buttons are makeover's `.segment`, which is what a segmented
4964 control is called there, and B3's question about whether this codebase has
4965 an analogue: it does, and this is it. Fill, bevel, hover, active and focus
4966 are the generated ones; this rule is geometry and type.
4967
4968 Selection was a --primary-dark fill with a --primary-light label, which is
4969 a second thing meaning "chosen" beside the generated `.chosen` (the segment
4970 presses IN — well fill, inset bevel). Taking the generated one; only the
4971 name stays local, same as the tabs. */
4972 .time-selector button {
4973 border: none;
4974 padding: var(--gap-peer) var(--gap-section);
4975 font-family: inherit;
4976 font-size: var(--text-note);
4977 cursor: pointer;
4978 }
4979
4980 .time-selector button.is-selected {
4981 background: var(--surface-well, var(--surface-page));
4982 box-shadow: var(--bevel-inset);
4983 }
4984
4985 /* Analytics */
4986 .analytics-grid {
4987 display: grid;
4988 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4989 gap: var(--gap-pane);
4990 margin-bottom: var(--gap-pane);
4991 }
4992 .analytics-list {
4993 list-style: none;
4994 }
4995
4996 .analytics-list li {
4997 padding: var(--gap-peer) 0;
4998 border-bottom: 1px solid var(--border);
4999 display: flex;
5000 justify-content: space-between;
5001 }
5002
5003 .analytics-list li:last-child {
5004 border-bottom: none;
5005 }
5006
5007 /* Filter controls */
5008 .filter-controls {
5009 display: flex;
5010 gap: var(--gap-section);
5011 margin-bottom: var(--gap-section);
5012 flex-wrap: wrap;
5013 align-items: center;
5014 }
5015
5016 .filter-group {
5017 display: flex;
5018 gap: var(--gap-peer);
5019 align-items: center;
5020 }
5021 /* UUID styling */
5022 .uuid {
5023 font-family: inherit;
5024 opacity: 0.5;
5025 font-size: var(--text-note);
5026 }
5027 /* ===========================================
5028 HTMX LOADING INDICATORS
5029 =========================================== */
5030
5031 .htmx-indicator {
5032 display: none;
5033 animation: htmx-spin 1s ease-in-out infinite;
5034 }
5035
5036 .htmx-request .htmx-indicator {
5037 display: inline;
5038 }
5039
5040 .htmx-request.htmx-indicator {
5041 display: inline;
5042 }
5043
5044 /* Loading spinner animation. Applied on .htmx-indicator above. */
5045 @keyframes htmx-spin {
5046 0% { opacity: 0.3; }
5047 50% { opacity: 1; }
5048 100% { opacity: 0.3; }
5049 }
5050
5051 /* Disable buttons during request */
5052 .htmx-request button[type="submit"] {
5053 opacity: 0.6;
5054 cursor: wait;
5055 }
5056
5057 /* ===========================================
5058 TOAST NOTIFICATIONS
5059 =========================================== */
5060
5061 .toast-container {
5062 position: fixed;
5063 bottom: 1.5rem;
5064 right: 1.5rem;
5065 z-index: var(--z-toast);
5066 display: flex;
5067 flex-direction: column;
5068 gap: var(--gap-peer);
5069 }
5070
5071 .toast {
5072 background: var(--primary-dark);
5073 color: var(--primary-light);
5074 padding: var(--gap-section);
5075 font-family: var(--font-mono);
5076 font-size: var(--text-note);
5077 box-shadow: var(--elevation-overlay);
5078 animation: toast-in var(--motion-fade) ease-out;
5079 max-width: 300px;
5080 display: flex;
5081 align-items: center;
5082 gap: var(--gap-section);
5083 }
5084
5085 .toast-dismiss {
5086 background: none;
5087 border: none;
5088 color: inherit;
5089 font-size: var(--text-lead);
5090 cursor: pointer;
5091 opacity: 0.7;
5092 padding: 0;
5093 line-height: 1;
5094 flex-shrink: 0;
5095 }
5096
5097 .toast-dismiss:hover {
5098 opacity: 1;
5099 }
5100
5101 .toast-success {
5102 background: var(--success);
5103 }
5104
5105 .toast-error {
5106 background: var(--danger);
5107 }
5108
5109 .toast-warning {
5110 background: var(--warning);
5111 }
5112
5113 .toast-info {
5114 background: var(--primary-dark);
5115 }
5116
5117 .toast-retry-btn {
5118 background: none;
5119 border: 1px solid currentColor;
5120 color: inherit;
5121 font-family: var(--font-mono);
5122 font-size: var(--text-fine);
5123 font-weight: 600;
5124 padding: var(--gap-bound) var(--gap-peer);
5125 margin-left: var(--gap-section);
5126 cursor: pointer;
5127 }
5128
5129 .toast-retry-btn:hover {
5130 background: color-mix(in oklch, var(--primary-light) 15%, transparent);
5131 }
5132
5133 .toast.fade-out {
5134 /* `Motion::Fade`, the same number the script waits before removing the node.
5135 A literal here and a token there is the two parting silently, and reduced
5136 motion zeroes the token. */
5137 animation: toast-out var(--motion-fade) ease-in forwards;
5138 }
5139
5140 @keyframes toast-in {
5141 from {
5142 transform: translateX(100%);
5143 opacity: 0;
5144 }
5145 to {
5146 transform: translateX(0);
5147 opacity: 1;
5148 }
5149 }
5150
5151 @keyframes toast-out {
5152 from {
5153 transform: translateX(0);
5154 opacity: 1;
5155 }
5156 to {
5157 transform: translateX(100%);
5158 opacity: 0;
5159 }
5160 }
5161
5162 /* ===========================================
5163 RESTART WARNING BANNER
5164 =========================================== */
5165
5166 /* Page-top behavior extensions on top of .banner.banner--warning.
5167 Color / typography come from the .banner primitive. */
5168 #restart-banner {
5169 position: fixed;
5170 top: 0;
5171 left: 0;
5172 right: 0;
5173 z-index: var(--z-banner);
5174 padding: var(--gap-group) var(--gap-section);
5175 animation: banner-slide-down 0.3s ease-out;
5176 }
5177
5178 @keyframes banner-slide-down {
5179 from {
5180 transform: translateY(-100%);
5181 }
5182 to {
5183 transform: translateY(0);
5184 }
5185 }
5186
5187 /* ===========================================
5188 SAVE STATUS INDICATORS
5189 =========================================== */
5190
5191 /* Username availability status */
5192 .username-status {
5193 display: block;
5194 margin-top: var(--gap-bound);
5195 font-size: var(--text-note);
5196 }
5197
5198 .username-status.available {
5199 color: var(--success);
5200 }
5201
5202 .username-status.taken {
5203 color: var(--danger);
5204 }
5205
5206 /* ===========================================
5207 HTMX TRANSITIONS
5208 =========================================== */
5209
5210 /* Fade in for swapped content */
5211 .htmx-added {
5212 animation: htmx-fade-in 0.2s ease-out;
5213 }
5214
5215 @keyframes htmx-fade-in {
5216 from {
5217 opacity: 0;
5218 }
5219 to {
5220 opacity: 1;
5221 }
5222 }
5223
5224 /* Settling animation for swapped content */
5225 .htmx-settling {
5226 opacity: 0.8;
5227 }
5228
5229 /* Swap animation */
5230 .htmx-swapping {
5231 opacity: 0.5;
5232 transition: opacity 0.2s ease-out;
5233 }
5234
5235 /* ===========================================
5236 ACCESSIBILITY - FOCUS STYLES
5237 =========================================== */
5238
5239 /* Focus-visible for keyboard navigation */
5240 *:focus-visible {
5241 outline: 2px solid var(--focus-ring);
5242 outline-offset: 2px;
5243 }
5244
5245 /* Remove default focus outline since we use focus-visible */
5246 *:focus:not(:focus-visible) {
5247 outline: none;
5248 }
5249
5250 /* Ensure buttons and links are keyboard accessible */
5251 button:focus-visible,
5252 a:focus-visible,
5253 input:focus-visible,
5254 select:focus-visible,
5255 textarea:focus-visible {
5256 outline: 2px solid var(--focus-ring);
5257 outline-offset: 2px;
5258 }
5259
5260 /* Tab focus is layout.css's, same ring as everything else at outline-offset
5261 2px. The local rule inset it to -2px, which is only worth doing where a
5262 strip clips its overflow; `.tabs` is flex/nowrap with no overflow set, so
5263 there was nothing to clip and the tabs read differently from every other
5264 focusable thing on the page for no reason. */
5265
5266 /* Canonical "selected" recipe (charter: docs/design-system.md).
5267 Apply `.is-selected` to any interactive container to mark it as the
5268 currently-selected option. A badge never takes it: a badge reports a status
5269 and nobody selects "Enabled", so badges take a `data-tone` instead.
5270 Visibility toggles (`.tab-content.active`, `.section-panel.active`) also
5271 keep `.active` since they're not selection state. */
5272 .is-selected {
5273 background: var(--highlight-faint);
5274 border-color: var(--focus-ring);
5275 }
5276
5277 /* Skip to main content link (for screen readers) */
5278 .skip-to-main {
5279 position: absolute;
5280 top: -100px;
5281 left: 0;
5282 background: var(--primary-dark);
5283 color: var(--primary-light);
5284 padding: 8px;
5285 z-index: var(--z-skip-link);
5286 transition: top 0.2s ease;
5287 }
5288
5289 .skip-to-main:focus {
5290 top: 0;
5291 }
5292
5293 /* Visually hidden but accessible to screen readers */
5294 .sr-only {
5295 position: absolute;
5296 width: 1px;
5297 height: 1px;
5298 padding: 0;
5299 margin: -1px;
5300 overflow: hidden;
5301 clip: rect(0, 0, 0, 0);
5302 white-space: nowrap;
5303 border: 0;
5304 }
5305
5306 /* Status indicator with text for accessibility */
5307 .status-indicator::after {
5308 content: attr(data-status);
5309 position: absolute;
5310 width: 1px;
5311 height: 1px;
5312 padding: 0;
5313 margin: -1px;
5314 overflow: hidden;
5315 clip: rect(0, 0, 0, 0);
5316 white-space: nowrap;
5317 border: 0;
5318 }
5319
5320 /* ===========================================
5321 PAYWALL
5322 =========================================== */
5323
5324 .paywall-section {
5325 margin: var(--gap-page) 0;
5326 padding: var(--gap-page);
5327 background: var(--surface-sunken);
5328 text-align: center;
5329 }
5330
5331 .paywall-box h2 {
5332 font-family: var(--font-display);
5333 font-weight: normal;
5334 margin-bottom: var(--gap-peer);
5335 }
5336
5337 .paywall-price {
5338 font-family: var(--font-mono);
5339 font-size: var(--text-head);
5340 margin-bottom: var(--gap-peer);
5341 }
5342
5343 .paywall-desc {
5344 font-family: var(--font-sans);
5345 color: var(--content-muted);
5346 margin-bottom: var(--gap-pane);
5347 }
5348
5349 .paywall-note {
5350 font-family: var(--font-mono);
5351 font-size: var(--text-note);
5352 color: var(--content-muted);
5353 margin-top: var(--gap-section);
5354 }
5355
5356 .paywall-note a {
5357 color: var(--action);
5358 }
5359
5360 /* ===========================================
5361 MODALS
5362 =========================================== */
5363
5364 .modal-overlay {
5365 position: fixed;
5366 inset: 0;
5367 background: var(--overlay);
5368 display: flex;
5369 align-items: center;
5370 justify-content: center;
5371 z-index: var(--z-modal);
5372 }
5373
5374 .modal {
5375 background: var(--surface-page);
5376 padding: var(--gap-page);
5377 max-width: 500px;
5378 width: 90%;
5379 max-height: 90vh;
5380 overflow-y: auto;
5381 }
5382
5383 .modal h2 {
5384 margin-bottom: var(--gap-pane);
5385 }
5386
5387 .modal .form-actions {
5388 display: flex;
5389 gap: var(--gap-section);
5390 justify-content: flex-end;
5391 margin-top: var(--gap-pane);
5392 }
5393
5394 /* ===========================================
5395 LINK ROWS (Dashboard Links Section)
5396 =========================================== */
5397
5398 .link-row {
5399 display: flex;
5400 gap: var(--gap-section);
5401 align-items: center;
5402 margin-bottom: var(--gap-section);
5403 padding: var(--gap-section);
5404 background: var(--surface-sunken);
5405 transition: opacity 0.2s ease, background 0.2s ease;
5406 }
5407
5408 .link-row input[type="text"],
5409 .link-row input[type="url"] {
5410 flex: 1;
5411 min-width: 0;
5412 }
5413
5414 .link-row .btn-danger {
5415 padding: var(--gap-peer) var(--gap-section);
5416 font-size: var(--text-note);
5417 }
5418
5419 /* Link order buttons */
5420 .link-order-buttons {
5421 display: flex;
5422 flex-direction: column;
5423 gap: 2px;
5424 }
5425
5426 .order-btn {
5427 background: var(--surface-sunken);
5428 border: 1px solid var(--border);
5429 padding: var(--gap-bound) var(--gap-peer);
5430 font-size: var(--text-fine);
5431 line-height: 1;
5432 cursor: pointer;
5433 opacity: 0.6;
5434 transition: opacity 0.15s ease, background 0.15s ease;
5435 }
5436
5437 .order-btn:hover {
5438 opacity: 1;
5439 background: var(--border);
5440 }
5441
5442 .order-btn:active {
5443 box-shadow: var(--shadow-inset);
5444 }
5445
5446 /* ===========================================
5447 TEXT EDITOR
5448 =========================================== */
5449
5450 .text-editor {
5451 width: 100%;
5452 }
5453
5454 .editor-footer {
5455 display: flex;
5456 justify-content: space-between;
5457 padding: var(--gap-peer) var(--gap-section);
5458 background: var(--surface-sunken);
5459 font-size: var(--text-note);
5460 opacity: 0.7;
5461 }
5462
5463 /* ===========================================
5464 RICH FIELD (crate::quasi::rich_field)
5465 =========================================== */
5466
5467 /* The wrapper is this app's; the group inside it is the renderer's, and
5468 `static/layout.css` carries every rule that decides what is showing. What is
5469 left for this file is the two things a description does not say: how tall the
5470 editor stands, and how the rendered preview reads.
5471
5472 Selected through the `[data-format="markdown"]` mark rather than through
5473 makeover's class names, so nothing here overlaps the generated sheet. */
5474
5475 .rich-field [data-format="markdown"] > textarea {
5476 padding: var(--gap-section);
5477 font-family: var(--font-mono);
5478 font-size: var(--text-note);
5479 line-height: 1.6;
5480 resize: vertical;
5481 border: none;
5482 background: var(--surface-sunken);
5483 }
5484
5485 /* Three sizes for four surfaces: the item editor and the blog editor were
5486 already the same surface written two ways (400px of CSS, and rows="20"). */
5487 .rich-field--compact [data-format="markdown"] > textarea,
5488 .rich-field--compact [data-editor-preview] {
5489 min-height: 9rem;
5490 }
5491
5492 .rich-field--standard [data-format="markdown"] > textarea,
5493 .rich-field--standard [data-editor-preview] {
5494 min-height: 15rem;
5495 }
5496
5497 .rich-field--tall [data-format="markdown"] > textarea,
5498 .rich-field--tall [data-editor-preview] {
5499 min-height: 400px;
5500 }
5501
5502 /* The pane stands where the control stood, so it reads as prose rather than as
5503 source. The rules below are the retired `.markdown-preview` set, unchanged
5504 except for what selects them. */
5505 .rich-field [data-editor-preview] {
5506 padding: var(--gap-pane);
5507 font-family: var(--font-sans);
5508 line-height: 1.8;
5509 }
5510
5511 .rich-field [data-editor-preview] h1,
5512 .rich-field [data-editor-preview] h2,
5513 .rich-field [data-editor-preview] h3 {
5514 margin-top: var(--gap-pane);
5515 margin-bottom: var(--gap-peer);
5516 }
5517
5518 .rich-field [data-editor-preview] h1 {
5519 font-size: var(--text-title);
5520 }
5521
5522 .rich-field [data-editor-preview] h2 {
5523 font-size: var(--text-head);
5524 }
5525
5526 .rich-field [data-editor-preview] h3 {
5527 font-size: var(--text-lead);
5528 }
5529
5530 .rich-field [data-editor-preview] p {
5531 margin-bottom: var(--gap-section);
5532 }
5533
5534 .rich-field [data-editor-preview] code {
5535 background: var(--surface-sunken);
5536 padding: var(--gap-bound);
5537 font-family: var(--font-mono);
5538 font-size: var(--text-note);
5539 }
5540
5541 /* ===========================================
5542 AUDIO UPLOAD
5543 =========================================== */
5544
5545 .audio-upload {
5546 width: 100%;
5547 }
5548
5549 .current-audio {
5550 display: flex;
5551 justify-content: space-between;
5552 align-items: center;
5553 padding: var(--gap-section);
5554 background: var(--surface-sunken);
5555 margin-bottom: var(--gap-section);
5556 }
5557
5558 .audio-info {
5559 display: flex;
5560 align-items: center;
5561 gap: var(--gap-section);
5562 }
5563
5564 .audio-icon {
5565 font-size: var(--text-head);
5566 opacity: 0.6;
5567 }
5568
5569 .audio-details {
5570 display: flex;
5571 flex-direction: column;
5572 }
5573
5574 .audio-filename {
5575 font-family: var(--font-mono);
5576 font-size: var(--text-note);
5577 }
5578
5579 .audio-duration {
5580 font-size: var(--text-note);
5581 opacity: 0.6;
5582 }
5583
5584 .upload-area .file-upload-area {
5585 padding: var(--gap-page);
5586 }
5587
5588 .upload-icon {
5589 font-size: var(--text-title);
5590 opacity: 0.4;
5591 margin-bottom: var(--gap-peer);
5592 }
5593
5594 .upload-text {
5595 font-size: var(--text-lead);
5596 margin-bottom: var(--gap-peer);
5597 }
5598
5599 .upload-hint {
5600 font-size: var(--text-note);
5601 opacity: 0.6;
5602 }
5603
5604 .upload-progress {
5605 padding: var(--gap-pane);
5606 background: var(--surface-sunken);
5607 }
5608
5609 .progress-info {
5610 display: flex;
5611 justify-content: space-between;
5612 margin-bottom: var(--gap-section);
5613 font-size: var(--text-note);
5614 }
5615
5616 /* Canonical progress bar primitive. One of these in the codebase.
5617 - .progress-bar-container is the track (8px default; --slim = 6px)
5618 - .progress-bar is the fill (success-green default; --highlight = purple)
5619 - Add .progress-bar-container--rounded for the 3px-radius pill variant
5620 Spacing (mb-4 / mt-2 etc.) is added by the caller, not baked in. */
5621 .progress-bar-container {
5622 height: 8px;
5623 background: var(--border);
5624 overflow: hidden;
5625 }
5626 .progress-bar-container--slim { height: 6px; }
5627 .progress-bar-container--rounded { border-radius: var(--radius-control); }
5628
5629 /* Fill proportion comes from the template as --fill, because it is
5630 per-render data. The property that reads it lives here. */
5631 .progress-bar {
5632 width: var(--fill, 0%);
5633 height: 100%;
5634 background: var(--success);
5635 transition: width 0.2s ease;
5636 }
5637 .progress-bar--highlight { background: var(--action); }
5638 .progress-bar--warn { background: var(--warning); }
5639 .progress-bar--danger { background: var(--danger); }
5640
5641 /* Upload status block (filename + percent label + progress bar). One of these
5642 in the codebase. Used by wizard upload flows and standalone audio/video upload partials. */
5643 .upload-status { margin-top: var(--gap-peer); }
5644 .upload-status-row {
5645 display: flex;
5646 align-items: center;
5647 gap: var(--gap-peer);
5648 }
5649 .upload-status-row span { font-size: var(--text-note); }
5650 .upload-status-msg { margin-top: var(--gap-peer); }
5651 .upload-status-msg.is-success { color: var(--success); }
5652 .upload-status-msg.is-error { color: var(--danger); }
5653
5654 /* Inline progress cell rendered inside a table-row status cell (batch uploads). */
5655 .upload-progress-row {
5656 display: flex;
5657 align-items: center;
5658 gap: var(--gap-peer);
5659 }
5660 .upload-progress-row .progress-bar-container { flex: 1; min-width: 60px; }
5661 .upload-progress-pct {
5662 font-size: var(--text-fine);
5663 min-width: 2.5em;
5664 text-align: right;
5665 }
5666
5667 .upload-success {
5668 padding: var(--gap-pane);
5669 background: var(--surface-sunken);
5670 text-align: center;
5671 }
5672 .upload-error {
5673 padding: var(--gap-pane);
5674 background: var(--surface-sunken);
5675 text-align: center;
5676 }
5677
5678 .hidden {
5679 display: none !important;
5680 }
5681
5682 /* ===========================================
5683 VIEW TOGGLE (Grid/List)
5684 =========================================== */
5685
5686 .view-controls {
5687 display: flex;
5688 gap: var(--gap-bound);
5689 margin-left: auto;
5690 }
5691
5692 .view-btn {
5693 background: var(--surface-sunken);
5694 border: none;
5695 padding: var(--gap-peer) var(--gap-group);
5696 font-family: var(--font-mono);
5697 font-size: var(--text-note);
5698 cursor: pointer;
5699 opacity: 0.5;
5700 transition: opacity 0.2s ease;
5701 }
5702
5703 .view-btn:hover {
5704 opacity: 0.8;
5705 }
5706
5707 .view-btn.is-selected {
5708 opacity: 1;
5709 background: var(--primary-dark);
5710 color: var(--primary-light);
5711 }
5712 .results-container.results-list .results-table {
5713 display: block;
5714 }
5715
5716 .results-container.results-list .results-grid {
5717 display: none;
5718 }
5719
5720 .results-container.results-grid .results-table {
5721 display: none;
5722 }
5723
5724 .results-container.results-grid .results-grid {
5725 display: grid;
5726 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
5727 gap: var(--gap-pane);
5728 border: 1px solid var(--border);
5729 border-top: none;
5730 padding: var(--gap-pane);
5731 background: var(--surface-overlay);
5732 }
5733
5734 /* Grid cards */
5735 .grid-card {
5736 background: var(--surface-overlay);
5737 text-decoration: none;
5738 color: var(--content);
5739 display: flex;
5740 flex-direction: column;
5741 transition: background 0.2s ease;
5742 }
5743
5744 .grid-card:hover {
5745 background: var(--surface-sunken);
5746 text-decoration: none;
5747 }
5748
5749 .grid-card-thumbnail {
5750 height: 120px;
5751 background: var(--border);
5752 display: flex;
5753 align-items: center;
5754 justify-content: center;
5755 font-size: var(--text-fine);
5756 opacity: 0.5;
5757 text-transform: uppercase;
5758 }
5759
5760 .grid-card-content {
5761 padding: var(--gap-section);
5762 display: flex;
5763 flex-direction: column;
5764 gap: var(--gap-bound);
5765 }
5766
5767 .grid-card-title {
5768 font-family: var(--font-display);
5769 font-size: var(--text-lead);
5770 }
5771
5772 .grid-card-meta {
5773 font-size: var(--text-note);
5774 opacity: 0.6;
5775 }
5776
5777 .grid-card-footer {
5778 display: flex;
5779 justify-content: space-between;
5780 align-items: center;
5781 margin-top: var(--gap-peer);
5782 font-size: var(--text-note);
5783 }
5784
5785 .grid-card-category {
5786 opacity: 0.6;
5787 }
5788
5789 .grid-card-price {
5790 font-weight: bold;
5791 }
5792
5793 .grid-card-stat {
5794 opacity: 0.6;
5795 }
5796
5797 /* ===========================================
5798 PROJECT PAGE VIEW TOGGLE
5799 =========================================== */
5800
5801 .items-header {
5802 display: flex;
5803 justify-content: space-between;
5804 align-items: center;
5805 margin-bottom: var(--gap-pane);
5806 }
5807
5808 .items-header .section-header {
5809 margin-bottom: 0;
5810 border-bottom: none;
5811 padding-bottom: 0;
5812 }
5813
5814 /* Grid view is default on project page */
5815 .items-container.items-grid-view .items-grid {
5816 display: grid;
5817 }
5818
5819 .items-container.items-grid-view .items-list {
5820 display: none;
5821 }
5822
5823 .items-container.items-list-view .items-grid {
5824 display: none;
5825 }
5826
5827 .items-container.items-list-view .items-list {
5828 display: block;
5829 }
5830
5831 /* List items styling */
5832 .items-list {
5833 display: none;
5834 }
5835
5836 .list-item {
5837 display: flex;
5838 justify-content: space-between;
5839 align-items: center;
5840 padding: var(--gap-section);
5841 background: var(--surface-overlay);
5842 margin-bottom: var(--gap-peer);
5843 text-decoration: none;
5844 color: var(--content);
5845 transition: background 0.2s ease;
5846 }
5847
5848 .list-item:hover {
5849 background: var(--surface-sunken);
5850 text-decoration: none;
5851 }
5852
5853 .list-item-info {
5854 display: flex;
5855 flex-direction: column;
5856 gap: var(--gap-bound);
5857 }
5858
5859 .list-item-title {
5860 font-family: var(--font-display);
5861 font-size: var(--text-lead);
5862 }
5863
5864 .list-item-meta {
5865 font-size: var(--text-note);
5866 opacity: 0.6;
5867 }
5868
5869 .list-item-price {
5870 font-size: var(--text-lead);
5871 font-weight: bold;
5872 }
5873
5874 /* ===========================================
5875 LANDING PAGE
5876 =========================================== */
5877
5878 .landing-hero {
5879 max-width: 720px;
5880 width: 100%;
5881 text-align: center;
5882 }
5883
5884 .landing-headline {
5885 font-family: var(--font-display);
5886 font-size: var(--text-head);
5887 margin-bottom: var(--gap-peer);
5888 }
5889
5890 .landing-sub {
5891 font-size: var(--text-body);
5892 line-height: 1.6;
5893 opacity: 0.75;
5894 margin-bottom: 0;
5895 }
5896
5897 .fork-revenue-line {
5898 font-size: var(--text-note);
5899 opacity: 0.8;
5900 margin: var(--gap-section) 0 var(--gap-peer);
5901 line-height: 1.5;
5902 }
5903
5904 .fork-revenue-line a {
5905 border-bottom: 1px solid transparent;
5906 transition: border-color 0.2s ease;
5907 }
5908
5909 .fork-revenue-line a:hover {
5910 border-bottom-color: currentColor;
5911 }
5912
5913 .landing-fork {
5914 display: grid;
5915 grid-template-columns: 1fr 1fr;
5916 gap: var(--gap-section);
5917 margin-top: var(--gap-page);
5918 text-align: left;
5919 }
5920
5921 .fork-heading {
5922 font-family: var(--font-mono);
5923 font-size: var(--text-note);
5924 font-weight: 600;
5925 text-transform: uppercase;
5926 letter-spacing: 0.05em;
5927 margin-bottom: var(--gap-section);
5928 }
5929
5930 .fork-list {
5931 list-style: none;
5932 margin: 0;
5933 padding: 0;
5934 flex: 1;
5935 }
5936
5937 .fork-list li {
5938 margin-bottom: var(--gap-group);
5939 font-size: var(--text-note);
5940 line-height: 1.5;
5941 }
5942
5943 .fork-list strong {
5944 font-family: var(--font-mono);
5945 font-size: var(--text-fine);
5946 }
5947
5948 .fork-actions {
5949 margin-top: auto;
5950 padding-top: var(--gap-section);
5951 display: flex;
5952 flex-direction: column;
5953 align-items: center;
5954 gap: var(--gap-peer);
5955 }
5956
5957 .fork-actions .big-button {
5958 width: 100%;
5959 }
5960
5961 .fork-secondary-link {
5962 font-family: var(--font-mono);
5963 font-size: var(--text-fine);
5964 opacity: 0.6;
5965 transition: opacity 0.2s ease;
5966 }
5967
5968 .fork-secondary-link:hover {
5969 opacity: 1;
5970 }
5971
5972 .fork-lede {
5973 font-size: var(--text-body);
5974 line-height: 1.55;
5975 margin: 0 0 var(--gap-section);
5976 }
5977
5978 .landing-browse-cta {
5979 text-align: center;
5980 margin: var(--gap-section) 0 0;
5981 }
5982
5983 .landing-mobile-note {
5984 text-align: center;
5985 margin: var(--gap-peer) 0 0;
5986 font-size: var(--text-fine);
5987 color: var(--content-muted);
5988 }
5989
5990 .founder-tagline {
5991 font-family: var(--font-sans);
5992 font-size: var(--text-body);
5993 line-height: 1.55;
5994 margin: var(--gap-section) auto 0;
5995 max-width: 560px;
5996 color: var(--content);
5997 display: flex;
5998 flex-direction: column;
5999 align-items: center;
6000 gap: var(--gap-peer);
6001 }
6002
6003 .landing-velocity {
6004 font-family: var(--font-mono);
6005 font-size: var(--text-fine);
6006 text-align: center;
6007 margin: var(--gap-section) auto 0;
6008 color: var(--content-muted);
6009 }
6010
6011 .landing-velocity a {
6012 color: var(--content);
6013 text-decoration: none;
6014 border-bottom: 1px solid var(--border);
6015 }
6016
6017 .landing-velocity a:hover {
6018 border-bottom-color: var(--content);
6019 }
6020
6021 .founder-tagline-detail {
6022 display: block;
6023 }
6024
6025 .founder-tagline-mark {
6026 font-family: var(--font-mono);
6027 font-size: var(--text-fine);
6028 font-weight: 600;
6029 text-transform: uppercase;
6030 letter-spacing: 0.08em;
6031 color: var(--action);
6032 padding: var(--gap-bound) var(--gap-peer);
6033 border: 1px solid var(--action);
6034 border-radius: var(--radius-control);
6035 white-space: nowrap;
6036 }
6037
6038 .fork-card--founder {
6039 border-color: var(--action);
6040 box-shadow: 0 0 0 1px var(--highlight-faint);
6041 }
6042
6043 .founder-tier-grid {
6044 display: grid;
6045 grid-template-columns: 1fr 1fr;
6046 gap: var(--gap-peer) var(--gap-section);
6047 margin-bottom: var(--gap-section);
6048 padding-bottom: var(--gap-section);
6049 border-bottom: 1px dashed var(--border);
6050 }
6051
6052 .founder-tier {
6053 display: flex;
6054 flex-direction: column;
6055 gap: var(--gap-bound);
6056 }
6057
6058 .founder-tier-name {
6059 font-family: var(--font-mono);
6060 font-size: var(--text-fine);
6061 font-weight: 600;
6062 text-transform: uppercase;
6063 letter-spacing: 0.05em;
6064 opacity: 0.7;
6065 }
6066
6067 .founder-tier-price {
6068 font-family: var(--font-display);
6069 font-size: var(--text-body);
6070 line-height: 1.1;
6071 }
6072
6073 .founder-tier-price del {
6074 opacity: 0.4;
6075 margin-right: var(--gap-bound);
6076 font-weight: 400;
6077 }
6078
6079 .founder-tier-price strong {
6080 color: var(--action);
6081 font-weight: 700;
6082 }
6083
6084 .founder-tier-unit {
6085 font-family: var(--font-mono);
6086 font-size: var(--text-fine);
6087 opacity: 0.6;
6088 margin-left: var(--gap-bound);
6089 }
6090
6091 .founder-slots {
6092 font-family: var(--font-mono);
6093 font-size: var(--text-fine);
6094 margin: 0 0 var(--gap-section);
6095 line-height: 1.3;
6096 }
6097
6098 .founder-slots-number {
6099 font-family: var(--font-display);
6100 font-size: var(--text-head);
6101 color: var(--action);
6102 font-weight: 700;
6103 margin-right: var(--gap-bound);
6104 }
6105
6106 .founder-slots-cap {
6107 opacity: 0.55;
6108 margin-left: var(--gap-bound);
6109 }
6110
6111 .founder-slots--cap {
6112 opacity: 0.7;
6113 font-size: var(--text-fine);
6114 }
6115
6116 .landing-stats-line {
6117 font-family: var(--font-mono);
6118 font-size: var(--text-note);
6119 opacity: 0.6;
6120 margin-top: var(--gap-page);
6121 }
6122
6123 .landing-stats-line a {
6124 opacity: 0.8;
6125 transition: opacity 0.2s ease;
6126 }
6127
6128 .landing-stats-line a:hover {
6129 opacity: 1;
6130 }
6131
6132 .landing-principles {
6133 margin-top: var(--gap-page);
6134 width: 100%;
6135 }
6136
6137 .landing-use-cases {
6138 margin-top: var(--gap-page);
6139 width: 100%;
6140 }
6141
6142 .use-case-line {
6143 font-family: var(--font-mono);
6144 font-size: var(--text-note);
6145 opacity: 0.7;
6146 letter-spacing: 0.02em;
6147 }
6148
6149 .landing-prose {
6150 font-size: var(--text-body);
6151 line-height: 1.7;
6152 margin-bottom: var(--gap-peer);
6153 }
6154
6155 .notify-form {
6156 display: flex;
6157 gap: var(--gap-peer);
6158 max-width: 400px;
6159 margin: 0 auto;
6160 }
6161
6162 .notify-input {
6163 flex: 1;
6164 padding: var(--gap-peer) var(--gap-section);
6165 border: 1px solid var(--border);
6166 border-radius: var(--radius-control);
6167 font-family: var(--font-sans);
6168 font-size: var(--text-note);
6169 background: var(--surface-page);
6170 color: var(--content);
6171 }
6172
6173 .notify-status {
6174 font-family: var(--font-mono);
6175 font-size: var(--text-fine);
6176 text-align: center;
6177 margin-top: var(--gap-peer);
6178 min-height: 1.2em;
6179 }
6180
6181 .notify-status.success {
6182 color: var(--content);
6183 }
6184
6185 .notify-status.error {
6186 color: var(--danger);
6187 }
6188
6189 .section-label {
6190 font-family: var(--font-mono);
6191 font-size: var(--text-note);
6192 letter-spacing: 0.05em;
6193 text-transform: uppercase;
6194 opacity: 0.6;
6195 margin-bottom: var(--gap-section);
6196 }
6197
6198 .tier-section {
6199 margin: var(--gap-page) 0;
6200 width: 100%;
6201 }
6202 /* Marketing pricing variant of .card--bordered. */
6203 .tier-name {
6204 font-family: var(--font-mono);
6205 font-size: var(--text-note);
6206 font-weight: 600;
6207 margin-bottom: var(--gap-bound);
6208 }
6209
6210 .tier-price {
6211 font-family: var(--font-display);
6212 font-size: var(--text-lead);
6213 margin-bottom: var(--gap-bound);
6214 }
6215
6216 .tier-desc {
6217 font-size: var(--text-note);
6218 opacity: 0.7;
6219 }
6220
6221 .tier-card.planned {
6222 opacity: 0.5;
6223 border-style: dashed;
6224 position: relative;
6225 }
6226 .landing-cta {
6227 display: flex;
6228 align-items: center;
6229 justify-content: center;
6230 gap: var(--gap-page);
6231 width: fit-content;
6232 margin: var(--gap-pane) auto var(--gap-page);
6233 }
6234 .secondary-links {
6235 display: flex;
6236 gap: var(--gap-pane);
6237 justify-content: center;
6238 margin-top: var(--gap-section);
6239 }
6240
6241 .secondary-links a {
6242 font-size: var(--text-note);
6243 opacity: 0.6;
6244 transition: opacity 0.2s ease;
6245 }
6246
6247 .secondary-links a:hover {
6248 opacity: 1;
6249 }
6250 .section-link {
6251 display: block;
6252 margin-top: var(--gap-section);
6253 font-family: var(--font-mono);
6254 font-size: var(--text-fine);
6255 opacity: 0.55;
6256 transition: opacity 0.2s ease;
6257 }
6258
6259 .section-link:hover {
6260 opacity: 1;
6261 }
6262
6263 .feature-grid {
6264 display: grid;
6265 grid-template-columns: 1fr 1fr;
6266 gap: var(--gap-section);
6267 text-align: left;
6268 }
6269
6270 .feature-name {
6271 font-family: var(--font-mono);
6272 font-size: var(--text-note);
6273 font-weight: 600;
6274 margin-bottom: var(--gap-bound);
6275 }
6276
6277 .feature-desc {
6278 font-size: var(--text-note);
6279 opacity: 0.7;
6280 }
6281
6282 .how-list {
6283 list-style: none;
6284 margin: 0;
6285 padding: 0;
6286 text-align: left;
6287 }
6288
6289 .how-list li {
6290 margin-bottom: var(--gap-group);
6291 font-size: var(--text-body);
6292 }
6293
6294 .how-list strong {
6295 font-family: var(--font-mono);
6296 font-size: var(--text-note);
6297 }
6298
6299 .do-rail {
6300 list-style: none;
6301 margin: var(--gap-page) 0 var(--gap-pane);
6302 padding: 0;
6303 display: grid;
6304 /* A do-card stops being readable under about 200px, which is a fact about
6305 the card. Three across is what that yields at full width. */
6306 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
6307 gap: var(--gap-section);
6308 width: 100%;
6309 }
6310
6311 .do-card {
6312 background: transparent;
6313 border: 1px solid var(--border);
6314 padding: var(--gap-section);
6315 text-align: left;
6316 display: flex;
6317 flex-direction: column;
6318 }
6319
6320 .do-card-title {
6321 font-family: var(--font-mono);
6322 font-size: var(--text-note);
6323 font-weight: 600;
6324 text-transform: uppercase;
6325 letter-spacing: 0.05em;
6326 margin: 0 0 var(--gap-peer);
6327 }
6328
6329 .do-card-lede {
6330 font-size: var(--text-note);
6331 line-height: 1.5;
6332 opacity: 0.78;
6333 margin: 0 0 var(--gap-section);
6334 }
6335
6336 .do-card-links {
6337 list-style: none;
6338 margin: auto 0 0;
6339 padding: var(--gap-section) 0 0;
6340 border-top: 1px solid var(--border);
6341 display: flex;
6342 flex-wrap: wrap;
6343 gap: var(--gap-peer) var(--gap-section);
6344 }
6345
6346 .do-card-links a {
6347 font-size: var(--text-note);
6348 border-bottom: 1px solid transparent;
6349 transition: border-color 0.2s ease;
6350 }
6351
6352 .do-card-links a:hover {
6353 border-bottom-color: currentColor;
6354 }
6355
6356 .do-card--wide {
6357 margin-top: var(--gap-page);
6358 width: 100%;
6359 }
6360
6361 .contrast-rail {
6362 list-style: none;
6363 margin: var(--gap-section) 0 0;
6364 padding: 0;
6365 display: grid;
6366 /* Title plus a short link list, so it holds up narrower than a do-card.
6367 Four across at full width. */
6368 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
6369 gap: var(--gap-section);
6370 width: 100%;
6371 }
6372
6373 .contrast-card {
6374 border-top: 1px solid var(--border);
6375 padding: var(--gap-section) var(--gap-bound) 0;
6376 text-align: left;
6377 }
6378
6379 .contrast-card-title {
6380 font-family: var(--font-mono);
6381 font-size: var(--text-fine);
6382 letter-spacing: 0.04em;
6383 text-transform: uppercase;
6384 opacity: 0.5;
6385 text-decoration: line-through;
6386 text-decoration-thickness: 1px;
6387 margin: 0 0 var(--gap-peer);
6388 font-weight: 600;
6389 }
6390
6391 .contrast-card-links {
6392 list-style: none;
6393 margin: 0;
6394 padding: 0;
6395 display: flex;
6396 flex-wrap: wrap;
6397 gap: var(--gap-peer) var(--gap-section);
6398 }
6399
6400 .contrast-card-links a {
6401 font-size: var(--text-note);
6402 border-bottom: 1px solid transparent;
6403 transition: border-color 0.2s ease;
6404 }
6405
6406 .contrast-card-links a:hover {
6407 border-bottom-color: currentColor;
6408 }
6409
6410 .explore-links {
6411 display: flex;
6412 flex-wrap: wrap;
6413 gap: var(--gap-section) var(--gap-pane);
6414 justify-content: center;
6415 }
6416
6417 .explore-links a {
6418 font-family: var(--font-mono);
6419 font-size: var(--text-note);
6420 opacity: 0.7;
6421 transition: opacity 0.2s ease;
6422 }
6423
6424 .explore-links a:hover {
6425 opacity: 1;
6426 }
6427
6428 /* ===========================================
6429 PRICING CALCULATOR
6430 =========================================== */
6431
6432 /* The calculator itself is a described screen since `1e35bc8a`
6433 (`crate::quasi::pricing`), so it is drawn in makeover's own classes and every
6434 rule this section held for it went with `pages/pricing.html`: the dials, the
6435 tier cards, the list/founder toggle, the verdict, the results table and the
6436 hand-drawn crossover bar. What is left is the one rule another template still
6437 matches. */
6438
6439 .pricing-currency {
6440 font-family: var(--font-display);
6441 font-size: var(--text-display);
6442 opacity: 0.5;
6443 }
6444
6445 /* ===========================================
6446 DOCUMENTATION PAGES
6447 =========================================== */
6448
6449 /* .doc-container takes the shared reading column near the top of this file. */
6450
6451 .doc-breadcrumb {
6452 font-family: var(--font-mono);
6453 font-size: var(--text-note);
6454 margin-bottom: var(--gap-page);
6455 }
6456
6457 .doc-breadcrumb a { color: var(--content); text-decoration: none; }
6458 .doc-breadcrumb a:hover { text-decoration: underline; }
6459
6460 .doc-title {
6461 font-family: var(--font-display);
6462 font-size: var(--text-display);
6463 font-weight: normal;
6464 line-height: 1.2;
6465 margin-bottom: var(--gap-pane);
6466 color: var(--content);
6467 text-align: left;
6468 }
6469
6470 .doc-body {
6471 font-family: var(--font-sans);
6472 font-size: var(--text-lead);
6473 line-height: 1.9;
6474 }
6475
6476 .doc-body p { margin-bottom: var(--gap-pane); }
6477
6478 .doc-body h1 {
6479 font-family: var(--font-display);
6480 font-size: var(--text-title);
6481 font-weight: normal;
6482 margin-top: var(--gap-page);
6483 margin-bottom: var(--gap-section);
6484 color: var(--content);
6485 text-align: left;
6486 }
6487
6488 .doc-body h2 {
6489 font-family: var(--font-mono);
6490 font-size: var(--text-head);
6491 font-weight: normal;
6492 margin-top: var(--gap-page);
6493 margin-bottom: var(--gap-section);
6494 color: var(--content);
6495 }
6496
6497 .doc-body h3 {
6498 font-family: var(--font-mono);
6499 font-size: var(--text-subhead);
6500 font-weight: normal;
6501 margin-top: var(--gap-page);
6502 margin-bottom: var(--gap-section);
6503 color: var(--content);
6504 }
6505
6506 .doc-body blockquote {
6507 border-left: 3px solid var(--action);
6508 padding-left: var(--gap-pane);
6509 margin: var(--gap-page) 0;
6510 font-style: italic;
6511 color: var(--content-muted);
6512 }
6513
6514 .doc-body ul, .doc-body ol { margin-bottom: var(--gap-pane); padding-left: var(--gap-pane); }
6515 .doc-body li { margin-bottom: var(--gap-peer); }
6516
6517 .doc-body a {
6518 color: var(--action);
6519 text-decoration: underline;
6520 text-decoration-color: color-mix(in oklch, var(--action) 40%, transparent);
6521 text-underline-offset: 2px;
6522 }
6523
6524 .doc-body a:hover { text-decoration-color: var(--action); }
6525 .doc-body strong { font-weight: bold; }
6526 .doc-body em { font-style: italic; }
6527
6528 .doc-body code {
6529 font-family: var(--font-mono);
6530 background: var(--surface-sunken);
6531 padding: var(--gap-bound) var(--gap-peer);
6532 font-size: var(--text-body);
6533 }
6534
6535 .doc-body pre {
6536 background: var(--surface-sunken);
6537 padding: var(--gap-section);
6538 overflow-x: auto;
6539 margin-bottom: var(--gap-pane);
6540 }
6541
6542 .doc-body pre code { background: none; padding: 0; }
6543
6544 .doc-body hr { border: none; text-align: center; margin: var(--gap-page) 0; }
6545 .doc-body hr::before { content: "* * *"; color: var(--content-muted); letter-spacing: 1rem; }
6546
6547 .doc-body table { width: 100%; border-collapse: collapse; margin-bottom: var(--gap-pane); }
6548 .doc-body th, .doc-body td { padding: var(--gap-section); text-align: left; border-bottom: 1px solid var(--border); }
6549 .doc-body th { font-family: var(--font-mono); font-weight: 600; }
6550
6551 .doc-backlinks {
6552 margin-top: var(--gap-page);
6553 padding-top: var(--gap-pane);
6554 border-top: 1px solid var(--border);
6555 }
6556
6557 .doc-backlinks-title {
6558 font-family: var(--font-mono);
6559 font-size: var(--text-note);
6560 font-weight: 600;
6561 text-transform: uppercase;
6562 letter-spacing: 0.05em;
6563 color: var(--content-muted);
6564 margin-bottom: var(--gap-section);
6565 }
6566
6567 .doc-backlinks-list {
6568 list-style: none;
6569 margin: 0;
6570 padding: 0;
6571 }
6572
6573 .doc-backlinks-list li { margin-bottom: var(--gap-peer); }
6574 .doc-backlinks-list a { color: var(--content); text-decoration: none; }
6575 .doc-backlinks-list a:hover { text-decoration: underline; }
6576
6577 /* Docs index page */
6578 /* .docs-index takes the shared reading column near the top of this file. */
6579
6580 .docs-index-title {
6581 font-family: var(--font-display);
6582 font-size: var(--text-display);
6583 font-weight: normal;
6584 line-height: 1.2;
6585 margin-bottom: var(--gap-page);
6586 color: var(--content);
6587 }
6588
6589 .docs-section {
6590 margin-bottom: var(--gap-page);
6591 }
6592
6593 .docs-section h2 {
6594 font-family: var(--font-mono);
6595 font-size: var(--text-subhead);
6596 font-weight: normal;
6597 color: var(--content-muted);
6598 margin-bottom: var(--gap-section);
6599 }
6600
6601 .docs-section ul {
6602 list-style: none;
6603 padding: 0;
6604 margin: 0;
6605 }
6606
6607 .docs-section li {
6608 margin-bottom: var(--gap-peer);
6609 }
6610
6611 .docs-section a {
6612 font-family: var(--font-sans);
6613 font-size: var(--text-lead);
6614 color: var(--content);
6615 text-decoration: none;
6616 }
6617
6618 .docs-section a:hover {
6619 color: var(--action);
6620 text-decoration: underline;
6621 }
6622
6623 .docs-section details {
6624 margin-bottom: var(--gap-section);
6625 }
6626
6627 .docs-section summary {
6628 font-family: var(--font-mono);
6629 font-size: var(--text-body);
6630 color: var(--content-muted);
6631 cursor: pointer;
6632 padding: var(--gap-bound) 0;
6633 list-style: disclosure-closed;
6634 }
6635
6636 .docs-section details[open] > summary {
6637 list-style: disclosure-open;
6638 margin-bottom: var(--gap-bound);
6639 }
6640
6641 .docs-section summary:hover {
6642 color: var(--content);
6643 }
6644
6645 .docs-section details ul {
6646 padding-left: var(--gap-section);
6647 }
6648
6649 /* Docs search */
6650 .docs-search-container {
6651 position: relative;
6652 margin-bottom: var(--gap-page);
6653 }
6654 .docs-search-inline {
6655 display: inline-block;
6656 margin-bottom: 0;
6657 margin-left: auto;
6658 float: right;
6659 }
6660 .docs-search-input {
6661 width: 100%;
6662 max-width: 320px;
6663 padding: var(--gap-peer) var(--gap-section);
6664 font-family: var(--font-sans);
6665 font-size: var(--text-body);
6666 background: var(--surface-sunken);
6667 border: 1px solid var(--border);
6668 color: var(--content);
6669 }
6670 .docs-search-input:focus {
6671 outline: 2px solid var(--action);
6672 border-color: var(--action);
6673 }
6674 .docs-search-input::placeholder { opacity: 0.5; }
6675 .docs-search-results {
6676 position: absolute;
6677 top: 100%;
6678 left: 0;
6679 right: 0;
6680 max-width: 320px;
6681 background: var(--surface-page);
6682 border: 1px solid var(--border);
6683 border-top: none;
6684 max-height: 300px;
6685 overflow-y: auto;
6686 z-index: var(--z-dropdown);
6687 box-shadow: var(--elevation-overlay);
6688 }
6689 .docs-search-result {
6690 display: flex;
6691 justify-content: space-between;
6692 align-items: baseline;
6693 padding: var(--gap-peer) var(--gap-section);
6694 text-decoration: none;
6695 color: var(--content);
6696 font-size: var(--text-note);
6697 border-bottom: 1px solid var(--border);
6698 }
6699 .docs-search-result:last-child { border-bottom: none; }
6700 .docs-search-result:hover { background: var(--surface-sunken); }
6701 .docs-search-result-title { font-family: var(--font-sans); }
6702 .docs-search-result-section { font-family: var(--font-mono); font-size: var(--text-fine); opacity: 0.5; }.alert .alert-title {
6703 font-family: var(--font-mono);
6704 font-size: var(--text-note);
6705 font-weight: bold;
6706 text-transform: uppercase;
6707 letter-spacing: 0.04em;
6708 margin-bottom: var(--gap-bound);
6709 }
6710 .alert p:last-child { margin-bottom: 0; }
6711 .alert-note { border-left-color: var(--focus-ring); }
6712 .alert-note .alert-title { color: var(--focus-ring); }
6713 .alert-tip { border-left-color: var(--success); }
6714 .alert-tip .alert-title { color: var(--success); }
6715 .alert-important { border-left-color: var(--action); }
6716 .alert-important .alert-title { color: var(--action); }
6717 .alert-warning { border-left-color: var(--warning); }
6718 .alert-warning .alert-title { color: var(--warning); }
6719 .alert-caution { border-left-color: var(--danger); }
6720 .alert-caution .alert-title { color: var(--danger); }
6721
6722 /* ===========================================
6723 USE CASES PAGE
6724 =========================================== */
6725
6726 .use-cases-page {
6727 max-width: 900px;
6728 margin: 0 auto;
6729 }
6730
6731 .use-cases-intro {
6732 font-size: var(--text-lead);
6733 margin-bottom: var(--gap-page);
6734 line-height: 1.6;
6735 }
6736
6737 .use-case-grid {
6738 display: grid;
6739 grid-template-columns: 1fr 1fr 1fr;
6740 gap: var(--gap-section);
6741 text-align: left;
6742 }
6743
6744 .use-case-grid-2col {
6745 grid-template-columns: 1fr 1fr;
6746 }
6747
6748 .use-case-title {
6749 font-family: var(--font-display);
6750 font-size: var(--text-lead);
6751 margin-bottom: var(--gap-bound);
6752 }
6753
6754 .use-case-who {
6755 font-family: var(--font-mono);
6756 font-size: var(--text-fine);
6757 opacity: 0.6;
6758 margin-bottom: var(--gap-peer);
6759 }
6760
6761 .use-case-desc {
6762 font-size: var(--text-note);
6763 line-height: 1.5;
6764 margin-bottom: var(--gap-peer);
6765 }
6766
6767 .use-case-features {
6768 list-style: none;
6769 margin: 0 0 var(--gap-section);
6770 padding: 0;
6771 }
6772
6773 .use-case-features li {
6774 font-size: var(--text-note);
6775 padding-left: var(--gap-section);
6776 position: relative;
6777 margin-bottom: var(--gap-bound);
6778 }
6779
6780 .use-case-features li::before {
6781 content: "-";
6782 position: absolute;
6783 left: 0;
6784 opacity: 0.5;
6785 }
6786
6787 .use-case-tier {
6788 font-family: var(--font-mono);
6789 font-size: var(--text-fine);
6790 opacity: 0.5;
6791 }
6792
6793 /* ===========================================
6794 RESPONSIVE, 768px (tablet)
6795 =========================================== */
6796
6797 /* ==========================================================================
6798 Git Source Browser
6799 ========================================================================== */
6800
6801 .git-repo-header { margin-bottom: var(--gap-pane); }
6802 .git-repo-name { font-size: var(--text-head); margin: 0 0 var(--gap-bound); }
6803 .git-repo-name a { color: var(--content); text-decoration: none; }
6804 .git-repo-name a:hover { opacity: 0.6; }
6805 .git-repo-name .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
6806 .git-repo-desc { opacity: 0.6; margin: 0 0 var(--gap-section); font-size: var(--text-note); }
6807 .git-clone-url {
6808 font-family: var(--font-mono);
6809 font-size: var(--text-fine);
6810 padding: var(--gap-peer) var(--gap-group);
6811 background: var(--surface-overlay);
6812 border: 1px solid var(--border);
6813 border-radius: var(--radius-control);
6814 display: inline-block;
6815 user-select: all;
6816 margin-bottom: var(--gap-section);
6817 }
6818 .git-linked-project { margin-top: var(--gap-peer); font-size: var(--text-note); }
6819 .git-linked-project a { color: var(--content); }
6820
6821 /* Ref bar (branch/tag selector + nav links) */
6822 .git-ref-bar {
6823 display: flex;
6824 align-items: center;
6825 gap: var(--gap-section);
6826 margin-bottom: var(--gap-section);
6827 font-size: var(--text-note);
6828 }
6829 .git-ref-select {
6830 font-family: var(--font-mono);
6831 font-size: var(--text-fine);
6832 padding: var(--gap-bound) var(--gap-peer);
6833 background: var(--surface-overlay);
6834 border: 1px solid var(--border);
6835 border-radius: var(--radius-control);
6836 }
6837 .git-nav-links { display: flex; gap: var(--gap-section); }
6838 .git-nav-links a { text-decoration: none; color: var(--content); opacity: 0.6; }
6839 .git-nav-links a:hover,
6840 .git-nav-links a.is-selected { opacity: 1; }
6841
6842 /* Breadcrumb navigation */
6843 .git-breadcrumb {
6844 font-family: var(--font-mono);
6845 font-size: var(--text-note);
6846 margin-bottom: var(--gap-section);
6847 }
6848 .git-breadcrumb a { color: var(--content); text-decoration: none; }
6849 .git-breadcrumb a:hover { text-decoration: underline; }
6850 .git-breadcrumb .sep { opacity: 0.3; margin: 0 var(--gap-bound); }
6851
6852 /* File tree table */
6853 .git-tree { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
6854 .git-tree th {
6855 text-align: left;
6856 padding: var(--gap-peer) var(--gap-group);
6857 border-bottom: 2px solid var(--border);
6858 font-family: var(--font-mono);
6859 font-size: var(--text-fine);
6860 opacity: 0.5;
6861 text-transform: uppercase;
6862 }
6863 .git-tree td { padding: var(--gap-peer) var(--gap-group); border-bottom: 1px solid var(--border); }
6864 .git-tree tr:last-child td { border-bottom: none; }
6865 .git-tree .icon { width: 1.5rem; text-align: center; opacity: 0.4; font-family: var(--font-mono); }
6866 .tree-icon-dir { font-weight: 700; color: var(--content); }
6867 .tree-icon-file { font-size: var(--text-note); }
6868 .git-tree .name a { text-decoration: none; color: var(--content); }
6869 .git-tree .name a:hover { text-decoration: underline; }
6870 .git-tree .size {
6871 text-align: right;
6872 opacity: 0.5;
6873 font-family: var(--font-mono);
6874 font-size: var(--text-fine);
6875 }
6876
6877 /* README rendering */
6878 .git-readme { margin-top: var(--gap-page); padding-top: var(--gap-pane); border-top: 1px solid var(--border); }
6879 .git-readme h2 { font-size: var(--text-body); opacity: 0.5; margin-bottom: var(--gap-section); }
6880 .git-readme-body { line-height: 1.6; }
6881 .git-readme-body h1,
6882 .git-readme-body h2,
6883 .git-readme-body h3 { margin-top: var(--gap-pane); margin-bottom: var(--gap-peer); }
6884 .git-readme-body pre {
6885 background: var(--surface-overlay);
6886 padding: var(--gap-section);
6887 border-radius: var(--radius-control);
6888 overflow-x: auto;
6889 font-family: var(--font-mono);
6890 font-size: var(--text-fine);
6891 }
6892 .git-readme-body code {
6893 font-family: var(--font-mono);
6894 font-size: var(--text-note);
6895 background: var(--surface-overlay);
6896 padding: var(--gap-bound);
6897 border-radius: var(--radius-fine);
6898 }
6899 .git-readme-body pre code { background: none; padding: 0; }
6900 .git-readme-body table { border-collapse: collapse; margin: var(--gap-section) 0; }
6901 .git-readme-body th,
6902 .git-readme-body td { border: 1px solid var(--border); padding: var(--gap-peer) var(--gap-group); }
6903
6904 /* File viewer */
6905 .git-file-header {
6906 display: flex;
6907 justify-content: space-between;
6908 align-items: center;
6909 padding: var(--gap-peer) var(--gap-group);
6910 background: var(--surface-overlay);
6911 border: 1px solid var(--border);
6912 border-bottom: none;
6913 border-radius: var(--radius-control) var(--radius-control) 0 0;
6914 font-size: var(--text-fine);
6915 }
6916 .git-file-meta {
6917 font-family: var(--font-mono);
6918 opacity: 0.6;
6919 }
6920 .git-file-actions a {
6921 font-family: var(--font-mono);
6922 text-decoration: none;
6923 color: var(--content);
6924 opacity: 0.6;
6925 font-size: var(--text-fine);
6926 }
6927 .git-file-actions a:hover { opacity: 1; }
6928 .git-file-content {
6929 border: 1px solid var(--border);
6930 border-radius: 0 0 var(--radius-control) var(--radius-control);
6931 overflow-x: auto;
6932 }
6933 .git-file-content table { border-collapse: collapse; width: 100%; }
6934 .git-file-content .line-number {
6935 width: 1px;
6936 white-space: nowrap;
6937 padding: 0 var(--gap-section);
6938 text-align: right;
6939 user-select: none;
6940 opacity: 0.3;
6941 font-family: var(--font-mono);
6942 font-size: var(--text-fine);
6943 border-right: 1px solid var(--border);
6944 vertical-align: top;
6945 }
6946 .git-file-content .line-code {
6947 padding: 0 var(--gap-section);
6948 white-space: pre;
6949 font-family: var(--font-mono);
6950 font-size: var(--text-fine);
6951 line-height: 1.5;
6952 }
6953 .git-file-content .line-code span {
6954 font-family: var(--font-mono);
6955 }
6956 .git-binary-notice {
6957 padding: var(--gap-page);
6958 text-align: center;
6959 opacity: 0.5;
6960 font-family: var(--font-mono);
6961 font-size: var(--text-note);
6962 border: 1px solid var(--border);
6963 border-radius: 0 0 var(--radius-control) var(--radius-control);
6964 }
6965
6966 /* Syntax highlighting (syntect class-based) */
6967 .git-file-content .source { color: var(--content); }
6968 .git-file-content .keyword,
6969 .git-file-content .storage { color: var(--syntax-keyword); }
6970 .git-file-content .string { color: var(--syntax-string); }
6971 .git-file-content .comment { color: var(--syntax-comment); font-style: italic; }
6972 .git-file-content .constant,
6973 .git-file-content .constant.numeric { color: var(--syntax-constant); }
6974 .git-file-content .entity.name { color: var(--syntax-entity); }
6975 .git-file-content .variable { color: var(--syntax-variable); }
6976 .git-file-content .support { color: var(--syntax-support); }
6977 .git-file-content .meta.preprocessor { color: var(--syntax-keyword); }
6978
6979 /* Commit list */
6980 .git-commit-list { list-style: none; }
6981 .git-commit { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
6982 .git-commit:last-child { border-bottom: none; }
6983 .git-commit-message { font-size: var(--text-note); margin: 0 0 var(--gap-bound); }
6984 .git-commit-message .summary { font-weight: 600; }
6985 .git-commit-meta {
6986 font-family: var(--font-mono);
6987 font-size: var(--text-fine);
6988 opacity: 0.5;
6989 display: flex;
6990 gap: var(--gap-section);
6991 flex-wrap: wrap;
6992 }
6993 .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); }
6994 .git-commit-oid a { color: var(--content); text-decoration: none; }
6995 .git-commit-oid a:hover { text-decoration: underline; }
6996 .git-pagination {
6997 display: flex;
6998 justify-content: center;
6999 gap: var(--gap-section);
7000 margin-top: var(--gap-pane);
7001 padding-top: var(--gap-section);
7002 font-size: var(--text-note);
7003 }
7004 .git-pagination a { color: var(--content); text-decoration: none; }
7005 .git-pagination a:hover { text-decoration: underline; }
7006
7007 /* Release items on repo page */
7008 .git-release { margin-bottom: var(--gap-pane); padding: var(--gap-section); background: var(--surface-overlay); }
7009 .git-release-title { margin: 0 0 var(--gap-bound); font-size: var(--text-lead); }
7010 .git-release-title a { color: var(--content); text-decoration: none; }
7011 .git-release-meta { font-size: var(--text-note); opacity: 0.6; margin-bottom: var(--gap-peer); }
7012 .git-release-desc { font-size: var(--text-note); opacity: 0.8; margin-bottom: var(--gap-section); }
7013 .git-release-versions { font-size: var(--text-fine); }
7014 .git-release-version {
7015 display: flex;
7016 gap: var(--gap-section);
7017 align-items: center;
7018 padding: var(--gap-bound) 0;
7019 border-top: 1px solid var(--border);
7020 }
7021 .git-release-version .version-number { font-family: var(--font-mono); }
7022 .git-release-version .version-badge {
7023 font-size: var(--text-fine);
7024 padding: var(--gap-bound) var(--gap-peer);
7025 background: var(--content);
7026 color: var(--surface-page);
7027 border-radius: var(--radius-fine);
7028 }
7029 .git-release-version .version-meta { opacity: 0.5; }
7030
7031 /* Git commit detail + diff */
7032 .git-commit-detail { margin-bottom: var(--gap-pane); }
7033 .git-commit-full-message {
7034 font-family: var(--font-mono);
7035 font-size: var(--text-note);
7036 white-space: pre-wrap;
7037 line-height: 1.5;
7038 padding: var(--gap-section);
7039 background: var(--surface-overlay);
7040 border: 1px solid var(--border);
7041 margin-bottom: var(--gap-section);
7042 }
7043 .git-commit-detail-meta { font-size: var(--text-note); opacity: 0.7; line-height: 1.8; }
7044 .git-commit-detail-meta .git-commit-oid { font-family: var(--font-mono); font-size: var(--text-fine); word-break: break-all; }
7045 .git-commit-parents a { font-family: var(--font-mono); color: var(--content); text-decoration: none; }
7046 .git-commit-parents a:hover { text-decoration: underline; }
7047 /* Commit trailers, lifted out of the message body and shown as what they are */
7048 .git-commit-trailers {
7049 display: grid;
7050 grid-template-columns: max-content 1fr;
7051 gap: var(--gap-bound) var(--gap-section);
7052 font-size: var(--text-note);
7053 margin-bottom: var(--gap-section);
7054 }
7055 .git-commit-trailers dt { opacity: 0.6; }
7056 .git-commit-trailers dd { margin: 0; font-family: var(--font-mono); font-size: var(--text-fine); }
7057 /* Tags, with the annotation an annotated tag carries */
7058 .git-tag-list { list-style: none; }
7059 .git-tag { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7060 .git-tag:last-child { border-bottom: none; }
7061 .git-tag-name { font-size: var(--text-note); font-weight: 600; margin: 0 0 var(--gap-bound); }
7062 .git-tag-name a { color: var(--content); text-decoration: none; }
7063 .git-tag-name a:hover { text-decoration: underline; }
7064 .git-tag-lightweight { font-weight: 400; font-size: var(--text-fine); opacity: 0.5; }
7065 .git-tag-message {
7066 font-family: var(--font-mono);
7067 font-size: var(--text-note);
7068 white-space: pre-wrap;
7069 line-height: 1.5;
7070 padding: var(--gap-peer) var(--gap-section);
7071 border-left: 2px solid var(--border);
7072 margin-bottom: var(--gap-bound);
7073 }
7074 .git-tag-meta {
7075 font-family: var(--font-mono);
7076 font-size: var(--text-fine);
7077 opacity: 0.5;
7078 display: flex;
7079 gap: var(--gap-section);
7080 flex-wrap: wrap;
7081 }
7082 .git-tag-meta a { color: var(--content); text-decoration: none; }
7083 .git-tag-meta a:hover { text-decoration: underline; }
7084 /* Signature state. Neutral by default: only a bad signature gets a colour. */
7085 .git-signature { font-size: var(--text-fine); opacity: 0.7; }
7086 .git-signature a { color: var(--content); }
7087 .git-signature.is-verified { opacity: 1; }
7088 .git-signature.is-bad { color: var(--danger); opacity: 1; }
7089 /* refs/replace/*: object substitutions, listed but not applied while browsing */
7090 .git-replace-intro { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7091 .git-replace-intro p { margin: 0; }
7092 .git-replace-notice {
7093 font-size: var(--text-note);
7094 border-left: 2px solid var(--border);
7095 padding: var(--gap-peer) var(--gap-section);
7096 margin: 0 0 var(--gap-section);
7097 }
7098 .git-replace-table { width: 100%; border-collapse: collapse; font-size: var(--text-note); }
7099 .git-replace-table th {
7100 text-align: left;
7101 font-weight: 400;
7102 opacity: 0.5;
7103 font-size: var(--text-fine);
7104 padding: var(--gap-bound) var(--gap-section) var(--gap-bound) 0;
7105 border-bottom: 1px solid var(--border);
7106 }
7107 .git-replace-table td { padding: var(--gap-peer) var(--gap-section) var(--gap-peer) 0; border-bottom: 1px solid var(--border); }
7108 .git-replace-table a { color: var(--content); text-decoration: none; }
7109 .git-replace-table a:hover { text-decoration: underline; }
7110 /* Git notes (refs/notes/*) shown against the object they annotate */
7111 .git-notes { margin-bottom: var(--gap-pane); }
7112 .git-note {
7113 border: 1px solid var(--border);
7114 border-left: 2px solid var(--content);
7115 margin-bottom: var(--gap-section);
7116 }
7117 .git-note-header {
7118 display: flex;
7119 align-items: baseline;
7120 justify-content: space-between;
7121 gap: var(--gap-group);
7122 padding: var(--gap-peer) var(--gap-section);
7123 border-bottom: 1px solid var(--border);
7124 font-size: var(--text-fine);
7125 }
7126 .git-note-namespace { font-family: var(--font-mono); }
7127 /* Sits beside the namespace rather than being spread across the header: the
7128 auto margin absorbs the free space `space-between` would otherwise put
7129 between the two, leaving the attribution on the right where it was. */
7130 .git-note-owned { margin-right: auto; opacity: 0.6; }
7131 .git-note-attribution { opacity: 0.6; }
7132 .git-note-namespaces {
7133 display: flex;
7134 flex-wrap: wrap;
7135 gap: var(--gap-group);
7136 padding: var(--gap-section) 0;
7137 border-bottom: 1px solid var(--border);
7138 margin-bottom: var(--gap-section);
7139 font-size: var(--text-note);
7140 }
7141 .git-note-namespaces a { color: var(--content); text-decoration: none; opacity: 0.6; }
7142 .git-note-namespaces a:hover { opacity: 1; }
7143 .git-note-namespaces a.is-selected { opacity: 1; text-decoration: underline; }
7144 .git-note-target-kind { opacity: 0.6; }
7145 .git-explore-intro { margin: 0 0 var(--gap-section); font-size: var(--text-note); opacity: 0.8; }
7146 .git-notes-fetch { padding-bottom: var(--gap-section); font-size: var(--text-note); }
7147 .git-notes-fetch p { margin: 0 0 var(--gap-peer); }
7148 .git-note-feed-cap { font-size: var(--text-fine); opacity: 0.6; padding-top: var(--gap-section); }
7149 .git-note-search {
7150 display: flex;
7151 flex-wrap: wrap;
7152 align-items: center;
7153 gap: var(--gap-group);
7154 padding-bottom: var(--gap-section);
7155 font-size: var(--text-note);
7156 }
7157 .git-note-search input[type="search"] { flex: 1 1 16rem; }
7158 .git-note-search label { display: flex; align-items: center; gap: var(--gap-bound); opacity: 0.8; }
7159 .git-note-excerpt {
7160 margin: var(--gap-bound) 0 0;
7161 font-size: var(--text-note);
7162 opacity: 0.7;
7163 }
7164 .git-note-paging {
7165 display: flex;
7166 gap: var(--gap-group);
7167 align-items: center;
7168 padding-top: var(--gap-section);
7169 font-size: var(--text-note);
7170 }
7171 .git-note-paging span { font-size: var(--text-fine); opacity: 0.6; }
7172 .git-note-feed-link { padding-top: var(--gap-peer); font-size: var(--text-fine); }
7173 .git-note-badge {
7174 font-size: var(--text-fine);
7175 padding: var(--gap-bound) var(--gap-peer);
7176 border: 1px solid var(--border);
7177 border-radius: var(--radius-fine);
7178 color: var(--content);
7179 text-decoration: none;
7180 opacity: 0.7;
7181 }
7182 .git-note-badge:hover { opacity: 1; }
7183 .git-note-body { padding: var(--gap-section); font-size: var(--text-note); }
7184 .git-note-body > :first-child { margin-top: 0; }
7185 .git-note-body > :last-child { margin-bottom: 0; }
7186 /* Writing a note. Only rendered for a reader who can push. */
7187 .git-notes-edit {
7188 border-top: 1px solid var(--border);
7189 padding-top: var(--gap-section);
7190 margin-bottom: var(--gap-pane);
7191 }
7192 .git-notes-edit textarea { width: 100%; font-family: var(--font-mono); font-size: var(--text-note); }
7193 .git-note-form { margin-bottom: var(--gap-section); }
7194 .git-note-delete { margin-bottom: var(--gap-pane); }
7195 .git-note-merged {
7196 border-left: 2px solid var(--content);
7197 padding: var(--gap-peer) var(--gap-section);
7198 margin-bottom: var(--gap-section);
7199 font-size: var(--text-note);
7200 }
7201 /* A reader's own annotation, which lives in their repository rather than this one. */
7202 .git-annotation {
7203 border-top: 1px solid var(--border);
7204 padding-top: var(--gap-section);
7205 margin-bottom: var(--gap-pane);
7206 }
7207 .git-annotation textarea { width: 100%; font-family: var(--font-mono); font-size: var(--text-note); }
7208 .git-annotations {
7209 border-top: 1px solid var(--border);
7210 padding-top: var(--gap-section);
7211 margin-bottom: var(--gap-pane);
7212 }
7213 .git-annotations-heading { font-size: var(--text-note); margin: 0 0 var(--gap-peer); }
7214 .git-annotations-privacy,
7215 .git-annotations-empty,
7216 .git-annotations-count { font-size: var(--text-note); color: var(--content-muted); }
7217 .git-annotations-list { list-style: none; margin: 0; padding: 0; }
7218 .git-annotation-entry {
7219 border: 1px solid var(--border);
7220 padding: var(--gap-section);
7221 margin-bottom: var(--gap-section);
7222 }
7223 .git-annotation-header {
7224 display: flex;
7225 gap: var(--gap-peer);
7226 flex-wrap: wrap;
7227 align-items: baseline;
7228 font-size: var(--text-note);
7229 margin-bottom: var(--gap-peer);
7230 }
7231 .git-annotation-target,
7232 .git-annotation-namespace,
7233 .git-annotation-when { font-family: var(--font-mono); color: var(--content-muted); }
7234 .git-annotation-orphan { font-size: var(--text-note); color: var(--content-muted); }
7235 .git-explore-mine { font-size: var(--text-note); }
7236 .git-diff-stats {
7237 font-size: var(--text-note);
7238 padding: var(--gap-section) 0;
7239 margin-bottom: var(--gap-section);
7240 border-bottom: 1px solid var(--border);
7241 }
7242 .git-diff-stats .additions { color: var(--diff-add); }
7243 .git-diff-stats .deletions { color: var(--diff-del); }
7244 .git-diff-file { margin-bottom: var(--gap-pane); border: 1px solid var(--border); }
7245 .git-diff-file-header {
7246 display: flex;
7247 align-items: center;
7248 gap: var(--gap-peer);
7249 padding: var(--gap-peer) var(--gap-section);
7250 background: var(--surface-overlay);
7251 border-bottom: 1px solid var(--border);
7252 font-size: var(--text-note);
7253 flex-wrap: wrap;
7254 }
7255 .git-diff-file-header a { color: var(--content); text-decoration: none; }
7256 .git-diff-file-header a:hover { text-decoration: underline; }
7257 .git-diff-file-stats { margin-left: auto; font-size: var(--text-fine); }
7258 .git-diff-file-stats .additions { color: var(--diff-add); }
7259 .git-diff-file-stats .deletions { color: var(--diff-del); margin-left: var(--gap-bound); }
7260 .diff-status-added, .diff-status-deleted, .diff-status-modified, .diff-status-renamed {
7261 display: inline-block;
7262 width: 1.2rem;
7263 text-align: center;
7264 font-size: var(--text-fine);
7265 font-weight: 700;
7266 font-family: var(--font-mono);
7267 border-radius: var(--radius-fine);
7268 line-height: 1.4;
7269 }
7270 .diff-status-added { background: color-mix(in oklch, var(--success) 14%, var(--surface-page)); color: var(--success); }
7271 .diff-status-deleted { background: color-mix(in oklch, var(--danger) 14%, var(--surface-page)); color: var(--danger); }
7272 .diff-status-modified { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
7273 .diff-status-renamed { background: color-mix(in oklch, var(--info) 14%, var(--surface-page)); color: var(--info); }
7274 .git-diff-table {
7275 width: 100%;
7276 border-collapse: collapse;
7277 font-family: var(--font-mono);
7278 font-size: var(--text-fine);
7279 table-layout: fixed;
7280 }
7281 .git-diff-hunk-header td {
7282 padding: var(--gap-bound) var(--gap-peer);
7283 background: var(--highlight-faint);
7284 color: var(--content);
7285 opacity: 0.6;
7286 font-size: var(--text-fine);
7287 }
7288 .git-diff-lineno {
7289 width: 3.5rem;
7290 padding: 0 var(--gap-peer);
7291 text-align: right;
7292 color: var(--content);
7293 opacity: 0.35;
7294 user-select: none;
7295 vertical-align: top;
7296 }
7297 .git-diff-origin {
7298 width: 1rem;
7299 text-align: center;
7300 user-select: none;
7301 vertical-align: top;
7302 }
7303 .git-diff-content {
7304 padding: 0 var(--gap-peer);
7305 white-space: pre-wrap;
7306 word-break: break-all;
7307 }
7308 .git-diff-line-add { background: var(--diff-add-bg); }
7309 .git-diff-line-add .git-diff-origin { color: var(--diff-add); }
7310 .git-diff-line-del { background: var(--diff-del-bg); }
7311 .git-diff-line-del .git-diff-origin { color: var(--diff-del); }
7312 .git-diff-line-ctx { background: transparent; }
7313 .git-diff-truncated { padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.5; font-style: italic; }
7314
7315 /* Git file view line linking */
7316 .git-file-content .line-number a {
7317 color: inherit;
7318 text-decoration: none;
7319 display: block;
7320 }
7321 .git-file-content .line-number a:hover { opacity: 0.7; }
7322 .git-file-content tr:target td,
7323 .git-file-content tr.highlighted td { background: var(--highlight-faint); }
7324
7325 /* Git blame view */
7326 .git-blame-content { overflow-x: auto; border: 1px solid var(--border); }
7327 .git-blame-content table { border-collapse: collapse; width: 100%; font-size: var(--text-fine); }
7328 .git-blame-info {
7329 padding: 0 var(--gap-peer);
7330 font-family: var(--font-mono);
7331 white-space: nowrap;
7332 width: 4.5rem;
7333 }
7334 .git-blame-info a { color: var(--content); text-decoration: none; }
7335 .git-blame-info a:hover { text-decoration: underline; }
7336 .git-blame-author {
7337 padding: 0 var(--gap-bound);
7338 font-size: var(--text-fine);
7339 opacity: 0.5;
7340 white-space: nowrap;
7341 max-width: 8rem;
7342 overflow: hidden;
7343 text-overflow: ellipsis;
7344 }
7345 .git-blame-date {
7346 padding: 0 var(--gap-bound);
7347 font-size: var(--text-fine);
7348 opacity: 0.35;
7349 white-space: nowrap;
7350 width: 5.5rem;
7351 }
7352 .git-blame-content .line-number {
7353 padding: 0 var(--gap-peer);
7354 text-align: right;
7355 opacity: 0.35;
7356 user-select: none;
7357 width: 3rem;
7358 font-family: var(--font-mono);
7359 }
7360 .git-blame-content .line-code {
7361 padding: 0 var(--gap-peer);
7362 white-space: pre;
7363 font-family: var(--font-mono);
7364 }
7365 .git-blame-boundary td { border-top: 1px solid var(--border); }
7366
7367 /* Git user repos listing */
7368 .git-repos-list { list-style: none; }
7369 .git-repos-item { padding: var(--gap-section) 0; border-bottom: 1px solid var(--border); }
7370 .git-repos-item:last-child { border-bottom: none; }
7371 .git-repos-item-header { display: flex; align-items: center; gap: var(--gap-peer); }
7372 .git-repos-item-name {
7373 font-size: var(--text-lead);
7374 font-weight: 600;
7375 color: var(--content);
7376 text-decoration: none;
7377 }
7378 .git-repos-item-name:hover { text-decoration: underline; }
7379 .git-repos-visibility {
7380 font-size: var(--text-fine);
7381 padding: var(--gap-bound) var(--gap-peer);
7382 border: 1px solid var(--border);
7383 border-radius: var(--radius-fine);
7384 opacity: 0.5;
7385 }
7386 .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); }
7387 .git-repos-owner { opacity: 0.6; }
7388 .git-repos-item-name .sep { opacity: 0.4; }
7389 .git-file-log-label {
7390 font-family: var(--font-mono);
7391 font-size: var(--text-fine);
7392 opacity: 0.5;
7393 margin-right: var(--gap-bound);
7394 }
7395
7396 /* Site-wide footer */
7397 .site-footer {
7398 margin-top: var(--gap-page);
7399 padding: var(--gap-page) 0;
7400 text-align: center;
7401 font-family: var(--font-mono);
7402 font-size: var(--text-fine);
7403 opacity: 0.5;
7404 }
7405 .site-footer-links { margin-bottom: var(--gap-peer); }
7406 .site-footer-links a { margin: 0 var(--gap-section); color: inherit; text-decoration: none; }
7407 .site-footer-links a:hover { text-decoration: underline; }
7408
7409 @media (max-width: 839px) {
7410 /* Git browser mobile */
7411 .git-ref-bar { flex-wrap: wrap; gap: var(--gap-peer); }
7412 .git-repo-name { font-size: var(--text-subhead); }
7413 .git-clone-url { font-size: var(--text-fine); word-break: break-all; }
7414 .git-tree .icon { display: none; }
7415 .git-tree td, .git-tree th { padding: var(--gap-bound) var(--gap-peer); }
7416 .git-file-header { flex-direction: column; align-items: flex-start; gap: var(--gap-bound); }
7417 .git-file-content .line-number { padding: 0 var(--gap-peer); }
7418 .git-file-content .line-code { padding: 0 var(--gap-peer); font-size: var(--text-fine); }
7419 .git-commit-meta { flex-direction: column; gap: var(--gap-bound); }
7420 .git-release-version { flex-wrap: wrap; gap: var(--gap-peer); }
7421 .git-diff-file-header { font-size: var(--text-fine); }
7422 .git-diff-table { font-size: var(--text-fine); }
7423 .git-diff-lineno { width: 2.5rem; font-size: var(--text-fine); }
7424 .git-blame-author { display: none; }
7425 .git-blame-date { display: none; }
7426
7427 .container {
7428 padding: var(--gap-section);
7429 }
7430
7431 .padded-page {
7432 padding: var(--gap-section);
7433 }
7434
7435 .form-row {
7436 grid-template-columns: 1fr;
7437 }
7438
7439 .tab {
7440 padding: var(--gap-group) var(--gap-section);
7441 font-size: var(--text-note);
7442 }
7443
7444 .tab-content {
7445 padding: var(--gap-section);
7446 }
7447
7448 .data-table th,
7449 .data-table td {
7450 padding: var(--gap-peer) var(--gap-group);
7451 font-size: var(--text-note);
7452 }
7453
7454 .modal {
7455 max-width: 95%;
7456 }
7457
7458 .stats-grid {
7459 grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
7460 }
7461
7462 .content-section {
7463 padding: var(--gap-section);
7464 }
7465
7466 .action-buttons {
7467 flex-direction: column;
7468 }
7469
7470 .action-buttons button {
7471 width: 100%;
7472 }
7473
7474 .analytics-grid {
7475 grid-template-columns: 1fr;
7476 }
7477
7478 /* Hamburger menu */
7479 .nav-toggle-label {
7480 display: block;
7481 }
7482
7483 .header-search {
7484 display: none;
7485 flex: none;
7486 margin: 0;
7487 width: 100%;
7488 }
7489
7490 .nav-toggle-checkbox:checked ~ .header-search {
7491 display: block;
7492 position: absolute;
7493 top: 100%;
7494 left: 0;
7495 right: 0;
7496 padding: var(--gap-section) var(--gap-pane);
7497 background: var(--surface-page);
7498 border-bottom: 1px solid var(--border);
7499 z-index: var(--z-header);
7500 }
7501
7502 .site-header nav {
7503 display: none;
7504 position: absolute;
7505 top: 100%;
7506 left: 0;
7507 right: 0;
7508 background: var(--surface-page);
7509 border-bottom: 1px solid var(--border);
7510 padding: var(--gap-section) var(--gap-pane);
7511 z-index: var(--z-nav);
7512 }
7513
7514 .site-header {
7515 position: relative;
7516 }
7517
7518 .nav-toggle-checkbox:checked ~ nav {
7519 display: block;
7520 }
7521
7522 .site-header .nav-links {
7523 flex-direction: column;
7524 gap: var(--gap-section);
7525 }
7526
7527 /* Animate hamburger to X when open */
7528 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(1) {
7529 transform: rotate(45deg) translate(5px, 5px);
7530 }
7531
7532 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(2) {
7533 opacity: 0;
7534 }
7535
7536 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(3) {
7537 transform: rotate(-45deg) translate(5px, -5px);
7538 }
7539 }
7540
7541 /* Doc pages, mobile */
7542 @media (max-width: 599px) {
7543 .doc-title { font-size: var(--text-title); }
7544 .doc-body { font-size: var(--text-body); }
7545 .docs-index-title { font-size: var(--text-title); }
7546 }
7547
7548 /* ===========================================
7549 RESPONSIVE, 480px (phone)
7550 =========================================== */
7551
7552 @media (max-width: 599px) {
7553 .container {
7554 padding: var(--gap-section);
7555 }
7556
7557 .padded-page {
7558 padding: var(--gap-section);
7559 }
7560
7561 h1 {
7562 font-size: var(--text-title);
7563 }
7564
7565 .centered-page h1 {
7566 font-size: var(--text-hero);
7567 }
7568
7569 .login-container,
7570 .form-container {
7571 max-width: 100%;
7572 padding: var(--gap-section);
7573 }
7574
7575 .big-button {
7576 width: 100%;
7577 }
7578
7579 .stats-grid {
7580 grid-template-columns: repeat(2, 1fr);
7581 }
7582
7583 .tab {
7584 padding: var(--gap-peer) var(--gap-section);
7585 font-size: var(--text-note);
7586 }
7587
7588 .tab-content {
7589 padding: var(--gap-section);
7590 }
7591
7592 .content-section {
7593 padding: var(--gap-section);
7594 }
7595
7596 .section-header {
7597 flex-direction: column;
7598 align-items: flex-start;
7599 gap: var(--gap-section);
7600 }
7601
7602 /* Landing page (merged from 500px) */
7603 .landing-fork,
7604 .tier-selector,
7605 .feature-grid,
7606 .use-case-grid,
7607 .use-case-grid-2col {
7608 grid-template-columns: 1fr;
7609 }
7610
7611 .explore-links {
7612 flex-direction: column;
7613 align-items: center;
7614 gap: var(--gap-section);
7615 }
7616
7617 .pricing-input {
7618 font-size: var(--text-title);
7619 }
7620
7621 .pricing-currency {
7622 font-size: var(--text-title);
7623 }
7624
7625 .calc-verdict {
7626 font-size: var(--text-body);
7627 }
7628
7629 .calc-dials {
7630 grid-template-columns: 1fr;
7631 }
7632
7633 .secondary-links {
7634 flex-direction: column;
7635 align-items: center;
7636 gap: var(--gap-section);
7637 }
7638
7639 .landing-cta {
7640 flex-direction: column;
7641 gap: var(--gap-section);
7642 }
7643
7644 .toast-container {
7645 left: 0.75rem;
7646 right: 0.75rem;
7647 bottom: 0.75rem;
7648 }
7649
7650 .toast {
7651 max-width: 100%;
7652 }
7653 }
7654
7655 /* ===========================================
7656 DISCOVER PAGE
7657 =========================================== */
7658
7659 .discover-layout { display: grid; grid-template-columns: 200px 1fr; gap: var(--gap-pane); }
7660 .discover-layout.no-sidebar { grid-template-columns: 1fr; }
7661 .discover-layout.no-sidebar .discover-sidebar { display: none; }
7662 .discover-sidebar { font-size: var(--text-note); }
7663 .filter-section { margin-bottom: var(--gap-section); }
7664 .filter-title { font-family: var(--font-display); font-weight: bold; font-size: var(--text-note); margin-bottom: var(--gap-peer); color: var(--content); }
7665 .filter-list { list-style: none; }
7666 /* The row carries selection (`.is-selected` recipe) and hover; the filter itself
7667 is a real <button> child so Enter/Space work without scripting. */
7668 .filter-item { display: flex; align-items: center; gap: var(--gap-bound); transition: opacity 0.1s ease; }
7669 .filter-item:hover { opacity: 0.6; }
7670 .filter-item.is-selected { font-weight: bold; }
7671 .filter-item .count { opacity: 0.4; font-size: var(--text-fine); }
7672 .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); }
7673 .tag-follow-btn:hover { opacity: 1; }
7674 .tag-follow-btn.is-selected { opacity: 0.85; border-color: var(--focus-ring); background: var(--highlight-faint); }
7675 .filter-checkbox { display: flex; align-items: center; gap: var(--gap-peer); padding: var(--gap-bound) 0; cursor: pointer; }
7676 .filter-checkbox input { width: auto; margin: 0; }
7677 .price-inputs { display: flex; gap: var(--gap-bound); align-items: center; margin-bottom: var(--gap-peer); }
7678 .price-inputs input { width: 60px; padding: var(--gap-bound); font-size: var(--text-fine); background: var(--surface-sunken); border: none; }
7679 .price-inputs span { opacity: 0.4; }
7680 .discover-main { min-width: 0; }
7681
7682 /* ---- Discover sidebar: tag spine ----------------------------------------
7683 Tags are the sidebar's structure, not one facet among several, so the spine
7684 reads as the primary surface and .filter-refine below it is subordinate.
7685 Everything here composes existing primitives: .filter-item carries the row
7686 and its .is-selected recipe, the described field draws its own list, and
7687 .breadcrumb carries the crumb idiom. */
7688 .tag-spine { padding-bottom: var(--gap-section); border-bottom: 1px solid var(--border); }
7689
7690 /* Selected tags. Also the active-filter summary, so they use the canonical
7691 selected tint rather than a new colour. */
7692 .tag-chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--gap-bound); margin-bottom: var(--gap-section); }
7693 .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); }
7694 .tag-chip-label { overflow-wrap: anywhere; }
7695 .tag-chip-remove { text-decoration: none; color: var(--content-secondary); font-size: var(--text-body); line-height: 1; padding: 0 var(--gap-bound); }
7696 .tag-chip-remove:hover { color: var(--content); }
7697 .tag-chip-clear { background: none; border: 1px dashed var(--border-strong); }
7698 .tag-chip-clear a { text-decoration: none; color: var(--content-secondary); font-size: var(--text-fine); }
7699 .tag-chip-clear a:hover { color: var(--content); }
7700
7701 /* Typeahead. The box, the list and everything that opens and closes it are
7702 the described field's, so all this carries is the positioning context the
7703 list is drawn against. */
7704 .tag-combobox { position: relative; margin-bottom: var(--gap-section); }
7705
7706 /* Drill-down. A rung either selects (assignable leaf) or navigates (category);
7707 rows with both put the chevron after the label. */
7708 .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); }
7709 .tag-crumbs li + li::before { content: "/"; opacity: 0.4; margin: 0 var(--gap-bound); }
7710 .tag-crumbs a { color: var(--content); text-decoration: none; }
7711 .tag-crumbs a:hover { text-decoration: underline; }
7712 .tag-drill-select { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7713 .tag-drill-select input { width: auto; margin: 0; }
7714 .tag-drill-select span:first-of-type { flex: 1; }
7715 .tag-drill-into { display: flex; align-items: center; gap: var(--gap-peer); flex: 1; text-decoration: none; color: inherit; padding: var(--gap-bound) 0; }
7716 .tag-drill-into span:first-child { flex: 1; }
7717 .tag-drill-chevron { opacity: 0.4; }
7718
7719 /* ---- Discover sidebar: refine block -------------------------------------
7720 Subordinate to the spine: same controls, quieter. */
7721 .filter-refine { margin-top: var(--gap-section); }
7722 .filter-fieldset { border: none; padding: 0; margin: 0; min-width: 0; }
7723 .filter-check { flex: 1; display: flex; align-items: center; gap: var(--gap-peer); cursor: pointer; padding: var(--gap-bound) 0; }
7724 .filter-check input { width: auto; margin: 0; }
7725 .filter-check span:first-of-type { flex: 1; }
7726
7727 /* A zero-count facet stays visible but inert, so the shape of the catalog
7728 stays legible as filters narrow. No existing rule covers a disabled input
7729 inside a label, so this is the checkbox counterpart of the button rule.
7730 `.is-empty` covers the drill rungs, which are links and carry no input at
7731 all; they were rendering identically to rungs with items. */
7732 .filter-item:has(input:disabled), .filter-item.is-empty { opacity: 0.5; }
7733 .filter-item:has(input:disabled) .filter-check { cursor: not-allowed; }
7734
7735 /* Price buckets are links carrying a range, not form state. */
7736 .price-buckets { margin-top: var(--gap-peer); }
7737 .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; }
7738
7739 .mode-toggle { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
7740 .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; }
7741 .toggle-btn:hover { opacity: 0.7; }
7742 .toggle-btn.is-selected { background: var(--primary-dark); color: var(--primary-light); }
7743 .table-controls { background: var(--surface-sunken); display: flex; align-items: center; gap: var(--gap-section); padding: var(--gap-peer) var(--gap-section); }
7744 .table-meta { font-size: var(--text-fine); opacity: 0.6; white-space: nowrap; flex-shrink: 0; }
7745 .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; }
7746 .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; }
7747 .table-header.projects-header { grid-template-columns: 1fr 100px 80px 70px; }
7748 /* Column headers describe the list layout only; hidden when the grid view is active. */
7749 .table-header.is-hidden { display: none; }
7750 .col-right { text-align: right; }
7751 .results-table { border: 1px solid var(--border); border-top: none; }
7752 .table-row { /* respec-ok: a five-column grid template. The generated `.table-row` sets a display
7753 but has no vocabulary for column tracks, so the tracks and the display that carries
7754 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; }
7755 .table-row-item { grid-template-columns: 1fr 30px; padding: 0; gap: 0; }
7756 .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; }
7757 .table-row-item .row-save { display: flex; align-items: center; justify-content: center; }
7758 .table-row.project-row { grid-template-columns: 1fr 100px 80px 70px; }
7759 .table-row:last-child { border-bottom: none; }
7760 .table-row:hover { background: var(--surface-sunken); text-decoration: none; }
7761 .row-type { font-size: var(--text-fine); background: var(--surface-sunken); padding: var(--gap-bound) var(--gap-peer); text-align: center; opacity: 0.7; }
7762 .row-info { min-width: 0; display: flex; flex-direction: column; gap: var(--gap-bound); }
7763 .row-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
7764 .row-creator { font-size: var(--text-fine); opacity: 0.5; }.row-category { font-size: var(--text-fine); opacity: 0.6; }
7765 .row-price { text-align: right; }
7766 .row-items { text-align: right; opacity: 0.6; font-size: var(--text-fine); }
7767 .row-date { text-align: right; opacity: 0.5; font-size: var(--text-fine); }.pagination { display: flex; gap: var(--gap-bound); }
7768 .pagination button { background: var(--surface-page); border: none; padding: var(--gap-bound) var(--gap-group); font-size: var(--text-fine); cursor: pointer; }
7769 .pagination button:disabled { opacity: 0.3; cursor: not-allowed; }
7770 .pagination button.is-selected { background: var(--primary-dark); color: var(--primary-light); }
7771
7772 /* Issue tabs (open / closed) inside the git browser */
7773 .issue-tabs { display: flex; gap: var(--gap-peer); margin-bottom: var(--gap-section); }
7774 .issue-tab {
7775 padding: var(--gap-peer) var(--gap-section);
7776 border: 1px solid var(--border);
7777 background: var(--surface-overlay);
7778 font-family: var(--font-mono);
7779 font-size: var(--text-note);
7780 color: var(--content);
7781 text-decoration: none;
7782 opacity: 0.7;
7783 }
7784 .issue-tab:hover { opacity: 1; }
7785 .issue-tab.is-selected {
7786 opacity: 1;
7787 background: var(--highlight-faint);
7788 border-color: var(--focus-ring);
7789 }
7790 .page-info { opacity: 0.6; }
7791 .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); }
7792 .padded-page .page-header h1 { font-size: var(--text-head); text-align: center; position: absolute; left: 50%; transform: translateX(-50%); }
7793 .padded-page .page-header nav { margin: 0; }
7794 .padded-page .page-header .nav-links { gap: var(--gap-pane); }
7795 .discover-filter-toggle {
7796 display: none;
7797 font-family: var(--font-mono);
7798 font-size: var(--text-note);
7799 padding: var(--gap-peer) var(--gap-section);
7800 background: var(--surface-overlay);
7801 border: 1px solid var(--border);
7802 cursor: pointer;
7803 margin-bottom: var(--gap-section);
7804 }
7805
7806 .discover-filter-toggle.active { background: var(--surface-sunken); border-color: var(--border-strong); }
7807
7808 .discover-filter-toggle .filter-count {
7809 background: var(--action);
7810 color: var(--primary-light);
7811 font-size: var(--text-fine);
7812 padding: var(--gap-bound) var(--gap-peer);
7813 border-radius: var(--radius-control);
7814 margin-left: var(--gap-bound);
7815 }
7816
7817 @media (max-width: 839px) {
7818 .discover-filter-toggle { display: inline-block; }
7819 .discover-layout { grid-template-columns: 1fr; }
7820 .discover-sidebar { display: none; }
7821 /* Open state lives on the layout, not the sidebar: the sidebar is replaced
7822 by an out-of-band swap on every filter change and would lose the class. */
7823 .discover-layout.filters-open .discover-sidebar { display: block; margin-bottom: var(--gap-section); }
7824 .table-header, .table-row { grid-template-columns: 40px 1fr 60px; }
7825 .table-row-item { grid-template-columns: 1fr 30px; }
7826 .table-row-item .table-row-link { grid-template-columns: 40px 1fr 60px; }
7827 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr 70px; }
7828 .row-date, .row-items, .row-category { display: none; }
7829 .table-header span:nth-child(3), .table-header span:nth-child(4), .table-header span:nth-child(5) { display: none; }
7830 .table-header.projects-header span:nth-child(3), .table-header.projects-header span:nth-child(4) { display: none; }
7831 }
7832
7833 @media (max-width: 599px) {
7834 .table-header, .table-row { grid-template-columns: 1fr 60px; }
7835 .table-row-item { grid-template-columns: 1fr 30px; }
7836 .table-row-item .table-row-link { grid-template-columns: 1fr 60px; }
7837 .row-type { display: none; }
7838 .table-header span:first-child { display: none; }
7839 .table-header.projects-header, .table-row.project-row { grid-template-columns: 1fr; }
7840 .table-header.projects-header span:nth-child(2) { display: none; }
7841 .row-price { font-size: var(--text-fine); }
7842 .table-controls { flex-wrap: wrap; }
7843 .mode-toggle { flex-wrap: wrap; }
7844 .table-footer { flex-direction: column; gap: var(--gap-peer); align-items: flex-start; }
7845 }
7846
7847 /* ── Feature Cards & Suggestion Input (shared by wizard + settings) ── */
7848
7849 .type-grid {
7850 display: grid;
7851 grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
7852 gap: var(--gap-section);
7853 margin-bottom: var(--gap-section);
7854 }
7855
7856 /* Canonical selectable-card primitive. One recipe; per-context size modifiers.
7857 Used by wizard type pickers, pricing-model pickers, content-choice pickers,
7858 monetization-mode pickers, anywhere a radio-like choice is rendered as a card. */
7859 .card--selectable {
7860 cursor: pointer;
7861 display: block;
7862 }
7863 .card--selectable input[type="radio"],
7864 .card--selectable input[type="checkbox"] {
7865 position: absolute;
7866 opacity: 0;
7867 pointer-events: none;
7868 }
7869
7870 .card--selectable-inner {
7871 display: flex;
7872 flex-direction: column;
7873 align-items: center;
7874 justify-content: center;
7875 padding: var(--gap-section);
7876 border: 2px solid var(--border);
7877 border-radius: var(--radius-square);
7878 background: var(--surface-page);
7879 text-align: center;
7880 transition: border-color 0.15s, background 0.15s;
7881 min-height: 60px;
7882 }
7883 /* Variant: column-aligned content (text-heavy pricing cards). */
7884 .card--selectable.is-text-heavy .card--selectable-inner {
7885 align-items: stretch;
7886 text-align: left;
7887 padding: var(--gap-section);
7888 }
7889
7890 .card--selectable input:checked + .card--selectable-inner,
7891 .card--selectable.is-selected .card--selectable-inner {
7892 border-color: var(--action);
7893 background: var(--highlight-faint);
7894 }
7895
7896 .card--selectable:hover .card--selectable-inner {
7897 background: var(--surface-sunken);
7898 }
7899
7900 /* Focus. The card is a <label> wrapping a visually hidden radio, so the focus
7901 lands on the input and the ring has to be drawn on the inner surface. The
7902 <a> form of the card takes focus itself. */
7903 .card--selectable:focus-visible,
7904 .card--selectable input:focus-visible + .card--selectable-inner {
7905 outline: 2px solid var(--focus-ring);
7906 outline-offset: 2px;
7907 }
7908
7909 .card--selectable-label {
7910 font-weight: bold;
7911 font-size: var(--text-note);
7912 }
7913
7914 .card--selectable-desc {
7915 font-size: var(--text-fine);
7916 color: var(--content-muted);
7917 margin-top: var(--gap-bound);
7918 }
7919
7920 /* Disabled selectable card, visually dimmed, ignores pointer events. */
7921 .card--selectable.is-disabled {
7922 opacity: 0.7;
7923 pointer-events: none;
7924 }
7925
7926 .suggestion-input {
7927 position: relative;
7928 }
7929
7930 .suggestion-dropdown {
7931 display: none;
7932 position: absolute;
7933 top: 100%;
7934 left: 0;
7935 right: 0;
7936 background: var(--surface-page);
7937 border: 1px solid var(--border);
7938 border-top: none;
7939 border-radius: 0 0 var(--radius-square) var(--radius-square);
7940 max-height: 200px;
7941 overflow-y: auto;
7942 z-index: var(--z-dropdown);
7943 box-shadow: var(--elevation-overlay);
7944 }
7945
7946 .suggestion-dropdown.open {
7947 display: block;
7948 }
7949
7950 .suggestion-item {
7951 padding: var(--gap-peer) var(--gap-section);
7952 cursor: pointer;
7953 font-size: var(--text-note);
7954 }
7955
7956 .suggestion-item:hover {
7957 background: var(--surface-overlay);
7958 }
7959
7960 .suggestion-create {
7961 font-style: italic;
7962 opacity: 0.8;
7963 border-top: 1px solid var(--border);
7964 }
7965
7966 /* ── Tips ── */
7967
7968 .tip-section {
7969 margin-top: var(--gap-section);
7970 text-align: center;
7971 }
7972
7973 .tip-toggle {
7974 font-family: var(--font-mono);
7975 font-size: var(--text-note);
7976 padding: var(--gap-peer) var(--gap-pane);
7977 background: none;
7978 border: 1px solid var(--content);
7979 color: var(--content);
7980 cursor: pointer;
7981 text-decoration: none;
7982 display: inline-block;
7983 }
7984
7985 .tip-toggle:hover {
7986 background: var(--content);
7987 color: var(--surface-page);
7988 }
7989
7990 .tip-form {
7991 margin-top: var(--gap-section);
7992 display: flex;
7993 flex-direction: column;
7994 gap: var(--gap-section);
7995 max-width: 300px;
7996 margin-left: auto;
7997 margin-right: auto;
7998 text-align: left;
7999 }
8000
8001 .tip-form.hidden {
8002 display: none;
8003 }
8004
8005 .tip-amount-row {
8006 display: flex;
8007 align-items: center;
8008 gap: var(--gap-bound);
8009 }
8010
8011 .tip-amount-row .pricing-currency {
8012 font-size: var(--text-subhead);
8013 opacity: 0.7;
8014 }
8015
8016 .tip-amount-input {
8017 width: 80px;
8018 font-size: var(--text-subhead);
8019 padding: var(--gap-peer);
8020 border: 1px solid var(--border);
8021 background: var(--surface-overlay);
8022 font-family: var(--font-mono);
8023 }
8024
8025 .tip-message-input {
8026 width: 100%;
8027 font-size: var(--text-note);
8028 padding: var(--gap-peer);
8029 border: 1px solid var(--border);
8030 background: var(--surface-overlay);
8031 font-family: var(--font-sans);
8032 resize: vertical;
8033 }
8034
8035 .tip-submit {
8036 font-family: var(--font-mono);
8037 font-size: var(--text-note);
8038 padding: var(--gap-peer) var(--gap-section);
8039 background: var(--content);
8040 color: var(--surface-page);
8041 border: none;
8042 cursor: pointer;
8043 }
8044
8045 .tip-submit:hover {
8046 opacity: 0.85;
8047 }
8048
8049 /* ===========================================
8050 COLLECTION PICKER (shared dropdown)
8051 =========================================== */
8052
8053 .collection-picker-anchor { position: relative; }
8054 .collection-picker {
8055 position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-picker);
8056 background: var(--surface-page); border: 1px solid var(--border);
8057 box-shadow: var(--elevation-overlay); min-width: 220px;
8058 }
8059 .collection-picker-list { max-height: 200px; overflow-y: auto; padding: var(--gap-bound) 0; }
8060 .collection-picker-item {
8061 display: flex; align-items: center; gap: var(--gap-peer);
8062 padding: var(--gap-peer) var(--gap-section); cursor: pointer; font-size: var(--text-note);
8063 }
8064 .collection-picker-item:hover { background: var(--surface-sunken); }
8065 .collection-picker-loading,
8066 .collection-picker-create { border-top: 1px solid var(--border); padding: var(--gap-peer) var(--gap-section); }
8067 .collection-picker-create form { display: flex; gap: var(--gap-peer); }
8068 .collection-picker-create input { flex: 1; padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-note); min-width: 0; }
8069 .collection-picker-create button { font-size: var(--text-note); white-space: nowrap; }
8070
8071 /* Save button on discover cards */
8072 .row-save, .grid-card-save {
8073 background: none; border: none; cursor: pointer;
8074 font-size: var(--text-note); padding: var(--gap-bound) var(--gap-peer); opacity: 0.4;
8075 color: var(--content); transition: opacity 0.15s;
8076 }
8077 .row-save:hover, .grid-card-save:hover { opacity: 1; }
8078 .grid-card-save {
8079 position: absolute; top: 0.4rem; right: 0.4rem; z-index: var(--z-raised);
8080 background: var(--surface-page); border-radius: var(--radius-control); padding: var(--gap-bound) var(--gap-peer);
8081 box-shadow: var(--elevation-overlay); opacity: 0;
8082 }
8083 .grid-card:hover .grid-card-save { opacity: 0.7; }
8084 .grid-card-save:hover { opacity: 1; }
8085
8086 /* Item page save button saved state */
8087 button.saved { border-color: var(--action); color: var(--action); }
8088
8089 /* ===========================================
8090 SEARCH BOX
8091 ===========================================
8092 The box and its suggestion list are a described Field (N8), which draws its
8093 own control and its own list. What is left here is where the toolbar puts
8094 it: a flex child that takes the rest of the row, and the positioning context
8095 the list is absolutely placed against. */
8096
8097 .search-wrapper { position: relative; flex: 1; min-width: 0; }
8098
8099 /* ===========================================
8100 DOC UI EXAMPLES (embedded live UI in docs)
8101 =========================================== */
8102
8103 .doc-ui {
8104 margin: var(--gap-pane) 0; border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden;
8105 }
8106 .doc-ui-frame {
8107 padding: var(--gap-section); background: var(--surface-overlay); pointer-events: none; user-select: none;
8108 }
8109 .doc-ui figcaption {
8110 padding: var(--gap-peer) var(--gap-section); font-size: var(--text-fine); opacity: 0.6;
8111 border-top: 1px solid var(--border); background: var(--surface-overlay);
8112 font-family: var(--font-mono);
8113 }
8114 .doc-ui-missing {
8115 padding: var(--gap-section); opacity: 0.4; font-style: italic;
8116 }
8117
8118 /* Library "New" badge for items with undownloaded versions. Not a status, so
8119 it takes no tone; it is the plain badge with the accent hue and a little
8120 more weight, since "New" is the one badge meant to catch the eye. */
8121 .badge-new {
8122 color: var(--action); font-weight: bold;
8123 margin-left: var(--gap-peer); vertical-align: middle;
8124 }
8125
8126 /* ===========================================
8127 BENEFIT LIST (bulleted feature lists, e.g. Stripe Connect prompt)
8128 =========================================== */
8129 .benefit-list {
8130 list-style: none;
8131 padding: 0;
8132 margin: 0 auto var(--gap-pane);
8133 max-width: 280px;
8134 text-align: left;
8135 display: flex;
8136 flex-direction: column;
8137 gap: var(--gap-peer);
8138 }
8139 .benefit-list li {
8140 position: relative;
8141 padding-left: var(--gap-section);
8142 font-size: var(--text-note);
8143 }
8144 .benefit-list li::before {
8145 content: "";
8146 position: absolute;
8147 left: 0.15rem;
8148 top: 0.55rem;
8149 width: 6px;
8150 height: 6px;
8151 border-radius: var(--radius-round);
8152 background: var(--success);
8153 }
8154
8155 /* ===========================================
8156 CHECKLIST (setup / onboarding step lists)
8157 =========================================== */
8158 .checklist {
8159 margin-bottom: var(--gap-pane);
8160 padding: var(--gap-section) var(--gap-pane);
8161 background: var(--surface-overlay);
8162 border: 1px solid var(--border);
8163 }
8164 .checklist-header {
8165 display: flex;
8166 justify-content: space-between;
8167 align-items: center;
8168 margin-bottom: var(--gap-section);
8169 }
8170 .checklist-title {
8171 margin: 0;
8172 font-family: var(--font-display);
8173 font-weight: bold;
8174 }
8175 .checklist-mark {
8176 width: 1.1rem;
8177 height: 1.1rem;
8178 border-radius: var(--radius-round);
8179 border: 2px solid var(--border);
8180 flex-shrink: 0;
8181 box-sizing: border-box;
8182 }
8183 .checklist-mark--done {
8184 border-color: var(--success);
8185 background: var(--success);
8186 }
8187 .checklist-label {
8188 flex: 1;
8189 font-size: var(--text-note);
8190 }
8191 .checklist-label--done {
8192 opacity: 0.5;
8193 text-decoration: line-through;
8194 }
8195
8196 /* ===========================================
8197 PAYMENTS TAB (replaces inline styles in user_payments.html)
8198 =========================================== */
8199 /* Canonical "stack row", title-on-left + actions-on-right header bar.
8200 One primitive; per-context modifiers handle gap, wrap, alignment, margins.
8201 Aliases below cover existing per-tab class names (kept as JS hooks). */
8202 .stack-row,
8203 .content-header,
8204 .user-media-header,
8205 .transactions-header,
8206 .analytics-range-bar,
8207 .cart-multi-bar {
8208 display: flex;
8209 justify-content: space-between;
8210 align-items: center;
8211 flex-wrap: wrap;
8212 gap: var(--gap-section);
8213 }
8214 .stack-row--tight { gap: var(--gap-section); flex-wrap: nowrap; }
8215 .stack-row--top { align-items: flex-start; }
8216 .stack-row--bordered,
8217 .transactions-header {
8218 margin-bottom: var(--gap-section);
8219 padding-bottom: var(--gap-section);
8220 border-bottom: 1px solid var(--border);
8221 }
8222 .content-header,
8223 .user-media-header,
8224 .analytics-range-bar { margin-bottom: var(--gap-section); }
8225 .cart-multi-bar {
8226 margin-bottom: var(--gap-pane);
8227 padding: var(--gap-section);
8228 background: var(--surface-sunken);
8229 border: 1px solid var(--border);
8230 }
8231
8232 /* Canonical list row (flex, gap, padding, bottom border). Aliases below cover
8233 the per-tab variants. They exist as JS hooks but share the same look. */
8234 .list-row,
8235 .psection-row,
8236 .section-mgmt-row,
8237 .bundle-picker-row,
8238 .checklist-row {
8239 display: flex;
8240 align-items: center;
8241 gap: var(--gap-section);
8242 padding: var(--gap-group) 0;
8243 border-bottom: 1px solid var(--border);
8244 }
8245 .list-row:last-child,
8246 .psection-row:last-child,
8247 .section-mgmt-row:last-child,
8248 .bundle-picker-row:last-child,
8249 .checklist-row:last-child { border-bottom: none; }
8250 .warn-glyph {
8251 font-size: var(--text-subhead);
8252 color: var(--warning);
8253 }
8254
8255 .card-h {
8256 margin: 0 0 var(--gap-section) 0;
8257 font-size: var(--text-body);
8258 }
8259
8260 .amount-big {
8261 font-size: var(--text-subhead);
8262 font-weight: bold;
8263 }
8264
8265 .account-details {
8266 margin-top: var(--gap-section);
8267 padding-top: var(--gap-section);
8268 border-top: 1px solid var(--border);
8269 }
8270 .account-details summary { cursor: pointer; }
8271 .account-details-id {
8272 font-size: var(--text-fine);
8273 opacity: 0.5;
8274 font-family: var(--font-mono);
8275 margin-top: var(--gap-bound);
8276 }
8277
8278 .stripe-actions {
8279 display: flex;
8280 gap: var(--gap-section);
8281 flex-wrap: wrap;
8282 }
8283 .stripe-actions-disconnect { margin-left: auto; }
8284
8285 .fee-breakdown {
8286 display: grid;
8287 grid-template-columns: 1fr 1fr;
8288 gap: var(--gap-peer);
8289 font-size: var(--text-note);
8290 }
8291 .fee-row {
8292 display: flex;
8293 justify-content: space-between;
8294 padding: var(--gap-peer) 0;
8295 border-bottom: 1px solid var(--border);
8296 }
8297
8298 .checkbox-row {
8299 display: flex;
8300 align-items: flex-start;
8301 gap: var(--gap-peer);
8302 cursor: pointer;
8303 font-size: var(--text-note);
8304 }
8305 .check-inline {
8306 width: auto;
8307 margin-top: var(--gap-bound);
8308 }
8309
8310 .stripe-connect-prompt { text-align: center; padding: var(--gap-pane); }
8311 .stripe-logo { opacity: 0.8; }
8312 .stripe-connect-btn {
8313 background: var(--stripe);
8314 padding: var(--gap-section) var(--gap-page);
8315 }
8316
8317 .export-row {
8318 display: flex;
8319 justify-content: flex-end;
8320 margin-bottom: var(--gap-section);
8321 }
8322
8323 .splits-grid {
8324 display: grid;
8325 grid-template-columns: 1fr 1fr;
8326 gap: var(--gap-section);
8327 margin-bottom: var(--gap-section);
8328 }
8329
8330 .tip-message {
8331 max-width: 300px;
8332 overflow: hidden;
8333 text-overflow: ellipsis;
8334 }
8335
8336 .transactions-title { font-size: var(--text-subhead); }
8337
8338 /* ===========================================
8339 CREATOR TAB (replaces inline styles in user_creator.html)
8340 =========================================== */
8341 .creator-tab-section {
8342 padding: var(--gap-page);
8343 }
8344 .creator-tab-section--centered { text-align: center; }
8345 .creator-tab-section--no-top { padding-top: 0; }
8346
8347 .creator-h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
8348 .creator-h2--sm { font-size: var(--text-subhead); }
8349
8350 .creator-section-label {
8351 margin-left: var(--gap-page);
8352 margin-right: var(--gap-page);
8353 }
8354 .creator-form-section { padding: 0 var(--gap-page); }
8355
8356 .creator-plan-box {
8357 background: var(--surface-overlay);
8358 padding: var(--gap-section) var(--gap-pane);
8359 }
8360 .creator-plan-head {
8361 display: flex;
8362 align-items: center;
8363 gap: var(--gap-section);
8364 margin-bottom: var(--gap-peer);
8365 flex-wrap: wrap;
8366 }
8367 .creator-plan-name {
8368 font-family: var(--font-display);
8369 font-weight: bold;
8370 font-size: var(--text-lead);
8371 }
8372
8373 /* Founder pricing, two states, and neither is a status tone: locked-for-life
8374 is emphasis and pending-lock-in is an aside. They were a --content fill and
8375 a --surface-sunken fill back when every badge was filled; as coloured text
8376 the same distinction is full-strength ink against muted. */
8377 .badge--founder-locked {
8378 color: var(--content);
8379 font-weight: bold;
8380 }
8381 .badge--founder-pending {
8382 color: var(--content-muted);
8383 }
8384
8385 .founder-callout {
8386 background: var(--surface-overlay);
8387 border-left: 3px solid var(--content);
8388 padding: var(--gap-section);
8389 }
8390
8391 .creator-tier-grid {
8392 display: grid;
8393 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
8394 gap: var(--gap-section);
8395 }
8396 .creator-tier-card {
8397 background: var(--surface-overlay);
8398 padding: var(--gap-section);
8399 text-align: center;
8400 }
8401 .creator-tier-name {
8402 font-family: var(--font-display);
8403 font-weight: bold;
8404 font-size: var(--text-body);
8405 margin-bottom: var(--gap-bound);
8406 }
8407 .creator-tier-link {
8408 color: inherit;
8409 text-decoration: none;
8410 border-bottom: 1px solid var(--border);
8411 }
8412 .creator-tier-sub {
8413 font-size: var(--text-fine);
8414 opacity: 0.7;
8415 }
8416 .creator-tier-storage { font-size: var(--text-fine); }
8417 .creator-tier-buttons {
8418 display: flex;
8419 flex-direction: column;
8420 gap: var(--gap-peer);
8421 }
8422 .creator-tier-btn {
8423 font-size: var(--text-note);
8424 width: 100%;
8425 }
8426
8427 .strike-dim {
8428 opacity: 0.5;
8429 text-decoration: line-through;
8430 }
8431
8432 .storage-box {
8433 background: var(--surface-overlay);
8434 padding: var(--gap-section) var(--gap-pane);
8435 }
8436 .storage-row {
8437 display: flex;
8438 justify-content: space-between;
8439 margin-bottom: var(--gap-peer);
8440 font-size: var(--text-note);
8441 }
8442 .storage-breakdown {
8443 display: grid;
8444 grid-template-columns: 1fr 1fr;
8445 gap: var(--gap-bound) var(--gap-pane);
8446 margin-top: var(--gap-section);
8447 font-size: var(--text-note);
8448 }
8449
8450 .broadcast-head {
8451 display: flex;
8452 justify-content: space-between;
8453 align-items: flex-start;
8454 }
8455 .broadcast-desc { flex: 1; }
8456 .broadcast-export { margin-left: var(--gap-section); }
8457
8458 .creator-divider {
8459 margin: 0 var(--gap-page);
8460 border-color: var(--border);
8461 }
8462
8463 .creator-intro { line-height: 1.6; }
8464 .creator-pitch-box {
8465 background: var(--surface-overlay);
8466 padding: var(--gap-pane);
8467 }
8468
8469 .mono-sm {
8470 font-family: var(--font-mono);
8471 font-size: var(--text-note);
8472 }
8473
8474 /* ===========================================
8475 PROJECT CONTENT TAB (replaces inline styles in project_content.html)
8476 =========================================== */
8477
8478 .empty-state--lg { padding: var(--gap-page) var(--gap-section); }
8479
8480 .content-filters {
8481 display: flex;
8482 gap: var(--gap-section);
8483 margin-bottom: var(--gap-section);
8484 align-items: center;
8485 flex-wrap: wrap;
8486 }
8487 .content-filter-search {
8488 flex: 1;
8489 min-width: 180px;
8490 padding: var(--gap-peer) var(--gap-group);
8491 font-size: var(--text-note);
8492 }
8493 .content-filter-select {
8494 padding: var(--gap-peer);
8495 font-size: var(--text-note);
8496 }
8497
8498 .bulk-action-bar {
8499 padding: var(--gap-section);
8500 margin-bottom: var(--gap-section);
8501 background: var(--surface-raised);
8502 border: 1px solid var(--border);
8503 border-radius: var(--radius-square);
8504 display: flex;
8505 align-items: center;
8506 gap: var(--gap-peer);
8507 flex-wrap: wrap;
8508 opacity: 0.4;
8509 }
8510 .bulk-action-bar--active { opacity: 1; }
8511 .bulk-count { font-weight: bold; min-width: 5rem; }
8512 .bulk-deselect { margin-left: auto; }
8513 .danger-text { color: var(--danger); }
8514
8515 .bulk-form {
8516 margin-bottom: var(--gap-section);
8517 padding: var(--gap-section);
8518 background: var(--surface-sunken);
8519 border: 1px solid var(--border);
8520 border-radius: var(--radius-square);
8521 }
8522 .bulk-form-row {
8523 display: flex;
8524 gap: var(--gap-section);
8525 align-items: end;
8526 }
8527 .bulk-form-label {
8528 font-size: var(--text-note);
8529 display: block;
8530 margin-bottom: var(--gap-bound);
8531 }
8532
8533 /* Sortable table column header
8534
8535 The cursor and the caret are NOT here. `.table-heading[data-sortable]` and
8536 `.table-heading[aria-sort="..."]::after` are generated into static/layout.css
8537 by makeover-webview out of `Column::sortable` and `Column::sorted`. This file
8538 carried two hand-written copies of the same fact -- `.sortable-th` here and
8539 `.data-table th.sortable` up at the data-table block -- and a third channel in
8540 tab-project-content.js, which wrote the glyph as text and toggled
8541 `.sort-arrow--active`. Three vocabularies, one state, and the generated pair
8542 matched nothing because no heading carried `.table-heading`.
8543
8544 The focus ring stays: it is this app's focus treatment, not a restatement of
8545 the caret, and makeover emits no focus rule for a heading. */
8546 .table-heading[data-sortable] {
8547 user-select: none;
8548 transition: background 0.2s ease;
8549 }
8550
8551 .table-heading[data-sortable]:hover {
8552 background: var(--border);
8553 }
8554
8555 .table-heading[data-sortable]:focus-visible {
8556 outline: 2px solid var(--focus-ring);
8557 outline-offset: 2px;
8558 }
8559
8560 /* Data-table column-width utilities (numeric width hints,
8561 kept as classes to avoid inline `style="width: NN%"`) */
8562 .col-3 { width: 3%; }
8563 .col-5 { width: 5%; }
8564 .col-8 { width: 8%; }
8565 .col-10 { width: 10%; }
8566 .col-12 { width: 12%; }
8567 .col-15 { width: 15%; }
8568 .col-20 { width: 20%; }
8569 .col-32 { width: 32%; }
8570 .col-45 { width: 45%; }
8571
8572 /* Pixel-width utilities for inputs and narrow fixed-width controls.
8573 Use on form inputs that can't be sized by their parent column. */
8574 .w-80 { width: 80px; }
8575 .w-100 { width: 100px; }
8576 .w-120 { width: 120px; }
8577 .w-180 { width: 180px; }
8578 .w-220 { width: 220px; }
8579 .w-260 { width: 260px; }
8580 .w-full { width: 100%; }
8581 .maxw-320 { max-width: 320px; }
8582
8583 /* Canonical input shape modifiers. One base form-input look (defined globally
8584 at line ~365 input[type], textarea, select) plus these size + feature variants.
8585 Apply alongside the per-tab class which acts as a JS hook. */
8586 .input--xs {
8587 padding: var(--gap-bound) var(--gap-peer);
8588 font-size: var(--text-note);
8589 }
8590 .input--sm {
8591 padding: var(--gap-bound) var(--gap-peer);
8592 font-size: var(--text-note);
8593 }
8594 .input--mono { font-family: var(--font-mono); }
8595 .input--upper { text-transform: uppercase; }
8596 .input--numeric { text-align: right; }
8597
8598 .order-arrow-btn {
8599 padding: var(--gap-bound);
8600 font-size: var(--text-fine);
8601 line-height: 1;
8602 }
8603
8604 .bundle-toggle {
8605 background: none;
8606 border: none;
8607 cursor: pointer;
8608 padding: 0 var(--gap-bound) 0 0;
8609 font-size: var(--text-fine);
8610 color: var(--content-muted);
8611 }
8612 .bundle-child-cell { padding-left: var(--gap-page); }
8613 .bundle-child-title { opacity: 0.85; }
8614 .bundle-child-arrow {
8615 color: var(--content-muted);
8616 margin-right: var(--gap-bound);
8617 }
8618
8619 .item-actions {
8620 display: flex;
8621 gap: var(--gap-peer);
8622 }
8623
8624 .badge--inline-tiny {
8625 font-size: var(--text-fine);
8626 margin-left: var(--gap-bound);
8627 opacity: 0.7;
8628 }
8629
8630 .deleted-items-details { margin-top: var(--gap-page); }
8631 .deleted-items-summary {
8632 cursor: pointer;
8633 font-size: var(--text-body);
8634 opacity: 0.7;
8635 }
8636
8637 .section-divider {
8638 margin: var(--gap-page) 0;
8639 border: none;
8640 border-top: 1px solid var(--border);
8641 }
8642
8643 .inline-rename-input {
8644 font-weight: bold;
8645 width: 100%;
8646 padding: var(--gap-bound) var(--gap-peer);
8647 font-size: inherit;
8648 }
8649
8650 /* ===========================================
8651 ITEM DETAILS TAB (replaces inline styles in item_details.html)
8652 =========================================== */
8653
8654 /* Compact button utility (small icon-side actions) */
8655 .btn-compact { padding: var(--gap-peer) var(--gap-section); }
8656
8657 .ml-2 { margin-left: var(--gap-peer); }
8658 .cursor-pointer { cursor: pointer; }
8659 .inline { display: inline; }
8660
8661 .tag-search-wrap { position: relative; }
8662 .tag-suggestions {
8663 position: absolute;
8664 z-index: var(--z-dropdown);
8665 background: var(--surface-sunken);
8666 border: 1px solid var(--border);
8667 width: 100%;
8668 max-height: 200px;
8669 overflow-y: auto;
8670 }
8671
8672 .advanced-details { margin-top: var(--gap-pane); }
8673 .advanced-summary {
8674 cursor: pointer;
8675 font-size: var(--text-body);
8676 opacity: 0.8;
8677 }
8678
8679 /* Bundle table inside item-details */
8680 .bundle-table {
8681 width: 100%;
8682 border-collapse: collapse;
8683 }
8684 .bundle-table-head {
8685 text-align: left;
8686 font-size: var(--text-fine);
8687 opacity: 0.7;
8688 text-transform: uppercase;
8689 letter-spacing: 0.03em;
8690 }
8691 .bundle-row { border-bottom: 1px solid var(--border); }
8692 .bundle-cell { padding: var(--gap-peer); }
8693 .bundle-cell--first { padding-left: 0; }
8694 .bundle-cell--actions { width: 60px; }
8695 .bundle-cell--desc { font-size: var(--text-note); opacity: 0.8; }
8696 .bundle-cell--file { font-size: var(--text-note); }
8697
8698 .bundle-remove-btn {
8699 padding: var(--gap-bound) var(--gap-peer);
8700 font-size: var(--text-fine);
8701 }
8702
8703 .bundle-add-row {
8704 margin-top: var(--gap-section);
8705 display: flex;
8706 gap: var(--gap-section);
8707 align-items: center;
8708 }
8709
8710 .bundle-existing-details { margin-top: var(--gap-section); }
8711 .bundle-existing-summary {
8712 cursor: pointer;
8713 font-size: var(--text-note);
8714 opacity: 0.7;
8715 }
8716 .bundle-existing-row {
8717 margin-top: var(--gap-peer);
8718 display: flex;
8719 gap: var(--gap-peer);
8720 align-items: center;
8721 }
8722 .bundle-existing-select {
8723 flex: 1;
8724 padding: var(--gap-peer);
8725 }
8726
8727 .sections-intro {
8728 font-size: var(--text-note);
8729 opacity: 0.7;
8730 margin: var(--gap-section) 0 var(--gap-section);
8731 }
8732
8733 .section-mgmt-title {
8734 flex: 1;
8735 font-weight: bold;
8736 }
8737 .section-edit-btn,
8738 .section-del-btn {
8739 padding: var(--gap-bound) var(--gap-group);
8740 font-size: var(--text-fine);
8741 }
8742
8743 .section-add-details { margin-top: var(--gap-section); }
8744 .section-add-summary {
8745 cursor: pointer;
8746 font-size: var(--text-body);
8747 }
8748
8749 .section-edit-modal {
8750 margin-top: var(--gap-section);
8751 padding: var(--gap-section);
8752 background: var(--surface-sunken);
8753 }
8754 .section-edit-actions {
8755 display: flex;
8756 gap: var(--gap-peer);
8757 }
8758
8759 .publish-note {
8760 margin: var(--gap-section) 0;
8761 opacity: 0.7;
8762 text-align: left;
8763 }
8764
8765 /* ===========================================
8766 CONTENT WIZARD STEP (replaces inline styles in wizards/steps/item/content.html)
8767 =========================================== */
8768
8769 /* (Wizard upload-status + slim progress moved into canonical .upload-status*
8770 / .progress-bar-container--slim primitives near top of file.) */
8771
8772 /* Bundle items picker, server-rendered rows and JS-appended rows share these. */
8773 .bundle-picker-row-title { flex: 1; }
8774 .bundle-picker-row-type { font-size: var(--text-fine); opacity: 0.6; }
8775 .bundle-picker-row-unlisted {
8776 font-size: var(--text-fine);
8777 display: flex;
8778 align-items: center;
8779 gap: var(--gap-bound);
8780 }
8781
8782 /* Batch upload queue table. */
8783 .batch-queue-table {
8784 width: 100%;
8785 border-collapse: collapse;
8786 }
8787 .batch-queue-table thead tr {
8788 border-bottom: 2px solid var(--border);
8789 text-align: left;
8790 }
8791 .batch-queue-table th,
8792 .batch-queue-table td {
8793 padding: var(--gap-peer);
8794 font-size: var(--text-note);
8795 }
8796 .batch-queue-table th:first-child,
8797 .batch-queue-table td:first-child { padding-left: 0; }
8798 .batch-queue-table th:last-child,
8799 .batch-queue-table td:last-child { padding-right: 0; text-align: right; }
8800 .batch-queue-table tbody tr { border-bottom: 1px solid var(--border); }
8801
8802 .batch-queue-name {
8803 max-width: 150px;
8804 overflow: hidden;
8805 text-overflow: ellipsis;
8806 white-space: nowrap;
8807 }
8808
8809 /* ===========================================
8810 ACCOUNT TAB (replaces inline styles in partials/tabs/user_account.html)
8811 =========================================== */
8812
8813 /* Canonical solid-tint callout (one of these in the codebase).
8814 Distinct from .alert (left-border) and .banner (full-bleed page notice).
8815 Variants:
8816 - .callout--danger (light bg, danger border, moderation, errors)
8817 - .callout--warning (light bg, warning border, DNS, email-not-verified)
8818 - .callout--solid-warning (full saturation warning bg, dark text, partial checkout) */
8819 .callout {
8820 padding: var(--gap-section);
8821 margin-bottom: var(--gap-section);
8822 font-size: var(--text-note);
8823 border: 1px solid;
8824 }
8825 .callout--danger {
8826 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
8827 border-color: var(--danger);
8828 }
8829 .callout--warning {
8830 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
8831 border-color: var(--warning);
8832 }
8833 .callout--solid-warning {
8834 background: var(--warning);
8835 color: var(--primary-dark);
8836 border-color: transparent;
8837 padding: var(--gap-section);
8838 }
8839 .callout-title { text-transform: capitalize; }
8840 .callout-meta {
8841 opacity: 0.6;
8842 margin-left: var(--gap-peer);
8843 }
8844 .callout-body { margin: var(--gap-peer) 0 0 0; }
8845 .callout-hint {
8846 font-size: var(--text-note);
8847 margin-top: var(--gap-peer);
8848 }
8849
8850 .account-mod-history { margin-top: var(--gap-section); }
8851 .account-mod-history summary {
8852 cursor: pointer;
8853 font-size: var(--text-note);
8854 opacity: 0.7;
8855 }
8856 .account-mod-history-list { margin-top: var(--gap-peer); }
8857 .account-mod-history-item {
8858 padding: var(--gap-peer) 0;
8859 border-bottom: 1px solid var(--border);
8860 font-size: var(--text-note);
8861 }
8862 .account-mod-history-reason {
8863 margin: var(--gap-bound) 0 0 0;
8864 opacity: 0.8;
8865 }
8866
8867 .account-tip-card-title {
8868 font-weight: bold;
8869 margin-bottom: var(--gap-peer);
8870 }
8871 .account-tip-list {
8872 display: flex;
8873 flex-direction: column;
8874 gap: var(--gap-bound);
8875 opacity: 0.8;
8876 }
8877
8878 /* Resend-verification button needs left spacing from preceding strong. */
8879 .account-resend-btn { margin-left: var(--gap-peer); }
8880
8881 /* Cluster of small inline forms (manage-billing / cancel / resume). */
8882 .inline-form { display: inline; }
8883
8884 /* Notification preferences, bare fieldset + monospace legend. */
8885 .pref-fieldset {
8886 border: none;
8887 padding: 0;
8888 margin: 0 0 var(--gap-section) 0;
8889 }
8890 .pref-fieldset--last { margin-bottom: var(--gap-peer); }
8891 .pref-legend {
8892 font-family: var(--font-mono);
8893 font-size: var(--text-note);
8894 font-weight: bold;
8895 margin-bottom: var(--gap-peer);
8896 }
8897
8898 /* The cannot-opt-out list, rendered from email::OperationalKind. Reads as
8899 reference material rather than as more settings: no controls, and the
8900 justification sits under its label rather than beside it. */
8901 .operational-mail {
8902 margin: var(--gap-peer) 0 0 0;
8903 }
8904 .operational-mail dt {
8905 font-weight: bold;
8906 font-size: var(--text-note);
8907 margin-top: var(--gap-peer);
8908 }
8909 .operational-mail dd {
8910 margin: 0;
8911 opacity: 0.8;
8912 font-size: var(--text-note);
8913 }
8914
8915 /* ===========================================
8916 PROJECT CODE TAB (replaces inline styles in partials/tabs/project_code.html)
8917 =========================================== */
8918
8919 .proj-code-docs-link { margin-left: var(--gap-section); }
8920
8921 .proj-code-linked-list { margin-bottom: var(--gap-pane); }
8922 .proj-code-linked-list > label { margin-bottom: var(--gap-peer); display: block; }
8923
8924 .proj-code-repo {
8925 margin-bottom: var(--gap-section);
8926 padding: var(--gap-section);
8927 background: var(--surface-overlay);
8928 }
8929 .proj-code-repo-row {
8930 display: flex;
8931 align-items: center;
8932 gap: var(--gap-section);
8933 }
8934 .proj-code-repo-link { flex: 1; }
8935 .proj-code-repo .danger { padding: var(--gap-bound) var(--gap-section); font-size: var(--text-note); }
8936
8937 .proj-code-collab-block {
8938 margin-top: var(--gap-peer);
8939 padding-top: var(--gap-peer);
8940 border-top: 1px solid var(--border);
8941 }
8942 .proj-code-collab-label { font-size: var(--text-note); margin-bottom: var(--gap-bound); display: block; }
8943 .proj-code-collab-row {
8944 display: flex;
8945 align-items: center;
8946 gap: var(--gap-peer);
8947 margin-bottom: var(--gap-bound);
8948 font-size: var(--text-note);
8949 }
8950 .proj-code-collab-name { flex: 1; }
8951 .proj-code-collab-readonly { opacity: 0.5; }
8952
8953 .proj-code-add-collab-row {
8954 display: flex;
8955 gap: var(--gap-peer);
8956 align-items: center;
8957 }
8958 .proj-code-add-collab-row .collab-username-input {
8959 flex: 1;
8960 font-size: var(--text-note);
8961 padding: var(--gap-bound) var(--gap-peer);
8962 }
8963 .proj-code-add-collab-row .collab-add-btn { white-space: nowrap; }
8964
8965 .proj-code-link-row {
8966 display: flex;
8967 gap: var(--gap-peer);
8968 align-items: center;
8969 }
8970 .proj-code-link-row #link-repo-select { flex: 1; }
8971 .proj-code-link-row #link-repo-btn { white-space: nowrap; }
8972
8973 .proj-code-create-section {
8974 margin-top: var(--gap-pane);
8975 padding-top: var(--gap-section);
8976 border-top: 1px solid var(--border);
8977 }
8978 .proj-code-create-section #create-repo-name { flex: 1; }
8979 .proj-code-create-section #create-repo-btn { white-space: nowrap; }
8980 .proj-code-create-status { margin-top: var(--gap-bound); }
8981 .proj-code-create-status.is-error { color: var(--danger); }
8982
8983 .proj-code-disabled {
8984 padding: var(--gap-page);
8985 text-align: center;
8986 opacity: 0.7;
8987 }
8988
8989 /* ===========================================
8990 ADMIN REPORT ENTRIES (replaces inline styles in partials/admin_report_entries.html)
8991 =========================================== */
8992
8993 .admin-report-table {
8994 width: 100%;
8995 border-collapse: collapse;
8996 font-size: var(--text-note);
8997 }
8998 .admin-report-table thead tr {
8999 border-bottom: 2px solid var(--border);
9000 text-align: left;
9001 }
9002 .admin-report-table tbody tr {
9003 border-bottom: 1px solid var(--border);
9004 }
9005 .admin-report-table th,
9006 .admin-report-table td {
9007 padding: var(--gap-peer);
9008 }
9009 .admin-report-table td.admin-report-reason {
9010 max-width: 200px;
9011 overflow: hidden;
9012 text-overflow: ellipsis;
9013 white-space: nowrap;
9014 }
9015 .admin-report-table td.admin-report-date {
9016 white-space: nowrap;
9017 }
9018
9019 .admin-report-target-type {
9020 opacity: 0.5;
9021 font-size: var(--text-fine);
9022 }
9023
9024 .admin-report-status-open {
9025 color: var(--action);
9026 font-weight: 600;
9027 }
9028 .admin-report-status-closed { opacity: 0.6; }
9029
9030 .admin-report-resolve-form {
9031 display: inline-flex;
9032 gap: var(--gap-bound);
9033 align-items: center;
9034 }
9035 .admin-report-resolve-form input[type="text"] {
9036 width: 120px;
9037 padding: var(--gap-bound) var(--gap-peer);
9038 font-size: var(--text-fine);
9039 }
9040 .admin-report-dismiss-form { display: inline; }
9041
9042 .admin-report-admin-notes {
9043 font-size: var(--text-fine);
9044 opacity: 0.6;
9045 }
9046
9047 /* ===========================================
9048 PROJECT PROMOTIONS TAB (replaces inline styles in partials/tabs/project_promotions.html)
9049 =========================================== */
9050
9051 .proj-promo-form {
9052 margin-bottom: var(--gap-page);
9053 }
9054
9055 .proj-promo-grid {
9056 display: grid;
9057 grid-template-columns: 1fr 1fr;
9058 gap: var(--gap-section) var(--gap-pane);
9059 max-width: 500px;
9060 }
9061
9062 .proj-promo-grid--bordered {
9063 margin-top: var(--gap-section);
9064 padding-top: var(--gap-section);
9065 border-top: 1px solid var(--border);
9066 }
9067
9068 .proj-promo-trial-fields {
9069 display: none;
9070 max-width: 500px;
9071 margin-top: var(--gap-section);
9072 }
9073
9074 .proj-promo-trial-fields.is-visible {
9075 display: block;
9076 }
9077
9078 .proj-promo-grid.is-hidden,
9079 .proj-promo-trial-fields.is-hidden {
9080 display: none;
9081 }
9082
9083 .proj-promo-form .form-group {
9084 margin-bottom: 0;
9085 }
9086
9087 .proj-promo-form .form-group label {
9088 font-size: var(--text-note);
9089 }
9090
9091 .proj-promo-trial-group {
9092 max-width: 240px;
9093 }
9094
9095 .proj-promo-scope {
9096 grid-column: 1 / -1;
9097 }
9098
9099 .proj-promo-actions {
9100 margin-top: var(--gap-section);
9101 }
9102
9103 .proj-promo-hint {
9104 margin-top: var(--gap-peer);
9105 }
9106
9107 /* ===========================================
9108 PROJECT SYNCKIT TAB (replaces inline styles in partials/tabs/project_synckit.html)
9109 =========================================== */
9110
9111 .proj-synckit-intro { margin-bottom: var(--gap-section); }
9112 .proj-synckit-intro-secondary { margin-bottom: var(--gap-pane); }
9113 .proj-synckit-create { margin-bottom: var(--gap-pane); }
9114 .proj-synckit-create-body {
9115 padding: var(--gap-section); background: var(--surface-overlay); border: 1px solid var(--border);
9116 }
9117 .proj-synckit-create-form {
9118 display: flex; flex-direction: row; gap: var(--gap-section); align-items: flex-end;
9119 }
9120 .proj-synckit-label {
9121 display: block; font-size: var(--text-note); margin-bottom: var(--gap-bound);
9122 }
9123 .proj-synckit-code { font-size: var(--text-fine); }
9124 .proj-synckit-code--wrap { font-size: var(--text-fine); word-break: break-all; }
9125 .proj-synckit-code--selectable { font-size: var(--text-note); word-break: break-all; user-select: all; }
9126 .proj-synckit-btn-tight {
9127 margin-left: var(--gap-peer); padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9128 }
9129 .proj-synckit-btn-row {
9130 padding: var(--gap-bound) var(--gap-peer); font-size: var(--text-fine);
9131 }
9132 .proj-synckit-btn-row + .proj-synckit-btn-row { margin-left: var(--gap-bound); }
9133 .proj-synckit-hint-inline {
9134 font-size: var(--text-fine); margin-left: var(--gap-bound);
9135 }
9136 .proj-synckit-hint-warn {
9137 font-size: var(--text-fine); color: var(--danger);
9138 }
9139 .proj-synckit-status-active { color: var(--success); }
9140 .proj-synckit-status-inactive { color: var(--danger); }
9141 .proj-synckit-new-key {
9142 padding: var(--gap-section); background: var(--surface-overlay);
9143 border: 1px solid var(--border); margin-top: var(--gap-peer);
9144 }
9145 .proj-synckit-new-key-heading {
9146 margin: 0 0 var(--gap-peer) 0; font-weight: 500; color: var(--danger);
9147 }
9148 .proj-synckit-create-status--error { color: var(--danger); margin-top: var(--gap-peer); }
9149 .proj-synckit-create-status--reset { margin-top: var(--gap-peer); }
9150
9151 /* ===========================================
9152 ITEM PAGE (replaces inline styles in pages/item.html)
9153 =========================================== */
9154
9155 .item-page .breadcrumb {
9156 display: flex;
9157 justify-content: space-between;
9158 align-items: center;
9159 }
9160
9161 .item-page .breadcrumb-edit-link {
9162 font-size: var(--text-note);
9163 color: var(--content);
9164 }
9165
9166 .item-page .video-player {
9167 width: 100%;
9168 max-height: 600px;
9169 background: var(--primary-dark);
9170 }
9171
9172 .item-page .item-cover-img {
9173 width: 100%;
9174 aspect-ratio: 1 / 1;
9175 object-fit: cover;
9176 display: block;
9177 }
9178
9179
9180 .item-page .bundle-notice-inline {
9181 background: var(--surface-sunken);
9182 padding: var(--gap-section);
9183 }
9184
9185 .item-page .bundle-notice-title {
9186 font-weight: bold;
9187 margin-bottom: var(--gap-peer);
9188 }
9189
9190 .item-page .bundle-notice-desc {
9191 opacity: 0.8;
9192 font-size: var(--text-note);
9193 }
9194
9195 .item-page .bundle-notice-entry {
9196 margin-top: var(--gap-section);
9197 }
9198
9199 .item-page .bundle-notice-entry a {
9200 font-weight: bold;
9201 }
9202
9203 .item-page .bundle-notice-price {
9204 opacity: 0.7;
9205 }
9206
9207 .item-page .bundle-notice-empty {
9208 margin-top: var(--gap-peer);
9209 opacity: 0.7;
9210 font-size: var(--text-note);
9211 }
9212
9213 .item-page .promo-details {
9214 margin-bottom: var(--gap-section);
9215 font-size: var(--text-note);
9216 }
9217
9218 .item-page .promo-summary {
9219 cursor: pointer;
9220 opacity: 0.7;
9221 }
9222
9223 .item-page .promo-form {
9224 display: flex;
9225 gap: var(--gap-peer);
9226 align-items: center;
9227 margin-top: var(--gap-peer);
9228 }
9229
9230 .item-page .promo-input {
9231 flex: 1;
9232 padding: var(--gap-peer) var(--gap-group);
9233 font-size: var(--text-note);
9234 }
9235
9236 .item-page .action-row {
9237 margin-top: var(--gap-section);
9238 }
9239
9240 .item-page .action-row-tight {
9241 margin-top: var(--gap-peer);
9242 }
9243
9244 .item-page .full-width-btn {
9245 width: 100%;
9246 font-size: var(--text-note);
9247 }
9248
9249 .item-page .license-preset-name {
9250 margin-bottom: var(--gap-section);
9251 }
9252
9253 .item-page .license-summary {
9254 cursor: pointer;
9255 opacity: 0.7;
9256 font-size: var(--text-note);
9257 }
9258
9259 .item-page .license-text {
9260 white-space: pre-wrap;
9261 font-family: var(--font-sans);
9262 font-size: var(--text-note);
9263 margin-top: var(--gap-section);
9264 padding: var(--gap-section);
9265 background: var(--surface-sunken);
9266 }
9267
9268 .item-page .license-download {
9269 margin-top: var(--gap-section);
9270 }
9271
9272 .item-page .license-download a {
9273 font-size: var(--text-note);
9274 color: var(--content);
9275 }
9276
9277 .item-page .bundle-list-entry {
9278 margin-top: var(--gap-peer);
9279 }
9280
9281 .item-page .footer-links {
9282 margin-top: var(--gap-peer);
9283 }
9284
9285 /* ===========================================
9286 JOIN COMPLETE STEP (replaces inline styles in wizards/steps/join/complete.html)
9287 =========================================== */
9288 .join-complete-intro { margin-bottom: var(--gap-pane); }
9289 .join-complete-choices {
9290 display: grid; grid-template-columns: 1fr 1fr;
9291 gap: var(--gap-section); max-width: 500px;
9292 }
9293 .join-complete-choice { text-decoration: none; color: inherit; display: block; }
9294 .join-complete-card {
9295 background: var(--surface-sunken); padding: var(--gap-section); text-align: center;
9296 border: 2px solid transparent; transition: border-color 0.15s;
9297 }
9298 .join-complete-choice:hover .join-complete-card,
9299 .join-complete-card--active { border-color: var(--content); }
9300 .join-complete-card-title {
9301 font-family: var(--font-display); font-weight: bold;
9302 font-size: var(--text-lead); margin-bottom: var(--gap-peer);
9303 }
9304 .join-complete-card-desc {
9305 font-size: var(--text-note); opacity: 0.7; margin: 0;
9306 }
9307 .join-complete-footnote {
9308 font-size: var(--text-note); opacity: 0.5;
9309 margin-top: var(--gap-section); text-align: center;
9310 }
9311
9312 /* ===========================================
9313 PROMO CODES LIST (replaces inline styles in partials/promo_codes_list.html)
9314 =========================================== */
9315 .promo-codes-row--expired { opacity: 0.5; }
9316 .promo-codes-row--pending { opacity: 0.7; }
9317 .promo-codes-schedule { font-size: var(--text-note); }
9318 .promo-codes-schedule-pending { color: var(--warning); }
9319 .promo-codes-schedule-expired { color: var(--danger); }
9320 .promo-codes-schedule-none { opacity: 0.5; }
9321 .promo-codes-actions { white-space: nowrap; }
9322 .promo-codes-edit-btn { font-size: var(--text-fine); }
9323 .promo-codes-edit-row { background: var(--surface-sunken); }
9324 .promo-codes-edit-cell { padding: var(--gap-section); }
9325 .promo-codes-edit-form {
9326 display: flex; gap: var(--gap-section); align-items: end; flex-wrap: wrap;
9327 }
9328 .promo-codes-edit-label {
9329 font-size: var(--text-fine); display: block; margin-bottom: var(--gap-bound);
9330 }
9331 .promo-codes-edit-save { font-size: var(--text-note); }
9332 .promo-codes-bulk-row { margin-top: var(--gap-section); }
9333 .promo-codes-bulk-btn { font-size: var(--text-note); opacity: 0.7; }
9334
9335 /* ===========================================
9336 USER MEDIA TAB (replaces inline styles in partials/tabs/user_media.html)
9337 =========================================== */
9338
9339 .user-media-intro {
9340 margin-bottom: var(--gap-section);
9341 }
9342
9343 .user-media-upload {
9344 border: 2px dashed var(--border);
9345 padding: var(--gap-pane);
9346 margin-bottom: var(--gap-pane);
9347 text-align: center;
9348 background: var(--surface-overlay);
9349 }
9350
9351 .user-media-upload--dragover {
9352 border-color: var(--action);
9353 }
9354
9355 .user-media-upload-row {
9356 margin-bottom: var(--gap-section);
9357 }
9358
9359 .user-media-upload-label {
9360 display: inline-block;
9361 font-size: var(--text-note);
9362 margin-right: var(--gap-section);
9363 }
9364
9365 .user-media-upload-folder-input {
9366 padding: var(--gap-bound);
9367 font-size: var(--text-note);
9368 width: 140px;
9369 margin-left: var(--gap-bound);
9370 }
9371
9372 .user-media-upload-progress {
9373 margin-top: var(--gap-section);
9374 }
9375
9376 .user-media-upload-status {
9377 font-size: var(--text-note);
9378 margin-bottom: var(--gap-bound);
9379 }
9380
9381 .user-media-upload-status--ok {
9382 color: var(--success);
9383 }
9384
9385 .user-media-upload-status--err {
9386 color: var(--danger);
9387 }
9388
9389 .user-media-folders {
9390 margin-bottom: var(--gap-section);
9391 display: flex;
9392 gap: var(--gap-peer);
9393 flex-wrap: wrap;
9394 align-items: center;
9395 }
9396
9397 .user-media-folders-label {
9398 font-size: var(--text-note);
9399 opacity: 0.7;
9400 }
9401
9402 .user-media-folders-btn {
9403 font-size: var(--text-fine);
9404 }
9405
9406 .user-media-grid {
9407 display: grid;
9408 grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
9409 gap: var(--gap-section);
9410 }
9411
9412 .user-media-card {
9413 background: var(--surface-overlay);
9414 border: 1px solid var(--border);
9415 padding: var(--gap-section);
9416 position: relative;
9417 }
9418
9419 .user-media-card-thumb {
9420 width: 100%;
9421 height: 120px;
9422 overflow: hidden;
9423 margin-bottom: var(--gap-peer);
9424 display: flex;
9425 align-items: center;
9426 justify-content: center;
9427 background: var(--surface-sunken);
9428 }
9429
9430 .user-media-card-thumb--video {
9431 font-size: var(--text-title);
9432 opacity: 0.4;
9433 }
9434
9435 .user-media-card-img {
9436 max-width: 100%;
9437 max-height: 120px;
9438 object-fit: contain;
9439 }
9440
9441 .user-media-card-name {
9442 font-size: var(--text-fine);
9443 word-break: break-all;
9444 margin-bottom: var(--gap-bound);
9445 }
9446
9447 .user-media-card-meta {
9448 font-size: var(--text-fine);
9449 opacity: 0.6;
9450 margin-bottom: var(--gap-peer);
9451 }
9452
9453 .user-media-card-actions {
9454 display: flex;
9455 gap: var(--gap-bound);
9456 }
9457
9458 .user-media-card-btn {
9459 font-size: var(--text-fine);
9460 padding: var(--gap-bound) var(--gap-peer);
9461 }
9462
9463 /* ===========================================
9464 ITEM VERSION UPLOAD (replaces inline styles in partials/item_version_upload.html)
9465 =========================================== */
9466
9467 .item-version-upload-cell-sm {
9468 font-size: var(--text-note);
9469 }
9470
9471 .item-version-upload-no-file {
9472 opacity: 0.5;
9473 }
9474
9475 .item-version-upload-btn {
9476 padding: var(--gap-peer) var(--gap-section);
9477 font-size: var(--text-note);
9478 }
9479
9480 .item-version-upload-add-btn {
9481 padding: var(--gap-peer) var(--gap-section);
9482 }
9483
9484 .item-version-upload-grid {
9485 display: grid;
9486 grid-template-columns: 1fr 1fr;
9487 gap: var(--gap-section);
9488 }
9489
9490 .item-version-upload-hint {
9491 margin-bottom: var(--gap-peer);
9492 }
9493
9494 .item-version-upload-file-table {
9495 width: 100%;
9496 border-collapse: collapse;
9497 }
9498
9499 .item-version-upload-file-table thead tr {
9500 text-align: left;
9501 font-size: var(--text-fine);
9502 opacity: 0.7;
9503 text-transform: uppercase;
9504 letter-spacing: 0.03em;
9505 }
9506
9507 .item-version-upload-file-table th:first-child {
9508 padding: var(--gap-peer) var(--gap-peer) var(--gap-peer) 0;
9509 }
9510
9511 .item-version-upload-file-table th + th {
9512 padding: var(--gap-peer);
9513 }
9514
9515 .item-version-upload-file-table th:last-child {
9516 width: 40px;
9517 }
9518
9519 .item-version-upload-add-row {
9520 margin-top: var(--gap-peer);
9521 }
9522
9523 .item-version-upload-queue {
9524 margin-bottom: var(--gap-section);
9525 }
9526
9527 .item-version-upload-speed {
9528 font-size: var(--text-fine);
9529 opacity: 0.6;
9530 margin-top: var(--gap-bound);
9531 }
9532
9533 /* ===========================================
9534 PROJECT PAGE (replaces inline styles in pages/project.html)
9535 =========================================== */
9536
9537 .project-page .store-cover {
9538 width: 120px;
9539 height: 120px;
9540 border-radius: var(--radius-panel);
9541 object-fit: cover;
9542 margin-bottom: var(--gap-pane);
9543 }
9544 .project-page .follow-btn.is-selected {
9545 opacity: 0.7;
9546 }
9547 .project-page .follower-count-muted {
9548 font-size: var(--text-note);
9549 opacity: 0.7;
9550 padding: var(--gap-peer) 0;
9551 }
9552 .project-page .promo-details {
9553 font-size: var(--text-note);
9554 margin-bottom: var(--gap-peer);
9555 }
9556 .project-page .promo-details summary {
9557 cursor: pointer;
9558 opacity: 0.7;
9559 }
9560 .project-page .promo-input {
9561 width: 100%;
9562 margin-top: var(--gap-peer);
9563 padding: var(--gap-bound) var(--gap-peer);
9564 font-size: var(--text-note);
9565 text-transform: uppercase;
9566 }
9567 .project-page .store-footer-links {
9568 margin-top: var(--gap-peer);
9569 }
9570
9571 /* ===========================================
9572 INSERTION LIST (replaces inline styles in partials/insertion_list.html)
9573 =========================================== */
9574
9575 .insertion-list-heading {
9576 font-family: var(--font-mono);
9577 font-size: var(--text-body);
9578 margin-bottom: var(--gap-section);
9579 }
9580 .insertion-list-toolbar {
9581 margin-bottom: var(--gap-section);
9582 }
9583 .insertion-list-upload {
9584 font-family: var(--font-mono);
9585 }
9586 .insertion-list-table {
9587 width: 100%;
9588 font-family: var(--font-sans);
9589 }
9590 .insertion-list-table th {
9591 text-align: left;
9592 font-family: var(--font-mono);
9593 font-size: var(--text-note);
9594 }
9595 .insertion-list-table th.insertion-list-th-actions {
9596 text-align: right;
9597 }
9598 .insertion-list-table .insertion-list-actions {
9599 text-align: right;
9600 }
9601 .insertion-list-table .insertion-list-action-btn {
9602 font-family: var(--font-mono);
9603 }
9604
9605 /* ===========================================
9606 PROJECT MEMBERS TAB (replaces inline styles in partials/tabs/project_members.html)
9607 =========================================== */
9608
9609 .proj-members-summary {
9610 background: var(--surface-sunken);
9611 padding: var(--gap-section);
9612 margin-bottom: var(--gap-section);
9613 }
9614
9615 .proj-members-summary-row {
9616 display: flex;
9617 justify-content: space-between;
9618 align-items: center;
9619 }
9620
9621 .proj-members-stat {
9622 font-size: var(--text-head);
9623 font-weight: bold;
9624 }
9625
9626 .proj-members-add {
9627 margin-bottom: var(--gap-page);
9628 }
9629
9630 .proj-members-add-grid {
9631 display: grid;
9632 grid-template-columns: 1fr auto auto;
9633 gap: var(--gap-section);
9634 align-items: end;
9635 }
9636
9637 .proj-members-field-label {
9638 font-size: var(--text-note);
9639 display: block;
9640 margin-bottom: var(--gap-bound);
9641 }
9642
9643 .proj-members-add-result {
9644 margin-top: var(--gap-peer);
9645 }
9646
9647 .proj-members-name-link {
9648 color: inherit;
9649 }
9650
9651 .proj-members-split-cell {
9652 font-weight: bold;
9653 }
9654
9655 /* ===========================================
9656 PLACEMENT LIST (replaces inline styles in partials/placement_list.html)
9657 =========================================== */
9658
9659 .placement-list-heading {
9660 font-family: var(--font-mono);
9661 font-size: var(--text-body);
9662 margin-bottom: var(--gap-section);
9663 }
9664 .placement-list-table {
9665 width: 100%;
9666 font-family: var(--font-sans);
9667 margin-bottom: var(--gap-section);
9668 }
9669 .placement-list-table th {
9670 text-align: left;
9671 font-family: var(--font-mono);
9672 font-size: var(--text-note);
9673 }
9674 .placement-list-table th.placement-list-th-actions {
9675 text-align: right;
9676 }
9677 .placement-list-table .placement-list-actions {
9678 text-align: right;
9679 }
9680 .placement-list-table .placement-list-remove {
9681 font-family: var(--font-mono);
9682 }
9683 .placement-list-form {
9684 display: flex;
9685 gap: var(--gap-peer);
9686 align-items: flex-end;
9687 flex-wrap: wrap;
9688 }
9689 .placement-list-label {
9690 display: block;
9691 font-family: var(--font-mono);
9692 font-size: var(--text-fine);
9693 margin-bottom: var(--gap-bound);
9694 }
9695 .placement-list-add {
9696 font-family: var(--font-mono);
9697 }
9698
9699 /* ===========================================
9700 USER SYNCKIT TAB (replaces inline styles in partials/tabs/user_synckit.html)
9701 =========================================== */
9702
9703 .user-synckit-create { margin-bottom: var(--gap-pane); }
9704 .user-synckit-create-body {
9705 padding: var(--gap-section);
9706 background: var(--surface-overlay);
9707 border: 1px solid var(--border);
9708 }
9709 .user-synckit-create-form {
9710 display: flex;
9711 flex-direction: row;
9712 gap: var(--gap-section);
9713 align-items: flex-end;
9714 }
9715 .user-synckit-create-form label {
9716 display: block;
9717 font-size: var(--text-note);
9718 margin-bottom: var(--gap-bound);
9719 }
9720 .user-synckit-create-form input[type="text"],
9721 .user-synckit-create-form select {
9722 padding: var(--gap-peer);
9723 font-size: var(--text-note);
9724 }
9725 .user-synckit-create-form input[type="text"] { width: 220px; }
9726 .user-synckit-create-form select { width: 180px; }
9727
9728 .user-synckit-code { font-size: var(--text-fine); }
9729 .user-synckit-hint {
9730 font-size: var(--text-fine);
9731 margin-left: var(--gap-bound);
9732 }
9733
9734 .user-synckit-inline-btn {
9735 margin-left: var(--gap-peer);
9736 padding: var(--gap-bound) var(--gap-peer);
9737 font-size: var(--text-fine);
9738 }
9739 .user-synckit-row-btn {
9740 padding: var(--gap-bound) var(--gap-peer);
9741 font-size: var(--text-fine);
9742 }
9743 .user-synckit-row-btn + .user-synckit-row-btn {
9744 margin-left: var(--gap-bound);
9745 }
9746
9747 .user-synckit-status-active { color: var(--success); }
9748 .user-synckit-status-inactive { color: var(--danger); }
9749
9750 .user-synckit-edit-btn {
9751 padding: var(--gap-bound) var(--gap-peer);
9752 font-size: var(--text-fine);
9753 }
9754
9755 .user-synckit-create-error {
9756 color: var(--danger);
9757 margin-top: var(--gap-peer);
9758 }
9759
9760 /* ===========================================
9761 TOTP (replaces inline styles in partials/totp_setup.html and totp_status.html)
9762 =========================================== */
9763
9764 /* Section wrapper between TOTP subsections (QR + backup codes + confirm,
9765 or regenerate + disable). */
9766 .totp-section { margin-bottom: var(--gap-pane); }
9767
9768 /* Confirm-setup section is separated from the backup codes by a top border. */
9769 .totp-confirm {
9770 border-top: 1px solid var(--border);
9771 padding-top: var(--gap-section);
9772 }
9773
9774 /* QR code container, centered above the manual-entry details. */
9775 .totp-qr {
9776 text-align: center;
9777 margin-bottom: var(--gap-section);
9778 }
9779 .totp-qr img {
9780 max-width: 200px;
9781 image-rendering: pixelated;
9782 }
9783
9784 /* Collapsible "Manual entry key" group. */
9785 .totp-manual { margin-bottom: var(--gap-section); }
9786 .totp-manual summary {
9787 cursor: pointer;
9788 opacity: 0.7;
9789 font-size: var(--text-note);
9790 }
9791 .totp-manual-secret {
9792 display: block;
9793 margin-top: var(--gap-peer);
9794 word-break: break-all;
9795 font-size: var(--text-note);
9796 padding: var(--gap-peer);
9797 background: var(--surface-sunken);
9798 }
9799
9800 /* Backup codes grid (rendered list of one-time codes). */
9801 .backup-codes-grid {
9802 display: grid;
9803 grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
9804 gap: var(--gap-peer);
9805 }
9806 .backup-codes-grid code {
9807 padding: var(--gap-peer);
9808 background: var(--surface-sunken);
9809 text-align: center;
9810 font-size: var(--text-note);
9811 }
9812
9813 /* Status row for the "Enabled" badge above the management forms. */
9814 .totp-status-row {
9815 display: flex;
9816 align-items: center;
9817 gap: var(--gap-peer);
9818 margin-bottom: var(--gap-section);
9819 }
9820
9821 /* Container where regenerated backup codes are swapped in via HTMX. */
9822 .totp-regen-output { margin-top: var(--gap-section); }
9823
9824 /* ===========================================
9825 DASHBOARD-USER SHELL (replaces inline styles in dashboards/dashboard-user.html)
9826 =========================================== */
9827
9828 .sandbox-banner {
9829 background: var(--action);
9830 color: var(--primary-light);
9831 padding: var(--gap-section) var(--gap-pane);
9832 text-align: center;
9833 font-family: var(--font-mono);
9834 font-size: var(--text-note);
9835 }
9836
9837 .sandbox-banner-link {
9838 color: var(--primary-light);
9839 text-decoration: underline;
9840 margin-left: var(--gap-peer);
9841 }
9842 .account-status-actions {
9843 display: flex;
9844 gap: var(--gap-section);
9845 flex-wrap: wrap;
9846 margin-top: var(--gap-section);
9847 }
9848
9849 .suspension-wrap {
9850 margin-bottom: var(--gap-section);
9851 }
9852
9853 .password-warning-banner {
9854 margin-bottom: var(--gap-section);
9855 padding: var(--gap-section);
9856 background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
9857 border: 1px solid var(--warning);
9858 font-size: var(--text-body);
9859 }
9860
9861 .dash-user-header-links {
9862 margin-bottom: var(--gap-peer);
9863 display: flex;
9864 gap: var(--gap-pane);
9865 }
9866
9867 .dash-user-header-link {
9868 font-size: var(--text-note);
9869 opacity: 0.7;
9870 }
9871
9872 .checklist-recovery {
9873 padding: var(--gap-section) 0;
9874 text-align: right;
9875 }
9876
9877 .checklist-recovery-link {
9878 font-size: var(--text-note);
9879 opacity: 0.7;
9880 }
9881
9882
9883 /* ===========================================
9884 USER PROFILE PAGE (replaces inline styles in pages/user.html)
9885 =========================================== */
9886
9887 .user-page .creator-paused-notice {
9888 margin-bottom: var(--gap-pane);
9889 padding: var(--gap-section);
9890 background: var(--surface-sunken);
9891 border: 1px solid var(--border);
9892 text-align: center;
9893 font-size: var(--text-body);
9894 }
9895
9896 .user-page .profile-action-row {
9897 margin-top: var(--gap-section);
9898 }
9899
9900 .user-page .profile-edit-btn {
9901 display: inline-block;
9902 padding: var(--gap-peer) var(--gap-section);
9903 text-decoration: none;
9904 font-size: var(--text-note);
9905 }
9906
9907 .user-page .follow-btn.is-selected {
9908 opacity: 0.7;
9909 }
9910
9911 .user-page .follower-count {
9912 margin-top: var(--gap-section);
9913 font-size: var(--text-note);
9914 opacity: 0.7;
9915 }
9916
9917 .user-page .profile-share-row {
9918 margin-top: var(--gap-section);
9919 display: flex;
9920 gap: var(--gap-section);
9921 justify-content: center;
9922 font-size: var(--text-note);
9923 }
9924
9925 .user-page .profile-share-link {
9926 opacity: 0.7;
9927 }
9928
9929 .user-page .collections-section {
9930 margin-bottom: var(--gap-page);
9931 }
9932
9933 /* ===========================================
9934 SUSPENSION BANNER (replaces inline styles in partials/suspension_banner.html)
9935 =========================================== */
9936
9937 .suspension-banner {
9938 background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
9939 border: 1px solid var(--danger);
9940 padding: var(--gap-pane);
9941 margin-bottom: var(--gap-pane);
9942 }
9943
9944 .suspension-banner-title {
9945 margin: 0 0 var(--gap-peer) 0;
9946 color: var(--danger);
9947 }
9948
9949 .suspension-banner-reason {
9950 font-size: var(--text-note);
9951 margin: 0 0 var(--gap-section) 0;
9952 }
9953
9954 .suspension-banner-denial {
9955 background: var(--surface-overlay);
9956 padding: var(--gap-section);
9957 margin-bottom: var(--gap-section);
9958 font-size: var(--text-note);
9959 }
9960
9961 .suspension-banner-denial-response {
9962 margin: var(--gap-bound) 0 0 0;
9963 }
9964
9965 .suspension-banner-denial-hint {
9966 margin: var(--gap-peer) 0 0 0;
9967 opacity: 0.7;
9968 }
9969
9970 .suspension-banner-pending {
9971 font-size: var(--text-note);
9972 opacity: 0.7;
9973 margin: 0;
9974 }
9975
9976 .suspension-banner-appeal {
9977 margin-top: var(--gap-peer);
9978 }
9979
9980 .suspension-banner-appeal-summary {
9981 cursor: pointer;
9982 font-size: var(--text-note);
9983 }
9984
9985 .suspension-banner-appeal-form {
9986 margin-top: var(--gap-section);
9987 }
9988
9989 .suspension-banner-appeal-text {
9990 width: 100%;
9991 margin-bottom: var(--gap-peer);
9992 font-size: var(--text-note);
9993 }
9994
9995 .suspension-banner-appeal-submit {
9996 font-size: var(--text-note);
9997 }
9998
9999 .suspension-banner-export {
10000 margin-top: var(--gap-section);
10001 }
10002
10003 .suspension-banner-export-link {
10004 font-size: var(--text-note);
10005 }
10006
10007 /* ===========================================
10008 REPORT MODAL (replaces inline styles in partials/report_modal.html)
10009 =========================================== */
10010
10011 .report-modal-content {
10012 background: var(--surface-page);
10013 padding: var(--gap-page);
10014 width: 90%;
10015 max-width: 480px;
10016 max-height: 90vh;
10017 overflow-y: auto;
10018 }
10019
10020 .report-modal-title {
10021 font-size: var(--text-subhead);
10022 margin: 0;
10023 }
10024
10025 .report-modal-fieldset {
10026 border: none;
10027 padding: 0;
10028 margin: 0 0 var(--gap-section) 0;
10029 }
10030
10031 .report-modal-legend {
10032 font-size: var(--text-note);
10033 font-weight: 600;
10034 margin-bottom: var(--gap-peer);
10035 }
10036
10037 .report-modal-option {
10038 display: block;
10039 margin-bottom: var(--gap-peer);
10040 cursor: pointer;
10041 }
10042
10043 .report-modal-details {
10044 margin-bottom: var(--gap-section);
10045 }
10046
10047 .report-modal-details-label {
10048 display: block;
10049 font-size: var(--text-note);
10050 margin-bottom: var(--gap-bound);
10051 }
10052
10053 .report-modal-textarea {
10054 width: 100%;
10055 font-size: var(--text-note);
10056 }
10057
10058 .report-modal-actions {
10059 display: flex;
10060 gap: var(--gap-peer);
10061 justify-content: flex-end;
10062 }
10063
10064 /* ===========================================
10065 LIBRARY COLLECTIONS TAB (partials/tabs/library_collections.html)
10066 =========================================== */
10067
10068 .badge.is-faded { opacity: 0.5; }
10069 .lib-coll-new { margin-top: var(--gap-pane); }
10070 .lib-coll-new-summary { cursor: pointer; font-weight: bold; }
10071 .lib-coll-new-form { margin-top: var(--gap-section); }
10072 .lib-coll-wishlist-heading {
10073 font-size: var(--text-lead);
10074 margin-top: var(--gap-page);
10075 margin-bottom: var(--gap-section);
10076 opacity: 0.8;
10077 }
10078 .lib-coll-wishlist-wrap {
10079 overflow-x: auto;
10080 -webkit-overflow-scrolling: touch;
10081 }
10082 .lib-coll-wishlist-table { min-width: 500px; }
10083 .lib-coll-row-actions { white-space: nowrap; }
10084 .lib-coll-row-actions .lib-coll-cart-btn { margin-right: var(--gap-bound); }
10085
10086 /* ===========================================
10087 USER ANALYTICS TAB (partials/tabs/user_analytics.html)
10088 =========================================== */
10089
10090 .analytics-export-link { margin-left: var(--gap-section); }
10091 .analytics-range-heading {
10092 font-size: var(--text-lead);
10093 margin: 0;
10094 }
10095 .analytics-section--comparison { margin-bottom: var(--gap-page); }
10096 .analytics-section--comparison h2 { margin-bottom: var(--gap-section); }
10097 .analytics-table-wrap {
10098 overflow-x: auto;
10099 -webkit-overflow-scrolling: touch;
10100 }
10101 .analytics-table { min-width: 500px; }
10102 .analytics-revenue-cell {
10103 display: flex;
10104 align-items: center;
10105 gap: var(--gap-peer);
10106 }
10107 .analytics-revenue-bar {
10108 width: var(--fill, 0%);
10109 height: 10px;
10110 min-width: 2px;
10111 max-width: 80px;
10112 background: var(--action);
10113 }
10114
10115 /* ===========================================
10116 ITEM EMBED TAB (templates/partials/tabs/item_embed.html)
10117 =========================================== */
10118 .embed-intro {
10119 opacity: 0.7;
10120 font-size: var(--text-note);
10121 margin-bottom: var(--gap-pane);
10122 }
10123 .embed-option {
10124 margin-bottom: var(--gap-pane);
10125 padding: var(--gap-section);
10126 border: 1px solid var(--border);
10127 border-radius: var(--radius-panel);
10128 }
10129 .embed-option h4 {
10130 margin-bottom: var(--gap-bound);
10131 font-size: var(--text-body);
10132 }
10133 .embed-option-desc {
10134 font-size: var(--text-note);
10135 opacity: 0.7;
10136 margin-bottom: var(--gap-peer);
10137 }
10138 .embed-preview {
10139 margin: var(--gap-section) 0;
10140 overflow: hidden;
10141 }
10142 .embed-iframe {
10143 border: none;
10144 border-radius: var(--radius-control);
10145 }
10146 .embed-layout-toggle {
10147 font-size: var(--text-note);
10148 margin-bottom: var(--gap-peer);
10149 display: flex;
10150 gap: var(--gap-section);
10151 }
10152 .embed-layout-toggle label {
10153 cursor: pointer;
10154 display: flex;
10155 align-items: center;
10156 gap: var(--gap-bound);
10157 }
10158 .embed-code-row {
10159 display: flex;
10160 align-items: center;
10161 gap: var(--gap-peer);
10162 }
10163 .embed-snippet {
10164 flex: 1;
10165 padding: var(--gap-peer) var(--gap-section);
10166 background: var(--surface-overlay);
10167 border-radius: var(--radius-control);
10168 font-size: var(--text-fine);
10169 overflow-x: auto;
10170 white-space: nowrap;
10171 font-family: var(--font-mono);
10172 border: 1px solid var(--border);
10173 }
10174 .copy-btn {
10175 padding: 6px 12px;
10176 font-size: var(--text-fine);
10177 background: var(--action);
10178 color: var(--primary-light);
10179 border: none;
10180 border-radius: var(--radius-control);
10181 cursor: pointer;
10182 white-space: nowrap;
10183 }
10184 .copy-btn:hover {
10185 opacity: 0.9;
10186 }
10187
10188 /* ===========================================
10189 ADMIN NAV (templates/partials/admin_nav.html)
10190 =========================================== */
10191 .admin-nav {
10192 display: flex;
10193 gap: var(--gap-peer);
10194 margin-bottom: var(--gap-pane);
10195 flex-wrap: wrap;
10196 font-family: var(--font-mono);
10197 font-size: var(--text-note);
10198 }
10199 .admin-nav a {
10200 padding: var(--gap-bound) var(--gap-section);
10201 text-decoration: none;
10202 }
10203
10204 /* Admin nav items and filter-bar toggles are .btn-primary / .btn-secondary,
10205 toggled by the templates to mark the current page or active filter. Colour
10206 and depth come from the button recipe; only the tighter admin metrics and
10207 the active weight are stated here. */
10208 .admin-nav a.btn-primary,
10209 .filter-bar button.btn-primary {
10210 font-weight: 700;
10211 }
10212 .admin-nav a.btn-secondary:hover,
10213 .filter-bar button.btn-secondary:hover {
10214 color: var(--content);
10215 }
10216
10217 /* ===========================================
10218 VIDEO PLAYER PAGE (templates/pages/video_player.html)
10219 =========================================== */
10220 .video-cover-img {
10221 width: 100%;
10222 display: block;
10223 background: var(--primary-dark);
10224 }
10225 .video-cover-placeholder {
10226 width: 100%;
10227 aspect-ratio: 16 / 9;
10228 background: var(--primary-dark);
10229 display: flex;
10230 align-items: center;
10231 justify-content: center;
10232 color: var(--content-muted);
10233 }
10234 .video-store-cta {
10235 margin: var(--gap-page) 0;
10236 padding: var(--gap-pane);
10237 background: var(--surface-sunken);
10238 text-align: center;
10239 }
10240 .video-cta-lead {
10241 font-size: var(--text-lead);
10242 margin-bottom: var(--gap-section);
10243 }
10244 .video-cta-price {
10245 font-size: var(--text-head);
10246 margin-bottom: var(--gap-section);
10247 }
10248 .video-cta-fineprint {
10249 font-size: var(--text-note);
10250 opacity: 0.7;
10251 margin-top: var(--gap-section);
10252 }
10253 .video-library-status {
10254 font-family: var(--font-mono);
10255 color: var(--content-muted);
10256 }
10257
10258 /* ===========================================
10259 LIBRARY LOCKED PAGE (templates/pages/library_locked.html)
10260 =========================================== */
10261 .library-locked-byline {
10262 opacity: 0.7;
10263 }
10264 .library-locked-cover {
10265 max-width: 320px;
10266 margin: var(--gap-section) 0;
10267 /* Reserve the space before the image loads (audit Run 17 Frontend). */
10268 aspect-ratio: 1 / 1;
10269 object-fit: cover;
10270 }
10271 .library-locked-purchase {
10272 margin-top: var(--gap-page);
10273 }
10274 .library-locked-lead {
10275 opacity: 0.85;
10276 }
10277 .library-locked-actions {
10278 margin-top: var(--gap-pane);
10279 display: flex;
10280 gap: var(--gap-section);
10281 flex-wrap: wrap;
10282 }
10283 .library-locked-bundles {
10284 margin-top: var(--gap-pane);
10285 }
10286 .library-locked-bundles-title {
10287 font-weight: bold;
10288 }
10289 .library-locked-bundle {
10290 margin-top: var(--gap-peer);
10291 }
10292 .library-locked-bundle-price {
10293 opacity: 0.7;
10294 }
10295
10296 /* ===========================================
10297 BLOG (replaces inline styles in project_blog.html
10298 and dashboard-blog-editor.html)
10299 =========================================== */
10300
10301 .blog-empty-state { padding: var(--gap-page) 0; }
10302 .blog-empty-hint {
10303 opacity: 0.7;
10304 font-size: var(--text-note);
10305 margin-top: var(--gap-peer);
10306 }
10307 .blog-post-title-link { font-weight: bold; }
10308 .blog-post-actions {
10309 display: flex;
10310 gap: var(--gap-peer);
10311 }
10312
10313 /* Blog editor dashboard (templates/dashboards/dashboard-blog-editor.html). */
10314 .blog-editor h1 {
10315 font-size: var(--text-title);
10316 margin-bottom: var(--gap-section);
10317 text-align: left;
10318 }
10319 .blog-editor .editor-form { max-width: 800px; }
10320 .blog-editor .editor-form .form-group { margin-bottom: var(--gap-section); }
10321 .blog-editor .editor-form textarea {
10322 font-family: var(--font-mono);
10323 font-size: var(--text-note);
10324 min-height: 400px;
10325 resize: vertical;
10326 }
10327 .blog-editor-slug-spinner { font-size: var(--text-note); }
10328 .blog-editor-actions {
10329 display: flex;
10330 gap: var(--gap-section);
10331 align-items: center;
10332 }
10333 .blog-editor-status { margin-top: var(--gap-peer); }
10334
10335 /* ===========================================
10336 AUDIO PLAYER (replaces inline styles in pages/audio_player.html)
10337 =========================================== */
10338
10339 .audio-store-cta {
10340 margin: var(--gap-page) 0;
10341 padding: var(--gap-pane);
10342 background: var(--surface-sunken);
10343 text-align: center;
10344 }
10345 .audio-store-cta .audio-access-msg {
10346 font-size: var(--text-lead);
10347 margin-bottom: var(--gap-section);
10348 }
10349 .audio-store-cta .audio-price {
10350 font-size: var(--text-head);
10351 margin-bottom: var(--gap-section);
10352 }
10353 .audio-store-cta .audio-fee-note {
10354 font-size: var(--text-note);
10355 opacity: 0.7;
10356 margin-top: var(--gap-section);
10357 }
10358 .audio-library-status {
10359 font-family: var(--font-mono);
10360 color: var(--content-muted);
10361 }
10362
10363 /* ===========================================
10364 SECTIONS WIZARD STEP (templates/wizards/steps/item/sections.html)
10365 Parallels .psection-row used by item_details + project-sections.js,
10366 but isolated so wizard markup can evolve independently.
10367 =========================================== */
10368
10369 .section-row {
10370 display: flex;
10371 align-items: center;
10372 gap: var(--gap-section);
10373 padding: var(--gap-group) 0;
10374 border-bottom: 1px solid var(--border);
10375 }
10376 .section-row-title { flex: 1; font-weight: bold; }
10377 .section-row-length { font-size: var(--text-fine); opacity: 0.6; }
10378 .section-row .section-delete-btn {
10379 padding: var(--gap-bound) var(--gap-group);
10380 font-size: var(--text-fine);
10381 }
10382
10383 .section-add-body { margin-top: var(--gap-section); }
10384
10385 .wizard-actions { margin-top: var(--gap-pane); }
10386
10387 /* ===========================================
10388 PROJECT SUBSCRIPTIONS TAB (templates/partials/tabs/project_subscriptions.html)
10389 =========================================== */
10390
10391 .subs-connect-info {
10392 margin-bottom: var(--gap-pane);
10393 padding: var(--gap-section);
10394 }
10395 .subs-new-tier { margin-bottom: var(--gap-page); }
10396
10397 .tier-row-desc {
10398 font-size: var(--text-note);
10399 opacity: 0.7;
10400 margin-top: var(--gap-bound);
10401 }
10402 .tier-action-btn {
10403 font-size: var(--text-note);
10404 padding: var(--gap-bound) var(--gap-group);
10405 }
10406
10407 .subs-members-bar {
10408 margin-top: var(--gap-page);
10409 display: flex;
10410 justify-content: space-between;
10411 align-items: center;
10412 }
10413
10414 /* ===========================================
10415 LIBRARY CONSUMPTION PAGES
10416 ===========================================
10417 Shared styles for the library-side consumption surfaces:
10418 text_reader.html, library_text.html (.article-page),
10419 library_downloads.html (.library-page),
10420 library_audio.html / library_video.html (.media-container + helpers).
10421 The .article-page and .library-page sections higher up in this file
10422 are activated by body classes set in the templates. */
10423
10424 /* Faded excerpt text on the public store-page text reader. */
10425 .article-page .article-body--excerpt { opacity: 0.85; }
10426
10427 /* Store-page call-to-action block (text_reader.html). */
10428 .article-page .store-cta {
10429 margin: var(--gap-page) 0;
10430 padding: var(--gap-pane);
10431 background: var(--surface-sunken);
10432 text-align: center;
10433 }
10434 .article-page .store-cta-lead {
10435 font-size: var(--text-lead);
10436 margin-bottom: var(--gap-section);
10437 }
10438 .article-page .store-cta-price {
10439 font-size: var(--text-head);
10440 margin-bottom: var(--gap-section);
10441 }
10442 .article-page .store-cta-note {
10443 font-size: var(--text-note);
10444 opacity: 0.7;
10445 margin-top: var(--gap-section);
10446 }
10447 .article-page .store-cta .library-status {
10448 font-family: var(--font-mono);
10449 color: var(--content-muted);
10450 }
10451
10452 /* Library back-link breadcrumb shared by the media library pages. */
10453 .media-container .library-back {
10454 font-size: var(--text-note);
10455 opacity: 0.7;
10456 margin: var(--gap-section) 0;
10457 }
10458 .media-container .library-back a { color: var(--content); }
10459
10460 /* Source-files download list under audio/video library pages.
10461 The .library-page version exists higher up; this block scopes the
10462 same look under .media-container for audio/video. */
10463 .media-container .library-downloads {
10464 background: var(--surface-overlay);
10465 padding: var(--gap-pane);
10466 margin-top: var(--gap-page);
10467 }
10468 .media-container .library-downloads h3 {
10469 font-size: var(--text-lead);
10470 margin-bottom: var(--gap-section);
10471 }
10472 .media-container .download-row {
10473 display: flex;
10474 align-items: center;
10475 gap: var(--gap-section);
10476 padding: var(--gap-peer) 0;
10477 border-bottom: 1px solid var(--border);
10478 }
10479 .media-container .download-row:last-child { border-bottom: none; }
10480 .media-container .download-version {
10481 min-width: 4em;
10482 font-family: var(--font-mono);
10483 font-size: var(--text-note);
10484 }
10485 .media-container .download-label { flex: 1; }
10486 .media-container .download-label--empty { flex: 1; opacity: 0.5; }
10487 .media-container .download-size {
10488 font-size: var(--text-note);
10489 opacity: 0.6;
10490 min-width: 5em;
10491 text-align: right;
10492 }
10493
10494 /* Compact download button used in both library_downloads.html and
10495 media library pages. */
10496 .btn-download-compact {
10497 padding: var(--gap-peer) var(--gap-section);
10498 font-size: var(--text-note);
10499 }
10500
10501 /* Faded "no label" placeholder on library_downloads.html download rows. */
10502 .library-page .download-label--empty { opacity: 0.5; }
10503
10504 /* Library-downloads description <details> summary + body. */
10505 .library-page .lib-summary {
10506 cursor: pointer;
10507 font-family: var(--font-display);
10508 font-size: var(--text-subhead);
10509 }
10510 .library-page .lib-summary-body { margin-top: var(--gap-section); }
10511
10512 /* License section on library_downloads.html. */
10513 .library-page .lib-license-name { margin-bottom: var(--gap-section); }
10514 .library-page .lib-license-download {
10515 font-size: var(--text-note);
10516 color: var(--content);
10517 }
10518
10519 /* Footer below the item-footer (was inline on library_downloads.html). */
10520 .library-page .item-footer-sub { margin-top: var(--gap-peer); }
10521
10522 /* library_text.html footer: a terse in-page footer for the library's own
10523 text view, unrelated to base.html's site-footer. */
10524 .article-page .lib-text-footer {
10525 text-align: center;
10526 padding: var(--gap-page);
10527 font-size: var(--text-note);
10528 opacity: 0.6;
10529 border-top: 1px solid var(--border);
10530 }
10531 .article-page .lib-text-footer a { color: var(--content); }
10532
10533 /* Media player play/pause icon initial state. The JS in media-player.js
10534 toggles via element.style.display directly, so we must NOT use
10535 !important here, element.style would still beat a normal-priority
10536 declaration, but !important in the stylesheet would defeat that. */
10537 .media-icon-hidden { display: none; }
10538
10539 /* Video element fills its .video-display parent. .video-display already
10540 provides a black background. */
10541 .video-display video { width: 100%; }
10542
10543 /* Cover image inside .cover-art on library_audio.html, the parent
10544 .cover-art already sets aspect ratio and centering; the inline
10545 width/aspect-ratio/object-fit was redundant with .cover-art img. */
10546
10547 /* ===========================================
10548 USER DASHBOARD TABS (support / sessions / buyer contacts)
10549 =========================================== */
10550
10551 /* Sessions tab (templates/partials/tabs/user_sessions.html). */
10552 .sessions-tab .sessions-table { width: 100%; }
10553 .sessions-tab .device-cell {
10554 max-width: 300px;
10555 overflow: hidden;
10556 text-overflow: ellipsis;
10557 white-space: nowrap;
10558 }
10559
10560 /* Buyer contacts tab (templates/partials/tabs/buyer_contacts.html). */
10561 .buyer-contacts .contacts-table { min-width: 500px; }
10562
10563 /* Creators page extras (templates/pages/creators.html). */
10564 .creators-page .pricing-fineprint {
10565 font-size: var(--text-note);
10566 opacity: 0.8;
10567 margin-top: var(--gap-peer);
10568 }
10569 .creators-page .trial-callout {
10570 font-size: var(--text-note);
10571 margin-top: var(--gap-pane);
10572 }
10573
10574 /* ===========================================
10575 LIBRARY TABS
10576 Shared classes for the library-dashboard tab partials:
10577 library_wishlists, library_contacts, library_purchases,
10578 library_subscriptions. Migrated from inline styles.
10579 =========================================== */
10580
10581 /* Tables in library tabs need a minimum width so columns stay readable
10582 while the outer container scrolls horizontally on narrow viewports. */
10583
10584 /* Empty-state hint paragraph (smaller, slightly faded). */
10585 .library-tab-empty-hint {
10586 font-size: var(--text-note);
10587 opacity: 0.7;
10588 margin-bottom: var(--gap-section);
10589 }
10590
10591 /* Compact row-action button (wishlist Add to Cart / Remove, etc.). */
10592
10593 .library-filter-wrap { margin-bottom: var(--gap-section); }
10594
10595 /* Purchases table "Open" action link + context menu trigger group. */
10596 .library-row-actions {
10597 display: flex;
10598 align-items: center;
10599 gap: var(--gap-peer);
10600 }
10601 .library-row-open {
10602 font-size: var(--text-fine);
10603 padding: var(--gap-bound) var(--gap-section);
10604 text-decoration: none;
10605 white-space: nowrap;
10606 }
10607
10608 /* Section sub-heading inside purchases tab (e.g. "Subscriptions"). */
10609 .library-tab-subheading {
10610 font-size: var(--text-lead);
10611 margin-top: var(--gap-page);
10612 margin-bottom: var(--gap-section);
10613 opacity: 0.8;
10614 }
10615
10616 /* Inline-block button styled as a link target on empty-state CTAs. */
10617 .library-tab-cta {
10618 display: inline-block;
10619 text-decoration: none;
10620 }
10621
10622 /* Context menu (dropdown) on each purchase row. */
10623 .context-menu-wrapper { position: relative; }
10624 .context-menu-btn {
10625 background: none;
10626 border: none;
10627 cursor: pointer;
10628 padding: var(--gap-peer);
10629 font-size: var(--text-subhead);
10630 line-height: 1;
10631 color: var(--content-muted);
10632 border-radius: var(--radius-control);
10633 }
10634 .context-menu-btn:hover {
10635 background: var(--surface-sunken);
10636 color: var(--content);
10637 }
10638 .context-menu {
10639 display: none;
10640 position: absolute;
10641 right: 0;
10642 top: 100%;
10643 background: var(--surface-overlay);
10644 border: 1px solid var(--border);
10645 box-shadow: var(--elevation-overlay);
10646 min-width: 160px;
10647 z-index: var(--z-context);
10648 }
10649 .context-menu.open { display: block; }
10650 .context-menu-item {
10651 display: block;
10652 width: 100%;
10653 padding: var(--gap-section);
10654 text-align: left;
10655 background: none;
10656 border: none;
10657 font-family: var(--font-sans);
10658 font-size: var(--text-note);
10659 color: var(--content);
10660 cursor: pointer;
10661 text-decoration: none;
10662 }
10663 .context-menu-item:hover { background: var(--surface-sunken); }
10664 .context-menu-item.danger { color: var(--danger); }
10665
10666 /* Inline license-key code chip shown beneath a purchase date. */
10667 .license-key-inline { margin-top: var(--gap-bound); }
10668 .key-code-small {
10669 font-size: var(--text-fine);
10670 padding: var(--gap-bound) var(--gap-peer);
10671 background: var(--surface-sunken);
10672 border-radius: var(--radius-control);
10673 cursor: pointer;
10674 user-select: all;
10675 }
10676 .key-code-small:hover { background: var(--border); }
10677
10678 /* Library row title link reads as plain text until hovered. */
10679 .library-title-link:hover { text-decoration: underline; }
10680 /* ===========================================
10681 ADMIN ENTRIES (replaces inline styles in partials/admin_user_entries.html,
10682 partials/admin_waitlist_entries.html,
10683 partials/admin_appeal_entries.html)
10684 =========================================== */
10685
10686 /* (`.badge-active` / `.badge-trusted` removed in wave 2 tier B2. They were a
10687 second, ad-hoc spelling of what data-tone="success" already says, and the
10688 admin entries take the tone now.) */
10689
10690 /* "Trust" toggle button sits next to a Trusted badge. */
10691 .admin-entries-trust-btn {
10692 margin-left: var(--gap-bound);
10693 }
10694
10695 /* Waitlist pitch cell, narrow body text. */
10696 .admin-entries-pitch {
10697 font-size: var(--text-note);
10698 max-width: 300px;
10699 }
10700 .admin-entries-pitch-empty {
10701 opacity: 0.5;
10702 font-style: italic;
10703 }
10704 .admin-entries-pitch-inviter {
10705 margin-top: var(--gap-bound);
10706 }
10707
10708 /* Appeal text columns. */
10709 .admin-entries-reason {
10710 font-size: var(--text-note);
10711 max-width: 200px;
10712 }
10713 .admin-entries-appeal {
10714 font-size: var(--text-note);
10715 max-width: 300px;
10716 }
10717
10718 /* Decide form textarea + stacked action buttons. */
10719 .admin-entries-decide-textarea {
10720 width: 200px;
10721 }
10722 .admin-entries-decide-actions {
10723 display: flex;
10724 flex-direction: column;
10725 gap: var(--gap-bound);
10726 }
10727
10728 /* Pagination row at the bottom of admin entry tables. */
10729 .admin-entries-pagination {
10730 display: flex;
10731 justify-content: center;
10732 align-items: center;
10733 gap: var(--gap-section);
10734 padding: var(--gap-section) 0;
10735 }
10736
10737 /* ===========================================
10738 ITEM TABS (item_settings, item_sales, item_overview partials)
10739 =========================================== */
10740
10741 /* Schedule form is hidden until "Schedule" button is clicked. JS toggles
10742 the `.hidden` utility class. */
10743 .item-settings-schedule-form {
10744 margin-top: var(--gap-section);
10745 }
10746
10747 .item-overview-embed {
10748 margin-top: var(--gap-pane);
10749 }
10750 .item-overview-embed > summary {
10751 cursor: pointer;
10752 font-weight: bold;
10753 font-size: var(--text-lead);
10754 }
10755
10756 /* ===========================================
10757 ITEM TEXT EDITOR (partials/item_text_editor.html)
10758 =========================================== */
10759
10760 .text-editor-actions {
10761 margin-top: var(--gap-section);
10762 }
10763
10764 /* ===========================================
10765 PROJECT ANALYTICS TAB (partials/tabs/project_analytics.html)
10766 =========================================== */
10767
10768 .tab-docs-extra {
10769 margin-left: var(--gap-section);
10770 }
10771
10772 .analytics-tab-header {
10773 display: flex;
10774 justify-content: space-between;
10775 align-items: center;
10776 margin-bottom: var(--gap-section);
10777 }
10778 .analytics-tab-header h2 {
10779 font-size: var(--text-lead);
10780 margin: 0;
10781 }
10782
10783 .analytics-empty-block {
10784 text-align: center;
10785 padding: var(--gap-page) var(--gap-section);
10786 opacity: 0.6;
10787 }
10788
10789 /* ===========================================
10790 RECEIPT PAGE INLINE FRAGMENTS (pages/receipt.html)
10791 =========================================== */
10792
10793 .receipt-page .receipt-brand {
10794 opacity: 0.6;
10795 font-size: var(--text-note);
10796 }
10797 .receipt-page .receipt-meta {
10798 text-align: right;
10799 }
10800 .receipt-page .receipt-meta-line {
10801 font-family: var(--font-mono);
10802 font-size: var(--text-note);
10803 opacity: 0.6;
10804 }
10805 .receipt-page .receipt-meta-sub {
10806 font-family: var(--font-mono);
10807 font-size: var(--text-fine);
10808 opacity: 0.4;
10809 margin-top: var(--gap-bound);
10810 }
10811 .receipt-page .print-btn {
10812 margin-top: var(--gap-section);
10813 font-size: var(--text-note);
10814 }
10815
10816 /* ===========================================
10817 IMPORT PAGE INLINE FRAGMENTS (dashboards/dashboard-import.html)
10818 =========================================== */
10819
10820 .import-page .import-preview-label,
10821 .import-page .import-mapping-label,
10822 .import-page .import-progress-label {
10823 font-family: var(--font-mono);
10824 font-size: var(--text-note);
10825 margin-bottom: var(--gap-peer);
10826 }
10827 .import-page .import-mapping-label {
10828 margin-bottom: var(--gap-section);
10829 }
10830 .import-page .import-progress-label {
10831 font-size: var(--text-note);
10832 }
10833 .import-page .import-start-btn {
10834 margin-top: var(--gap-section);
10835 }
10836 .import-page .import-history-heading {
10837 font-size: var(--text-subhead);
10838 margin-bottom: var(--gap-section);
10839 }
10840
10841 /* ===========================================
10842 SLUG STATUS PARTIAL (partials/slug_status.html)
10843 =========================================== */
10844
10845 .slug-status {
10846 font-size: var(--text-note);
10847 }
10848 .slug-status.available { color: var(--success); }
10849 .slug-status.taken { color: var(--danger); }
10850
10851 .slug-suggestions {
10852 font-size: var(--text-note);
10853 display: block;
10854 margin-top: var(--gap-bound);
10855 }
10856 .slug-suggestion-link {
10857 margin-right: var(--gap-peer);
10858 }
10859
10860 /* ===========================================
10861 STRIPE DISCLAIMER PAGE (pages/stripe_disclaimer.html)
10862 =========================================== */
10863
10864 .stripe-disclaimer-page .container {
10865 max-width: 700px;
10866 margin: var(--gap-pane) auto;
10867 padding: 0 var(--gap-page);
10868 }
10869 .stripe-disclaimer-page .stripe-disclaimer-title {
10870 text-align: center;
10871 }
10872 .stripe-disclaimer-page .tagline {
10873 font-size: var(--text-note);
10874 opacity: 0.7;
10875 margin-bottom: var(--gap-page);
10876 text-align: center;
10877 }
10878 .stripe-disclaimer-page h2 {
10879 font-size: var(--text-head);
10880 margin-bottom: var(--gap-pane);
10881 }
10882 .stripe-disclaimer-page .stripe-disclaimer-lead {
10883 margin-bottom: var(--gap-pane);
10884 opacity: 0.8;
10885 }
10886 .stripe-disclaimer-page .section { margin-bottom: var(--gap-page); }
10887 .stripe-disclaimer-page .section h3 {
10888 font-size: var(--text-lead);
10889 margin-bottom: var(--gap-section);
10890 font-family: var(--font-mono);
10891 }
10892 .stripe-disclaimer-page .section ul {
10893 margin: 0;
10894 padding-left: var(--gap-section);
10895 }
10896 .stripe-disclaimer-page .section li {
10897 margin-bottom: var(--gap-peer);
10898 line-height: 1.5;
10899 }
10900 .stripe-disclaimer-page .stripe-disclaimer-fee-lead {
10901 margin-bottom: var(--gap-section);
10902 }
10903 .stripe-disclaimer-page .fee-example {
10904 background: var(--surface-page);
10905 padding: var(--gap-section);
10906 margin: var(--gap-section) 0;
10907 font-family: var(--font-mono);
10908 font-size: var(--text-note);
10909 }
10910 .stripe-disclaimer-page .fee-row {
10911 display: flex;
10912 justify-content: space-between;
10913 padding: var(--gap-bound) 0;
10914 }
10915 .stripe-disclaimer-page .fee-row.highlight {
10916 font-weight: bold;
10917 border-top: 1px solid var(--border);
10918 margin-top: var(--gap-peer);
10919 padding-top: var(--gap-peer);
10920 }
10921 .stripe-disclaimer-page .fee-row .zero { color: var(--content); }
10922 .stripe-disclaimer-page .stripe-disclaimer-note {
10923 font-size: var(--text-note);
10924 opacity: 0.8;
10925 }
10926 .stripe-disclaimer-page .acceptance {
10927 background: var(--surface-page);
10928 padding: var(--gap-section);
10929 margin: var(--gap-pane) 0;
10930 }
10931 .stripe-disclaimer-page .checkbox-group {
10932 display: flex;
10933 align-items: flex-start;
10934 gap: var(--gap-section);
10935 }
10936 .stripe-disclaimer-page .checkbox-group input[type="checkbox"] {
10937 margin-top: var(--gap-bound);
10938 flex-shrink: 0;
10939 }
10940 .stripe-disclaimer-page .checkbox-group label {
10941 line-height: 1.5;
10942 cursor: pointer;
10943 }
10944 .stripe-disclaimer-page .button-row {
10945 display: flex;
10946 gap: var(--gap-section);
10947 margin-top: var(--gap-pane);
10948 }
10949 .stripe-disclaimer-page .button-row button { flex: 1; }
10950 .stripe-disclaimer-page button:disabled {
10951 opacity: 0.5;
10952 cursor: not-allowed;
10953 }
10954 .stripe-disclaimer-page .note {
10955 font-size: var(--text-note);
10956 opacity: 0.6;
10957 margin-top: var(--gap-section);
10958 text-align: center;
10959 }
10960
10961 /* ===========================================
10962 LONG-TAIL CLEANUP, shared utilities promoted from the final inline-style sweep.
10963 Each rule replaces ≥2 inline-style instances across templates.
10964 =========================================== */
10965
10966 /* Tab-spinner loading indicator (used by 3+ dashboards). */
10967
10968 /* Inline-block secondary action link styled as a button. */
10969 .btn-link {
10970 display: inline-block;
10971 padding: var(--gap-peer) var(--gap-section);
10972 text-decoration: none;
10973 }
10974 .btn-link--sm { padding: var(--gap-peer) var(--gap-section); }
10975
10976 /* Username-availability spinner (wizards + standalone account form). */
10977 .username-spinner { font-size: var(--text-note); }
10978
10979 /* Centered foot-link beneath wizard cards. */
10980 .foot-link--centered { margin-top: var(--gap-section); text-align: center; }
10981
10982 /* Health-page status dots. */
10983 .health-indicator-dot { width: 8px; height: 8px; margin-right: var(--gap-peer); }
10984 .health-error-text { color: var(--danger); }
10985
10986 /* Inline error fragments (login_error, error_fragment) toggle this on. */
10987 .error-message.is-active { display: block; }
10988
10989 /* Cart count badge in nav. */
10990 .cart-badge-count { font-size: var(--text-fine); }
10991
10992 /* Empty-row inside a data table. */
10993 .table-empty-row { text-align: center; opacity: 0.6; }
10994
10995 /* Discover grid: card wrapper + body anchor. */
10996 .discover-grid-card { position: relative; }
10997 .discover-card-link {
10998 text-decoration: none;
10999 color: inherit;
11000 display: flex;
11001 flex-direction: column;
11002 flex: 1;
11003 }
11004 .discover-filter-checkbox {
11005 display: flex;
11006 align-items: center;
11007 gap: var(--gap-peer);
11008 cursor: pointer;
11009 font-size: var(--text-note);
11010 }
11011
11012 /* Tag suggestion list. */
11013 .tag-suggest-label {
11014 font-size: var(--text-fine);
11015 opacity: 0.7;
11016 margin-bottom: var(--gap-peer);
11017 overflow-wrap: anywhere;
11018 }
11019 .tag-suggest-pill {
11020 background: var(--surface-sunken);
11021 border: 1px solid var(--border);
11022 padding: var(--gap-bound) var(--gap-peer);
11023 font-size: var(--text-fine);
11024 cursor: pointer;
11025 margin: var(--gap-bound);
11026 }
11027
11028 /* Dashboard-export header layout + Export-all CTA. */
11029 .dashboard-export-header {
11030 display: flex;
11031 justify-content: space-between;
11032 align-items: flex-start;
11033 }
11034 .export-all-btn {
11035 white-space: nowrap;
11036 margin-top: var(--gap-peer);
11037 }
11038
11039 /* Tiny inline action button (admin-metrics, dashboard-item share). */
11040 .dashboard-item-share-btn {
11041 font-size: var(--text-fine);
11042 padding: var(--gap-bound) var(--gap-peer);
11043 opacity: 0.6;
11044 }
11045
11046 /* Monospaced page sub-headers. */
11047 .section-mono-meta {
11048 font-family: var(--font-mono);
11049 font-size: var(--text-note);
11050 opacity: 0.6;
11051 margin-bottom: var(--gap-pane);
11052 }
11053 /* Email-result single-card page. */
11054 .email-result-wrap {
11055 max-width: 600px;
11056 margin: 50px auto;
11057 padding: 20px;
11058 }
11059
11060 /* Admin lottery form (admin-waitlist). */
11061 .lottery-form {
11062 display: flex;
11063 gap: var(--gap-section);
11064 align-items: flex-end;
11065 }
11066
11067 /* Login screen: "or" divider + passkey button + error slot. */
11068 .login-divider {
11069 text-align: center;
11070 margin: var(--gap-pane) 0 var(--gap-section);
11071 opacity: 0.5;
11072 font-size: var(--text-note);
11073 }
11074 .login-passkey-btn { width: 100%; }
11075 .login-passkey-error { margin-top: var(--gap-peer); }
11076
11077 /* Upload-progress sibling text (item_audio_upload). */
11078 .upload-speed-text {
11079 font-size: var(--text-fine);
11080 opacity: 0.6;
11081 margin-top: var(--gap-bound);
11082 }
11083
11084 /* Faded "Following" follow-button state. */
11085 .follow-btn.is-selected { opacity: 0.7; }
11086
11087 /* Project blog footer. */
11088 .project-blog-footer {
11089 margin-top: var(--gap-page);
11090 padding-top: var(--gap-page);
11091 border-top: 1px solid var(--border);
11092 opacity: 0.6;
11093 }
11094 .project-blog-footer a { color: var(--content); }
11095
11096 /* Compact alert / paragraph margin variants used 2+ times. */
11097
11098 /* Paywall trust line. */
11099 .paywall-trust {
11100 font-size: var(--text-note);
11101 opacity: 0.7;
11102 margin-top: var(--gap-section);
11103 line-height: 1.4;
11104 }
11105
11106 /* Confirm-delete: button background already covered by .danger class.
11107 The wizard step uses `.primary` and inlines the danger background; we just
11108 change the class instead. No CSS rule needed here. */
11109
11110 /* Paywall-style centered narrow form-group used inside project_paywall. */
11111 .form-group--centered-narrow {
11112 max-width: 240px;
11113 margin: var(--gap-section) auto;
11114 }
11115
11116 /* Inline-form margin-left for adjacent pills (collection.html private badge). */
11117 .badge--inline-l { margin-left: var(--gap-peer); }
11118
11119 /* ===== Team page ===== */
11120
11121 .team-page { max-width: 56rem; margin: 0 auto; }
11122 .team-intro {
11123 font-size: var(--text-body);
11124 line-height: 1.55;
11125 margin: 0 0 var(--gap-page);
11126 opacity: 0.85;
11127 }
11128 .team-section { margin-top: var(--gap-page); }
11129 .team-section + .team-section { margin-top: var(--gap-page); }
11130 .team-card-experience {
11131 margin: var(--gap-section) 0 var(--gap-peer);
11132 line-height: 1.5;
11133 }
11134 .team-card-links {
11135 margin: var(--gap-peer) 0;
11136 font-size: var(--text-body);
11137 }
11138 .team-card-link { font-weight: 500; }
11139 .team-card-bio {
11140 margin-top: var(--gap-section);
11141 border-top: 1px solid var(--border);
11142 padding-top: var(--gap-section);
11143 }
11144 .team-card-bio > summary {
11145 cursor: pointer;
11146 font-weight: 500;
11147 font-size: var(--text-body);
11148 list-style: none;
11149 }
11150 .team-card-bio > summary::-webkit-details-marker { display: none; }
11151 .team-card-bio > summary::before {
11152 content: "+ ";
11153 display: inline-block;
11154 width: 1.2em;
11155 opacity: 0.6;
11156 }
11157 .team-card-bio[open] > summary::before { content: ""; }
11158 .team-card-bio > p {
11159 margin-top: var(--gap-section);
11160 line-height: 1.6;
11161 }
11162
11163 /* ── SyncKit billing panel ──
11164 Per-app accordion shown inside the SyncKit dashboard tab (both user and
11165 project levels). The .synckit-billing root carries pricing constants as
11166 data-attrs that static/synckit-billing.js reads to mirror monthly_price_cents.
11167 */
11168 .synckit-billing {
11169 background: var(--surface-raised);
11170 border: 1px solid var(--border);
11171 border-radius: var(--radius-control);
11172 padding: var(--gap-peer) var(--gap-section);
11173 min-width: 220px;
11174 }
11175 .synckit-billing-summary {
11176 cursor: pointer;
11177 display: flex;
11178 align-items: center;
11179 gap: var(--gap-section);
11180 font-size: var(--text-note);
11181 }
11182 .synckit-billing-toggle { margin-left: auto; color: var(--content-muted); }
11183 .synckit-billing-status {
11184 display: inline-block;
11185 padding: 2px var(--gap-peer);
11186 border-radius: var(--radius-fine);
11187 font-size: var(--text-fine);
11188 font-weight: 600;
11189 }
11190 .synckit-billing-status--draft { background: var(--surface-sunken); }
11191 .synckit-billing-status--active { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); color: var(--success); }
11192 .synckit-billing-status--past-due { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); color: var(--warning); }
11193 .synckit-billing-status--canceled { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); color: var(--danger); }
11194 .synckit-billing-price { font-weight: 600; }
11195 .synckit-billing-body {
11196 padding-top: var(--gap-section);
11197 display: flex;
11198 flex-direction: column;
11199 gap: var(--gap-section);
11200 }
11201 .synckit-billing-section { display: flex; flex-direction: column; gap: var(--gap-peer); }
11202 .synckit-billing-subheading { font-size: var(--text-note); margin: 0; font-weight: 600; }
11203 .synckit-knob-row {
11204 display: flex;
11205 align-items: center;
11206 gap: var(--gap-peer);
11207 flex-wrap: wrap;
11208 }
11209 .synckit-knob-row label { min-width: 110px; }
11210 .synckit-knob-row input[type="range"] { flex: 1 1 160px; min-width: 120px; }
11211 .synckit-radio { display: inline-flex; align-items: center; gap: var(--gap-bound); min-width: 0; }
11212 .synckit-billing-summary-line {
11213 display: flex;
11214 align-items: baseline;
11215 gap: var(--gap-peer);
11216 padding-top: var(--gap-peer);
11217 border-top: 1px solid var(--border);
11218 }
11219 .synckit-price-preview { font-size: var(--text-lead); font-weight: 600; }
11220 .synckit-billing-actions { display: flex; gap: var(--gap-peer); }
11221 .synckit-billing-manage { flex-direction: row; gap: var(--gap-peer); }
11222 .synckit-billing-status-msg { font-size: var(--text-note); min-height: 1.25em; }
11223 .synckit-billing-status-msg--info { color: var(--content-muted); }
11224 .synckit-billing-status-msg--error { color: var(--danger); }
11225 .synckit-billing-period { margin: 0; }
11226 .synckit-gauge { display: flex; flex-direction: column; gap: 2px; }
11227 .synckit-gauge-label {
11228 display: flex;
11229 justify-content: space-between;
11230 font-size: var(--text-fine);
11231 }
11232 .synckit-gauge-value { color: var(--content-muted); }
11233
11234 /* ============================================================================
11235 Carousel: the widget tier's first consumer.
11236
11237 The markup is no longer written in a template. It comes from a description --
11238 `crate::quasi::widgets::carousel` says "an ordered set of pictures, called a
11239 carousel" -- and quasi-webview turns that into a region carrying
11240 `data-widget="carousel"` with one `.picture` or `.picture-img` per frame.
11241
11242 So these rules key on the widget name rather than on a hand-written class.
11243 That is what "a renderer that recognises the name draws it its own way"
11244 means on this host: the name is the hook, and the stylesheet plus the island
11245 are the browser's idea of a carousel.
11246
11247 THE FALLBACK RUNS THE OTHER WAY NOW. This used to render frame one and hide
11248 the rest, because the controls that reach them are the scripted part -- so
11249 with JS off, two of three screenshots were unreachable. Now the unenhanced
11250 page is the whole gallery, stacked in order.
11251
11252 THE CHROME IS DESCRIBED TOO, as of makeover-layout 0.23.0. The description
11253 says `Showing::One` -- an ordered set of pictures with one showing -- and
11254 quasi-webview derives prev/position/next from that, once, for every widget
11255 there will ever be. Nothing here or in any renderer matches on the name
11256 "carousel" to get it. The island stopped injecting chrome and now only binds
11257 what is already on the page; the dot strip and the overlaid arrows are gone.
11258
11259 The controls are still not shipped live to a reader with no script: makeover
11260 keeps `.showing` hidden until something sets [data-ready], so the markup no
11261 longer has to be absent to avoid lying.
11262 ========================================================================= */
11263
11264 /* Public gallery carousel sections (item + project pages). */
11265 .item-gallery,
11266 .project-gallery {
11267 margin: var(--gap-pane) 0;
11268 }
11269
11270 /* <mnw-carousel> wraps the described region rather than being it: a custom
11271 element is what the browser upgrades automatically after an htmx swap, and
11272 the description emits a plain div because it does not know this host's tag
11273 prefix. Default display for an unknown element is inline, so say otherwise. */
11274 mnw-carousel {
11275 display: block;
11276 }
11277
11278 /* The default is ONE frame, which is what a visitor with JavaScript sees, so
11279 the page is settled from first paint and nothing rearranges on load. Showing
11280 all three and collapsing them was a 141px -> 58px jump on every load that
11281 everyone with JS paid; `no-js.css`, loaded from a <noscript>, is what opens
11282 the stack out for a visitor without scripting.
11283
11284 This is the one half of the carousel that stays local, and it is host policy
11285 rather than a gap. makeover's generated sheet collapses the stack on
11286 [data-ready], which is the honest default for a renderer that cannot know
11287 whether this page has script: it ships every child and takes them away once
11288 something binds them. Trading a load-time jump for that is a decision about
11289 this site's landing page, and <noscript> is a mechanism a generated
11290 stylesheet has no way to reach.
11291
11292 No padding-bottom and no position: relative any more. Both were for chrome
11293 that is gone -- the strip that had to be reserved and the arrows that were
11294 absolutely positioned over the frame. */
11295 [data-widget="carousel"] > .showing-frame:not(.current) {
11296 /* respec-ok: the generated rule is the same claim gated on [data-ready], and
11297 what it cannot express is <noscript>. A renderer that cannot know whether a
11298 page has script has to ship every child and take them away once something
11299 binds them; this page collapses from first paint instead and lets no-js.css
11300 open it back, which is a trade about this landing page rather than about
11301 the vocabulary. */
11302 display: none;
11303 }
11304
11305 [data-widget="carousel"] .picture {
11306 margin: 0;
11307 }
11308
11309 /* Only the corner. `display`, `width`, `height` and the raised fill and bevel
11310 all come from makeover's own `.picture-img` rule, which layout.css puts on
11311 every page through crate::shell::head. Restating any of them here is a
11312 components-layer declaration quietly beating the design system, which is what the
11313 drift check in build.rs exists to catch -- and did, on the first attempt at
11314 this file. */
11315 [data-widget="carousel"] .picture-img {
11316 border-radius: var(--radius-control);
11317 }
11318
11319 /* No colour: makeover's `.picture-caption` already sets it to
11320 --content-muted, and this said the same thing one layer louder. What is
11321 local is the centring and the mono face, which are this widget's look and
11322 not a claim about what a caption is. */
11323 [data-widget="carousel"] .picture-caption {
11324 margin-top: var(--gap-group);
11325 font-family: var(--font-mono);
11326 font-size: var(--text-note);
11327 text-align: center;
11328 }
11329
11330 /* Landing showcase only: close the top edge.
11331
11332 The three landing frames are screenshots of this site, so the parchment they
11333 were shot on is the parchment they sit on, and a 1px border between two
11334 identical grounds is not an edge anyone sees.
11335
11336 THE ELEVATION IS GONE, and that is the point of the port. This carried
11337 `box-shadow: 0 4px 10px var(--elevation), 0 14px 36px var(--elevation)`,
11338 which is the defect `c0b63ea9` names: in-flow depth is a bevel and elevation
11339 is for what sits *over* the page. It survived this long because it was
11340 invisible -- a declaration in a later layer on the same class makeover
11341 sets, so it beat the design system's bevel silently. The drift check in build.rs found it
11342 the moment the frame became a described `.picture-img`.
11343
11344 So the frame is raised by makeover's bevel now, from `Depth::Raised`, and
11345 what is left here is the one thing that was never about depth: a stronger
11346 border colour, because a shadow falling downward does nothing for the top
11347 edge and the top edge is where a parchment screenshot dissolves into a
11348 parchment page. */
11349 .landing-showcase .picture-img {
11350 border-color: var(--border-strong);
11351 }
11352
11353 /* ---------------------------------------------------------------------------
11354 Enhanced. Everything below needs the island, and the island sets
11355 [data-ready] once it has upgraded and bound the controls.
11356 --------------------------------------------------------------------------- */
11357
11358 [data-widget="carousel"][data-ready]:focus-visible {
11359 outline: 2px solid var(--focus-ring);
11360 outline-offset: 3px;
11361 }
11362
11363 /* The control row is makeover's `.showing`, rendered by the description rather
11364 than injected here, and it needs no rules: it is two `.button`s and a muted
11365 readout, all three of which the generated sheet already styles. What used to
11366 sit here was `.carousel-nav`, `.carousel-prev`, `.carousel-next`,
11367 `.carousel-dots` and `.carousel-dot` -- 60 lines for chrome this host drew by
11368 hand because nothing could describe it.
11369
11370 The arrows were absolutely positioned over the frame, which Max called
11371 cluttered and which a terminal cannot honestly do at all; the dots had no
11372 form past a handful of frames, and two of the three galleries here are
11373 creator uploads of arbitrary length. Both halves of that were the same half. */
11374
11375
11376 /* ---------------------------------------------------------------------------
11377 Custom page editor (dashboard/custom_page_editor.html)
11378 Moved out of an inline <style> block per the extract-shared-CSS rule.
11379 --------------------------------------------------------------------------- */
11380 .cp-editor { max-width: 1200px; margin: 0 auto; padding: var(--gap-section); }
11381 .cp-editor-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--gap-section); flex-wrap: wrap; }
11382 .cp-editor-actions { display: flex; gap: var(--gap-section); }
11383 .cp-intro { color: var(--content-muted); margin: var(--gap-bound) 0 var(--gap-section); }
11384 /* Form beside preview. A code textarea below about 380px is not editable, so
11385 that number is the pane's, not the viewport's, and the pair stacks itself. */
11386 .cp-editor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: var(--gap-section); align-items: start; }
11387 .cp-editor-form { display: flex; flex-direction: column; gap: var(--gap-peer); }
11388 .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; }
11389 .cp-editor-buttons { display: flex; align-items: center; gap: var(--gap-section); margin-top: var(--gap-peer); }
11390 .cp-preview-pane { border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden; }
11391 .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); }
11392 .cp-preview { width: 100%; height: 520px; border: 0; background: var(--primary-light); display: block; }
11393 .cp-blocked { margin-top: var(--gap-section); }
11394 .cp-blocked-title { font-weight: 600; margin-bottom: var(--gap-peer); }
11395 .cp-blocked-empty { color: var(--content-muted); }
11396 .cp-blocked-list { list-style: none; padding: 0; margin: 0; }
11397 .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; }
11398 .cp-blocked-kind { font-weight: 600; }
11399 .cp-blocked-loc { color: var(--content-muted); }
11400 .cp-blocked-val { background: var(--surface-sunken); padding: 0 var(--gap-bound); }
11401 .cp-status.cp-ok { color: var(--success); }
11402 .cp-status.cp-err { color: var(--warning); }
11403 .cp-reset { margin-top: var(--gap-pane); }
11404 .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)); }
11405 /* Layout-shift fix (audit Run 17 Frontend): reserve space for a cover image
11406 whose class didn't already set an aspect-ratio / fixed size. */
11407 .proj-image-preview img { width: 120px; height: 120px; object-fit: cover; }
11408
11409 /* ---- Discover search: how well a result matched -------------------------
11410 Two surfaces, doing two different jobs (db/discover.rs, migration 179).
11411
11412 .results-tier-break is the single heading, and it sits exactly where the
11413 claim it makes turns true: above it every row holds at least one word the
11414 searcher typed, below it none of them do. .row-match-note is the per-row
11415 half, "Matched 3 of 5 words", because coverage varies row to row and one
11416 page can hold a 4-of-5 beside a 1-of-5.
11417
11418 Both are deliberately quiet. They explain a result the reader is already
11419 looking at; competing with the title would make search feel like it is
11420 apologising. Muted colour and small type, no accent, no border of their own
11421 beyond the rule that separates the tiers. */
11422 .results-tier-break {
11423 display: flex;
11424 align-items: baseline;
11425 gap: var(--gap-peer);
11426 flex-wrap: wrap;
11427 padding: var(--gap-section) var(--gap-section) var(--gap-peer);
11428 border-top: 1px solid var(--border);
11429 background: var(--surface-page);
11430 }
11431
11432 /* .results-table sets border-top: none and lets .table-row draw the rules, so
11433 the break supplies its own top border. Mid-list that sits against the
11434 preceding row's border-bottom and reads as one heavier 2px rule, which is
11435 wanted here: it is a section change, not another row. At the top of an
11436 all-fuzzy page there is no preceding row, and the same border supplies the
11437 table's missing top edge. */
11438 .results-tier-label {
11439 font-family: var(--font-display);
11440 font-weight: bold;
11441 font-size: var(--text-fine);
11442 text-transform: uppercase;
11443 letter-spacing: 0.03em;
11444 color: var(--content);
11445 }
11446
11447 .results-tier-hint {
11448 font-size: var(--text-fine);
11449 color: var(--content-muted);
11450 }
11451
11452 /* The grid is `repeat(auto-fill, minmax(280px, 1fr))`, so without this the
11453 heading takes one card's slot and the tiers interleave visually. */
11454 .results-container.results-grid .results-tier-break {
11455 grid-column: 1 / -1;
11456 padding-left: 0;
11457 padding-right: 0;
11458 background: none;
11459 }
11460
11461 /* In list view this rides inside .row-info, under the creator line, not as
11462 another child of .table-row-link: that is a fixed five-column grid and a
11463 sixth child would invent a column and shift every row that carried one. */
11464 .row-match-note {
11465 font-family: var(--font-mono);
11466 font-size: var(--text-fine);
11467 color: var(--content-muted);
11468 white-space: nowrap;
11469 overflow: hidden;
11470 text-overflow: ellipsis;
11471 }
11472
11473 /* Grid cards have room to breathe; give the note its own line under the meta. */
11474 .grid-card .row-match-note {
11475 display: block;
11476 margin-top: var(--gap-bound);
11477 }
11478
11479 /* The narrow list layouts drop columns rather than wrap, and .row-info is the
11480 column that survives. Keeping the note there would crowd the title on a
11481 phone, where the tier heading above still carries the important half. */
11482 @media (max-width: 599px) {
11483 .table-row .row-match-note { /* respec-ok: hiding a cell at a breakpoint. The
11484 generated sheet describes one `.table-row`; which of its children survive a
11485 narrow viewport is this page's call, not the design system's. */ display: none; }
11486 }
11487
11488
11489 /* ============================================================================
11490 LAYER HANDOFFS
11491
11492 Everything below gives a property back to @layer makeover rather than
11493 setting one. This file is in @layer components and the order statement puts
11494 it after `makeover`, so every rule here wins over the design system whatever
11495 the specificity -- including the bare `button`, `a` and `input` rules further
11496 up, which carry no class and so reach every described component the site
11497 renders. That is the whole reason this section exists: without it a described
11498 act takes this file's chrome instead of the one makeover drew for it, and the
11499 difference is invisible in the source of either file.
11500
11501 `revert-layer` is the deferral. It names no value, so the design system can
11502 change what a chip looks like without this file learning about it.
11503
11504 Two rules govern how these are written, and both are load-bearing:
11505
11506 - Hand a property back on the arms makeover actually paints, not on the
11507 plain class. Makeover gives a chip a colour only when it is disabled, so
11508 `.chip { color: revert-layer }` would roll a live chip back past this
11509 file's `button { color }` to the UA default rather than to anything
11510 makeover chose.
11511 - Outrank the rule being deferred past. Both rules are this file's and both
11512 are in the same layer, so that contest is decided by specificity and then
11513 by source order. `.field` does not beat `input[type="text"]`, which is
11514 why the field handoffs are element-qualified, and this section sits at
11515 the end of the file so an equal-specificity handoff still wins.
11516
11517 makeover-build's check_vocabulary reads both halves and fails the build on a
11518 pair that has neither a handoff nor a reviewed exception, so this section
11519 cannot fall behind the design system without somebody being told.
11520 ============================================================================ */
11521
11522 /* The surfaces a bare `button` was taking. `.card`, `.tab` and the two facet
11523 controls are all rendered as buttons by the described screens. */
11524 .card,
11525 .tab,
11526 .facet-take,
11527 .facet-prune {
11528 /* respec-ok: a layer handoff, not a value. */
11529 background: revert-layer;
11530 box-shadow: revert-layer;
11531 }
11532
11533 .card:hover,
11534 .tab:hover,
11535 .facet-take:hover,
11536 .facet-prune:hover {
11537 /* respec-ok: a layer handoff, not a value. */
11538 background: revert-layer;
11539 }
11540
11541 .card:active,
11542 .tab:active,
11543 .facet-take:active,
11544 .facet-prune:active {
11545 /* respec-ok: a layer handoff, not a value. */
11546 background: revert-layer;
11547 box-shadow: revert-layer;
11548 }
11549
11550 /* A facet control's colour is makeover's in every state: muted when disabled,
11551 secondary when the value it names is not standing, inherited otherwise.
11552 There is no arm of it this file has an opinion about. */
11553 .facet-take,
11554 .facet-prune {
11555 /* respec-ok: a layer handoff, not a value. */
11556 color: revert-layer;
11557 }
11558
11559 /* The disabled arm. Makeover paints all four properties here -- muted text, the
11560 not-allowed cursor, and the surface it keeps rather than dropping -- and this
11561 file's `button:disabled` was taking every one of them. */
11562 .button:disabled,
11563 .button[aria-disabled="true"],
11564 .card:disabled,
11565 .card[aria-disabled="true"],
11566 .chip:disabled,
11567 .chip[aria-disabled="true"],
11568 .tab:disabled,
11569 .tab[aria-disabled="true"],
11570 .segment:disabled,
11571 .segment[aria-disabled="true"],
11572 .toggle:disabled,
11573 .toggle[aria-disabled="true"],
11574 .field:disabled,
11575 .field[aria-disabled="true"],
11576 .facet-take:disabled,
11577 .facet-take[aria-disabled="true"],
11578 .facet-prune:disabled,
11579 .facet-prune[aria-disabled="true"] {
11580 /* respec-ok: a layer handoff, not a value. */
11581 background: revert-layer;
11582 box-shadow: revert-layer;
11583 color: revert-layer;
11584 cursor: revert-layer;
11585 }
11586
11587 /* The focus ring. Makeover insets a field's so it does not collide with the
11588 bevel and leaves every other one outside, and this file's `:focus-visible`
11589 rules were flattening the distinction on every described control. */
11590 .button:focus-visible,
11591 .card:focus-visible,
11592 .chip:focus-visible,
11593 .link:focus-visible,
11594 .tab:focus-visible,
11595 .segment:focus-visible,
11596 .toggle:focus-visible,
11597 .facet-take:focus-visible,
11598 .facet-prune:focus-visible {
11599 /* respec-ok: a layer handoff, not a value. */
11600 outline: revert-layer;
11601 outline-offset: revert-layer;
11602 }
11603
11604 /* A described field is an `<input>`, a `<select>` or a `<textarea>`, and the
11605 rules for those three carry an attribute, so the handoff has to as well: a
11606 bare `.field` is the weaker selector and loses inside this layer. The `:focus`
11607 arm is here because this file sets `outline: none` on it, which removes the
11608 ring makeover draws on `:focus-visible` rather than replacing it. */
11609 input.field,
11610 select.field,
11611 textarea.field,
11612 input.field:hover,
11613 select.field:hover,
11614 textarea.field:hover,
11615 input.field:active,
11616 select.field:active,
11617 textarea.field:active {
11618 /* respec-ok: a layer handoff, not a value. */
11619 background: revert-layer;
11620 box-shadow: revert-layer;
11621 }
11622
11623 input.field:focus,
11624 select.field:focus,
11625 textarea.field:focus,
11626 input.field:focus-visible,
11627 select.field:focus-visible,
11628 textarea.field:focus-visible {
11629 /* respec-ok: a layer handoff, not a value. */
11630 outline: revert-layer;
11631 outline-offset: revert-layer;
11632 }
11633
11634 /* A described toggle is an `<input>` too. The hover and active arms are here
11635 for the same reason as the field's: this file dresses `input[type="submit"]`
11636 as a raised button in every state, and a toggle is an input. */
11637 input.toggle,
11638 input.toggle:hover,
11639 input.toggle:active,
11640 input.toggle:focus,
11641 input.toggle:focus-visible {
11642 /* respec-ok: a layer handoff, not a value. */
11643 background: revert-layer;
11644 box-shadow: revert-layer;
11645 outline: revert-layer;
11646 outline-offset: revert-layer;
11647 }
11648
11649 /* A described link on hover. `.link` above hands the resting colour back; this
11650 file's `a:hover` is the more specific of the two and was taking the hover
11651 colour and the underline with it. */
11652 .link:hover {
11653 /* respec-ok: a layer handoff, not a value. */
11654 color: revert-layer;
11655 text-decoration: revert-layer;
11656 }
11657
11658 /* Text makeover mutes: a facet's name, and the line a placeholder shows when
11659 there is nothing to list. This file's bare `p` rule was giving both the body
11660 colour. */
11661 .facet-name,
11662 .placeholder-text {
11663 /* respec-ok: a layer handoff, not a value. */
11664 color: revert-layer;
11665 }
11666 }
11667
11668 /* ===========================================
11669 DESCRIBED MODAL (quasi RegionKind::Modal)
11670 ===========================================
11671 The first described modal on this host is the media picker; these two rules
11672 are what "modal" means here, and every one after it gets them for free. The
11673 description says the region IS a modal and says nothing about where it sits,
11674 which is the division the vocabulary draws: `role="dialog" aria-modal="true"`
11675 comes off the renderer, and a dialog that lays out in flow would make that
11676 attribute a lie.
11677
11678 Two boxes because a scrim and a panel are two boxes: the modal region covers
11679 the viewport and takes the presses the page behind it must not get, and the
11680 region inside it is the panel. */
11681 .region.modal {
11682 position: fixed;
11683 inset: 0;
11684 z-index: var(--z-overlay);
11685 display: flex;
11686 align-items: center;
11687 justify-content: center;
11688 background: var(--overlay);
11689 }
11690
11691 .region.modal > .region {
11692 position: relative;
11693 z-index: var(--z-modal);
11694 width: 90%;
11695 max-width: 700px;
11696 max-height: 80vh;
11697 overflow-y: auto;
11698 padding: var(--gap-section);
11699 background: var(--surface-overlay);
11700 box-shadow: var(--elevation-overlay);
11701 }
11702
11703 /* ===========================================
11704 MEDIA PICKER (crate::quasi::media_picker)
11705 ===========================================
11706 Addressed by the widget names the description carries, not by ids: a document
11707 holds one picker per markdown editor and their ids differ by destination,
11708 while what they are drawn like is the same assembly every time. */
11709 [data-widget="media-picker-grid"] {
11710 display: grid;
11711 grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
11712 gap: var(--gap-peer);
11713 margin-top: var(--gap-section);
11714 }
11715
11716 /* The tile IS the control, since quasi 0.69.0 (`Act::shows`). It was a
11717 `media-tile` region holding a picture and a button as siblings, and only the
11718 button answered a press; a reader aiming at the thumbnail hit nothing. The
11719 widget name went with the region, so the tile is addressed as the button
11720 inside the grid. */
11721 [data-widget="media-picker-grid"] .button {
11722 display: flex;
11723 flex-direction: column;
11724 gap: var(--gap-peer);
11725 min-width: 0;
11726 /* The file name wraps rather than pushing the tile wide. */
11727 overflow-wrap: anywhere;
11728 text-align: left;
11729 font-size: var(--text-note);
11730 }
11731
11732 /* A cap, not a height, and never a bare `img`. The design system already sets
11733 the picture's width, height and background, and an app rule taking any of
11734 those wins over `@layer makeover` silently — which is what `makeover-build`'s
11735 drift check refuses, and it is right to. `max-height` is not one of them, and
11736 it is all this needs: `Fit::Contain` is in the description, so a capped
11737 picture letterboxes inside the cap rather than being cropped or stretched. */
11738 [data-widget="media-picker-grid"] .picture-img {
11739 max-height: 80px;
11740 }
11741