Add discovery modes, track tags and an artist graph

Tracks get tags from their title, description, artist name and BPM
(styles such as riddim, versions such as VIP/dub/flip, and tempo), stored
in track_tags and shown on cards. Related pages the crawler reads are kept
in track_related, which links artists to each other.

The feed takes ?mode=: riddim (140 BPM, riddim artists and their graph
neighbours first), fresh, deep cuts, and an artist radio that plays an
artist and the artists around them. Opening a radio crawls that artist's
related pages first. The crawler searches riddim-adjacent terms first.

The filters panel picks the feed, each card has a radio button (r), and a
chip in the top bar returns to the normal feed.
This commit is contained in:
nvrl committed 2026-11-01 13:46:52 +01:00
1 parent b4470850bf
commit f6c505907a
14 files changed
+737 -36

No files matched your search

+22 -2
View File
@@ -82,6 +82,13 @@ button {
vertical-align: middle;
}
#bar button[aria-expanded="true"] { background: rgba(255, 255, 255, 0.22); }
#mode-chip {
max-width: 12rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: color-mix(in oklab, var(--accent) 40%, transparent);
}
.panel {
position: fixed;
@@ -95,7 +102,19 @@ button {
box-shadow: 0 16px 40px #000a;
}
#filters { display: flex; gap: 0.8rem; align-items: center; flex-wrap: wrap; }
#filters { display: flex; gap: 0.8rem; align-items: center; flex-wrap: wrap; max-width: 24rem; }
.modes { display: flex; gap: 0.4rem; flex-wrap: wrap; flex-basis: 100%; margin: 0; padding: 0; border: 0; }
.modes legend { color: var(--muted); font-size: 0.8rem; padding: 0; margin-bottom: 0.3rem; }
.modes label { cursor: pointer; }
.modes input { position: absolute; opacity: 0; }
.modes label:has(input) {
padding: 0.25em 0.8em;
border-radius: 999px;
border: 1px solid #ffffff40;
font-size: 0.85rem;
}
.modes label:has(input:checked) { background: var(--accent); border-color: transparent; color: #000; }
.modes label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
#filters input[type=number] {
width: 4.5em;
background: #222;
@@ -266,7 +285,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
.byline { display: flex; align-items: center; gap: 0.6rem; }
.artist { color: var(--muted); text-decoration: none; }
.artist:hover { text-decoration: underline; }
.follow {
.follow, .radio {
font-size: 0.75rem;
padding: 0.15em 0.75em;
border-radius: 999px;
@@ -285,6 +304,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
.chips .free { background: #1f7a4d; }
.chips span:first-child { background: color-mix(in oklab, var(--card-accent) 35%, transparent); }
.chips .preview { background: #6a4b16; }
.chips .tag { background: transparent; box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--card-accent) 60%, transparent); }
.desc {
margin: 0;