development #1

Merged
sschuhmann merged 11 commits from development into main 2026-04-10 07:57:43 +00:00
6 changed files with 81 additions and 81 deletions
Showing only changes of commit 312f3dd161 - Show all commits

View File

@@ -63,7 +63,7 @@ function MiniWave({ songId, active }: { songId: string; active: boolean }) {
height: `${h}%`,
borderRadius: 1,
background: active
? `rgba(139,92,246,${0.3 + (h / 100) * 0.5})`
? `rgba(20,184,166,${0.3 + (h / 100) * 0.5})`
: "rgba(255,255,255,0.1)",
transition: "background 0.15s",
}}
@@ -76,7 +76,7 @@ function MiniWave({ songId, active }: { songId: string; active: boolean }) {
// ── Tag badge ─────────────────────────────────────────────────────────────────
const TAG_COLORS: Record<string, { bg: string; color: string }> = {
jam: { bg: "rgba(139,92,246,0.12)", color: "#a78bfa" },
jam: { bg: "rgba(20,184,166,0.12)", color: "#2dd4bf" },
riff: { bg: "rgba(34,211,238,0.1)", color: "#67e8f9" },
idea: { bg: "rgba(52,211,153,0.1)", color: "#6ee7b7" },
};
@@ -117,19 +117,19 @@ function TrackRow({
padding: "9px 20px",
cursor: "pointer",
position: "relative",
background: active ? "rgba(139,92,246,0.06)" : hovered ? "rgba(255,255,255,0.025)" : "transparent",
background: active ? "rgba(20,184,166,0.06)" : hovered ? "rgba(255,255,255,0.025)" : "transparent",
transition: "background 0.12s",
}}
>
{active && (
<div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: 2, background: "linear-gradient(to bottom, #7b5cf6, #22d3ee)" }} />
<div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: 2, background: "linear-gradient(to bottom, #0d9488, #22d3ee)" }} />
)}
<span style={{ fontSize: 10, color: "rgba(232,233,240,0.2)", width: 20, textAlign: "right", flexShrink: 0, fontFamily: "monospace" }}>
{String(index + 1).padStart(2, "0")}
</span>
<span style={{ fontSize: 13, fontWeight: 600, flex: 1, color: active ? "#a78bfa" : "#e8e9f0", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", transition: "color 0.12s" }}>
<span style={{ fontSize: 13, fontWeight: 600, flex: 1, color: active ? "#2dd4bf" : "#e8e9f0", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", transition: "color 0.12s" }}>
{song.title}
</span>
@@ -191,7 +191,7 @@ function SessionGroup({
>
<span style={{
fontSize: 10, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase",
background: "linear-gradient(135deg, #7b5cf6, #22d3ee)",
background: "linear-gradient(135deg, #0d9488, #22d3ee)",
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent",
}}>
{formatSessionDate(session.date)}
@@ -280,7 +280,7 @@ export function LibraryPanel({ bandId, selectedSongId, onSelectSong }: LibraryPa
flex: 1,
display: "flex",
flexDirection: "column",
background: "#10131f",
background: "#0c1612",
height: "100%",
overflow: "hidden",
}}>
@@ -292,8 +292,8 @@ export function LibraryPanel({ bandId, selectedSongId, onSelectSong }: LibraryPa
</h2>
{/* Search */}
<div style={{ display: "flex", alignItems: "center", gap: 8, background: "#151828", border: `1px solid ${border}`, borderRadius: 8, padding: "8px 12px", transition: "border-color 0.15s" }}
onFocusCapture={(e) => (e.currentTarget.style.borderColor = "rgba(139,92,246,0.4)")}
<div style={{ display: "flex", alignItems: "center", gap: 8, background: "#101c18", border: `1px solid ${border}`, borderRadius: 8, padding: "8px 12px", transition: "border-color 0.15s" }}
onFocusCapture={(e) => (e.currentTarget.style.borderColor = "rgba(20,184,166,0.4)")}
onBlurCapture={(e) => (e.currentTarget.style.borderColor = border)}
>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ color: "rgba(232,233,240,0.25)", flexShrink: 0 }}>
@@ -304,7 +304,7 @@ export function LibraryPanel({ bandId, selectedSongId, onSelectSong }: LibraryPa
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search recordings…"
style={{ background: "none", border: "none", outline: "none", fontFamily: "inherit", fontSize: 13, color: "#e8e9f0", flex: 1, caretColor: "#a78bfa" }}
style={{ background: "none", border: "none", outline: "none", fontFamily: "inherit", fontSize: 13, color: "#e8e9f0", flex: 1, caretColor: "#2dd4bf" }}
/>
{search && (
<button onClick={() => setSearch("")} style={{ background: "none", border: "none", cursor: "pointer", color: "rgba(232,233,240,0.3)", padding: 0, display: "flex", lineHeight: 1 }}>
@@ -324,9 +324,9 @@ export function LibraryPanel({ bandId, selectedSongId, onSelectSong }: LibraryPa
onClick={() => setFilterTag(chip.value)}
style={{
fontSize: 11, fontWeight: 600, padding: "4px 12px", borderRadius: 20, cursor: "pointer", whiteSpace: "nowrap",
border: on ? "1px solid rgba(139,92,246,0.4)" : `1px solid ${border}`,
background: on ? "rgba(139,92,246,0.1)" : "transparent",
color: on ? "#a78bfa" : "rgba(232,233,240,0.35)",
border: on ? "1px solid rgba(20,184,166,0.4)" : `1px solid ${border}`,
background: on ? "rgba(20,184,166,0.1)" : "transparent",
color: on ? "#2dd4bf" : "rgba(232,233,240,0.35)",
fontFamily: "inherit",
transition: "all 0.12s",
}}

View File

@@ -62,7 +62,7 @@ const MEMBER_COLORS = [
{ bg: "rgba(91,156,240,0.18)", border: "rgba(91,156,240,0.6)", text: "#7aabf0" },
{ bg: "rgba(200,90,180,0.18)", border: "rgba(200,90,180,0.6)", text: "#d070c0" },
{ bg: "rgba(52,211,153,0.18)", border: "rgba(52,211,153,0.6)", text: "#34d399" },
{ bg: "rgba(139,92,246,0.18)", border: "rgba(139,92,246,0.6)", text: "#a78bfa" },
{ bg: "rgba(20,184,166,0.18)", border: "rgba(20,184,166,0.6)", text: "#2dd4bf" },
{ bg: "rgba(34,211,238,0.18)", border: "rgba(34,211,238,0.6)", text: "#22d3ee" },
];
@@ -124,13 +124,13 @@ function WaveformPins({
onClick={() => { onSeek(c.timestamp!); onScrollToComment(c.id); }}
>
{isHovered && (
<div style={{ position: "absolute", bottom: "calc(100% + 6px)", left: "50%", transform: "translateX(-50%)", background: "#1a1e30", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 8, padding: "8px 10px", width: 180, zIndex: 50, pointerEvents: "none" }}>
<div style={{ position: "absolute", bottom: "calc(100% + 6px)", left: "50%", transform: "translateX(-50%)", background: "#142420", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 8, padding: "8px 10px", width: 180, zIndex: 50, pointerEvents: "none" }}>
<div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
<div style={{ width: 18, height: 18, borderRadius: "50%", background: mc.bg, color: mc.text, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 8, fontWeight: 700 }}>
{getInitials(c.author_name)}
</div>
<span style={{ fontSize: 11, fontWeight: 500, color: "rgba(232,233,240,0.72)" }}>{c.author_name}</span>
<span style={{ fontSize: 10, fontFamily: "monospace", color: "#a78bfa", marginLeft: "auto" }}>{formatTime(c.timestamp!)}</span>
<span style={{ fontSize: 10, fontFamily: "monospace", color: "#2dd4bf", marginLeft: "auto" }}>{formatTime(c.timestamp!)}</span>
</div>
<div style={{ fontSize: 11, color: "rgba(232,233,240,0.42)", lineHeight: 1.4 }}>
{c.body.length > 80 ? c.body.slice(0, 80) + "…" : c.body}
@@ -271,7 +271,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
// ── Render ────────────────────────────────────────────────────────────────
return (
<div style={{ display: "flex", flexDirection: "column", flex: 1, height: "100%", overflow: "hidden", background: "#0c0e1a", minWidth: 0 }}>
<div style={{ display: "flex", flexDirection: "column", flex: 1, height: "100%", overflow: "hidden", background: "#080f0d", minWidth: 0 }}>
{/* Breadcrumb / header */}
<div style={{ padding: "11px 20px", borderBottom: `1px solid ${border}`, display: "flex", alignItems: "center", gap: 8, flexShrink: 0 }}>
@@ -304,7 +304,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
<div style={{ display: "flex", gap: 4, flexShrink: 0 }}>
{versions.map((v) => (
<button key={v.id} onClick={() => setSelectedVersionId(v.id)}
style={{ background: v.id === activeVersion ? "rgba(139,92,246,0.12)" : "transparent", border: `1px solid ${v.id === activeVersion ? "rgba(139,92,246,0.3)" : "rgba(255,255,255,0.09)"}`, borderRadius: 6, padding: "4px 10px", color: v.id === activeVersion ? "#a78bfa" : "rgba(232,233,240,0.38)", cursor: "pointer", fontSize: 11, fontFamily: "monospace" }}>
style={{ background: v.id === activeVersion ? "rgba(20,184,166,0.12)" : "transparent", border: `1px solid ${v.id === activeVersion ? "rgba(20,184,166,0.3)" : "rgba(255,255,255,0.09)"}`, borderRadius: 6, padding: "4px 10px", color: v.id === activeVersion ? "#2dd4bf" : "rgba(232,233,240,0.38)", cursor: "pointer", fontSize: 11, fontFamily: "monospace" }}>
v{v.version_number}{v.label ? ` · ${v.label}` : ""}
</button>
))}
@@ -340,7 +340,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
{/* Time bar */}
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 6, padding: "0 1px" }}>
<span style={{ fontSize: 10, fontFamily: "monospace", color: "#a78bfa" }}>{formatTime(currentTime)}</span>
<span style={{ fontSize: 10, fontFamily: "monospace", color: "#2dd4bf" }}>{formatTime(currentTime)}</span>
<span style={{ fontSize: 10, fontFamily: "monospace", color: "rgba(232,233,240,0.22)" }}>{isReady && duration > 0 ? formatTime(duration / 2) : "—"}</span>
<span style={{ fontSize: 10, fontFamily: "monospace", color: "rgba(232,233,240,0.22)" }}>{isReady ? formatTime(duration) : "—"}</span>
</div>
@@ -352,9 +352,9 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
<button
onClick={isPlaying ? pause : play}
disabled={!activeVersion}
style={{ width: 46, height: 46, background: "linear-gradient(135deg, #7b5cf6, #06b6d4)", borderRadius: "50%", border: "none", display: "flex", alignItems: "center", justifyContent: "center", cursor: activeVersion ? "pointer" : "default", opacity: activeVersion ? 1 : 0.4, flexShrink: 0, transition: "transform 0.12s, box-shadow 0.12s", boxShadow: "0 4px 16px rgba(139,92,246,0.35)" }}
onMouseEnter={(e) => { if (activeVersion) { e.currentTarget.style.boxShadow = "0 6px 24px rgba(139,92,246,0.55)"; e.currentTarget.style.transform = "scale(1.04)"; } }}
onMouseLeave={(e) => { e.currentTarget.style.boxShadow = "0 4px 16px rgba(139,92,246,0.35)"; e.currentTarget.style.transform = "scale(1)"; }}
style={{ width: 46, height: 46, background: "linear-gradient(135deg, #0d9488, #06b6d4)", borderRadius: "50%", border: "none", display: "flex", alignItems: "center", justifyContent: "center", cursor: activeVersion ? "pointer" : "default", opacity: activeVersion ? 1 : 0.4, flexShrink: 0, transition: "transform 0.12s, box-shadow 0.12s", boxShadow: "0 4px 16px rgba(20,184,166,0.35)" }}
onMouseEnter={(e) => { if (activeVersion) { e.currentTarget.style.boxShadow = "0 6px 24px rgba(20,184,166,0.55)"; e.currentTarget.style.transform = "scale(1.04)"; } }}
onMouseLeave={(e) => { e.currentTarget.style.boxShadow = "0 4px 16px rgba(20,184,166,0.35)"; e.currentTarget.style.transform = "scale(1)"; }}
>
{isPlaying ? <IconPause /> : <IconPlay />}
</button>
@@ -369,7 +369,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
<div style={{ padding: "12px 15px", borderBottom: `1px solid ${border}`, display: "flex", alignItems: "center", justifyContent: "space-between", flexShrink: 0 }}>
<span style={{ fontSize: 13, fontWeight: 500, color: "rgba(232,233,240,0.72)" }}>Comments</span>
{comments && comments.length > 0 && (
<span style={{ fontSize: 11, background: "rgba(139,92,246,0.12)", color: "#a78bfa", padding: "1px 8px", borderRadius: 10 }}>{comments.length}</span>
<span style={{ fontSize: 11, background: "rgba(20,184,166,0.12)", color: "#2dd4bf", padding: "1px 8px", borderRadius: 10 }}>{comments.length}</span>
)}
</div>
@@ -383,8 +383,8 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
)}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 6 }}>
<div style={{ fontSize: 11, fontFamily: "monospace", background: "rgba(139,92,246,0.1)", color: "#a78bfa", border: "1px solid rgba(139,92,246,0.22)", padding: "3px 9px", borderRadius: 20, display: "flex", alignItems: "center", gap: 6 }}>
{isPlaying && <div style={{ width: 6, height: 6, borderRadius: "50%", background: "#a78bfa", animation: "pp-blink 1.1s infinite" }} />}
<div style={{ fontSize: 11, fontFamily: "monospace", background: "rgba(20,184,166,0.1)", color: "#2dd4bf", border: "1px solid rgba(20,184,166,0.22)", padding: "3px 9px", borderRadius: 20, display: "flex", alignItems: "center", gap: 6 }}>
{isPlaying && <div style={{ width: 6, height: 6, borderRadius: "50%", background: "#2dd4bf", animation: "pp-blink 1.1s infinite" }} />}
{formatTime(currentTime)}
</div>
<span style={{ fontSize: 11, color: "rgba(232,233,240,0.2)" }}>· pins to playhead</span>
@@ -395,7 +395,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
onFocus={() => setComposeFocused(true)}
onBlur={() => { if (!commentBody.trim()) setComposeFocused(false); }}
placeholder="What do you hear at this moment…"
style={{ width: "100%", background: "rgba(255,255,255,0.04)", border: composeFocused ? "1px solid rgba(139,92,246,0.35)" : `1px solid rgba(255,255,255,0.07)`, borderRadius: 7, padding: "8px 10px", color: "#e8e9f0", fontSize: 12, resize: "none", outline: "none", fontFamily: "inherit", height: composeFocused ? 68 : 42, transition: "height 0.18s, border-color 0.15s", boxSizing: "border-box" }}
style={{ width: "100%", background: "rgba(255,255,255,0.04)", border: composeFocused ? "1px solid rgba(20,184,166,0.35)" : `1px solid rgba(255,255,255,0.07)`, borderRadius: 7, padding: "8px 10px", color: "#e8e9f0", fontSize: 12, resize: "none", outline: "none", fontFamily: "inherit", height: composeFocused ? 68 : 42, transition: "height 0.18s, border-color 0.15s", boxSizing: "border-box" }}
/>
{composeFocused && (
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 7 }}>
@@ -410,7 +410,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
<button
onClick={() => { if (commentBody.trim()) addCommentMutation.mutate({ body: commentBody.trim(), timestamp: currentTime, tag: selectedTag }); }}
disabled={!commentBody.trim() || addCommentMutation.isPending}
style={{ padding: "5px 14px", borderRadius: 6, background: "linear-gradient(135deg, #7b5cf6, #06b6d4)", border: "none", color: "white", cursor: commentBody.trim() ? "pointer" : "default", fontSize: 12, fontWeight: 600, fontFamily: "inherit", opacity: commentBody.trim() ? 1 : 0.35, transition: "opacity 0.12s" }}>
style={{ padding: "5px 14px", borderRadius: 6, background: "linear-gradient(135deg, #0d9488, #06b6d4)", border: "none", color: "white", cursor: commentBody.trim() ? "pointer" : "default", fontSize: 12, fontWeight: 600, fontFamily: "inherit", opacity: commentBody.trim() ? 1 : 0.35, transition: "opacity 0.12s" }}>
Post
</button>
</div>
@@ -427,15 +427,15 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
return (
<div key={c.id} id={`comment-${c.id}`}
style={{ marginBottom: 14, paddingBottom: 14, borderBottom: `1px solid rgba(255,255,255,0.04)`, borderRadius: isNearPlayhead ? 6 : undefined, background: isNearPlayhead ? "rgba(139,92,246,0.04)" : undefined, border: isNearPlayhead ? "1px solid rgba(139,92,246,0.12)" : undefined, padding: isNearPlayhead ? 8 : undefined }}>
style={{ marginBottom: 14, paddingBottom: 14, borderBottom: `1px solid rgba(255,255,255,0.04)`, borderRadius: isNearPlayhead ? 6 : undefined, background: isNearPlayhead ? "rgba(20,184,166,0.04)" : undefined, border: isNearPlayhead ? "1px solid rgba(20,184,166,0.12)" : undefined, padding: isNearPlayhead ? 8 : undefined }}>
<div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 5 }}>
<Avatar name={c.author_name} avatarUrl={c.author_avatar_url} authorId={c.author_id} size={21} />
<span style={{ fontSize: 12, fontWeight: 500, color: "rgba(232,233,240,0.72)" }}>{c.author_name}</span>
{c.timestamp != null && (
<button onClick={() => seekTo(c.timestamp!)}
style={{ marginLeft: "auto", fontSize: 10, fontFamily: "monospace", color: "#a78bfa", background: "rgba(139,92,246,0.1)", border: "none", borderRadius: 3, padding: "1px 5px", cursor: "pointer" }}
onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(139,92,246,0.2)")}
onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(139,92,246,0.1)")}
style={{ marginLeft: "auto", fontSize: 10, fontFamily: "monospace", color: "#2dd4bf", background: "rgba(20,184,166,0.1)", border: "none", borderRadius: 3, padding: "1px 5px", cursor: "pointer" }}
onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(20,184,166,0.2)")}
onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(20,184,166,0.1)")}
>
{formatTime(c.timestamp)}
</button>

View File

@@ -71,7 +71,7 @@ function NavItem({ icon, label, active, onClick, disabled, badge, collapsed }: N
const [hovered, setHovered] = useState(false);
const fg = active
? "#a78bfa"
? "#2dd4bf"
: disabled
? "rgba(255,255,255,0.16)"
: hovered
@@ -79,7 +79,7 @@ function NavItem({ icon, label, active, onClick, disabled, badge, collapsed }: N
: "rgba(232,233,240,0.35)";
const bg = active
? "rgba(139,92,246,0.12)"
? "rgba(20,184,166,0.12)"
: hovered && !disabled
? "rgba(255,255,255,0.04)"
: "transparent";
@@ -115,7 +115,7 @@ function NavItem({ icon, label, active, onClick, disabled, badge, collapsed }: N
<div style={{
position: "absolute", left: 0, top: "20%", bottom: "20%",
width: 2, borderRadius: "0 2px 2px 0",
background: "linear-gradient(to bottom, #7b5cf6, #22d3ee)",
background: "linear-gradient(to bottom, #0d9488, #22d3ee)",
}} />
)}
<span style={{ width: 20, height: 20, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
@@ -129,7 +129,7 @@ function NavItem({ icon, label, active, onClick, disabled, badge, collapsed }: N
{!collapsed && badge != null && badge > 0 && (
<span style={{
fontSize: 9, fontWeight: 700, padding: "2px 6px", borderRadius: 20,
background: "linear-gradient(135deg, #7b5cf6, #22d3ee)", color: "white",
background: "linear-gradient(135deg, #0d9488, #22d3ee)", color: "white",
flexShrink: 0,
}}>
{badge}
@@ -168,13 +168,13 @@ export function Sidebar({ children }: { children: React.ReactNode }) {
const border = "rgba(255,255,255,0.06)";
return (
<div style={{ display: "flex", height: "100vh", overflow: "hidden", background: "#0c0e1a", color: "#e8e9f0", fontFamily: "-apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif", fontSize: 13 }}>
<div style={{ display: "flex", height: "100vh", overflow: "hidden", background: "#080f0d", color: "#e8e9f0", fontFamily: "-apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif", fontSize: 13 }}>
{/* ── Sidebar ── */}
<aside style={{
width: sidebarWidth,
minWidth: sidebarWidth,
background: "#10131f",
background: "#0c1612",
borderRight: `1px solid ${border}`,
display: "flex",
flexDirection: "column",
@@ -191,14 +191,14 @@ export function Sidebar({ children }: { children: React.ReactNode }) {
title={collapsed ? "Expand menu" : "Collapse menu"}
style={{
width: 40, height: 40, borderRadius: 12, flexShrink: 0,
background: "linear-gradient(135deg, #7b5cf6, #06b6d4)",
background: "linear-gradient(135deg, #0d9488, #06b6d4)",
display: "flex", alignItems: "center", justifyContent: "center",
border: "none", cursor: "pointer",
boxShadow: "0 0 20px rgba(139,92,246,0.3)",
boxShadow: "0 0 20px rgba(20,184,166,0.3)",
transition: "box-shadow 0.2s",
}}
onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "0 0 30px rgba(139,92,246,0.5)")}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "0 0 20px rgba(139,92,246,0.3)")}
onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "0 0 30px rgba(20,184,166,0.5)")}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "0 0 20px rgba(20,184,166,0.3)")}
>
<IconMenu />
</button>
@@ -269,7 +269,7 @@ export function Sidebar({ children }: { children: React.ReactNode }) {
</aside>
{/* ── Main content ── */}
<main style={{ flex: 1, overflow: "hidden", display: "grid", gridTemplateRows: "44px 1fr", background: "#0c0e1a", minWidth: 0 }}>
<main style={{ flex: 1, overflow: "hidden", display: "grid", gridTemplateRows: "44px 1fr", background: "#080f0d", minWidth: 0 }}>
<TopBandBar />
<div style={{ overflow: "auto", display: "flex", flexDirection: "column" }}>
{children}

View File

@@ -48,7 +48,7 @@ export function TopBandBar() {
gap: 8,
padding: "0 20px",
borderBottom: `1px solid ${border}`,
background: "#10131f",
background: "#0c1612",
zIndex: 10,
}}>
{/* Band switcher */}
@@ -72,10 +72,10 @@ export function TopBandBar() {
<>
<div style={{
width: 22, height: 22, borderRadius: 6, flexShrink: 0,
background: "rgba(139,92,246,0.15)",
border: "1px solid rgba(139,92,246,0.3)",
background: "rgba(20,184,166,0.15)",
border: "1px solid rgba(20,184,166,0.3)",
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: 9, fontWeight: 800, color: "#a78bfa",
fontSize: 9, fontWeight: 800, color: "#2dd4bf",
}}>
{getInitials(activeBand.name)}
</div>
@@ -93,7 +93,7 @@ export function TopBandBar() {
<div style={{
position: "absolute", top: "calc(100% + 6px)", left: 0,
minWidth: 220,
background: "#1a1e30",
background: "#142420",
border: "1px solid rgba(255,255,255,0.1)",
borderRadius: 10, padding: 6, zIndex: 100,
boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
@@ -109,7 +109,7 @@ export function TopBandBar() {
style={{
width: "100%", display: "flex", alignItems: "center", gap: 8,
padding: "7px 9px", marginBottom: 1,
background: band.id === currentBandId ? "rgba(139,92,246,0.1)" : "transparent",
background: band.id === currentBandId ? "rgba(20,184,166,0.1)" : "transparent",
border: "none", borderRadius: 6,
cursor: "pointer", color: "#e8e9f0",
textAlign: "left", fontFamily: "inherit",
@@ -118,14 +118,14 @@ export function TopBandBar() {
onMouseEnter={(e) => { if (band.id !== currentBandId) e.currentTarget.style.background = "rgba(255,255,255,0.04)"; }}
onMouseLeave={(e) => { if (band.id !== currentBandId) e.currentTarget.style.background = "transparent"; }}
>
<div style={{ width: 22, height: 22, borderRadius: 6, background: "rgba(139,92,246,0.15)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 9, fontWeight: 700, color: "#a78bfa", flexShrink: 0 }}>
<div style={{ width: 22, height: 22, borderRadius: 6, background: "rgba(20,184,166,0.15)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 9, fontWeight: 700, color: "#2dd4bf", flexShrink: 0 }}>
{getInitials(band.name)}
</div>
<span style={{ flex: 1, fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{band.name}
</span>
{band.id === currentBandId && (
<span style={{ fontSize: 10, color: "#a78bfa", flexShrink: 0 }}></span>
<span style={{ fontSize: 10, color: "#2dd4bf", flexShrink: 0 }}></span>
)}
</button>
))}

View File

@@ -16,11 +16,11 @@ input, textarea, button, select {
/* ── Design system (dark only — no light mode in v1) ─────────────────────── */
:root {
/* v2 dark-space palette */
--bg: #0c0e1a;
--bg-card: #10131f;
--bg-raised: #151828;
--bg-hover: #1a1e30;
/* v2 dark-teal palette */
--bg: #080f0d;
--bg-card: #0c1612;
--bg-raised: #101c18;
--bg-hover: #142420;
--bg-subtle: rgba(255,255,255,0.025);
--bg-inset: rgba(255,255,255,0.04);
--border: rgba(255,255,255,0.06);
@@ -29,12 +29,12 @@ input, textarea, button, select {
--text: #e8e9f0;
--text-muted: rgba(232,233,240,0.55);
--text-subtle: rgba(232,233,240,0.28);
/* Violet accent */
--accent: #8b5cf6;
--accent-light: #a78bfa;
--accent-hover: #9f70f8;
--accent-bg: rgba(139,92,246,0.12);
--accent-border: rgba(139,92,246,0.3);
/* Teal accent */
--accent: #14b8a6;
--accent-light: #2dd4bf;
--accent-hover: #10a89a;
--accent-bg: rgba(20,184,166,0.12);
--accent-border: rgba(20,184,166,0.3);
--accent-fg: #ffffff;
--teal: #34d399;
--teal-bg: rgba(52,211,153,0.1);
@@ -53,7 +53,7 @@ input, textarea, button, select {
/* Bottom Navigation Bar */
nav[style*="position: fixed"] {
display: flex;
background: #0b0b0e;
background: #060d0b;
border-top: 1px solid rgba(255, 255, 255, 0.06);
padding: 8px 16px;
}

View File

@@ -95,8 +95,8 @@ function Input(props: React.InputHTMLAttributes<HTMLInputElement>) {
onBlur={(e) => { setFocused(false); props.onBlur?.(e); }}
style={{
width: "100%", padding: "8px 12px",
background: "#151828",
border: `1px solid ${focused ? "rgba(139,92,246,0.4)" : border}`,
background: "#101c18",
border: `1px solid ${focused ? "rgba(20,184,166,0.4)" : border}`,
borderRadius: 8, color: "#e8e9f0",
fontSize: 13, fontFamily: "inherit",
outline: "none", boxSizing: "border-box",
@@ -114,13 +114,13 @@ function SaveBtn({ pending, saved, onClick }: { pending: boolean; saved: boolean
disabled={pending}
style={{
padding: "8px 20px", borderRadius: 8,
background: saved ? "rgba(52,211,153,0.12)" : "linear-gradient(135deg, #7b5cf6, #06b6d4)",
background: saved ? "rgba(52,211,153,0.12)" : "linear-gradient(135deg, #0d9488, #06b6d4)",
border: saved ? "1px solid rgba(52,211,153,0.3)" : "none",
color: saved ? "#34d399" : "white",
cursor: pending ? "default" : "pointer",
fontSize: 13, fontWeight: 600, fontFamily: "inherit",
transition: "all 0.2s",
boxShadow: saved ? "none" : "0 2px 12px rgba(139,92,246,0.3)",
boxShadow: saved ? "none" : "0 2px 12px rgba(20,184,166,0.3)",
}}
>
{pending ? "Saving…" : saved ? "Saved ✓" : "Save"}
@@ -211,7 +211,7 @@ function ProfileSection({ me }: { me: MemberRead }) {
} finally { setUploading(false); }
}}
/>
<label htmlFor="avatar-upload" style={{ padding: "7px 14px", borderRadius: 8, background: "rgba(139,92,246,0.12)", border: "1px solid rgba(139,92,246,0.3)", color: "#a78bfa", cursor: "pointer", fontSize: 12, fontWeight: 600 }}>
<label htmlFor="avatar-upload" style={{ padding: "7px 14px", borderRadius: 8, background: "rgba(20,184,166,0.12)", border: "1px solid rgba(20,184,166,0.3)", color: "#2dd4bf", cursor: "pointer", fontSize: 12, fontWeight: 600 }}>
{uploading ? "Uploading…" : "Upload"}
</label>
<button
@@ -421,7 +421,7 @@ function StorageSection({ bandId, band, amAdmin, me }: { bandId: string; band: B
<Input value={folderInput} onChange={(e) => setFolderInput(e.target.value)} placeholder={defaultPath} style={{ fontFamily: "monospace" }} />
<div style={{ display: "flex", gap: 8, marginTop: 8 }}>
<button onClick={() => pathMutation.mutate(folderInput)} disabled={pathMutation.isPending}
style={{ padding: "6px 14px", background: "rgba(139,92,246,0.12)", border: "1px solid rgba(139,92,246,0.3)", borderRadius: 6, color: "#a78bfa", cursor: "pointer", fontSize: 12, fontWeight: 600, fontFamily: "inherit" }}>
style={{ padding: "6px 14px", background: "rgba(20,184,166,0.12)", border: "1px solid rgba(20,184,166,0.3)", borderRadius: 6, color: "#2dd4bf", cursor: "pointer", fontSize: 12, fontWeight: 600, fontFamily: "inherit" }}>
{pathMutation.isPending ? "Saving…" : "Save"}
</button>
<button onClick={() => setEditingPath(false)}
@@ -498,16 +498,16 @@ function MembersSection({ bandId, band, amAdmin, members, membersLoading }: { ba
<div style={{ marginBottom: 16 }}>
<button
onClick={() => inviteMutation.mutate()} disabled={inviteMutation.isPending}
style={{ padding: "8px 16px", background: "rgba(139,92,246,0.12)", border: "1px solid rgba(139,92,246,0.3)", borderRadius: 8, color: "#a78bfa", cursor: "pointer", fontSize: 13, fontWeight: 600, fontFamily: "inherit" }}>
style={{ padding: "8px 16px", background: "rgba(20,184,166,0.12)", border: "1px solid rgba(20,184,166,0.3)", borderRadius: 8, color: "#2dd4bf", cursor: "pointer", fontSize: 13, fontWeight: 600, fontFamily: "inherit" }}>
{inviteMutation.isPending ? "Generating…" : "+ Generate invite link"}
</button>
</div>
)}
{inviteLink && (
<div style={{ background: "rgba(139,92,246,0.06)", border: "1px solid rgba(139,92,246,0.22)", borderRadius: 10, padding: "12px 16px", marginBottom: 16 }}>
<div style={{ background: "rgba(20,184,166,0.06)", border: "1px solid rgba(20,184,166,0.22)", borderRadius: 10, padding: "12px 16px", marginBottom: 16 }}>
<p style={{ color: "rgba(232,233,240,0.35)", fontSize: 11, margin: "0 0 5px" }}>Invite link (copied · valid 72h):</p>
<code style={{ color: "#a78bfa", fontSize: 12, wordBreak: "break-all", fontFamily: "monospace" }}>{inviteLink}</code>
<code style={{ color: "#2dd4bf", fontSize: 12, wordBreak: "break-all", fontFamily: "monospace" }}>{inviteLink}</code>
<button onClick={() => setInviteLink(null)} style={{ display: "block", marginTop: 6, background: "none", border: "none", color: "rgba(232,233,240,0.28)", cursor: "pointer", fontSize: 11, padding: 0, fontFamily: "inherit" }}>
Dismiss
</button>
@@ -528,7 +528,7 @@ function MembersSection({ bandId, band, amAdmin, members, membersLoading }: { ba
<div style={{ fontSize: 13, color: "rgba(232,233,240,0.75)" }}>{m.display_name}</div>
<div style={{ fontSize: 11, color: "rgba(232,233,240,0.28)", marginTop: 1 }}>{m.email}</div>
</div>
<span style={{ fontSize: 10, fontFamily: "monospace", padding: "2px 7px", borderRadius: 4, background: m.role === "admin" ? "rgba(139,92,246,0.1)" : "rgba(255,255,255,0.05)", color: m.role === "admin" ? "#a78bfa" : "rgba(232,233,240,0.38)", border: `1px solid ${m.role === "admin" ? "rgba(139,92,246,0.28)" : border}`, whiteSpace: "nowrap" }}>
<span style={{ fontSize: 10, fontFamily: "monospace", padding: "2px 7px", borderRadius: 4, background: m.role === "admin" ? "rgba(20,184,166,0.1)" : "rgba(255,255,255,0.05)", color: m.role === "admin" ? "#2dd4bf" : "rgba(232,233,240,0.38)", border: `1px solid ${m.role === "admin" ? "rgba(20,184,166,0.28)" : border}`, whiteSpace: "nowrap" }}>
{m.role}
</span>
{amAdmin && m.role !== "admin" && (
@@ -545,7 +545,7 @@ function MembersSection({ bandId, band, amAdmin, members, membersLoading }: { ba
{/* Role legend */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginBottom: amAdmin ? 0 : 0 }}>
<div style={{ padding: "10px 12px", background: "rgba(255,255,255,0.02)", border: `1px solid ${border}`, borderRadius: 8 }}>
<div style={{ fontSize: 12, color: "#a78bfa", marginBottom: 3 }}>Admin</div>
<div style={{ fontSize: 12, color: "#2dd4bf", marginBottom: 3 }}>Admin</div>
<div style={{ fontSize: 11, color: "rgba(232,233,240,0.28)", lineHeight: 1.55 }}>Upload, delete, manage members and storage</div>
</div>
<div style={{ padding: "10px 12px", background: "rgba(255,255,255,0.02)", border: `1px solid ${border}`, borderRadius: 8 }}>
@@ -627,9 +627,9 @@ function BandSection({ bandId, band }: { bandId: string; band: Band }) {
<Label>Genre tags</Label>
<div style={{ display: "flex", gap: 5, flexWrap: "wrap", marginBottom: 8 }}>
{tags.map((t) => (
<span key={t} style={{ background: "rgba(139,92,246,0.1)", color: "#a78bfa", fontSize: 11, padding: "3px 10px", borderRadius: 20, display: "flex", alignItems: "center", gap: 5 }}>
<span key={t} style={{ background: "rgba(20,184,166,0.1)", color: "#2dd4bf", fontSize: 11, padding: "3px 10px", borderRadius: 20, display: "flex", alignItems: "center", gap: 5 }}>
{t}
<button onClick={() => setTags((p) => p.filter((x) => x !== t))} style={{ background: "none", border: "none", color: "#a78bfa", cursor: "pointer", fontSize: 13, padding: 0, lineHeight: 1, fontFamily: "inherit" }}>×</button>
<button onClick={() => setTags((p) => p.filter((x) => x !== t))} style={{ background: "none", border: "none", color: "#2dd4bf", cursor: "pointer", fontSize: 13, padding: 0, lineHeight: 1, fontFamily: "inherit" }}>×</button>
</span>
))}
</div>
@@ -671,13 +671,13 @@ function NavItem({ label, active, onClick }: { label: string; active: boolean; o
padding: "8px 10px", borderRadius: 8,
border: "none", cursor: "pointer",
fontSize: 13, fontFamily: "inherit", fontWeight: active ? 600 : 400,
background: active ? "rgba(139,92,246,0.1)" : hovered ? "rgba(255,255,255,0.04)" : "transparent",
color: active ? "#a78bfa" : hovered ? "rgba(232,233,240,0.75)" : "rgba(232,233,240,0.45)",
background: active ? "rgba(20,184,166,0.1)" : hovered ? "rgba(255,255,255,0.04)" : "transparent",
color: active ? "#2dd4bf" : hovered ? "rgba(232,233,240,0.75)" : "rgba(232,233,240,0.45)",
transition: "all 0.12s",
position: "relative",
}}
>
{active && <div style={{ position: "absolute", left: 0, top: "20%", bottom: "20%", width: 2, borderRadius: "0 2px 2px 0", background: "linear-gradient(to bottom, #7b5cf6, #22d3ee)" }} />}
{active && <div style={{ position: "absolute", left: 0, top: "20%", bottom: "20%", width: 2, borderRadius: "0 2px 2px 0", background: "linear-gradient(to bottom, #0d9488, #22d3ee)" }} />}
{label}
</button>
);
@@ -744,7 +744,7 @@ export function SettingsPage() {
{section !== "profile" || searchParams.has("section") ? (
<div>
<button onClick={() => setSearchParams({})}
style={{ display: "flex", alignItems: "center", gap: 6, padding: "12px 16px", background: "none", border: "none", cursor: "pointer", color: "#a78bfa", fontSize: 13, fontFamily: "inherit" }}>
style={{ display: "flex", alignItems: "center", gap: 6, padding: "12px 16px", background: "none", border: "none", cursor: "pointer", color: "#2dd4bf", fontSize: 13, fontFamily: "inherit" }}>
Settings
</button>
<div style={{ padding: "0 16px 24px" }}>
@@ -782,7 +782,7 @@ export function SettingsPage() {
<div style={{ display: "flex", height: "100%", overflow: "hidden" }}>
{/* Left nav */}
<nav style={{ width: 220, minWidth: 220, background: "#10131f", borderRight: `1px solid ${border}`, padding: "20px 12px", display: "flex", flexDirection: "column", overflow: "hidden", flexShrink: 0 }}>
<nav style={{ width: 220, minWidth: 220, background: "#0c1612", borderRight: `1px solid ${border}`, padding: "20px 12px", display: "flex", flexDirection: "column", overflow: "hidden", flexShrink: 0 }}>
<h1 style={{ fontSize: 16, fontWeight: 800, color: "#e8e9f0", margin: "0 0 4px 8px", letterSpacing: -0.3 }}>Settings</h1>
<div style={{ height: 1, background: border, margin: "12px 0" }} />
@@ -803,7 +803,7 @@ export function SettingsPage() {
// Stay on same section if possible
setSearchParams({ section }, { replace: true });
}}
style={{ width: "100%", padding: "6px 8px", background: "#151828", border: `1px solid ${borderBright}`, borderRadius: 7, color: "#e8e9f0", fontSize: 12, fontFamily: "inherit", cursor: "pointer", outline: "none" }}
style={{ width: "100%", padding: "6px 8px", background: "#101c18", border: `1px solid ${borderBright}`, borderRadius: 7, color: "#e8e9f0", fontSize: 12, fontFamily: "inherit", cursor: "pointer", outline: "none" }}
>
{bands?.map((b) => (
<option key={b.id} value={b.id}>{b.name}</option>