Files
Levyraati26_go/static/style.css
T
Esa Kataja ac2cfaebac Add CalVer versioning and rebuild the song page's fact line
Versions are YYYY.MM.DD-N, injected with -ldflags from a git tag, so no file
in the repo carries the number and a local build honestly says dev. The
version shows in the footer, the startup log and /healthz.

The song page's metadata was five different kinds of fact — artist, genre,
duration, provenance and a badge about the viewer — in one flat run with two
competing pills. Now the artist has its own line in the display face, and the
facts sit in four labelled cells like the spine of a cassette insert. The
submitter links to their profile, "oma kappale" became "lähetti: sinä", and
the clock time is gone: nobody needs the minute a song was published.
2026-07-31 23:38:54 +03:00

983 lines
33 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Levyraati — dark-only rock club poster. Tokens first; nothing below invents a colour, a spacing
step, a radius or a duration that isn't declared here. */
/* Oswald variable, latin subset, vendored — no CDN, and no webfont for body text. 400700 only:
asking for 900 makes the browser synthesise a fake bold that differs per platform. */
@font-face {
font-family: "Oswald";
src: url("/static/fonts/oswald.woff2") format("woff2");
font-weight: 400 700;
font-display: swap;
font-style: normal;
}
:root {
/* Surfaces */
--bg: #111111;
--surface: #1a1a1a;
--surface-raised: #222222;
--surface-header: #1e1e1e;
--bar: #0d0d0d;
--hairline: #2a2a2a;
--divider: #333333;
--input-border: #444444;
--track: #2a2a2a;
/* Text */
--text: #e0e0e0;
--muted: #888888;
--text-strong: #f0f0f0;
/* Accent — bronze/amber, not neon */
--primary: #a0693a;
--primary-hover: #b57848;
--primary-wash: rgba(160, 105, 58, 0.2);
--primary-inverse: #111111;
--gold-1: #c4a96a;
--gold-2: #b89558;
/* Status, deliberately desaturated */
--error: #e05959;
--error-bg: rgba(224, 89, 89, 0.08);
--success: #7db07d;
--success-bg: rgba(125, 176, 125, 0.08);
--pending: #8f4a44;
--pending-bg: #2e2020;
--unreviewed: #7a3a36;
--unreviewed-hover: #a04540;
/* Scale — 4px base, six steps, nothing in between */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
--radius: 4px;
--radius-toast: 6px;
--radius-pill: 99px;
--duration-fast: 150ms;
--ease-out: cubic-bezier(0.4, 0, 0.2, 1);
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.03);
--shadow-card-hover: 0 4px 16px rgba(0, 0, 0, 0.5), 0 0 24px rgba(180, 120, 72, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
--shadow-focus: 0 0 0 3px rgba(196, 169, 106, 0.35);
/* Native controls (audio, checkbox, range, scrollbars) follow this — it is what stops the
<audio> element rendering as a white slab against the dark page. */
color-scheme: dark;
--content: 1450px;
--font-display: "Oswald", Impact, system-ui, sans-serif;
--font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font-body);
line-height: 1.5;
}
/* One focus treatment everywhere, on :focus-visible only. Keyboard navigation is the only way
through some admin tables — this ring is not optional. */
a:focus-visible, button:focus-visible, input:focus-visible,
textarea:focus-visible, select:focus-visible, summary:focus-visible,
[role="button"]:focus-visible {
outline: none;
box-shadow: var(--shadow-focus);
border-radius: var(--radius);
}
h1, h2, h3 {
font-family: var(--font-display);
font-weight: 700;
letter-spacing: -0.01em;
margin: 0 0 var(--space-3);
}
/* Headings step downward in brightness with level. */
h1 { font-size: 2rem; color: var(--gold-1); }
h2 { font-size: 1.5rem; color: var(--gold-2); }
h3 { font-size: 1.2rem; color: var(--primary); }
h4 { font-family: var(--font-body); font-weight: 800; letter-spacing: -0.02em; }
a { color: var(--primary); text-decoration-color: var(--primary-wash); }
a:hover { color: var(--primary-hover); }
/* --- top bar --- */
header.topbar {
background: var(--bar);
border-bottom: 2px solid var(--divider);
padding: var(--space-3) var(--space-5);
position: relative;
}
/* Three columns, not space-between: this is what keeps the links optically centred whatever the
brand and user block weigh. */
.topbar-inner {
max-width: var(--content);
margin: 0 auto;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: var(--space-4);
}
.brand {
font-family: var(--font-display);
font-size: 1.4rem;
font-weight: 700;
letter-spacing: -0.03em;
text-transform: uppercase;
color: var(--text);
text-decoration: none;
}
.navlinks { display: flex; gap: var(--space-2); justify-content: center; }
.navlinks a {
padding: 0.4rem var(--space-3);
border-radius: var(--radius);
color: var(--text);
text-decoration: none;
font-size: 0.9rem;
transition: background var(--duration-fast) var(--ease-out),
color var(--duration-fast) var(--ease-out);
}
/* Hover and current route look the same, on purpose. */
.navlinks a:hover, .navlinks a[aria-current="page"] {
background: var(--surface-raised);
color: var(--primary);
}
.userblock {
display: flex;
align-items: center;
justify-content: flex-end;
gap: var(--space-3);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius);
line-height: 1.2;
}
.userblock .lines { text-align: right; display: flex; flex-direction: column; }
.userblock .name { font-size: 0.85rem; font-weight: 600; }
.userblock .email { font-size: 0.7rem; color: var(--muted); }
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
flex: none;
border-radius: 50%;
background: var(--surface-raised);
border: 1px solid var(--input-border);
color: var(--gold-1);
font-family: var(--font-display);
font-size: 0.9rem;
font-weight: 600;
}
.avatar.small { width: 1.8rem; height: 1.8rem; font-size: 0.75rem; }
/* --- layout --- */
main { max-width: var(--content); margin: 0 auto; padding: var(--space-6) var(--space-5); }
main.narrow { max-width: 420px; padding-top: 8vh; }
section { margin-bottom: var(--space-6); }
footer.sitefooter {
text-align: center;
color: var(--muted);
font-size: 0.8rem;
padding: var(--space-6) var(--space-4) var(--space-4);
}
.muted { color: var(--muted); }
.small { font-size: 0.8rem; }
.nowrap { white-space: nowrap; }
.error { color: var(--error); display: block; font-size: 0.85rem; }
.empty { font-family: var(--font-display); color: var(--muted); padding: var(--space-6) 0; }
/* --- badges --- */
.badge {
display: inline-block;
border-radius: var(--radius-pill);
padding: 0.15rem var(--space-2);
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
}
.badge.genre { background: var(--surface-raised); color: var(--primary); border: 1px solid var(--input-border); }
.badge.admin { background: #3a1a1a; color: var(--gold-2); }
.badge.reviewed { background: var(--success-bg); color: var(--success); border: 1px solid var(--success); }
.badge.pending { background: var(--pending-bg); color: var(--pending); }
/* --- song grid --- */
.songgrid { display: grid; grid-template-columns: 1fr; gap: var(--space-4); }
@media (min-width: 640px) { .songgrid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .songgrid { grid-template-columns: repeat(3, 1fr); } }
.songcard {
position: relative;
display: flex;
flex-direction: column;
gap: var(--space-1);
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: var(--radius);
padding: var(--space-4);
padding-right: var(--space-6);
text-decoration: none;
color: var(--text);
box-shadow: var(--shadow-card);
transition: transform var(--duration-fast) var(--ease-out),
border-color var(--duration-fast) var(--ease-out),
box-shadow var(--duration-fast) var(--ease-out);
}
.songcard:hover {
transform: translateY(-2px);
border-color: var(--primary);
box-shadow: var(--shadow-card-hover);
}
/* The single most important state in the app: what still needs a review. Carried by the border
AND the pending badge — never by colour alone. */
.songcard.unreviewed { border-color: var(--unreviewed); }
.songcard.unreviewed:hover { border-color: var(--unreviewed-hover); }
.songcard .title { font-size: 1.1rem; font-weight: 700; color: var(--text-strong); }
.songcard .artist { font-size: 0.9rem; color: #cccccc; }
.songcard .meta { font-size: 0.75rem; color: var(--muted); display: flex; gap: var(--space-2);
align-items: center; flex-wrap: wrap; margin-top: var(--space-2); }
.songcard .scorebadge {
position: absolute;
top: var(--space-3);
right: var(--space-3);
font-family: var(--font-display);
font-size: 1.4rem;
font-weight: 700;
color: var(--gold-1);
background: rgba(0, 0, 0, 0.5);
border-radius: var(--radius);
padding: 0 var(--space-2);
}
/* --- song page and reviews --- */
/* The artist is the second most important thing on the page, so it gets the display face and a
line of its own rather than being one bold word inside a run of metadata. */
.by {
font-family: var(--font-display);
font-size: 1.35rem;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--text);
margin: 0 0 var(--space-3);
}
/* Four kinds of fact, four labelled cells — the spine of a cassette insert. */
.spec {
display: flex;
flex-wrap: wrap;
margin: 0 0 var(--space-5);
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: var(--radius);
overflow: hidden;
}
.spec > div {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 6rem;
padding: var(--space-2) var(--space-4);
border-right: 1px solid var(--hairline);
}
.spec > div:last-child { border-right: 0; }
.spec dt {
font-family: var(--font-display);
font-size: 0.65rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.spec dd { margin: 0; font-size: 0.95rem; }
.intro { white-space: pre-wrap; background: var(--surface); padding: var(--space-4);
border-left: 3px solid var(--hairline); border-radius: 0 var(--radius) var(--radius) 0; }
.average { font-family: var(--font-display); font-size: 1.2rem; color: var(--gold-1); }
.review {
padding: var(--space-4);
border-left: 3px solid var(--input-border);
background: var(--surface);
border-radius: 0 var(--radius) var(--radius) 0;
margin-bottom: var(--space-2);
}
.review.own { border-left-color: var(--primary); }
.review header { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-2); }
.review .who { font-weight: 700; color: var(--primary); }
.review .score { font-family: var(--font-display); font-size: 1.8rem; font-weight: 700; color: var(--gold-1); }
.review p { white-space: pre-wrap; line-height: 1.6; margin: 0; }
.review .meta { font-size: 0.8rem; color: var(--muted); }
.player { width: 100%; margin: var(--space-3) 0 var(--space-5); }
/* --- forms --- */
form.stack { display: flex; flex-direction: column; gap: var(--space-4); }
form.stack label { display: flex; flex-direction: column; gap: var(--space-1); }
form.stack label.row { flex-direction: row; align-items: center; gap: var(--space-2); }
/* Buttons size to their label rather than stretching across the column. */
form.stack > button, form.stack > .actions { align-self: flex-start; }
input, select, textarea {
background: var(--surface);
color: var(--text);
border: 1px solid var(--input-border);
border-radius: var(--radius);
padding: var(--space-2) var(--space-3);
font: inherit;
width: 100%;
transition: border-color var(--duration-fast) var(--ease-out);
}
input[type="checkbox"], input[type="range"] { width: auto; }
input:hover, select:hover, textarea:hover { border-color: var(--muted); }
input:focus, select:focus, textarea:focus { border-color: var(--primary); }
textarea { resize: vertical; line-height: 1.6; }
button, .btn {
background: var(--primary);
color: var(--primary-inverse);
border: 1px solid var(--primary);
border-radius: var(--radius);
padding: var(--space-2) var(--space-4);
font: inherit;
font-weight: 600;
cursor: pointer;
transition: background var(--duration-fast) var(--ease-out),
border-color var(--duration-fast) var(--ease-out),
color var(--duration-fast) var(--ease-out);
}
button:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
button:disabled { opacity: 0.5; cursor: not-allowed; background: var(--surface-raised);
border-color: var(--input-border); color: var(--muted); }
button.ghost {
background: transparent;
border-color: var(--input-border);
color: var(--text);
font-size: 0.85rem;
}
button.ghost:hover { border-color: var(--primary); color: var(--primary);
background: rgba(180, 120, 72, 0.05); }
button.danger { background: transparent; border-color: var(--pending); color: var(--pending); }
button.danger:hover { background: var(--pending-bg); border-color: var(--unreviewed-hover);
color: var(--error); }
button.link { background: none; border: 0; color: var(--primary); padding: 0;
font-weight: 400; font-size: 0.9rem; text-decoration: underline; }
button.link:hover { background: none; color: var(--primary-hover); }
/* --- the channel strip --- */
/* Score and text side by side: the two things you do at once stop being a screen apart. */
.strip {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-5);
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: var(--radius);
padding: var(--space-5);
box-shadow: var(--shadow-card);
margin-bottom: var(--space-5);
}
.deck { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.deck .grow { flex: 1; }
.deck .player { margin: 0; }
.deckfoot { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.fader { display: grid; grid-template-columns: auto auto; grid-template-rows: 1fr auto;
gap: var(--space-2); align-items: stretch; }
.ticks { display: flex; flex-direction: column; justify-content: space-between; text-align: right;
font-size: 0.7rem; color: var(--muted); font-family: var(--font-display); }
/* Vertical is native: writing-mode plus rtl gives bottom-to-top travel with no custom widget. */
.fader input[type="range"] {
writing-mode: vertical-lr;
direction: rtl;
-webkit-appearance: none;
appearance: none;
width: 2rem;
height: 100%;
min-height: 15rem;
padding: 0;
border: 0;
background: transparent;
cursor: ns-resize;
}
.fader input[type="range"]::-webkit-slider-runnable-track {
width: 8px;
background: var(--track);
border: 1px solid var(--input-border);
border-radius: var(--radius-pill);
}
.fader input[type="range"]::-moz-range-track {
width: 8px;
background: var(--track);
border: 1px solid var(--input-border);
border-radius: var(--radius-pill);
}
/* A cap, not a knob: this is a fader. */
.fader input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 2rem;
height: 14px;
margin-left: -12px;
border-radius: 2px;
background: linear-gradient(var(--gold-1), var(--gold-2));
border: 1px solid var(--bar);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
}
.fader input[type="range"]::-moz-range-thumb {
width: 2rem;
height: 14px;
border-radius: 2px;
background: linear-gradient(var(--gold-1), var(--gold-2));
border: 1px solid var(--bar);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
}
.readout {
grid-column: 1 / -1;
font-family: var(--font-display);
font-size: 2rem;
font-weight: 700;
color: var(--gold-1);
text-align: center;
background: var(--bar);
border: 1px solid var(--input-border);
border-radius: var(--radius);
padding: 0 var(--space-2);
}
/* --- the reveal: one channel per reviewer --- */
.channels { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: flex-end;
margin-bottom: var(--space-5); }
.chan { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); width: 2.6rem; }
.chan-track {
position: relative;
width: 8px;
height: 8rem;
background: var(--track);
border: 1px solid var(--input-border);
border-radius: var(--radius-pill);
animation: chan-rise var(--duration-fast) var(--ease-out) backwards;
animation-delay: calc(var(--i, 0) * 40ms);
}
/* The cap sits at the score: the row's silhouette is the spread. */
.chan-cap {
position: absolute;
left: 50%;
bottom: calc(var(--v) * 1%);
transform: translate(-50%, 50%);
width: 1.8rem;
height: 10px;
border-radius: 2px;
background: var(--primary);
border: 1px solid var(--bar);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
/* The filled part below the cap reads as level. */
.chan-cap::after {
content: "";
position: absolute;
left: 50%;
top: 100%;
transform: translateX(-50%);
width: 4px;
height: 8rem;
background: linear-gradient(var(--primary), transparent);
opacity: 0.5;
}
.chan.own .chan-cap { background: var(--gold-1); }
.chan.own .chan-cap::after { background: linear-gradient(var(--gold-1), transparent); }
.chan.own .chan-score { color: var(--gold-1); }
.chan.avg .chan-track { background: transparent; border-left: 1px dashed var(--input-border); width: 1px; }
.chan.avg .chan-cap { background: var(--gold-2); width: 2rem; height: 2px; border: 0; }
.chan.avg .chan-cap::after { display: none; }
.chan-score { font-family: var(--font-display); font-size: 0.95rem; color: var(--text); }
.chan-who { font-size: 0.7rem; color: var(--muted); }
@keyframes chan-rise {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: none; }
}
/* Hidden scores are hatched rather than absent: "locked", not "no data". */
.sealed {
display: inline-block;
width: 2.2rem;
height: 1rem;
vertical-align: -2px;
border-radius: 2px;
background: repeating-linear-gradient(-45deg, rgba(224, 224, 224, 0.18) 0 6px, transparent 6px 12px),
var(--bar);
border: 1px solid var(--input-border);
}
.sealed-note { color: var(--muted); display: flex; align-items: center; gap: var(--space-2); }
.reviewtext { white-space: pre-wrap; line-height: 1.6; }
.nextup { font-family: var(--font-display); text-transform: uppercase; font-size: 1.1rem; }
/* --- drop zone --- */
.dropzone {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-1);
padding: var(--space-6) var(--space-4);
border: 2px dashed var(--input-border);
border-radius: var(--radius);
background: var(--surface);
cursor: pointer;
text-align: center;
transition: border-color var(--duration-fast) var(--ease-out),
background var(--duration-fast) var(--ease-out);
}
.dropzone:hover { border-color: var(--primary); }
.dropzone.over { border-color: var(--primary); background: var(--surface-raised); }
.dropzone.has-file { border-style: solid; border-color: var(--primary); }
.dropzone input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; }
.dropzone:focus-within { box-shadow: var(--shadow-focus); }
.dz-title { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.02em; }
.filename { color: var(--primary); word-break: break-all; }
.or { text-align: center; color: var(--muted); text-transform: uppercase;
font-family: var(--font-display); margin: 0; }
/* --- submission status --- */
.status { background: var(--surface); border-left: 3px solid var(--primary);
border-radius: 0 var(--radius) var(--radius) 0; padding: var(--space-4);
margin-bottom: var(--space-5); }
.status p { margin: 0 0 var(--space-2); }
.status.failed { border-left-color: var(--pending); }
.saved { color: var(--muted); font-size: 0.85rem; min-height: 1.2em; }
.editbox { background: var(--surface); border: 1px solid var(--hairline);
border-radius: var(--radius); padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-5); }
.editbox summary { cursor: pointer; font-family: var(--font-display);
text-transform: uppercase; color: var(--primary); }
.editbox form { margin: var(--space-4) 0; }
.actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: flex-start; }
.actions form { display: flex; gap: var(--space-2); }
.actions input { width: 12rem; }
/* --- tables and admin --- */
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: var(--space-2); border-bottom: 1px solid var(--hairline); }
th { font-family: var(--font-display); text-transform: uppercase; font-size: 0.8rem;
font-weight: 600; color: var(--muted); }
tr.banned { opacity: 0.55; }
td.break { word-break: break-all; font-size: 0.8rem; }
.adminsection { background: var(--surface); border: 1px solid var(--hairline);
border-radius: var(--radius); margin-bottom: var(--space-6); }
.adminsection > header { background: var(--surface-header); padding: var(--space-4) var(--space-5);
border-bottom: 1px solid var(--hairline); display: flex;
align-items: center; justify-content: space-between; gap: var(--space-4); }
.adminsection > header h2 { margin: 0; }
.adminsection .body { padding: var(--space-4) var(--space-5); }
.dot { display: inline-block; width: 0.9rem; height: 0.9rem; border-radius: 50%; }
.dot.on { background: #5a8f5a; }
.dot.off { background: #8f4a44; }
code { background: var(--surface-raised); padding: 0.1rem var(--space-1);
border-radius: var(--radius); font-size: 0.85rem; }
/* --- toasts --- */
.toasts { position: fixed; right: var(--space-4); bottom: var(--space-4); z-index: 1000;
display: flex; flex-direction: column; gap: var(--space-2);
max-width: min(360px, 100vw - 24px); pointer-events: none; }
.toast {
pointer-events: auto;
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-toast);
border-left: 4px solid var(--success);
background: var(--surface);
box-shadow: var(--shadow-card);
animation: toast-in var(--duration-fast) var(--ease-out);
}
.toast p { margin: 0; }
.toast .dismiss { background: none; border: 0; color: var(--text); font-size: 1.5rem;
line-height: 1; padding: 0; opacity: 0.7; cursor: pointer; }
.toast .dismiss:hover { opacity: 1; background: none; }
@keyframes toast-in {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: none; }
}
/* --- mobile --- */
.mobilenav { display: none; }
@media (max-width: 768px) {
.topbar-inner { grid-template-columns: 1fr auto; }
.navlinks, .userblock .lines { display: none; }
.mobilenav { display: block; justify-self: end; }
.mobilenav > summary {
list-style: none;
cursor: pointer;
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--input-border);
border-radius: var(--radius);
font-size: 1.4rem;
color: var(--text);
transition: border-color var(--duration-fast) var(--ease-out),
background var(--duration-fast) var(--ease-out);
}
.mobilenav > summary::-webkit-details-marker { display: none; }
.mobilenav[open] > summary, .mobilenav > summary:hover {
border-color: var(--primary);
background: rgba(180, 120, 72, 0.05);
}
.mobilenav .panel {
position: absolute;
left: 0;
right: 0;
top: 100%;
background: var(--surface);
border-bottom: 2px solid var(--primary);
box-shadow: var(--shadow-card-hover);
padding: var(--space-5) var(--space-4) var(--space-4);
display: flex;
flex-direction: column;
z-index: 95;
animation: panel-down var(--duration-fast) var(--ease-out);
}
.mobilenav .panel a, .mobilenav .panel button {
font-family: var(--font-display);
text-transform: uppercase;
font-size: 1.15rem;
font-weight: 600;
letter-spacing: 0.02em;
padding: var(--space-4);
text-align: left;
background: none;
border: 0;
color: var(--text);
text-decoration: none;
}
main { padding: var(--space-5) var(--space-4); }
.toasts { left: var(--space-4); max-width: none; }
/* A 200px fader on a phone is worse than a horizontal one. */
.strip { grid-template-columns: 1fr; gap: var(--space-3); padding: var(--space-4); }
.fader { grid-template-columns: 1fr auto; grid-template-rows: auto; align-items: center; }
.fader input[type="range"] { writing-mode: horizontal-tb; direction: ltr;
width: 100%; height: auto; min-height: 0; }
/* row-reverse: the markup is top-to-bottom (100 first), a horizontal fader runs 1 → 100. */
.ticks { flex-direction: row-reverse; justify-content: space-between; order: 2;
grid-column: 1 / -1; }
.readout { grid-column: auto; font-size: 1.5rem; }
}
@keyframes panel-down {
from { transform: translateY(-100%); opacity: 0; }
to { transform: none; opacity: 1; }
}
/* Keep the fades, drop the movement. */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; }
.songcard:hover { transform: none; }
}
/* --- stats --- */
.boards { display: grid; grid-template-columns: 1fr; gap: var(--space-5); }
@media (min-width: 700px) { .boards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .boards { grid-template-columns: repeat(3, 1fr); } }
.board { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--radius);
padding: var(--space-4); margin: 0; box-shadow: var(--shadow-card); }
.board h2 { font-size: 1.1rem; margin-bottom: var(--space-3); }
/* A counter rather than a list marker: `display: grid` on the <li> suppresses markers. */
.board-list { list-style: none; counter-reset: rank; margin: 0; padding: 0; }
.board-list li { display: grid; grid-template-columns: 1.6rem 1fr auto;
column-gap: var(--space-2); align-items: baseline;
padding: var(--space-2) 0; border-bottom: 1px solid var(--hairline); }
.board-list li:last-child { border-bottom: 0; }
.board-list li::before { counter-increment: rank; content: counter(rank) "."; color: var(--muted);
font-family: var(--font-display); }
.board-list li a { grid-column: 2; }
.board-list li .value { grid-column: 3; grid-row: 1; font-family: var(--font-display);
font-size: 1.1rem; color: var(--gold-1); }
.board-list li .small { grid-column: 2; }
.board-list li .small:last-child { grid-column: 3; text-align: right; }
/* --- profile --- */
.profilehead { display: flex; align-items: center; gap: var(--space-4); margin-bottom: var(--space-5); }
.profilehead h1 { margin: 0; }
.avatar.big { width: 4.5rem; height: 4.5rem; font-size: 1.6rem; object-fit: cover; }
img.avatar { object-fit: cover; }
.statgrid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-4);
margin-bottom: var(--space-6); }
@media (min-width: 700px) { .statgrid { grid-template-columns: repeat(4, 1fr); } }
.statcard { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--radius);
padding: var(--space-5); text-align: center; display: flex; flex-direction: column;
gap: var(--space-1); box-shadow: var(--shadow-card); }
.statvalue { font-family: var(--font-display); font-size: 2rem; font-weight: 700; color: var(--gold-1); }
/* --- leaderboard bars --- */
.bar { grid-column: 2 / -1; position: relative; height: 4px; margin-top: var(--space-1);
background: var(--track); border-radius: var(--radius-pill); overflow: hidden; }
.bar .fill { position: absolute; top: 0; bottom: 0; left: 0; background: var(--primary);
border-radius: var(--radius-pill); }
.bar.range .fill { background: linear-gradient(90deg, var(--primary), var(--gold-1)); }
/* --- nav queue count --- */
.count { display: inline-block; min-width: 1.4em; padding: 0 0.35em; border-radius: var(--radius-pill);
background: var(--primary); color: var(--primary-inverse); font-size: 0.75rem;
font-weight: 700; text-align: center; }
/* --- profile comparison --- */
.statcard.wide { grid-column: span 2; }
.channels.compare { justify-content: center; gap: var(--space-5); margin: 0; }
.channels.compare .chan-track { height: 5rem; }
.channels.compare .chan-cap::after { height: 5rem; }
.channels.compare .chan { width: 4rem; }
/* --- submission progress --- */
.segments { display: flex; gap: var(--space-1); list-style: none; margin: 0 0 var(--space-3);
padding: 0; font-family: var(--font-display); text-transform: uppercase;
font-size: 0.75rem; letter-spacing: 0.04em; }
.segments li { flex: 1; padding: var(--space-1) var(--space-2); text-align: center;
color: var(--muted); background: var(--bar); border: 1px solid var(--hairline);
border-radius: var(--radius); }
.segments li.now { color: var(--primary-inverse); background: var(--primary); border-color: var(--primary); }
.segments li.done { color: var(--gold-1); border-color: var(--input-border); }
.segments li.failed { color: var(--pending); border-color: var(--pending); background: var(--pending-bg); }
/* The score chip on a card, sealed: the score exists, you just haven't earned it yet.
The specificity has to beat .songcard .scorebadge, which sets its own background. */
.songcard .scorebadge.sealed {
display: block;
width: 2.4rem;
height: 1.4rem;
padding: 0;
background: repeating-linear-gradient(-45deg, rgba(224, 224, 224, 0.22) 0 5px, transparent 5px 10px),
var(--bar);
border: 1px solid var(--input-border);
}
/* --- transport --- */
.playerwrap { margin: 0 0 var(--space-3); }
.playerwrap .player { width: 100%; }
/* Once enhanced the native element is only a source of sound. */
.playerwrap:has(.transport) .player { display: none; }
.transport {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: var(--space-4);
background: var(--bar);
border: 1px solid var(--divider);
border-radius: var(--radius);
padding: var(--space-3) var(--space-4);
}
.tp-play {
width: 3rem;
height: 3rem;
flex: none;
padding: 0;
border-radius: 50%;
background: var(--primary);
border: 0;
display: grid;
place-items: center;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}
.tp-play:hover { background: var(--primary-hover); }
/* Drawn shapes, not glyphs: ▶ and ❚❚ render differently on every platform. */
.tp-icon {
width: 0;
height: 0;
border-style: solid;
border-width: 9px 0 9px 14px;
border-color: transparent transparent transparent var(--primary-inverse);
margin-left: 3px;
}
.playing .tp-icon {
width: 14px;
height: 16px;
margin-left: 0;
border: 0;
background: linear-gradient(90deg, var(--primary-inverse) 0 5px, transparent 5px 9px,
var(--primary-inverse) 9px 14px);
}
.tp-mid { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
/* A range input for seeking, so arrow keys and the focus ring come free. */
.tp-seek {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 14px;
padding: 0;
border: 0;
background: transparent;
cursor: pointer;
}
.tp-seek::-webkit-slider-runnable-track {
height: 6px;
border-radius: var(--radius-pill);
background: linear-gradient(90deg, var(--primary) 0 var(--pct, 0%), var(--track) var(--pct, 0%));
border: 1px solid var(--input-border);
}
.tp-seek::-moz-range-track {
height: 6px;
border-radius: var(--radius-pill);
background: var(--track);
border: 1px solid var(--input-border);
}
.tp-seek::-moz-range-progress { height: 6px; border-radius: var(--radius-pill); background: var(--primary); }
.tp-seek::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
margin-top: -4px;
border-radius: 50%;
background: var(--gold-1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.tp-seek::-moz-range-thumb {
width: 12px;
height: 12px;
border: 0;
border-radius: 50%;
background: var(--gold-1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.tp-time { font-family: var(--font-display); font-size: 0.95rem; color: var(--muted); white-space: nowrap; }
.tp-time b { color: var(--text); font-weight: 700; }
/* --- level meter --- */
.meter { display: flex; flex-direction: column; gap: 3px; }
.meter-row { display: flex; gap: var(--space-2); align-items: center; }
.meter-row .lbl { font-family: var(--font-display); font-size: 0.65rem; color: var(--muted); width: 0.8rem; }
.meter .segs { display: flex; gap: 2px; }
.seg { width: 6px; height: 11px; border-radius: 1px; background: var(--track);
border: 1px solid rgba(0, 0, 0, 0.4); }
.seg.on { background: var(--gold-2); }
.seg.on.hot { background: var(--primary); }
.seg.on.peak { background: #c0392b; }
@media (max-width: 768px) {
.transport { gap: var(--space-3); padding: var(--space-3); }
.tp-play { width: 2.6rem; height: 2.6rem; }
.seg { width: 4px; }
}
.pager { display: flex; gap: var(--space-5); margin-top: var(--space-5); }
/* The slogan is a mark, so it is set in the display face rather than the body face. */
.slogan { font-family: var(--font-display); letter-spacing: 0.02em; }
.slogan.hero { color: var(--gold-2); font-size: 1.1rem; text-transform: uppercase;
margin: calc(-1 * var(--space-2)) 0 var(--space-5); }
.copyright { color: var(--muted); }
.copyright::before { content: "·"; margin: 0 var(--space-2); }
.version { color: var(--muted); font-family: var(--font-display); }
.version::before { content: "·"; margin: 0 var(--space-2); }