mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-23 05:11:34 +00:00
The section_map plugin pins a ~20px clickable bar (#section-map, z-index:5) to the top of #player. The v3 left-rail hover-catcher (.v3-railzone::before) is full-height at z-index:30 with pointer-events:auto, so its top-left corner swallowed every click on the section map's first section — the left-most section was never clickable on the v3 desktop (macOS/Windows) UI. Drop the catcher below the 20px bar when the section map is present, mirroring the existing #section-map ~ #player-hud special-case in static/style.css. The rail still reveals from anywhere below the bar. Adds a Playwright regression test (hit-test of the top-left corner) with a negative control that re-raises the catcher to reproduce the bug. Fixes #616 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1120 lines
43 KiB
CSS
1120 lines
43 KiB
CSS
/*
|
|
* fee[dB]ack v0.3.0 — non-Tailwind bits for the v3 shell.
|
|
* Body radial-gradient background + custom scrollbars. Tokens mirror the
|
|
* `fb` palette in tailwind.config.js.
|
|
*/
|
|
|
|
/* The v3 tuner card replaces the tuner plugin's floating launcher — hide it. */
|
|
#tuner-toggle-btn { display: none !important; }
|
|
|
|
/* The navy radial wash lives on the SIDEBAR (the content area is the solid
|
|
darker tone). Brighter toward the top. */
|
|
#v3-sidebar {
|
|
background-image: radial-gradient(circle at top, #1e293b 0%, #0f172a 100%);
|
|
background-attachment: fixed;
|
|
}
|
|
|
|
/* Custom 6px scrollbars on the scroll regions (WebKit/Blink). */
|
|
#v3-main::-webkit-scrollbar,
|
|
#v3-nav::-webkit-scrollbar,
|
|
.v3-scroll::-webkit-scrollbar {
|
|
width: 6px;
|
|
height: 6px;
|
|
}
|
|
#v3-main::-webkit-scrollbar-track,
|
|
#v3-nav::-webkit-scrollbar-track,
|
|
.v3-scroll::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
#v3-main::-webkit-scrollbar-thumb,
|
|
#v3-nav::-webkit-scrollbar-thumb,
|
|
.v3-scroll::-webkit-scrollbar-thumb {
|
|
background: #334155;
|
|
border-radius: 3px;
|
|
}
|
|
#v3-main::-webkit-scrollbar-thumb:hover,
|
|
#v3-nav::-webkit-scrollbar-thumb:hover,
|
|
.v3-scroll::-webkit-scrollbar-thumb:hover {
|
|
background: #475569;
|
|
}
|
|
|
|
/* Firefox scrollbars. */
|
|
#v3-main,
|
|
#v3-nav,
|
|
.v3-scroll {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #334155 transparent;
|
|
}
|
|
|
|
/* ══ v3 player chrome (P22) ════════════════════════════════════════════════
|
|
Persistent top HUD + Up-Next pill, hover-reveal left rail with feature
|
|
popovers, and an auto-hiding bottom transport. Scoped under #player; this
|
|
markup only exists in static/v3/index.html, so the legacy player is
|
|
unaffected. Behavior: static/v3/player-chrome.js. */
|
|
|
|
#player { background: #050508; position: relative; }
|
|
#player #highway { position: relative; z-index: 1; }
|
|
|
|
/* — Venue Mood FX — Phase 1: bottom strip disabled; real scene ships in 3D bg — */
|
|
.venue-mood-fx {
|
|
display: none !important;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Edge-only wash above 3D canvas, below HUD/transport (z-index 3) */
|
|
#player .v3-venue-scene-wash {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 3;
|
|
pointer-events: none;
|
|
background:
|
|
linear-gradient(180deg, rgba(34, 211, 238, .16) 0%, transparent 14%),
|
|
linear-gradient(0deg, rgba(30, 64, 110, .2) 0%, transparent 16%),
|
|
linear-gradient(90deg, rgba(34, 211, 238, .1) 0%, transparent 10%),
|
|
linear-gradient(270deg, rgba(34, 211, 238, .1) 0%, transparent 10%);
|
|
}
|
|
|
|
/* Non-blocking Venue mode label — below HUD, above canvas */
|
|
#player .v3-venue-mode-badge {
|
|
position: absolute;
|
|
top: 4.75rem;
|
|
left: 1.25rem;
|
|
z-index: 18;
|
|
pointer-events: none;
|
|
max-width: min(22rem, calc(100% - 2.5rem));
|
|
padding: .35rem .65rem;
|
|
font-size: .7rem;
|
|
line-height: 1.35;
|
|
letter-spacing: .02em;
|
|
color: rgba(165, 243, 252, .95);
|
|
background: rgba(8, 47, 73, .78);
|
|
border: 1px solid rgba(34, 211, 238, .38);
|
|
border-radius: .375rem;
|
|
box-shadow: 0 2px 10px rgba(2, 8, 23, .35);
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
|
|
#player .v3-venue-scene-wash.hidden,
|
|
#player .v3-venue-mode-badge.hidden {
|
|
display: none;
|
|
}
|
|
|
|
/* Legacy ::before wash retained for tests; superseded by .v3-venue-scene-wash */
|
|
#player.is-venue-visualization.venue-scene-pending::before {
|
|
content: none;
|
|
}
|
|
|
|
/* Legacy strip rules retained for when STRIP_OVERLAY_ENABLED returns */
|
|
.venue-mood-fx:not(.hidden) { opacity: 1; }
|
|
.venue-mood-fx.hidden { display: none; }
|
|
|
|
.venue-mood-lights,
|
|
.venue-mood-crowd,
|
|
.venue-mood-haze {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Stage wash — original gradients only */
|
|
.venue-mood-lights {
|
|
background:
|
|
radial-gradient(ellipse 55% 85% at 10% 100%, rgba(251, 191, 36, .22) 0%, transparent 58%),
|
|
radial-gradient(ellipse 55% 85% at 90% 100%, rgba(56, 189, 248, .2) 0%, transparent 58%),
|
|
radial-gradient(ellipse 90% 120% at 50% 110%, rgba(30, 58, 95, .55) 0%, transparent 68%),
|
|
linear-gradient(180deg, transparent 0%, rgba(8, 12, 22, .35) 55%, rgba(4, 6, 12, .82) 100%);
|
|
opacity: .75;
|
|
}
|
|
|
|
/* Crowd silhouettes — CSS shapes, no external assets */
|
|
.venue-mood-crowd {
|
|
bottom: 0;
|
|
top: auto;
|
|
height: 72%;
|
|
background:
|
|
radial-gradient(ellipse 8% 42% at 6% 100%, rgba(48, 72, 118, .96) 0%, rgba(36, 56, 92, .96) 58%, transparent 59%),
|
|
radial-gradient(ellipse 7% 38% at 14% 100%, rgba(52, 78, 124, .94) 0%, rgba(40, 62, 98, .94) 56%, transparent 57%),
|
|
radial-gradient(ellipse 9% 44% at 24% 100%, rgba(44, 68, 110, .95) 0%, rgba(34, 54, 88, .95) 58%, transparent 59%),
|
|
radial-gradient(ellipse 8% 40% at 34% 100%, rgba(50, 74, 120, .94) 0%, rgba(38, 58, 94, .94) 57%, transparent 58%),
|
|
radial-gradient(ellipse 10% 46% at 46% 100%, rgba(42, 66, 108, .96) 0%, rgba(32, 52, 86, .96) 60%, transparent 61%),
|
|
radial-gradient(ellipse 9% 42% at 58% 100%, rgba(48, 72, 116, .95) 0%, rgba(36, 56, 90, .95) 58%, transparent 59%),
|
|
radial-gradient(ellipse 8% 40% at 68% 100%, rgba(52, 78, 122, .93) 0%, rgba(40, 62, 96, .93) 56%, transparent 57%),
|
|
radial-gradient(ellipse 9% 44% at 78% 100%, rgba(44, 68, 112, .95) 0%, rgba(34, 54, 88, .95) 58%, transparent 59%),
|
|
radial-gradient(ellipse 8% 38% at 88% 100%, rgba(50, 74, 118, .94) 0%, rgba(38, 58, 92, .94) 56%, transparent 57%),
|
|
radial-gradient(ellipse 7% 36% at 96% 100%, rgba(46, 70, 114, .93) 0%, rgba(36, 56, 90, .93) 55%, transparent 56%);
|
|
opacity: .55;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.venue-mood-haze {
|
|
background: linear-gradient(180deg, transparent 0%, rgba(15, 23, 42, .12) 40%, rgba(30, 41, 59, .28) 100%);
|
|
opacity: 0;
|
|
transition: opacity .6s ease;
|
|
}
|
|
|
|
/* Player shell atmosphere when venue enabled */
|
|
#player.venue-mood-subtle::before,
|
|
#player.venue-mood-full::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
background: radial-gradient(ellipse 120% 80% at 50% 100%, rgba(30, 64, 110, .12) 0%, transparent 55%);
|
|
transition: background .6s ease, opacity .6s ease;
|
|
}
|
|
#player.venue-mood-full::before {
|
|
background:
|
|
radial-gradient(ellipse 90% 55% at 50% 0%, rgba(34, 211, 238, .06) 0%, transparent 60%),
|
|
radial-gradient(ellipse 120% 80% at 50% 100%, rgba(30, 64, 110, .18) 0%, transparent 55%);
|
|
}
|
|
|
|
/* Subtle vs full intensity */
|
|
#player.venue-mood-subtle .venue-mood-lights { opacity: .65; }
|
|
#player.venue-mood-subtle .venue-mood-crowd { opacity: .48; }
|
|
#player.venue-mood-full .venue-mood-lights {
|
|
background:
|
|
radial-gradient(ellipse 52% 95% at 8% 100%, rgba(251, 191, 36, .42) 0%, transparent 58%),
|
|
radial-gradient(ellipse 52% 95% at 92% 100%, rgba(56, 189, 248, .38) 0%, transparent 58%),
|
|
radial-gradient(ellipse 95% 130% at 50% 115%, rgba(59, 130, 246, .48) 0%, transparent 72%),
|
|
linear-gradient(180deg, transparent 0%, rgba(15, 23, 42, .45) 42%, rgba(8, 12, 24, .94) 100%);
|
|
opacity: .92;
|
|
}
|
|
#player.venue-mood-full .venue-mood-crowd { opacity: .78; }
|
|
|
|
/* Full idle baseline — visible before any notes are judged */
|
|
#player.venue-mood-full .venue-mood-fx .venue-mood-lights { opacity: .88; }
|
|
#player.venue-mood-full .venue-mood-fx .venue-mood-crowd { opacity: .72; }
|
|
#player.venue-mood-full .venue-mood-fx .venue-mood-haze {
|
|
opacity: .28;
|
|
background: linear-gradient(180deg, transparent 0%, rgba(56, 189, 248, .08) 35%, rgba(30, 64, 110, .22) 100%);
|
|
}
|
|
|
|
/* Performance state visuals */
|
|
.venue-mood-fx.venue-mood-state-idle .venue-mood-lights { opacity: .45; }
|
|
.venue-mood-fx.venue-mood-state-idle .venue-mood-crowd { opacity: .32; }
|
|
#player.venue-mood-full .venue-mood-fx.venue-mood-state-idle .venue-mood-lights { opacity: .88; }
|
|
#player.venue-mood-full .venue-mood-fx.venue-mood-state-idle .venue-mood-crowd { opacity: .72; }
|
|
|
|
.venue-mood-fx.venue-mood-state-steady .venue-mood-lights {
|
|
background:
|
|
radial-gradient(ellipse 85% 110% at 50% 108%, rgba(51, 85, 120, .5) 0%, transparent 66%),
|
|
linear-gradient(180deg, transparent 0%, rgba(10, 16, 28, .4) 60%, rgba(4, 6, 12, .85) 100%);
|
|
}
|
|
|
|
.venue-mood-fx.venue-mood-state-strong .venue-mood-lights {
|
|
background:
|
|
radial-gradient(ellipse 70% 95% at 50% 105%, rgba(34, 211, 238, .28) 0%, transparent 62%),
|
|
radial-gradient(ellipse 90% 115% at 50% 110%, rgba(51, 85, 120, .58) 0%, transparent 68%),
|
|
linear-gradient(180deg, transparent 0%, rgba(8, 14, 26, .35) 55%, rgba(4, 6, 12, .88) 100%);
|
|
}
|
|
.venue-mood-fx.venue-mood-state-strong .venue-mood-crowd { opacity: .62; }
|
|
|
|
.venue-mood-fx.venue-mood-state-fire .venue-mood-lights {
|
|
background:
|
|
radial-gradient(ellipse 55% 80% at 50% 95%, rgba(251, 146, 60, .42) 0%, transparent 58%),
|
|
radial-gradient(ellipse 85% 110% at 50% 108%, rgba(180, 70, 20, .35) 0%, transparent 65%),
|
|
linear-gradient(180deg, transparent 0%, rgba(20, 10, 6, .3) 50%, rgba(4, 6, 12, .9) 100%);
|
|
}
|
|
.venue-mood-fx.venue-mood-state-fire .venue-mood-crowd { opacity: .78; }
|
|
|
|
.venue-mood-fx.venue-mood-state-recovery .venue-mood-lights {
|
|
background:
|
|
radial-gradient(ellipse 90% 105% at 50% 110%, rgba(120, 90, 30, .32) 0%, transparent 66%),
|
|
linear-gradient(180deg, transparent 0%, rgba(18, 14, 8, .42) 58%, rgba(4, 6, 12, .86) 100%);
|
|
}
|
|
.venue-mood-fx.venue-mood-state-recovery .venue-mood-crowd { opacity: .38; }
|
|
|
|
.venue-mood-fx.venue-mood-state-smoke .venue-mood-lights { opacity: .35; }
|
|
.venue-mood-fx.venue-mood-state-smoke .venue-mood-crowd { opacity: .22; transform: translateY(6%); }
|
|
.venue-mood-fx.venue-mood-state-smoke .venue-mood-haze {
|
|
opacity: .72;
|
|
background:
|
|
linear-gradient(180deg, transparent 0%, rgba(71, 85, 105, .22) 35%, rgba(51, 65, 85, .45) 100%),
|
|
radial-gradient(ellipse 100% 80% at 50% 100%, rgba(100, 116, 139, .25) 0%, transparent 70%);
|
|
}
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
.venue-mood-fx.venue-mood-state-steady .venue-mood-crowd,
|
|
#player.venue-mood-full .venue-mood-fx.venue-mood-state-steady .venue-mood-crowd {
|
|
animation: v3-venue-crowd-sway 4.8s ease-in-out infinite;
|
|
}
|
|
.venue-mood-fx.venue-mood-state-strong .venue-mood-crowd,
|
|
#player.venue-mood-full .venue-mood-fx.venue-mood-state-strong .venue-mood-crowd {
|
|
animation: v3-venue-crowd-sway 3.6s ease-in-out infinite;
|
|
}
|
|
.venue-mood-fx.venue-mood-state-fire .venue-mood-crowd,
|
|
#player.venue-mood-full .venue-mood-fx.venue-mood-state-fire .venue-mood-crowd {
|
|
animation: v3-venue-crowd-bounce 2.2s ease-in-out infinite;
|
|
}
|
|
.venue-mood-fx.venue-mood-state-fire .venue-mood-lights {
|
|
animation: v3-venue-fire-glow 2.8s ease-in-out infinite;
|
|
}
|
|
.venue-mood-fx.venue-mood-state-smoke .venue-mood-haze {
|
|
animation: v3-venue-smoke-drift 5.5s ease-in-out infinite;
|
|
}
|
|
}
|
|
|
|
@keyframes v3-venue-crowd-sway {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-2px); }
|
|
}
|
|
@keyframes v3-venue-crowd-bounce {
|
|
0%, 100% { transform: translateY(0) scaleY(1); }
|
|
35% { transform: translateY(-4px) scaleY(1.02); }
|
|
65% { transform: translateY(-1px) scaleY(1); }
|
|
}
|
|
@keyframes v3-venue-fire-glow {
|
|
0%, 100% { filter: brightness(1); }
|
|
50% { filter: brightness(1.12); }
|
|
}
|
|
@keyframes v3-venue-smoke-drift {
|
|
0%, 100% { opacity: .65; transform: translateY(0); }
|
|
50% { opacity: .78; transform: translateY(-3px); }
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.venue-mood-fx .venue-mood-crowd,
|
|
.venue-mood-fx .venue-mood-lights,
|
|
.venue-mood-fx .venue-mood-haze {
|
|
animation: none !important;
|
|
}
|
|
}
|
|
|
|
/* — Up Next pill (top-right, persistent) — */
|
|
#player-hud .v3-upnext {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: .5rem;
|
|
padding: .45rem .9rem;
|
|
border-radius: .75rem;
|
|
background: rgba(15, 23, 42, .7);
|
|
border: 1px solid rgba(51, 65, 85, .5);
|
|
backdrop-filter: blur(6px);
|
|
font-size: .85rem;
|
|
pointer-events: auto;
|
|
}
|
|
#player-hud .v3-upnext.hidden { display: none; }
|
|
|
|
/* — Live performance HUD (top-right, read-only) — */
|
|
.v3-live-performance-hud {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: .2rem;
|
|
min-width: 9.5rem;
|
|
padding: .55rem .85rem;
|
|
border-radius: .85rem;
|
|
background: rgba(15, 23, 42, .78);
|
|
border: 1px solid rgba(71, 85, 105, .55);
|
|
backdrop-filter: blur(8px);
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
|
|
pointer-events: none;
|
|
transition: border-color .35s ease, box-shadow .35s ease, background .35s ease;
|
|
}
|
|
.v3-live-performance-hud.hidden { display: none; }
|
|
|
|
/* Highway scoreboard preference (static/v3/scoreboard-pref.js).
|
|
One note-detection scoreboard at a time on the highway:
|
|
core (default) → core live-performance HUD; hide the note_detect plugin HUD
|
|
detailed → note_detect .nd-hud; hide the core HUD
|
|
off → hide both
|
|
The default rule keys off "not detailed and not off" so it's correct even
|
|
before the pref script sets data-scoreboard (avoids a flash of both). */
|
|
html:not([data-scoreboard="detailed"]):not([data-scoreboard="off"]) .nd-hud { display: none !important; }
|
|
html[data-scoreboard="detailed"] #v3-live-performance-hud { display: none !important; }
|
|
html[data-scoreboard="off"] .nd-hud,
|
|
html[data-scoreboard="off"] #v3-live-performance-hud { display: none !important; }
|
|
|
|
.v3-live-performance-heading {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: .45rem;
|
|
line-height: 1;
|
|
}
|
|
.v3-live-performance-tag {
|
|
font-size: .68rem;
|
|
font-weight: 800;
|
|
letter-spacing: .14em;
|
|
color: #94a3b8;
|
|
}
|
|
.v3-live-performance-percent {
|
|
font-size: 2rem;
|
|
font-weight: 900;
|
|
letter-spacing: -.03em;
|
|
color: #f8fafc;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.v3-live-performance-hits,
|
|
.v3-live-performance-streak {
|
|
font-size: .78rem;
|
|
font-weight: 600;
|
|
color: #cbd5e1;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.v3-live-performance-state {
|
|
font-size: .72rem;
|
|
font-weight: 700;
|
|
color: #e2e8f0;
|
|
min-height: 1rem;
|
|
}
|
|
.v3-live-performance-hud.is-idle .v3-live-performance-percent { color: #94a3b8; }
|
|
.v3-live-performance-hud.is-fire {
|
|
border-color: rgba(251, 146, 60, .75);
|
|
background: linear-gradient(145deg, rgba(67, 20, 7, .82), rgba(15, 23, 42, .88));
|
|
box-shadow: 0 0 22px rgba(251, 146, 60, .28);
|
|
animation: v3-live-fire-pulse 2.4s ease-in-out infinite;
|
|
}
|
|
.v3-live-performance-hud.is-fire .v3-live-performance-percent {
|
|
color: #fdba74;
|
|
text-shadow: 0 0 12px rgba(251, 146, 60, .45);
|
|
}
|
|
.v3-live-performance-hud.is-fire .v3-live-performance-state { color: #fb923c; }
|
|
.v3-live-performance-hud.is-strong {
|
|
border-color: rgba(34, 211, 238, .55);
|
|
box-shadow: 0 0 16px rgba(34, 211, 238, .18);
|
|
}
|
|
.v3-live-performance-hud.is-strong .v3-live-performance-percent { color: #67e8f9; }
|
|
.v3-live-performance-hud.is-steady {
|
|
border-color: rgba(100, 116, 139, .55);
|
|
}
|
|
.v3-live-performance-hud.is-recovery {
|
|
border-color: rgba(250, 204, 21, .45);
|
|
background: rgba(30, 27, 12, .72);
|
|
}
|
|
.v3-live-performance-hud.is-recovery .v3-live-performance-percent { color: #fde68a; }
|
|
.v3-live-performance-hud.is-smoke {
|
|
border-color: rgba(100, 116, 139, .35);
|
|
background: rgba(15, 23, 42, .62);
|
|
opacity: .92;
|
|
animation: v3-live-smoke-haze 3.2s ease-in-out infinite;
|
|
}
|
|
.v3-live-performance-hud.is-smoke .v3-live-performance-percent { color: #94a3b8; }
|
|
.v3-live-performance-hud.is-smoke .v3-live-performance-state { color: #cbd5e1; }
|
|
|
|
@keyframes v3-live-fire-pulse {
|
|
0%, 100% { box-shadow: 0 0 18px rgba(251, 146, 60, .22); }
|
|
50% { box-shadow: 0 0 26px rgba(251, 146, 60, .38); }
|
|
}
|
|
@keyframes v3-live-smoke-haze {
|
|
0%, 100% { transform: translateY(0); opacity: .92; }
|
|
50% { transform: translateY(-1px); opacity: .84; }
|
|
}
|
|
|
|
.v3-upnext-name {
|
|
font-weight: 800;
|
|
background: linear-gradient(90deg, #22d3ee, #a855f7, #f472b6);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
}
|
|
|
|
/* — Left rail hover-zone — */
|
|
.v3-railzone {
|
|
position: absolute;
|
|
left: 0; top: 0; bottom: 0;
|
|
z-index: 30;
|
|
display: flex;
|
|
align-items: center;
|
|
pointer-events: none; /* only the catcher + rail take pointer events */
|
|
}
|
|
.v3-railzone::before { /* invisible left-edge strip that reveals the rail */
|
|
content: "";
|
|
position: absolute;
|
|
left: 0; top: 0; bottom: 0;
|
|
width: 96px;
|
|
pointer-events: auto;
|
|
}
|
|
/* The Section Map plugin pins a ~20px clickable bar to the very top of #player
|
|
(#section-map, z-index:5). The rail catcher above is full-height at z-index:30,
|
|
so its top-left corner swallows clicks on the section map's first section. When
|
|
the bar is present, drop the catcher below it so the top strip stays clickable;
|
|
the rail still reveals from anywhere below the bar. Mirrors the core
|
|
`#section-map ~ #player-hud` special-case in static/style.css. */
|
|
#section-map ~ #v3-railzone::before { top: 20px; }
|
|
|
|
.v3-rail {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: .25rem;
|
|
padding: 1.5rem .75rem;
|
|
pointer-events: auto;
|
|
opacity: 0;
|
|
transform: translateX(-12px);
|
|
transition: opacity .25s ease, transform .25s ease;
|
|
}
|
|
.v3-railzone:hover .v3-rail,
|
|
.v3-railzone:focus-within .v3-rail,
|
|
#player.pop-open .v3-rail { opacity: 1; transform: none; }
|
|
|
|
.v3-rail-icon {
|
|
position: relative;
|
|
width: 70px; height: 70px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: transparent;
|
|
border: 0;
|
|
cursor: pointer;
|
|
color: #8ba3ac;
|
|
transition: transform .18s ease, color .18s ease;
|
|
}
|
|
.v3-rail-border {
|
|
position: absolute;
|
|
inset: 0;
|
|
border: 2px solid rgba(139, 163, 172, .45);
|
|
border-radius: 50%;
|
|
transition: border-color .18s ease;
|
|
}
|
|
.v3-rail-border-gear {
|
|
border-color: rgba(139, 163, 172, .45);
|
|
}
|
|
.v3-rail-svg { width: 32px; height: 32px; fill: currentcolor; z-index: 1; }
|
|
.v3-rail-dot { width: 6px; height: 6px; background: #4a5d6e; border-radius: 2px; margin: 1rem 0; }
|
|
|
|
/* pop-out + active state */
|
|
.v3-rail-icon:hover,
|
|
.v3-rail-icon:focus-visible { color: #e8eef1; }
|
|
.v3-rail-icon:hover .v3-rail-border,
|
|
.v3-rail-icon:focus-visible .v3-rail-border { border-color: rgba(203, 213, 225, .85); }
|
|
.v3-rail-icon.is-active { color: #e2e8f0; }
|
|
.v3-rail-icon.is-active .v3-rail-border { border-color: #22d3ee; }
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
.v3-rail-icon:hover,
|
|
.v3-rail-icon:focus-visible { transform: translateX(6px) scale(1.12); }
|
|
}
|
|
|
|
/* — Section Practice circular rail icon (v3 chrome) — */
|
|
#v3-player-rail .section-practice-control--v3 {
|
|
padding: 0;
|
|
margin: 0;
|
|
width: auto;
|
|
display: flex;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill {
|
|
position: relative;
|
|
width: 70px;
|
|
height: 70px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
gap: 0;
|
|
min-width: 0;
|
|
color: #8ba3ac;
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
cursor: pointer;
|
|
transition: transform .18s ease, color .18s ease;
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill-text,
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill-caret {
|
|
display: none !important;
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1;
|
|
font-size: 32px;
|
|
line-height: 1;
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill-icon .v3-rail-svg,
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill-svg {
|
|
width: 32px;
|
|
height: 32px;
|
|
fill: currentcolor;
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill:hover,
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill:focus-visible {
|
|
color: #e8eef1;
|
|
background: transparent;
|
|
box-shadow: none;
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill:hover .v3-rail-border,
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill:focus-visible .v3-rail-border {
|
|
border-color: rgba(203, 213, 225, .85);
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill[aria-expanded="true"],
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--active {
|
|
color: #e2e8f0;
|
|
background: transparent;
|
|
box-shadow: none;
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill[aria-expanded="true"] .v3-rail-border,
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--active .v3-rail-border {
|
|
border-color: #22d3ee;
|
|
}
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill:hover,
|
|
#v3-player-rail .section-practice-control--v3 .section-practice-pill:focus-visible {
|
|
transform: translateX(6px) scale(1.12);
|
|
}
|
|
}
|
|
#v3-player-rail .section-practice-control--v3 #section-practice-bar {
|
|
left: calc(100% + 10px);
|
|
right: auto;
|
|
bottom: auto;
|
|
top: 0;
|
|
min-width: 230px;
|
|
max-width: min(340px, calc(100vw - 120px));
|
|
padding: .9rem;
|
|
border-radius: .9rem;
|
|
border: 1px solid rgba(51, 65, 85, .6);
|
|
background: rgba(15, 23, 42, .96);
|
|
box-shadow: 0 12px 40px rgba(0, 0, 0, .5);
|
|
}
|
|
|
|
/* — Rail feature popovers — */
|
|
.v3-rail-pop {
|
|
position: absolute;
|
|
left: 92px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
min-width: 230px;
|
|
max-width: 340px;
|
|
pointer-events: auto;
|
|
background: rgba(15, 23, 42, .96);
|
|
border: 1px solid rgba(51, 65, 85, .6);
|
|
border-radius: .9rem;
|
|
box-shadow: 0 12px 40px rgba(0, 0, 0, .5);
|
|
padding: .9rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: .6rem;
|
|
z-index: 40;
|
|
}
|
|
.v3-rail-pop.hidden { display: none; }
|
|
.v3-pop-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: .75rem;
|
|
font-size: .8rem;
|
|
color: #cbd5e1;
|
|
}
|
|
.v3-pop-label {
|
|
color: #94a3b8;
|
|
font-size: .72rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: .04em;
|
|
white-space: nowrap;
|
|
}
|
|
.v3-pop-select,
|
|
.v3-pop-btn {
|
|
background: #1e293b;
|
|
border: 1px solid #334155;
|
|
color: #e2e8f0;
|
|
border-radius: .5rem;
|
|
padding: .35rem .6rem;
|
|
font-size: .8rem;
|
|
outline: none;
|
|
cursor: pointer;
|
|
}
|
|
.v3-pop-btn:hover { background: #334155; }
|
|
.v3-pop-pin {
|
|
background: #1e293b;
|
|
border: 1px solid #334155;
|
|
color: #94a3b8;
|
|
border-radius: .5rem;
|
|
width: 2rem; height: 2rem;
|
|
cursor: pointer;
|
|
}
|
|
.v3-pop-val { color: #94a3b8; font-size: .75rem; min-width: 3rem; text-align: right; }
|
|
.v3-pop-close {
|
|
margin-top: .25rem;
|
|
background: #1e293b;
|
|
border: 1px solid #334155;
|
|
color: #94a3b8;
|
|
border-radius: .5rem;
|
|
padding: .4rem;
|
|
font-size: .8rem;
|
|
cursor: pointer;
|
|
}
|
|
.v3-pop-close:hover { background: rgba(127, 29, 29, .5); color: #fca5a5; }
|
|
|
|
/* — Bottom transport (auto-hide) — */
|
|
#player .v3-transport {
|
|
position: absolute;
|
|
left: 0; right: 0; bottom: 0;
|
|
z-index: 20;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 1.25rem;
|
|
padding: 1rem;
|
|
background: transparent;
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
transition: opacity .25s ease, transform .25s ease;
|
|
}
|
|
#player.chrome-active .v3-transport,
|
|
#player.pop-open .v3-transport,
|
|
#player .v3-transport:hover { opacity: 1; transform: none; }
|
|
#player.chrome-idle { cursor: none; }
|
|
|
|
.v3-transport-mid { display: flex; align-items: center; gap: .75rem; }
|
|
.v3-seek {
|
|
width: 2.5rem; height: 2.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(30, 41, 59, .7);
|
|
border: 1px solid rgba(51, 65, 85, .6);
|
|
border-radius: 999px;
|
|
color: #cbd5e1;
|
|
cursor: pointer;
|
|
transition: background .15s;
|
|
}
|
|
.v3-seek:hover { background: rgba(51, 65, 85, .9); }
|
|
.v3-play {
|
|
width: 4rem; height: 2.6rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(180deg, #475569, #334155);
|
|
border: 1px solid rgba(100, 116, 139, .6);
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 14px rgba(0, 0, 0, .4);
|
|
}
|
|
.v3-play:hover { filter: brightness(1.15); }
|
|
|
|
/* speed widget (bars + slider + label + presets) */
|
|
.v3-speed-cluster {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: .5rem;
|
|
flex-shrink: 1;
|
|
min-width: 0;
|
|
max-width: min(52vw, 28rem);
|
|
}
|
|
.v3-speed { display: flex; align-items: center; gap: .5rem; flex-shrink: 0; }
|
|
.v3-speed-presets {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
align-items: center;
|
|
gap: .2rem;
|
|
min-width: 0;
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
}
|
|
.v3-speed-presets::-webkit-scrollbar { display: none; }
|
|
.v3-speed-preset-btn {
|
|
flex: 0 0 auto;
|
|
padding: .15rem .45rem;
|
|
min-width: 1.75rem;
|
|
font-size: .7rem;
|
|
font-weight: 700;
|
|
line-height: 1.25;
|
|
color: #cbd5e1;
|
|
background: rgba(30, 41, 59, .9);
|
|
border: 1px solid rgba(71, 85, 105, .85);
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
|
|
}
|
|
.v3-speed-preset-btn:hover {
|
|
color: #e2e8f0;
|
|
background: rgba(51, 65, 85, .9);
|
|
border-color: rgba(100, 116, 139, .8);
|
|
}
|
|
.v3-speed-preset-btn[data-sweet-spot="1"]:not(.v3-speed-preset-active) {
|
|
border-color: rgba(34, 211, 238, .35);
|
|
}
|
|
.v3-speed-preset-active {
|
|
color: #0f172a;
|
|
background: #22d3ee;
|
|
border-color: #22d3ee;
|
|
}
|
|
.v3-speed-preset-btn[data-sweet-spot="1"].v3-speed-preset-active {
|
|
box-shadow: 0 0 8px rgba(34, 211, 238, .45);
|
|
}
|
|
.v3-speed-bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 22px; }
|
|
.v3-speed-bars i {
|
|
width: 3px;
|
|
background: #475569;
|
|
border-radius: 1px;
|
|
transition: background .15s, height .15s;
|
|
}
|
|
.v3-speed-bars i:nth-child(1) { height: 5px; }
|
|
.v3-speed-bars i:nth-child(2) { height: 7px; }
|
|
.v3-speed-bars i:nth-child(3) { height: 9px; }
|
|
.v3-speed-bars i:nth-child(4) { height: 11px; }
|
|
.v3-speed-bars i:nth-child(5) { height: 13px; }
|
|
.v3-speed-bars i:nth-child(6) { height: 16px; }
|
|
.v3-speed-bars i:nth-child(7) { height: 19px; }
|
|
.v3-speed-bars i:nth-child(8) { height: 22px; }
|
|
.v3-speed-bars i.on { background: #22d3ee; }
|
|
.v3-speed-slider { width: 90px; }
|
|
.v3-speed-label { color: #94a3b8; font-size: .8rem; min-width: 2.4rem; }
|
|
@media (max-width: 720px) {
|
|
.v3-speed-cluster {
|
|
flex-wrap: wrap;
|
|
max-width: min(62vw, 18rem);
|
|
}
|
|
.v3-speed-presets { flex-wrap: wrap; overflow-x: visible; }
|
|
}
|
|
|
|
/* rainbow chevrons (speed-level indicator) */
|
|
.v3-chevrons { display: inline-flex; align-items: center; gap: 1px; flex-shrink: 0; }
|
|
.v3-chevrons i {
|
|
width: 10px; height: 14px;
|
|
background: #475569;
|
|
opacity: .35;
|
|
transition: opacity .15s;
|
|
clip-path: polygon(0 0, 55% 50%, 0 100%, 45% 100%, 100% 50%, 45% 0);
|
|
}
|
|
.v3-chevrons i.on { opacity: 1; }
|
|
.v3-chevrons i:nth-child(1).on { background: #f87171; }
|
|
.v3-chevrons i:nth-child(2).on { background: #fb923c; }
|
|
.v3-chevrons i:nth-child(3).on { background: #facc15; }
|
|
.v3-chevrons i:nth-child(4).on { background: #4ade80; }
|
|
.v3-chevrons i:nth-child(5).on { background: #22d3ee; }
|
|
|
|
/* — Plugin-control slot (host re-homes plugin controls here from the transport) — */
|
|
.v3-plugin-slot {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: .4rem;
|
|
}
|
|
.v3-plugin-slot > * { margin: 0 !important; } /* neutralize legacy ml-1/ml-auto spacing */
|
|
.v3-pop-empty { color: #64748b; font-size: .75rem; font-style: italic; }
|
|
/* small count badge on the Plugins rail icon */
|
|
.v3-rail-badge {
|
|
position: absolute;
|
|
top: 6px; right: 6px;
|
|
min-width: 16px; height: 16px;
|
|
padding: 0 4px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: #22d3ee; color: #0f172a;
|
|
font-size: 10px; font-weight: 800; line-height: 1;
|
|
border-radius: 999px; z-index: 2;
|
|
}
|
|
.v3-rail-badge[hidden] { display: none; }
|
|
|
|
/* ── v3 UI font: Rubik ──────────────────────────────────────────────────────
|
|
`font-display` is a SHARED Tailwind token (Inter, used by the v2 body too), so
|
|
it can't be repointed in tailwind.config without changing v2. v3.css is loaded
|
|
only by the v3 page, so scope Rubik here. `body.font-display` (element+class)
|
|
outranks Tailwind's `.font-display` (class) by specificity. The Rubik webfont
|
|
is loaded via a <link> in static/v3/index.html. */
|
|
body.font-display { font-family: Rubik, system-ui, sans-serif; }
|
|
|
|
/* ── v3 Plugins page — Pedalboard ────────────────────────────────────────────
|
|
Plugins grouped onto category "pedalboards"; each plugin is a draggable
|
|
stompbox pedal with decorative patch cables (static/v3/pedal-cables.js)
|
|
sagging between them. Custom CSS (not Tailwind utilities) so the prebuilt
|
|
stylesheet needn't be regenerated. Palette mirrors the fb-* tokens. */
|
|
/* The title sits on the board's top-right as a browser-style tab; it also
|
|
doubles as the collapse toggle. The section reserves a row of height for it. */
|
|
.v3-board-section { position: relative; padding-top: 30px; margin-bottom: 2rem; }
|
|
.v3-board-title {
|
|
position: absolute; top: 0; right: 22px; z-index: 3;
|
|
display: flex; align-items: center; gap: .4rem;
|
|
height: 31px; box-sizing: border-box; padding: 0 .75rem;
|
|
cursor: pointer; text-align: left; font-family: inherit;
|
|
font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
|
|
color: #cbd5e1;
|
|
/* tab body: matches the board's top gradient stop, rounded only on top, and
|
|
its bottom overlaps the board's top edge so they read as one piece. */
|
|
background: #1c2740;
|
|
border: 1px solid rgba(51, 65, 85, .55);
|
|
border-bottom: none;
|
|
border-radius: .55rem .55rem 0 0;
|
|
}
|
|
.v3-board-title:hover { color: #fff; background: #243150; }
|
|
.v3-board-title:focus-visible { outline: 2px solid #0ea5e9; outline-offset: 1px; }
|
|
/* When collapsed there's no board below, so close the tab into a full pill. */
|
|
.v3-board-section.collapsed .v3-board-title {
|
|
border-bottom: 1px solid rgba(51, 65, 85, .55);
|
|
border-radius: .55rem;
|
|
}
|
|
.v3-board-chevron {
|
|
width: 14px; height: 14px; flex: none; color: #64748b;
|
|
transition: transform .15s ease;
|
|
}
|
|
.v3-board-title:hover .v3-board-chevron { color: #94a3b8; }
|
|
.v3-board-section.collapsed .v3-board-chevron { transform: rotate(-90deg); }
|
|
.v3-board-section.collapsed .v3-pedalboard { display: none; }
|
|
.v3-board-count {
|
|
font-size: .65rem; font-weight: 700; color: #cbd5e1;
|
|
background: rgba(51, 65, 85, .5); border-radius: 999px; padding: .05rem .45rem;
|
|
}
|
|
|
|
/* The board surface: a dark, subtly textured panel the pedals sit on. */
|
|
.v3-pedalboard {
|
|
position: relative;
|
|
border-radius: 1rem;
|
|
border: 1px solid rgba(51, 65, 85, .5); /* fb-border */
|
|
background:
|
|
repeating-linear-gradient(45deg, rgba(255, 255, 255, .012) 0 2px, transparent 2px 9px),
|
|
radial-gradient(120% 140% at 50% 0%, #1c2740 0%, #131b2e 60%, #0d1422 100%);
|
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .03), inset 0 -18px 40px rgba(0, 0, 0, .35);
|
|
min-height: 220px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Decorative patch-cable overlay — never blocks pedal clicks. */
|
|
.v3-cable-layer {
|
|
position: absolute; inset: 0;
|
|
width: 100%; height: 100%;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
.v3-cable {
|
|
fill: none;
|
|
stroke: #e2683a; /* warm 1/4" cable */
|
|
stroke-width: 4;
|
|
stroke-linecap: round;
|
|
opacity: .85;
|
|
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .45));
|
|
}
|
|
|
|
/* Stompbox pedal — a transparent pedal photo "skin" (static/v3/pedals/*) set as
|
|
the card background; the plugin's name + description overlay in a label panel.
|
|
The skin is random-per-plugin and persisted (see plugins-page.js). The card
|
|
dimensions must match PEDAL_W/PEDAL_H in plugins-page.js. */
|
|
.v3-pedal {
|
|
position: absolute;
|
|
/* width/height are set responsively per board in plugins-page.js so a fixed
|
|
number of pedals fit per row. */
|
|
box-sizing: border-box;
|
|
color: #f8fafc;
|
|
cursor: grab;
|
|
user-select: none;
|
|
touch-action: none; /* let pointer-drag own the gesture */
|
|
z-index: 2;
|
|
transition: filter .12s ease;
|
|
}
|
|
/* The skin photo lives on ::before so the disabled-dim filter only touches it,
|
|
not the glow ring / label / badges (which must stay full-colour). */
|
|
.v3-pedal::before {
|
|
content: ''; position: absolute; inset: 0; z-index: 0;
|
|
background-image: var(--skin);
|
|
background-size: 100% 100%; background-position: center; background-repeat: no-repeat;
|
|
}
|
|
.v3-pedal:hover { filter: brightness(1.06); }
|
|
.v3-pedal:focus-visible { outline: 2px solid #0ea5e9; outline-offset: 3px; border-radius: .8rem; }
|
|
.v3-pedal-dragging { cursor: grabbing; z-index: 5; }
|
|
|
|
/* Footswitch status glow: green when the plugin is active, red when disabled.
|
|
Full-pedal overlay (same crop+pad as the skins) so the ring sits on the
|
|
footswitch and scales with the pedal. */
|
|
.v3-pedal-glow {
|
|
position: absolute; inset: 0; z-index: 1; pointer-events: none;
|
|
background-image: url('/static/v3/overlay-active.png');
|
|
background-size: 100% 100%; background-position: center; background-repeat: no-repeat;
|
|
}
|
|
.v3-pedal-off .v3-pedal-glow { background-image: url('/static/v3/overlay-inactive.png'); }
|
|
|
|
/* Footswitch click hotspot over the photo's stomp button (toggles on/off). */
|
|
.v3-pedal-foot {
|
|
position: absolute; left: 50%; bottom: 16%; transform: translate(-50%, -2px);
|
|
width: 16%; aspect-ratio: 1 / 1; border-radius: 50%; /* scales with pedal */
|
|
background: transparent; border: 0; padding: 0; cursor: pointer; z-index: 5;
|
|
}
|
|
.v3-pedal-foot:focus-visible { outline: 2px solid #0ea5e9; outline-offset: 2px; border-radius: 50%; }
|
|
|
|
/* Disabled plugin: dim + desaturate only the SKIN (so the red glow stays vivid),
|
|
and show an "off" badge. */
|
|
.v3-pedal-off::before { filter: grayscale(.85) brightness(.5); }
|
|
.v3-pedal-offbadge {
|
|
position: absolute; top: 5px; left: 5px; z-index: 6; display: none;
|
|
font-size: .55rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
|
|
color: #fecaca; background: rgba(60, 10, 10, .8);
|
|
border: 1px solid rgba(239, 68, 68, .6); border-radius: .3rem; padding: .05rem .35rem;
|
|
}
|
|
.v3-pedal-off .v3-pedal-offbadge { display: block; }
|
|
|
|
/* The drawn cable plug picks up a soft drop shadow so it lifts off the cable. */
|
|
.v3-cable-plug { filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, .55)); }
|
|
|
|
/* Overlay label on the pedal face — over the artwork band between the photo's
|
|
knobs (top) and footswitch (bottom). Translucent so the skin still reads. */
|
|
.v3-pedal-label {
|
|
position: absolute; left: 9%; right: 9%; top: 31%; bottom: calc(28% + 4px); z-index: 2;
|
|
transform: translateY(-10px); /* nudge up */
|
|
display: flex; flex-direction: column; align-items: center; gap: .3rem;
|
|
padding: .55rem .7rem;
|
|
background: rgba(7, 10, 16, .74);
|
|
border: 1px solid rgba(255, 255, 255, .1);
|
|
border-radius: .5rem;
|
|
backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
|
|
box-shadow: 0 4px 14px rgba(0, 0, 0, .5);
|
|
overflow: hidden; text-align: center;
|
|
}
|
|
/* Thumbnail fills the panel (description is now a hover tooltip, not on the face). */
|
|
.v3-pedal-thumb {
|
|
flex: 1 1 auto; min-height: 0;
|
|
width: 100%; height: auto; object-fit: contain; border-radius: .35rem;
|
|
display: block; -webkit-user-drag: none;
|
|
}
|
|
.v3-pedal-name {
|
|
flex: none;
|
|
font-size: .86rem; font-weight: 700; line-height: 1.15;
|
|
max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.v3-pedal-err { font-size: .6rem; color: #fca5a5; overflow-wrap: break-word; }
|
|
.v3-pedal-pill {
|
|
display: inline-block;
|
|
font-size: .58rem; font-weight: 700; padding: .08rem .4rem; border-radius: 999px;
|
|
}
|
|
.v3-pill-bad { background: rgba(239, 68, 68, .25); color: #fca5a5; }
|
|
.v3-pill-wait { background: rgba(234, 179, 8, .25); color: #fde68a; }
|
|
.v3-pedal-bundled {
|
|
position: absolute; top: 4px; right: 4px; z-index: 4;
|
|
font-size: .52rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
|
|
color: #ddd6fe; background: rgba(76, 29, 149, .6);
|
|
border: 1px solid rgba(139, 92, 246, .4); border-radius: .3rem; padding: .04rem .28rem;
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════════════════════════════
|
|
Tabbed settings page (v3) — feat/v3-settings-tabbed
|
|
Card-row layout: leading icon + title/description + right-aligned control,
|
|
grouped under a horizontal tab bar. Pure CSS (no Tailwind rebuild); colors
|
|
mirror the fb-* design tokens so the page matches the rest of the v3 shell.
|
|
════════════════════════════════════════════════════════════════════════ */
|
|
#settings .fb-settings { max-width: 56rem; margin: 0 auto; padding: 6rem 1.5rem 4rem; }
|
|
.fb-settings-back {
|
|
display: inline-flex; align-items: center; gap: .25rem;
|
|
font-size: .8rem; color: #94a3b8; background: none; border: none; cursor: pointer;
|
|
padding: 0; margin-bottom: 1rem; transition: color .15s;
|
|
}
|
|
.fb-settings-back:hover { color: #f8fafc; }
|
|
.fb-settings-back svg { width: 1rem; height: 1rem; }
|
|
.fb-settings-title { font-size: 1.875rem; font-weight: 800; color: #f8fafc; }
|
|
|
|
/* Tab bar */
|
|
.fb-tabbar {
|
|
display: flex; flex-wrap: wrap; gap: .25rem;
|
|
border-bottom: 1px solid rgba(51, 65, 85, .6);
|
|
margin: 1.25rem 0 1.5rem;
|
|
}
|
|
.fb-tab {
|
|
appearance: none; background: none; border: none; cursor: pointer;
|
|
padding: .55rem .85rem; font-size: .85rem; font-weight: 600;
|
|
color: #94a3b8; border-bottom: 2px solid transparent;
|
|
margin-bottom: -1px; transition: color .15s, border-color .15s; white-space: nowrap;
|
|
}
|
|
.fb-tab:hover { color: #e2e8f0; }
|
|
.fb-tab.active { color: #f8fafc; border-bottom-color: #0ea5e9; }
|
|
|
|
/* Panels */
|
|
.fb-tabpanel { display: none; }
|
|
.fb-tabpanel.active { display: block; }
|
|
.fb-tabpanel-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
|
|
.fb-tabpanel-head h3 { font-size: 1.1rem; font-weight: 700; color: #f8fafc; }
|
|
|
|
/* Card rows */
|
|
.fb-srows { display: flex; flex-direction: column; gap: .6rem; }
|
|
.fb-srow {
|
|
display: flex; align-items: center; gap: 1rem;
|
|
background: #1e293b; border: 1px solid rgba(51, 65, 85, .55);
|
|
border-radius: .75rem; padding: .85rem 1rem;
|
|
}
|
|
.fb-srow-stack { flex-direction: column; align-items: stretch; gap: .65rem; }
|
|
.fb-srow-icon {
|
|
flex: none; width: 2.25rem; height: 2.25rem; border-radius: .6rem;
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: rgba(14, 165, 233, .12); color: #38bdf8;
|
|
}
|
|
.fb-srow-icon svg { width: 1.15rem; height: 1.15rem; }
|
|
.fb-srow-main { flex: 1 1 auto; min-width: 0; }
|
|
.fb-srow-title { font-size: .9rem; font-weight: 600; color: #e2e8f0; display: flex; align-items: center; }
|
|
.fb-srow-desc { font-size: .75rem; color: #94a3b8; margin-top: .15rem; }
|
|
.fb-srow-control { flex: none; display: flex; align-items: center; gap: .5rem; }
|
|
.fb-srow-control select,
|
|
.fb-srow-control input[type="text"] { min-width: 11rem; }
|
|
.fb-srow-stack .fb-srow-control { width: 100%; }
|
|
.fb-srow-stack .fb-srow-control input[type="text"] { flex: 1 1 auto; min-width: 0; }
|
|
.fb-srow-wide { width: 100%; }
|
|
|
|
/* Toggle switch */
|
|
.fb-switch { position: relative; display: inline-block; width: 2.6rem; height: 1.5rem; flex: none; }
|
|
.fb-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
|
|
.fb-switch .fb-switch-track {
|
|
position: absolute; inset: 0; cursor: pointer;
|
|
background: #334155; border-radius: 999px; transition: background .15s;
|
|
}
|
|
.fb-switch .fb-switch-track::before {
|
|
content: ""; position: absolute; height: 1.1rem; width: 1.1rem; left: .2rem; top: .2rem;
|
|
background: #f8fafc; border-radius: 50%; transition: transform .15s;
|
|
}
|
|
.fb-switch input:checked + .fb-switch-track { background: #0ea5e9; }
|
|
.fb-switch input:checked + .fb-switch-track::before { transform: translateX(1.1rem); }
|
|
.fb-switch input:focus-visible + .fb-switch-track { box-shadow: 0 0 0 2px rgba(56, 189, 248, .5); }
|
|
|
|
/* "Not yet active" stub badge */
|
|
.fb-stub-note {
|
|
font-size: .6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
|
|
color: #fde68a; background: rgba(234, 179, 8, .12);
|
|
border-radius: 999px; padding: .1rem .45rem; margin-left: .5rem; white-space: nowrap;
|
|
}
|
|
|
|
/* Per-category reset */
|
|
.fb-reset-btn {
|
|
display: inline-flex; align-items: center; gap: .35rem;
|
|
font-size: .8rem; color: #94a3b8; background: none; border: none; cursor: pointer;
|
|
padding: .35rem .5rem; border-radius: .5rem; transition: color .15s, background .15s;
|
|
}
|
|
.fb-reset-btn:hover { color: #fca5a5; background: rgba(239, 68, 68, .08); }
|
|
.fb-reset-btn svg { width: .9rem; height: .9rem; }
|
|
|
|
/* Keybinds reference */
|
|
.fb-kbd {
|
|
display: inline-block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
font-size: .72rem; color: #e2e8f0; background: #0b1220;
|
|
border: 1px solid rgba(51, 65, 85, .8); border-bottom-width: 2px;
|
|
border-radius: .35rem; padding: .1rem .4rem; min-width: 1.4rem; text-align: center;
|
|
}
|
|
.fb-kbd-group-title {
|
|
font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
|
|
color: #94a3b8; margin: 1.25rem 0 .5rem;
|
|
}
|
|
.fb-settings-note { font-size: .75rem; color: #64748b; margin-top: 1rem; }
|
|
.fb-tabpanel-empty { font-size: .8rem; color: #64748b; padding: .5rem 0; }
|
|
|
|
/* ── Immersive (full-screen) plugin screens ──────────────────────────────────
|
|
A plugin that declares `"fullscreen": true` in its manifest gets the whole
|
|
content area when its screen is active. shell.js toggles `html.fb-immersive`
|
|
on navigation (see syncActive). DAW-style plugin UIs (e.g. a practice studio)
|
|
need the viewport, not a scrolling content page below the topbar — the cause
|
|
of the "cut off at the bottom / too much top padding" reports on the embedded
|
|
layout. Mirrors the proven chrome-hide pattern in static/v3/index.html
|
|
(`html.ss-follower-pre …`), but keeps the sidebar as a functional icon rail
|
|
so the user is never trapped (Escape is bound only on player/settings
|
|
scopes, not plugin screens). */
|
|
html.fb-immersive #v3-topbar { display: none !important; }
|
|
|
|
/* Sidebar → icon rail: narrow it, drop the wordmark + group headers + labels,
|
|
center the remaining icons. The .v3-nav-label / .v3-nav-group hooks are added
|
|
by shell.js so these rules don't depend on Tailwind utility class strings. */
|
|
html.fb-immersive #v3-sidebar { width: 4.5rem; }
|
|
html.fb-immersive #v3-brand { display: none; }
|
|
html.fb-immersive #v3-nav { padding-left: .5rem; padding-right: .5rem; }
|
|
html.fb-immersive #v3-nav .v3-nav-group,
|
|
html.fb-immersive #v3-nav .v3-nav-label { display: none; }
|
|
html.fb-immersive #v3-nav a {
|
|
justify-content: center;
|
|
gap: 0;
|
|
padding-left: 0;
|
|
padding-right: 0;
|
|
}
|
|
|
|
/* The active plugin screen fills the now-topbar-less content area. #v3-main is
|
|
position:relative, so pinning the screen avoids relying on the auto-height
|
|
block flow that made a 100vh plugin overflow #v3-main and force a scroll. */
|
|
html.fb-immersive #v3-main > .screen.active {
|
|
position: absolute;
|
|
inset: 0;
|
|
overflow: hidden;
|
|
}
|