Files
plm-lottery/app/static/style.css
T
davideandClaude Opus 5 c4b2dc3ea2 Advertise the jackpot that will actually be paid (B-65)
participant_count and jackpot_sats were computed over every round_participants
row, while the draw only picks from confirmed participants and the payout only
spends their sats. So the advertised jackpot could exceed the one paid out, and a
player whose bet was later abandoned appeared in the count and then vanished
again.

Counting only confirmed rows would have fixed the arithmetic and broken something
else: the player who just bet would see neither themselves nor their money for a
whole block. So this is the same confirmed/in-flight split the balance already
exposes (balance_sats vs pending_balance_sats): participant_count and
jackpot_sats are now the confirmed, authoritative figures, and
pending_participant_count/pending_jackpot_sats/has_pending_bets report what is in
flight — inclusive figures, not deltas, matching the balance pair's convention.

/'s round card shows the confirmed numbers big and the difference as an amber
"+N in attesa" suffix, reusing .balance-pending's colour for the same "not
settled yet" meaning. The two new spans render from server data through t(), so
they carry no data-i18n and onLanguageChange() repaints them from the last
response — the one-mechanism-per-element rule. Both strings are in all 7
languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 11:52:10 +02:00

485 lines
23 KiB
CSS

@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap');
:root {
--color-background: #F8FAFC;
--color-surface: #FFFFFF;
--color-surface-inset: #F1F5F9;
--color-foreground: #0F172A;
--color-muted-foreground: #64748B;
--color-border: #E2E8F0;
--color-primary: #F59E0B;
--color-on-primary: #0F172A;
--color-secondary: #FBBF24;
--color-accent: #7C3AED;
--color-destructive: #DC2626;
--color-destructive-bg: #FEF2F2;
--color-success: #16A34A;
--color-success-bg: #F0FDF4;
--color-ring: #F59E0B;
--radius-sm: 8px;
--radius: 14px;
--radius-lg: 20px;
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05);
--shadow-md: 0 8px 24px -8px rgba(15, 23, 42, 0.14);
--content-width: 480px;
--nav-bottom-height: 68px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
font-family: 'IBM Plex Sans', system-ui, sans-serif;
background: var(--color-background);
color: var(--color-foreground);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.app-shell { max-width: var(--content-width); margin: 0 auto; padding: 20px 20px 32px; }
.mono { font-family: 'Fira Code', ui-monospace, monospace; }
h1, h2, h3 { font-family: inherit; letter-spacing: -0.01em; }
.section-label {
font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
color: var(--color-muted-foreground); margin: 0 2px 10px;
}
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
padding: 20px;
margin-bottom: 16px;
}
.card h2 { font-size: 1rem; font-weight: 600; margin: 0 0 4px; }
.card .hint { color: var(--color-muted-foreground); font-size: 0.85rem; margin: 0 0 14px; line-height: 1.45; }
.card .hint:last-child { margin-bottom: 0; }
.tabs { display: flex; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--color-border); }
.tab {
flex: 1; text-align: center; padding: 10px 0; font-weight: 600; font-size: 0.9rem;
color: var(--color-muted-foreground); cursor: pointer; border-bottom: 2px solid transparent;
margin-bottom: -1px; transition: color 150ms, border-color 150ms;
}
.tab.active { color: var(--color-foreground); border-bottom-color: var(--color-primary); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
label { display: block; font-size: 0.85rem; font-weight: 500; color: var(--color-muted-foreground); margin-top: 14px; margin-bottom: 6px; }
label:first-child { margin-top: 0; }
input, textarea {
width: 100%; min-height: 44px; padding: 10px 12px; font-size: 0.95rem; font-family: inherit;
border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-surface);
color: var(--color-foreground); transition: border-color 150ms, box-shadow 150ms;
}
textarea { resize: vertical; }
input:focus, textarea:focus {
outline: none; border-color: var(--color-ring);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-ring) 25%, transparent);
}
input:disabled, textarea:disabled { background: var(--color-surface-inset); color: var(--color-muted-foreground); }
button {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
min-height: 44px; padding: 0 18px; margin-top: 16px; width: 100%;
font-family: inherit; font-size: 0.95rem; font-weight: 600;
background: var(--color-primary); color: var(--color-on-primary);
border: none; border-radius: var(--radius-sm); cursor: pointer;
box-shadow: var(--shadow-sm);
transition: filter 150ms, transform 150ms, box-shadow 150ms;
}
button:hover { filter: brightness(0.96); box-shadow: var(--shadow-md); }
button:active { transform: scale(0.98); }
button:disabled { opacity: 0.6; cursor: default; box-shadow: none; }
button:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
button.secondary {
width: auto; margin-top: 0; padding: 0 12px; min-height: 40px;
background: var(--color-surface-inset); color: var(--color-foreground);
border: 1px solid var(--color-border); box-shadow: none;
}
button.secondary:hover { filter: none; background: var(--color-border); box-shadow: none; }
button.link, a.link {
width: auto; min-height: 44px; margin-top: 0; padding: 0 2px;
display: inline-flex; align-items: center;
background: none; color: var(--color-muted-foreground); font-weight: 500;
font-size: 0.82rem; box-shadow: none; text-decoration: none;
}
button.link:hover, a.link:hover { filter: none; color: var(--color-foreground); box-shadow: none; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.checkbox-row {
display: flex; align-items: center; gap: 8px;
font-size: 0.85rem; font-weight: 500; color: var(--color-foreground);
margin-top: 14px; cursor: pointer;
}
.checkbox-row input[type="checkbox"] {
width: 18px; height: 18px; min-height: auto; flex-shrink: 0; accent-color: var(--color-primary); cursor: pointer;
}
/* --- app navbar: brand/balance row (sticky top) + section nav (bottom tab bar on
mobile, promoted back to an inline tab strip once there's room — see the
min-width breakpoint below), shown only when logged in --- */
.app-navbar-top {
position: sticky; top: 0; z-index: 20;
background: color-mix(in srgb, var(--color-surface) 90%, transparent);
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
border-bottom: 1px solid var(--color-border);
}
.app-navbar-top-inner {
max-width: var(--content-width); margin: 0 auto; padding: 12px 20px;
display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.app-navbar-top .brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 1rem; letter-spacing: -0.01em; }
.app-navbar-top .brand-mark { width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; display: block; }
.app-navbar-account { display: flex; align-items: center; gap: 6px; }
.app-navbar-account .navbar-username { display: none; font-weight: 600; font-size: 0.85rem; margin-right: 2px; }
@media (min-width: 420px) { .app-navbar-account .navbar-username { display: inline; } }
.app-navbar-account .navbar-balance {
display: inline-flex; align-items: center; gap: 5px;
font-weight: 700; font-size: 0.85rem; white-space: nowrap;
background: color-mix(in srgb, var(--color-primary) 14%, transparent);
border: 1px solid color-mix(in srgb, var(--color-primary) 30%, transparent);
color: color-mix(in srgb, var(--color-primary) 70%, var(--color-foreground));
padding: 6px 10px; border-radius: 999px;
}
.app-navbar-account .icon-link {
width: 36px; height: 36px; min-height: 36px; padding: 0; margin: 0; border-radius: 999px;
justify-content: center;
}
.app-navbar-account .icon-link:hover { background: var(--color-surface-inset); }
.app-navbar-account .icon-link .icon { width: 18px; height: 18px; }
/* Bottom tab bar on narrow (mobile) viewports — thumb-reachable, app-like. */
.app-navbar-tabs {
position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
max-width: var(--content-width); margin: 0 auto;
background: color-mix(in srgb, var(--color-surface) 94%, transparent);
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
border-top: 1px solid var(--color-border);
box-shadow: 0 -8px 24px -12px rgba(15, 23, 42, 0.18);
display: flex; padding: 4px 8px calc(4px + env(safe-area-inset-bottom, 0px));
}
.app-navbar-tabs button.navbar-tab {
flex: 1; width: auto; min-height: 56px; margin-top: 0; padding: 8px 4px;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
font-size: 0.68rem; font-weight: 600; font-family: inherit; cursor: pointer;
background: none; color: var(--color-muted-foreground); box-shadow: none;
border: none; border-radius: var(--radius-sm);
transition: color 150ms, background 150ms;
}
.app-navbar-tabs button.navbar-tab .icon { width: 20px; height: 20px; }
.app-navbar-tabs button.navbar-tab.active { color: var(--color-primary); }
.app-navbar-tabs button.navbar-tab.active .icon { color: var(--color-primary); }
.app-navbar-tabs button.navbar-tab:hover { filter: none; color: var(--color-foreground); background: var(--color-surface-inset); }
.app-navbar-tabs button.navbar-tab.active:hover { color: var(--color-primary); background: none; }
/* Reserve room so fixed content never sits under the bottom bar or the
iOS/Android home-indicator safe area. */
.app-shell { padding-bottom: calc(var(--nav-bottom-height) + env(safe-area-inset-bottom, 0px) + 20px); }
@media (min-width: 720px) {
:root { --content-width: 620px; }
/* Promote the bottom tab bar back to an ordinary inline strip once there's
enough width for it to sit comfortably under the top bar instead of
floating over thumb-reach real estate. */
.app-navbar-tabs {
position: static; box-shadow: none; border-top: none;
border-bottom: 1px solid var(--color-border);
padding: 4px 12px; gap: 4px;
}
.app-navbar-tabs button.navbar-tab { flex-direction: row; min-height: 44px; font-size: 0.85rem; }
.app-shell { padding-bottom: 40px; }
}
.address-box {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
background: var(--color-surface-inset); border: 1px solid var(--color-border);
border-radius: var(--radius-sm); padding: 10px 12px; font-size: 0.85rem; word-break: break-all;
}
.balance-value { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
.balance-unit { color: var(--color-muted-foreground); font-size: 1rem; font-weight: 500; }
/* Green once everything is confirmed; amber while a bet/withdrawal's change is
still unconfirmed — the displayed number already includes that change (see
compute_pending_balance), the color just flags that it isn't settled yet. */
.balance-confirmed { color: var(--color-success); }
.balance-pending { color: var(--color-primary); }
/* The in-flight part of the round's own figures (B-65): the players/jackpot next
to it are the confirmed ones the draw and the payout will actually use, and this
is what has been bet but hasn't confirmed yet. Same amber as .balance-pending,
for the same "not settled" meaning. */
.pending-suffix { color: var(--color-primary); font-size: 0.8rem; font-weight: 600; }
.icon { width: 16px; height: 16px; flex-shrink: 0; }
.dash-panel { display: none; }
.dash-panel.active { display: block; }
.qr-box { display: flex; justify-content: center; padding: 16px; background: #fff; border: 1px solid var(--color-border); border-radius: var(--radius-sm); margin-top: 14px; }
.qr-box img { width: 200px; height: 200px; image-rendering: pixelated; }
.hidden { display: none !important; }
.draw-state { display: none; text-align: center; padding: 8px 0 4px; }
.draw-state.active { display: block; }
.draw-spinner {
width: 40px; height: 40px; margin: 0 auto 10px;
border: 3px solid var(--color-border); border-top-color: var(--color-primary);
border-radius: 50%; animation: spin 900ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.draw-state .draw-label { font-weight: 600; font-size: 0.95rem; }
.draw-result { font-size: 1.05rem; font-weight: 700; padding: 6px 0; }
.draw-result.win { color: var(--color-success); }
.draw-result.lose { color: var(--color-muted-foreground); }
#toast-container {
position: fixed; left: 50%; transform: translateX(-50%);
bottom: calc(var(--nav-bottom-height) + env(safe-area-inset-bottom, 0px) + 12px);
display: flex; flex-direction: column; gap: 8px; z-index: 100; width: calc(100% - 40px); max-width: 440px;
}
@media (min-width: 720px) { #toast-container { bottom: 20px; } }
.toast {
display: flex; align-items: flex-start; gap: 8px;
padding: 12px 14px; border-radius: var(--radius-sm); font-size: 0.85rem; font-weight: 500;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12);
animation: toast-in 200ms ease-out;
}
.toast.success { background: var(--color-success-bg); color: var(--color-success); }
.toast.error { background: var(--color-destructive-bg); color: var(--color-destructive); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
/* --- /report-bug: a standalone page (no logged-in navbar), so it gets its
own slim top bar rather than the app's bottom tab bar / sticky header. --- */
.app-shell-bugreport { padding-bottom: 32px; }
.bugreport-topbar {
display: flex; align-items: center; justify-content: space-between; gap: 12px;
padding: 4px 0 20px; margin-bottom: 20px; border-bottom: 1px solid var(--color-border);
}
.bugreport-topbar .brand {
display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 1rem;
letter-spacing: -0.01em; color: var(--color-foreground); text-decoration: none;
}
.bugreport-topbar .brand-mark { width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; display: block; }
.bugreport-topbar-right { display: flex; align-items: center; gap: 12px; }
/* Pill button, same idiom as .trust-pill / .chain-status-pill elsewhere on the
site: a bordered chip rather than a bare text link, so "go back" reads as an
actual control instead of fading into the surrounding copy. */
.back-home-btn {
display: inline-flex; align-items: center; gap: 6px;
font-size: 0.8rem; font-weight: 500; color: var(--color-muted-foreground);
background: var(--color-surface); border: 1px solid var(--color-border);
padding: 6px 12px 6px 10px; border-radius: 999px; text-decoration: none;
transition: color 150ms, border-color 150ms, background 150ms;
}
.back-home-btn .icon { width: 15px; height: 15px; }
.back-home-btn:hover {
color: var(--color-foreground); background: var(--color-surface-inset);
border-color: color-mix(in srgb, var(--color-ring) 40%, var(--color-border));
}
.back-home-btn:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
.bugreport-hero { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 20px; }
.bugreport-hero-icon {
width: 44px; height: 44px; flex-shrink: 0; border-radius: 999px;
background: color-mix(in srgb, var(--color-primary) 14%, transparent);
color: var(--color-primary);
display: flex; align-items: center; justify-content: center;
}
.bugreport-hero-icon .icon { width: 22px; height: 22px; }
.bugreport-hero h1 { font-size: 1.3rem; font-weight: 700; margin: 2px 0 4px; text-wrap: balance; }
.bugreport-hero p { color: var(--color-muted-foreground); font-size: 0.9rem; margin: 0; max-width: 46ch; }
/* Info callout, anchored inside the form card right above the field it
applies to — not a warning (that's what the amber status badges below are
for), so it gets the accent hue instead, keeping the two meanings visually
distinct. */
.field-note {
display: flex; align-items: flex-start; gap: 10px;
background: color-mix(in srgb, var(--color-accent) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--color-accent) 28%, transparent);
color: color-mix(in srgb, var(--color-accent) 75%, var(--color-foreground));
border-radius: var(--radius-sm); padding: 10px 12px; font-size: 0.82rem; line-height: 1.4;
margin-bottom: 16px;
}
.field-note .icon { width: 16px; height: 16px; margin-top: 1px; flex-shrink: 0; }
.char-count {
font-variant-numeric: tabular-nums; text-align: right;
font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 4px;
}
.badge {
display: inline-block; font-size: 0.72rem; font-weight: 600; padding: 2px 8px;
border-radius: 999px; background: var(--color-background); border: 1px solid var(--color-border);
flex-shrink: 0;
}
.badge.bug-status-open { background: color-mix(in srgb, var(--color-primary) 16%, transparent); color: #92400E; border-color: color-mix(in srgb, var(--color-primary) 55%, transparent); }
.badge.bug-status-read { background: var(--color-background); color: var(--color-muted-foreground); }
.badge.bug-status-resolved { background: var(--color-success-bg); color: var(--color-success); border-color: var(--color-success); }
.report-row {
display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
padding: 12px 0; border-bottom: 1px solid var(--color-border);
}
.report-row:first-child { padding-top: 0; }
.report-row:last-child { padding-bottom: 0; border-bottom: none; }
.report-row-desc {
flex: 1 1 200px; font-size: 0.88rem;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.report-row-date {
font-size: 0.78rem; color: var(--color-muted-foreground); white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.report-empty { margin: 0; }
/* --- landing hero (shown only when logged out) --- */
body {
position: relative;
isolation: isolate;
}
body::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
background:
radial-gradient(600px circle at 20% -10%, color-mix(in srgb, var(--color-primary) 16%, transparent), transparent 60%),
radial-gradient(500px circle at 90% 10%, color-mix(in srgb, var(--color-accent) 12%, transparent), transparent 60%);
}
.hero { text-align: center; padding: 8px 0 28px; }
.hero .eyebrow {
display: inline-flex; align-items: center; gap: 6px;
font-size: 0.75rem; font-weight: 600; letter-spacing: 0.02em;
color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--color-primary) 30%, transparent);
padding: 4px 10px; border-radius: 999px; margin-bottom: 14px;
}
.hero h1 {
font-size: 1.85rem; font-weight: 700; letter-spacing: -0.02em; margin: 0 0 8px;
background: linear-gradient(135deg, var(--color-foreground), var(--color-accent) 120%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero p.lead { color: var(--color-muted-foreground); font-size: 0.95rem; margin: 0 auto; max-width: 360px; }
.hero-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 22px 0; }
.hero-step {
background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius);
box-shadow: var(--shadow-sm);
padding: 14px 8px; transition: transform 150ms, border-color 150ms, box-shadow 150ms;
}
.hero-step:hover { transform: translateY(-2px); border-color: var(--color-ring); box-shadow: var(--shadow-md); }
.hero-step .step-icon {
width: 32px; height: 32px; margin: 0 auto 8px; border-radius: 999px;
background: color-mix(in srgb, var(--color-primary) 14%, transparent); color: var(--color-primary);
display: flex; align-items: center; justify-content: center;
}
.hero-step .step-icon .icon { width: 16px; height: 16px; }
.hero-step .step-title { font-size: 0.8rem; font-weight: 600; margin-bottom: 2px; }
.hero-step .step-hint { font-size: 0.72rem; color: var(--color-muted-foreground); line-height: 1.35; }
.trust-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 24px; }
.trust-pill {
font-size: 0.72rem; font-weight: 500; color: var(--color-muted-foreground);
background: var(--color-surface); border: 1px solid var(--color-border);
padding: 5px 10px; border-radius: 999px; display: inline-flex; align-items: center; gap: 5px;
}
.trust-pill .icon { width: 13px; height: 13px; color: var(--color-success); flex-shrink: 0; }
/* --- round status: a "hero" ticket-style card, always visible above the panels --- */
.card.round-card {
background:
radial-gradient(320px circle at 100% 0%, color-mix(in srgb, var(--color-accent) 10%, transparent), transparent 70%),
var(--color-surface);
border-color: color-mix(in srgb, var(--color-primary) 25%, var(--color-border));
}
.round-card .hint { margin: 0; }
/* --- glowing card while a round is drawing --- */
.card.drawing-glow {
border-color: color-mix(in srgb, var(--color-primary) 55%, var(--color-border));
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 25%, transparent),
0 0 24px color-mix(in srgb, var(--color-primary) 22%, transparent);
animation: glow-pulse 2200ms ease-in-out infinite;
}
@keyframes glow-pulse {
0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 25%, transparent), 0 0 16px color-mix(in srgb, var(--color-primary) 16%, transparent); }
50% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 45%, transparent), 0 0 28px color-mix(in srgb, var(--color-primary) 30%, transparent); }
}
.jackpot-bump { animation: jackpot-bump 420ms ease-out; }
@keyframes jackpot-bump {
0% { transform: scale(1); }
30% { transform: scale(1.12); color: var(--color-primary); }
100% { transform: scale(1); }
}
/* --- network / lottery status strip, shown on every screen --- */
.chain-bar {
display: flex; align-items: center; justify-content: space-between; gap: 10px;
font-size: 0.78rem; padding: 12px 2px 16px; margin-bottom: 4px;
border-bottom: 1px solid var(--color-border);
}
.chain-status-pill { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; color: var(--color-foreground); }
.chain-bar-right { display: inline-flex; align-items: center; gap: 10px; flex-shrink: 0; }
/* Language switcher: a plain <select> styled down to look like the muted text
around it, so it reads as part of the status strip rather than as a form
control. Text labels, not flag emoji — flags don't render on every platform
and don't map one-to-one onto languages anyway. */
select.lang-switcher {
font: inherit; font-size: 0.78rem; color: var(--color-muted-foreground);
background: none; border: none; box-shadow: none; padding: 2px 4px;
border-radius: 6px; cursor: pointer;
-webkit-appearance: none; appearance: none;
}
select.lang-switcher:hover { color: var(--color-foreground); background: var(--color-surface-inset); }
select.lang-switcher:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
select.lang-switcher option { color: var(--color-foreground); background: var(--color-surface); }
.status-dot {
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
background: var(--color-muted-foreground);
}
.status-dot.status-open {
background: var(--color-success);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-success) 18%, transparent);
}
.status-dot.status-drawing {
background: var(--color-primary);
animation: status-dot-pulse 1400ms ease-in-out infinite;
}
.status-dot.status-waiting { background: var(--color-muted-foreground); }
.status-dot.status-offline { background: var(--color-destructive); }
@keyframes status-dot-pulse {
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-primary) 45%, transparent); }
50% { box-shadow: 0 0 0 5px transparent; }
}
.chain-block { color: var(--color-muted-foreground); white-space: nowrap; }
.maintenance-banner {
display: flex; align-items: flex-start; gap: 8px;
background: color-mix(in srgb, var(--color-primary) 12%, var(--color-surface));
border: 1px solid color-mix(in srgb, var(--color-primary) 40%, transparent);
color: var(--color-foreground); border-radius: var(--radius);
padding: 12px 14px; font-size: 0.82rem; line-height: 1.4; margin-bottom: 16px;
}
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; }
}