/* ===========================================
PROFILE TAB (replaces inline styles in partials/tabs/user_profile.html)
=========================================== */
/* Inline form row: input(s) + button laid out horizontally, bottom-aligned.
Distinct from .form-row (which is a 1fr 1fr grid). */
.field-row {
display: flex;
gap: var(--gap-section);
align-items: flex-end;
}
.field-row > .form-group { margin-bottom: 0; }
.field-row > .form-group.is-grow { flex: 1; }
.field-row > .form-group.is-grow-2 { flex: 2; }
.field-row > button { margin-bottom: 0; }
/* Custom domain section. */
.profile-domain-header { margin-bottom: var(--gap-section); }
.profile-domain-status {
margin-left: var(--gap-peer);
}
.profile-domain-status.is-verified { color: var(--success); }
.profile-domain-status.is-pending { color: var(--warning); }
.dns-row {
display: flex;
align-items: center;
gap: var(--gap-peer);
margin-bottom: var(--gap-peer);
}
.dns-row:last-of-type { margin-bottom: 0; }
.dns-row-label {
font-size: var(--text-fine);
opacity: 0.7;
min-width: 70px;
}
.dns-row-value {
font-size: var(--text-note);
flex: 1;
}
.dns-row-copy {
padding: var(--gap-bound) var(--gap-peer);
font-size: var(--text-fine);
}
.profile-domain-actions {
display: flex;
gap: var(--gap-section);
}
.profile-domain-result { margin-top: var(--gap-section); }
.profile-domain-lead {
font-size: var(--text-note);
opacity: 0.8;
}
.profile-domain-add-result { margin-top: var(--gap-section); }
/* Personal feed row: URL input + Copy button. */
.profile-feed-row {
display: flex;
gap: var(--gap-peer);
align-items: center;
}
.profile-feed-input {
flex: 1;
font-size: var(--text-note);
font-family: var(--font-mono);
opacity: 0.8;
}
.profile-feed-lead { margin-bottom: var(--gap-section); }
.purchase-page .checkout-footer {
margin-top: var(--gap-page);
padding-top: var(--gap-pane);
border-top: 1px solid var(--border);
opacity: 0.6;
text-align: center;
font-size: var(--text-note);
}
.purchase-page .checkout-footer a { color: var(--content); }
@media (max-width: 599px) {
.purchase-page .container { padding: 0 var(--gap-section); }
.purchase-page .item-summary { flex-direction: column; }
.purchase-page .item-thumbnail { width: 100%; height: 160px; }
.purchase-page .checkout-box { padding: var(--gap-pane); }
.purchase-page h2 { font-size: var(--text-head); }
}
/* Feed page (src/quasi/feeds.rs). Compact tabular layout of items from
followed users / projects / tags.
The page is described rather than templated as of 2026-08-31, so the
selectors below name what quasi and makeover emit. It has no .container:
a described document is
> > the screen's region, so the 900px
measure the container carried sits on here. */
.feed-page main { max-width: 900px; margin: 0 auto; padding: var(--gap-page); }
.feed-page main h1 { font-size: var(--text-head); margin-bottom: var(--gap-section); }
/* The "Showing X-Y of N items" line, which the description states as a plain
text node above the table. */
.feed-page .pane > p.text {
font-size: var(--text-fine);
opacity: 0.6;
margin-bottom: var(--gap-section);
}
.feed-page .table-head {
/* respec-ok: the same five-column grid the rows carry, on the heading row
that has to line up with them. */
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;
}
.feed-page .table {
border: 1px solid var(--border);
}
.feed-page .table-row {
/* respec-ok: a five-column grid template. The generated `.table-row` sets a
display but has no vocabulary for column tracks, so the tracks and the
display that carries 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;
}
.feed-page .table-row:last-child { border-bottom: none; }
.feed-page .table-row:hover { background: var(--surface-sunken); }
/* The two-line name cell: `Cell::part` puts the creator under the name in one
cell, and each part is emitted as its own `.cell-value`. */
.feed-page .col-Name {
display: flex;
flex-direction: column;
gap: var(--gap-bound);
min-width: 0;
}
.feed-page .table-row .col-Name .cell-value:first-child {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.feed-page .table-row .col-Name .cell-value:last-child {
font-size: var(--text-fine);
opacity: 0.5;
}
/* The feed's badges shout a little: caps and tracking. The sunken fill that
used to come with them went with the filled-badge form in B2. */
.feed-page .badge {
text-transform: uppercase;
letter-spacing: 0.03em;
}
/* The empty state and the numbered strip carry no page-specific rules any
more: `Node::empty` and `Rest` emit their own classes, styled once for every
described screen rather than again per page. */
@media (max-width: 599px) {
.feed-page main { padding: var(--gap-section); }
.feed-page .table-head,
.feed-page .table-row { grid-template-columns: 1fr 70px; }
/* Name and Date survive the narrow viewport, which is what the five-column
header dropped to before the conversion. */
.feed-page .col-Type,
.feed-page .col-Tag,
.feed-page .col-Price { display: none; }
}
/* (Stripe Connect disclaimer page rules defined in the canonical
STRIPE DISCLAIMER PAGE section near end of file.) */
/* The creators page moved to the description layer. Its headings, its ordered
list, its stat box, its four-column table and its call-to-action box are the
design system's now: the count is a `Figure`, the tiers are a `Node::Table`
with stated column widths, and the CTA is one or two `Act`s. `.wave-table`
went with it and had no other user. */
/* Tag tree browse page (templates/pages/tag_tree.html). */
.tag-tree-page .breadcrumbs {
font-size: var(--text-note);
margin-bottom: var(--gap-pane);
opacity: 0.7;
}
.tag-tree-page .breadcrumbs a:hover { opacity: 0.6; }
.tag-tree-page .breadcrumbs .sep { margin: 0 var(--gap-bound); opacity: 0.4; }
.tag-tree-page .tag-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: var(--gap-section);
}
.tag-tree-page .tag-card {
background: var(--surface-sunken);
padding: var(--gap-section);
text-decoration: none;
color: var(--content);
border: 1px solid var(--border);
transition: background 0.1s ease;
}
.tag-tree-page .tag-card:hover {
background: var(--surface-sunken);
text-decoration: none;
}
.tag-tree-page .tag-card-name {
font-family: var(--font-display);
font-weight: bold;
font-size: var(--text-body);
}
.tag-tree-page .tag-card-meta {
font-size: var(--text-fine);
opacity: 0.5;
margin-top: var(--gap-bound);
}
.tag-tree-page .empty-state {
opacity: 0.5;
font-size: var(--text-note);
padding: var(--gap-page) 0;
}
/* Confirm delete prompt (templates/pages/confirm_delete.html). Overrides
the global .warning-box to use --danger-bg (matches the delete-account
semantic). Also tokenized previously-broken fallbacks. */
.confirm-delete-page .warning-box {
background: color-mix(in oklch, var(--danger) 10%, var(--surface-page));
border: 1px solid var(--danger);
border-radius: var(--radius-panel);
padding: var(--gap-pane);
margin-bottom: var(--gap-pane);
}
.confirm-delete-page .warning-box p {
margin: 0;
color: var(--danger);
}
/* Project paywall page (templates/pages/project_paywall.html). */
.project-paywall-page .paywall-cover { margin-bottom: var(--gap-pane); }
.project-paywall-page .paywall-cover img {
width: 120px;
height: 120px;
border-radius: var(--radius-panel);
object-fit: cover;
}
.project-paywall-page .paywall-title {
font-family: var(--font-display);
font-size: var(--text-hero);
margin-bottom: var(--gap-peer);
}
.project-paywall-page .paywall-creator {
font-size: var(--text-body);
opacity: 0.7;
margin-bottom: var(--gap-pane);
}
.project-paywall-page .paywall-creator a { color: var(--content); }
.project-paywall-page .paywall-description {
font-size: var(--text-lead);
max-width: 800px;
margin: 0 auto var(--gap-page);
text-align: left;
}
.project-paywall-page .paywall-item-count {
font-size: var(--text-body);
opacity: 0.7;
margin-bottom: var(--gap-peer);
}
.project-paywall-page .paywall-tiers {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--gap-pane);
margin-top: var(--gap-pane);
text-align: left;
}
.project-paywall-page .paywall-tiers .tier-card {
background: var(--surface-overlay);
padding: var(--gap-page);
display: flex;
flex-direction: column;
}
.project-paywall-page .paywall-tiers .tier-card h3 {
font-family: var(--font-display);
font-weight: bold;
font-size: var(--text-subhead);
margin-bottom: var(--gap-peer);
}
.project-paywall-page .paywall-tiers .tier-price {
font-size: var(--text-head);
margin-bottom: var(--gap-section);
}
.project-paywall-page .paywall-tiers .tier-card form,
.project-paywall-page .paywall-tiers .tier-card .button { margin-top: auto; }
.project-paywall-page .paywall-tiers .tier-card button,
.project-paywall-page .paywall-tiers .tier-card .button { width: 100%; }
/* Collection detail page (templates/pages/collection.html). */
/* The collection page moved to the description layer. Its header, its owner
line, its per-item rows and its empty state are the design system's now: the
items are a `Node::Table` whose meta line became three droppable columns
rather than one string that wraps, and the empty state is `Node::empty`.
What is left is the measure, on . */
.collection-page main { max-width: 700px; }
/* Project blog index page. Twelve rules stood here for markup the template
wrote by hand -- `.blog-header`, `.post-entry`, `.post-title` and the rest.
The page is a described screen (`crate::quasi::project_blog`) and its
heading, list and rows are makeover's, which is what the design system is
for. What is left is the one thing that is this page's rather than the
system's. */
.project-blog-page main { max-width: 800px; }
/* Canonical "page-level h1", left-aligned, smaller than the global centered
default. Used by dashboards, admin, health, import, delete-account, export,
receipt. Aliased via comma-grouped selectors so each page picks it up via
its body class without redefining. */
.dashboard-page h1,
.admin-page h1,
.health-page h1,
.import-page h1,
.delete-account-page h1,
.export-page h1,
.receipt-page h1 {
font-size: var(--text-title);
margin-bottom: var(--gap-peer);
text-align: left;
}
.delete-account-page h1 { color: var(--danger); }
/* Canonical subtitle under a page h1, muted, with bottom space. */
.health-page .subtitle,
.import-page .subtitle,
.delete-account-page .subtitle,
.export-page .subtitle {
opacity: 0.7;
margin-bottom: var(--gap-page);
text-align: left;
}
.health-page .subtitle { text-align: center; }
/* User Settings tab (templates/partials/tabs/user_settings.html).
Left-rail nav layout for the Settings dashboard tab. Selection uses the
canonical .is-selected modifier. */
.settings-layout { display: flex; gap: var(--gap-page); }
.settings-nav { flex-shrink: 0; min-width: 140px; }
.settings-nav a {
display: block;
padding: var(--gap-peer) var(--gap-section);
text-decoration: none;
color: var(--content);
opacity: 0.6;
font-family: var(--font-mono);
font-size: var(--text-note);
transition: opacity 0.15s ease;
}
.settings-nav a:hover { opacity: 1; }
.settings-nav a.is-selected { opacity: 1; background: var(--surface-sunken); }
.settings-body { flex: 1; min-width: 0; }
@media (max-width: 839px) {
.settings-layout { flex-direction: column; gap: var(--gap-section); }
.settings-nav { display: flex; flex-wrap: wrap; gap: 0; min-width: 0; }
.settings-nav a { padding: var(--gap-peer) var(--gap-group); font-size: var(--text-note); }
}.library-page .feed-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;
}
.library-page .feed-col-right { text-align: right; }
.library-page .feed-results-table { border: 1px solid var(--border); border-top: none; }
.library-page .feed-table-row {
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;
}
.library-page .feed-table-row:last-child { border-bottom: none; }
.library-page .feed-table-row:hover { background: var(--surface-sunken); }
.library-page .feed-item-name-cell {
display: flex;
flex-direction: column;
gap: var(--gap-bound);
min-width: 0;
}
.library-page .feed-item-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.library-page .feed-item-creator { font-size: var(--text-fine); opacity: 0.5; }
.library-page .feed-pagination {
display: flex;
gap: var(--gap-bound);
justify-content: center;
margin-top: var(--gap-section);
}
.library-page .feed-pagination a,
.library-page .feed-pagination span {
padding: var(--gap-peer) var(--gap-section);
font-size: var(--text-note);
text-decoration: none;
color: var(--content);
border: 1px solid var(--border);
}
.library-page .feed-pagination span.current {
background: var(--primary-dark);
color: var(--primary-light);
border-color: var(--primary-dark);
}
.library-page .feed-pagination a:hover { background: var(--surface-sunken); }
@media (max-width: 599px) {
.library-page .feed-table-header,
.library-page .feed-table-row {
grid-template-columns: 1fr 70px;
}
.library-page .feed-table-header span:nth-child(1),
.library-page .feed-table-row .badge:first-child,
.library-page .feed-table-header span:nth-child(3),
.library-page .feed-table-header span:nth-child(4),
.library-page .feed-table-row span:nth-child(3),
.library-page .feed-table-row span:nth-child(4) {
display: none;
}
}
/* User-level dashboard (templates/dashboards/dashboard-user.html).
Scoped under .dashboard-user-page to avoid colliding with .project-card
/ .project-title / .project-meta on the public profile (user.html) and
the user_projects tab partial. */
.dashboard-user-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
.dashboard-user-page .project-card {
background: var(--surface-overlay);
padding: var(--gap-pane);
margin-bottom: var(--gap-section);
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.dashboard-user-page .project-info { flex: 1; }
.dashboard-user-page .project-title {
font-family: var(--font-display);
font-weight: bold;
font-size: var(--text-subhead);
margin-bottom: var(--gap-bound);
}
.dashboard-user-page .project-meta {
font-size: var(--text-note);
opacity: 0.7;
margin-bottom: var(--gap-peer);
}
.dashboard-user-page .project-stats { font-size: var(--text-note); }
.dashboard-user-page .project-actions {
display: flex;
gap: var(--gap-peer);
}
.dashboard-user-page .project-actions button {
padding: var(--gap-peer) var(--gap-section);
font-size: var(--text-note);
}
@media (max-width: 839px) {
.dashboard-user-page .project-card { flex-direction: column; }
.dashboard-user-page .project-actions { margin-top: var(--gap-section); }
}
/* Project dashboard (templates/dashboards/dashboard-project.html). */
.dashboard-project-page .project-meta { font-size: var(--text-note); opacity: 0.7; }
.dashboard-project-page .stat-value { font-size: var(--text-title); margin-bottom: var(--gap-bound); }
.dashboard-project-page .content-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--gap-pane);
}
.dashboard-project-page .content-header h2 { font-size: var(--text-subhead); }
.dashboard-project-page .item-thumbnail {
width: 40px;
height: 40px;
background: var(--border);
display: inline-block;
vertical-align: middle;
margin-right: var(--gap-peer);
}
.dashboard-project-page .item-actions {
display: flex;
gap: var(--gap-peer);
}
.dashboard-project-page .item-actions button {
padding: var(--gap-peer) var(--gap-section);
font-size: var(--text-note);
}
.dashboard-project-page .data-section { margin-bottom: var(--gap-page); }
.dashboard-project-page .data-section h2 {
font-size: var(--text-subhead);
margin-bottom: var(--gap-section);
padding-bottom: var(--gap-peer);
border-bottom: 1px solid var(--border);
}
/* Item dashboard (templates/dashboards/dashboard-item.html). */
.dashboard-item-page .item-meta {
font-size: var(--text-note);
opacity: 0.7;
display: flex;
gap: var(--gap-section);
align-items: center;
}
.dashboard-item-page .stat-value { font-size: var(--text-head); }
.dashboard-item-page .section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--gap-pane);
padding-bottom: var(--gap-section);
border-bottom: 1px solid var(--border);
}
.dashboard-item-page .section-header h2 { font-size: var(--text-subhead); }
@media (max-width: 839px) {
.dashboard-item-page .section-header {
flex-direction: column;
align-items: flex-start;
gap: var(--gap-section);
}
.dashboard-item-page .section-header .action-buttons { width: 100%; }
}
/* Changelog page (templates/pages/changelog.html). */
.changelog-page .container { max-width: 900px; margin: 0 auto; }
.changelog-page h1 { font-size: var(--text-display); margin-bottom: var(--gap-peer); }
.changelog-page .changelog-entry {
margin-bottom: var(--gap-page);
padding-bottom: var(--gap-page);
border-bottom: 1px solid var(--border);
}
.changelog-page .changelog-entry:last-child { border-bottom: none; }
.changelog-page .changelog-date {
font-family: var(--font-mono);
font-size: var(--text-note);
opacity: 0.6;
margin-bottom: var(--gap-peer);
}
.changelog-page .changelog-entry h2 { font-size: var(--text-subhead); margin-bottom: var(--gap-section); }
.changelog-page .changelog-entry ul {
padding-left: var(--gap-pane);
margin: var(--gap-peer) 0;
line-height: 1.7;
}
.changelog-page .changelog-entry li { margin-bottom: var(--gap-bound); }
/* Policy page (templates/pages/policy.html). */
/* The content policy moved to the description layer. Its headings, its section
spacing and its list indents are the design system's now; the prose itself
goes through docengine, the same renderer `/docs/*` uses. What is left is the
measure, on rather than on a `.container`, as every converted document
does. */
.policy-page main { max-width: 900px; margin: 0 auto; }
/* Admin page extras (in addition to global .admin-page h1). */
.admin-page .metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--gap-section);
margin-bottom: var(--gap-page);
}
.admin-page .metric-card {
padding: var(--gap-section);
border: 1px solid var(--border);
border-radius: var(--radius-square);
}
.admin-page .metric-card .value {
font-size: var(--text-title);
font-family: var(--font-mono);
font-weight: 600;
}
.admin-page .metric-card .label {
font-size: var(--text-note);
color: var(--content-muted);
margin-top: var(--gap-bound);
}
.admin-page .metric-card.warn .value { color: var(--warning); }
.admin-page .metric-card.error .value { color: var(--danger); }
.admin-page .section-heading {
font-size: var(--text-subhead);
margin: var(--gap-pane) 0 var(--gap-section);
font-family: var(--font-mono);
}
/* Scan pipeline dashboard. See docs/scan-pipeline-audit.md ยง 5. */
.admin-scan-section {
margin-top: var(--gap-page);
}
.admin-section-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: var(--gap-section);
gap: var(--gap-section);
}
/* Typeface comes from .subsection-title on the heading itself; this sets only
what the flex row needs. */
.admin-section-header h2 {
font-size: var(--text-lead);
margin: 0;
}
.layer-health-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: var(--gap-section);
}
.layer-health-card {
padding: var(--gap-section);
border: 1px solid var(--border);
border-radius: var(--radius-square);
border-left-width: 4px;
font-size: var(--text-note);
}
.layer-health-card .layer-name {
font-family: var(--font-mono);
font-weight: 600;
font-size: var(--text-body);
margin-bottom: var(--gap-peer);
}
.layer-health-card .layer-stat {
display: flex;
justify-content: space-between;
margin-bottom: 2px;
}
.layer-health-card .layer-stat .dimmed {
color: var(--content-muted);
}
.layer-health-card.layer-ok { border-left-color: var(--success); }
.layer-health-card.layer-degraded { border-left-color: var(--warning); }
.layer-health-card.layer-down { border-left-color: var(--danger); }
.layer-chips {
display: flex;
flex-wrap: wrap;
gap: 4px;
max-width: 360px;
}
.layer-chip {
display: inline-block;
font-family: var(--font-mono);
font-size: var(--text-fine);
padding: 1px 6px;
border-radius: var(--radius-control);
border: 1px solid transparent;
cursor: help;
line-height: 1.4;
}
.layer-chip-pass { background: color-mix(in oklch, var(--success) 12%, var(--surface-page)); border-color: var(--success); color: var(--success); }
.layer-chip-skip { background: var(--surface-page); border-color: var(--border); color: var(--content-muted); }
.layer-chip-fail { background: color-mix(in oklch, var(--danger) 10%, var(--surface-page)); border-color: var(--danger); color: var(--danger); font-weight: 600; }
.layer-chip-error { background: color-mix(in oklch, var(--warning) 14%, var(--surface-page)); border-color: var(--warning); color: var(--warning); font-weight: 600; }
.scan-history-grid table { font-size: var(--text-note); }
.scan-history-grid summary {
cursor: pointer;
font-family: var(--font-mono);
padding: var(--gap-peer) 0;
user-select: none;
}
.scan-row-meta {
font-size: var(--text-fine);
color: var(--content-muted);
margin-top: 2px;
}
.audit-filter-form {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: var(--gap-section);
padding: var(--gap-section) 0;
margin-bottom: var(--gap-section);
border-bottom: 1px solid var(--border);
}
.audit-filter-form label {
display: flex;
flex-direction: column;
gap: 2px;
}
.audit-filter-form input,
.audit-filter-form select {
font-size: var(--text-note);
}
.layer-chips-cell { vertical-align: top; }
.layer-detail-disclosure {
margin-top: var(--gap-peer);
}
.layer-detail-disclosure summary {
cursor: pointer;
user-select: none;
padding: 2px 0;
}
.layer-detail-list {
margin: var(--gap-peer) 0 0;
display: grid;
grid-template-columns: max-content 1fr;
gap: 2px var(--gap-section);
font-size: var(--text-fine);
}
.layer-detail-list dt {
font-family: var(--font-mono);
}
.layer-detail-list dd {
margin: 0;
color: var(--content-muted);
word-break: break-word;
}
.admin-page table {
width: 100%;
border-collapse: collapse;
font-size: var(--text-note);
}
.admin-page th,
.admin-page td {
text-align: left;
padding: var(--gap-peer) var(--gap-group);
border-bottom: 1px solid var(--border);
}
.admin-page th {
font-family: var(--font-mono);
font-size: var(--text-fine);
text-transform: uppercase;
color: var(--content-muted);
}
.admin-page td.mono { font-family: var(--font-mono); }
.admin-page .refresh-note {
font-size: var(--text-fine);
color: var(--content-muted);
margin-bottom: var(--gap-section);
font-family: var(--font-mono);
}
.admin-page .appeal-count {
font-size: var(--text-note);
opacity: 0.7;
margin-bottom: var(--gap-pane);
}
.admin-page .decide-form {
display: flex;
gap: var(--gap-peer);
align-items: flex-end;
flex-wrap: wrap;
}
.admin-page .decide-form textarea {
min-width: 200px;
font-size: var(--text-note);
}
.admin-page .suspend-form {
display: flex;
gap: var(--gap-peer);
align-items: flex-end;
}
.admin-page .suspend-form input[type="text"] {
min-width: 200px;
font-size: var(--text-note);
}
.admin-page .lottery-form {
background: var(--surface-overlay);
padding: var(--gap-pane);
margin: var(--gap-pane) 0;
display: flex;
gap: var(--gap-section);
align-items: flex-end;
}
.admin-page .lottery-form .form-group { margin-bottom: 0; }
.admin-page .lottery-form label {
font-size: var(--text-note);
display: block;
margin-bottom: var(--gap-bound);
}
.admin-page .lottery-form input[type="number"] { width: 80px; }
/* Page-top notice (charter primitive, replaces #restart-banner and
the inline sandbox banner in dashboard-project). Full-bleed across
the viewport, monospace, one short sentence + optional action.
Hero callouts on the landing page use `.alert` instead (multi-paragraph). */
.banner {
padding: var(--gap-section) var(--gap-pane);
font-family: var(--font-mono);
font-size: var(--text-note);
text-align: center;
color: var(--content);
}
.banner--info {
background: var(--highlight-faint);
border-bottom: 2px solid var(--focus-ring);
}
.banner--warning {
background: color-mix(in oklch, var(--warning) 14%, var(--surface-page));
border-bottom: 2px solid var(--warning);
}
/* A described `Node::Notice` carries its tone as an attribute rather than in
the class, which is what `quasi-webview` emits and what `makeover` keys every
other tone off. The two `banner--` modifiers above are this site's own
full-bleed notices and are untouched. */
.banner[data-tone="danger"] {
background: color-mix(in oklch, var(--danger) 14%, var(--surface-page));
border-bottom: 2px solid var(--danger);
}
.banner[data-tone="success"] {
background: color-mix(in oklch, var(--success) 14%, var(--surface-page));
border-bottom: 2px solid var(--success);
}
.banner[data-tone="info"] {
background: var(--highlight-faint);
border-bottom: 2px solid var(--focus-ring);
}
.banner a {
color: inherit;
text-decoration: underline;
}