development #1
@@ -63,7 +63,7 @@ function MiniWave({ songId, active }: { songId: string; active: boolean }) {
|
|||||||
height: `${h}%`,
|
height: `${h}%`,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
background: active
|
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)",
|
: "rgba(255,255,255,0.1)",
|
||||||
transition: "background 0.15s",
|
transition: "background 0.15s",
|
||||||
}}
|
}}
|
||||||
@@ -76,7 +76,7 @@ function MiniWave({ songId, active }: { songId: string; active: boolean }) {
|
|||||||
// ── Tag badge ─────────────────────────────────────────────────────────────────
|
// ── Tag badge ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const TAG_COLORS: Record<string, { bg: string; color: string }> = {
|
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" },
|
riff: { bg: "rgba(34,211,238,0.1)", color: "#67e8f9" },
|
||||||
idea: { bg: "rgba(52,211,153,0.1)", color: "#6ee7b7" },
|
idea: { bg: "rgba(52,211,153,0.1)", color: "#6ee7b7" },
|
||||||
};
|
};
|
||||||
@@ -117,19 +117,19 @@ function TrackRow({
|
|||||||
padding: "9px 20px",
|
padding: "9px 20px",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
position: "relative",
|
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",
|
transition: "background 0.12s",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{active && (
|
{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" }}>
|
<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")}
|
{String(index + 1).padStart(2, "0")}
|
||||||
</span>
|
</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}
|
{song.title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ function SessionGroup({
|
|||||||
>
|
>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: 10, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase",
|
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",
|
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent",
|
||||||
}}>
|
}}>
|
||||||
{formatSessionDate(session.date)}
|
{formatSessionDate(session.date)}
|
||||||
@@ -280,7 +280,7 @@ export function LibraryPanel({ bandId, selectedSongId, onSelectSong }: LibraryPa
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
background: "#10131f",
|
background: "#0c1612",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}>
|
}}>
|
||||||
@@ -292,8 +292,8 @@ export function LibraryPanel({ bandId, selectedSongId, onSelectSong }: LibraryPa
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Search */}
|
{/* 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" }}
|
<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(139,92,246,0.4)")}
|
onFocusCapture={(e) => (e.currentTarget.style.borderColor = "rgba(20,184,166,0.4)")}
|
||||||
onBlurCapture={(e) => (e.currentTarget.style.borderColor = border)}
|
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 }}>
|
<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}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
placeholder="Search recordings…"
|
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 && (
|
{search && (
|
||||||
<button onClick={() => setSearch("")} style={{ background: "none", border: "none", cursor: "pointer", color: "rgba(232,233,240,0.3)", padding: 0, display: "flex", lineHeight: 1 }}>
|
<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)}
|
onClick={() => setFilterTag(chip.value)}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 11, fontWeight: 600, padding: "4px 12px", borderRadius: 20, cursor: "pointer", whiteSpace: "nowrap",
|
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}`,
|
border: on ? "1px solid rgba(20,184,166,0.4)" : `1px solid ${border}`,
|
||||||
background: on ? "rgba(139,92,246,0.1)" : "transparent",
|
background: on ? "rgba(20,184,166,0.1)" : "transparent",
|
||||||
color: on ? "#a78bfa" : "rgba(232,233,240,0.35)",
|
color: on ? "#2dd4bf" : "rgba(232,233,240,0.35)",
|
||||||
fontFamily: "inherit",
|
fontFamily: "inherit",
|
||||||
transition: "all 0.12s",
|
transition: "all 0.12s",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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(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(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(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" },
|
{ 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); }}
|
onClick={() => { onSeek(c.timestamp!); onScrollToComment(c.id); }}
|
||||||
>
|
>
|
||||||
{isHovered && (
|
{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={{ 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 }}>
|
<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)}
|
{getInitials(c.author_name)}
|
||||||
</div>
|
</div>
|
||||||
<span style={{ fontSize: 11, fontWeight: 500, color: "rgba(232,233,240,0.72)" }}>{c.author_name}</span>
|
<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>
|
||||||
<div style={{ fontSize: 11, color: "rgba(232,233,240,0.42)", lineHeight: 1.4 }}>
|
<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}
|
{c.body.length > 80 ? c.body.slice(0, 80) + "…" : c.body}
|
||||||
@@ -271,7 +271,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
|
|||||||
// ── Render ────────────────────────────────────────────────────────────────
|
// ── Render ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Breadcrumb / header */}
|
||||||
<div style={{ padding: "11px 20px", borderBottom: `1px solid ${border}`, display: "flex", alignItems: "center", gap: 8, flexShrink: 0 }}>
|
<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 }}>
|
<div style={{ display: "flex", gap: 4, flexShrink: 0 }}>
|
||||||
{versions.map((v) => (
|
{versions.map((v) => (
|
||||||
<button key={v.id} onClick={() => setSelectedVersionId(v.id)}
|
<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}` : ""}
|
v{v.version_number}{v.label ? ` · ${v.label}` : ""}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -340,7 +340,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
|
|||||||
|
|
||||||
{/* Time bar */}
|
{/* Time bar */}
|
||||||
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 6, padding: "0 1px" }}>
|
<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 && duration > 0 ? formatTime(duration / 2) : "—"}</span>
|
||||||
<span style={{ fontSize: 10, fontFamily: "monospace", color: "rgba(232,233,240,0.22)" }}>{isReady ? formatTime(duration) : "—"}</span>
|
<span style={{ fontSize: 10, fontFamily: "monospace", color: "rgba(232,233,240,0.22)" }}>{isReady ? formatTime(duration) : "—"}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -352,9 +352,9 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={isPlaying ? pause : play}
|
onClick={isPlaying ? pause : play}
|
||||||
disabled={!activeVersion}
|
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)" }}
|
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(139,92,246,0.55)"; e.currentTarget.style.transform = "scale(1.04)"; } }}
|
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(139,92,246,0.35)"; e.currentTarget.style.transform = "scale(1)"; }}
|
onMouseLeave={(e) => { e.currentTarget.style.boxShadow = "0 4px 16px rgba(20,184,166,0.35)"; e.currentTarget.style.transform = "scale(1)"; }}
|
||||||
>
|
>
|
||||||
{isPlaying ? <IconPause /> : <IconPlay />}
|
{isPlaying ? <IconPause /> : <IconPlay />}
|
||||||
</button>
|
</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 }}>
|
<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>
|
<span style={{ fontSize: 13, fontWeight: 500, color: "rgba(232,233,240,0.72)" }}>Comments</span>
|
||||||
{comments && comments.length > 0 && (
|
{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>
|
</div>
|
||||||
|
|
||||||
@@ -383,8 +383,8 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
|
|||||||
)}
|
)}
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 6 }}>
|
<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 }}>
|
<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: "#a78bfa", animation: "pp-blink 1.1s infinite" }} />}
|
{isPlaying && <div style={{ width: 6, height: 6, borderRadius: "50%", background: "#2dd4bf", animation: "pp-blink 1.1s infinite" }} />}
|
||||||
{formatTime(currentTime)}
|
{formatTime(currentTime)}
|
||||||
</div>
|
</div>
|
||||||
<span style={{ fontSize: 11, color: "rgba(232,233,240,0.2)" }}>· pins to playhead</span>
|
<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)}
|
onFocus={() => setComposeFocused(true)}
|
||||||
onBlur={() => { if (!commentBody.trim()) setComposeFocused(false); }}
|
onBlur={() => { if (!commentBody.trim()) setComposeFocused(false); }}
|
||||||
placeholder="What do you hear at this moment…"
|
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 && (
|
{composeFocused && (
|
||||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 7 }}>
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 7 }}>
|
||||||
@@ -410,7 +410,7 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => { if (commentBody.trim()) addCommentMutation.mutate({ body: commentBody.trim(), timestamp: currentTime, tag: selectedTag }); }}
|
onClick={() => { if (commentBody.trim()) addCommentMutation.mutate({ body: commentBody.trim(), timestamp: currentTime, tag: selectedTag }); }}
|
||||||
disabled={!commentBody.trim() || addCommentMutation.isPending}
|
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
|
Post
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -427,15 +427,15 @@ export function PlayerPanel({ songId, bandId, onBack }: PlayerPanelProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={c.id} id={`comment-${c.id}`}
|
<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 }}>
|
<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} />
|
<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>
|
<span style={{ fontSize: 12, fontWeight: 500, color: "rgba(232,233,240,0.72)" }}>{c.author_name}</span>
|
||||||
{c.timestamp != null && (
|
{c.timestamp != null && (
|
||||||
<button onClick={() => seekTo(c.timestamp!)}
|
<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" }}
|
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(139,92,246,0.2)")}
|
onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(20,184,166,0.2)")}
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(139,92,246,0.1)")}
|
onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(20,184,166,0.1)")}
|
||||||
>
|
>
|
||||||
{formatTime(c.timestamp)}
|
{formatTime(c.timestamp)}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function NavItem({ icon, label, active, onClick, disabled, badge, collapsed }: N
|
|||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
||||||
const fg = active
|
const fg = active
|
||||||
? "#a78bfa"
|
? "#2dd4bf"
|
||||||
: disabled
|
: disabled
|
||||||
? "rgba(255,255,255,0.16)"
|
? "rgba(255,255,255,0.16)"
|
||||||
: hovered
|
: hovered
|
||||||
@@ -79,7 +79,7 @@ function NavItem({ icon, label, active, onClick, disabled, badge, collapsed }: N
|
|||||||
: "rgba(232,233,240,0.35)";
|
: "rgba(232,233,240,0.35)";
|
||||||
|
|
||||||
const bg = active
|
const bg = active
|
||||||
? "rgba(139,92,246,0.12)"
|
? "rgba(20,184,166,0.12)"
|
||||||
: hovered && !disabled
|
: hovered && !disabled
|
||||||
? "rgba(255,255,255,0.04)"
|
? "rgba(255,255,255,0.04)"
|
||||||
: "transparent";
|
: "transparent";
|
||||||
@@ -115,7 +115,7 @@ function NavItem({ icon, label, active, onClick, disabled, badge, collapsed }: N
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: "absolute", left: 0, top: "20%", bottom: "20%",
|
position: "absolute", left: 0, top: "20%", bottom: "20%",
|
||||||
width: 2, borderRadius: "0 2px 2px 0",
|
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 }}>
|
<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 && (
|
{!collapsed && badge != null && badge > 0 && (
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: 9, fontWeight: 700, padding: "2px 6px", borderRadius: 20,
|
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,
|
flexShrink: 0,
|
||||||
}}>
|
}}>
|
||||||
{badge}
|
{badge}
|
||||||
@@ -168,13 +168,13 @@ export function Sidebar({ children }: { children: React.ReactNode }) {
|
|||||||
const border = "rgba(255,255,255,0.06)";
|
const border = "rgba(255,255,255,0.06)";
|
||||||
|
|
||||||
return (
|
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 ── */}
|
{/* ── Sidebar ── */}
|
||||||
<aside style={{
|
<aside style={{
|
||||||
width: sidebarWidth,
|
width: sidebarWidth,
|
||||||
minWidth: sidebarWidth,
|
minWidth: sidebarWidth,
|
||||||
background: "#10131f",
|
background: "#0c1612",
|
||||||
borderRight: `1px solid ${border}`,
|
borderRight: `1px solid ${border}`,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@@ -191,14 +191,14 @@ export function Sidebar({ children }: { children: React.ReactNode }) {
|
|||||||
title={collapsed ? "Expand menu" : "Collapse menu"}
|
title={collapsed ? "Expand menu" : "Collapse menu"}
|
||||||
style={{
|
style={{
|
||||||
width: 40, height: 40, borderRadius: 12, flexShrink: 0,
|
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",
|
display: "flex", alignItems: "center", justifyContent: "center",
|
||||||
border: "none", cursor: "pointer",
|
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",
|
transition: "box-shadow 0.2s",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "0 0 30px rgba(139,92,246,0.5)")}
|
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(139,92,246,0.3)")}
|
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "0 0 20px rgba(20,184,166,0.3)")}
|
||||||
>
|
>
|
||||||
<IconMenu />
|
<IconMenu />
|
||||||
</button>
|
</button>
|
||||||
@@ -269,7 +269,7 @@ export function Sidebar({ children }: { children: React.ReactNode }) {
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* ── Main content ── */}
|
{/* ── 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 />
|
<TopBandBar />
|
||||||
<div style={{ overflow: "auto", display: "flex", flexDirection: "column" }}>
|
<div style={{ overflow: "auto", display: "flex", flexDirection: "column" }}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function TopBandBar() {
|
|||||||
gap: 8,
|
gap: 8,
|
||||||
padding: "0 20px",
|
padding: "0 20px",
|
||||||
borderBottom: `1px solid ${border}`,
|
borderBottom: `1px solid ${border}`,
|
||||||
background: "#10131f",
|
background: "#0c1612",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
}}>
|
}}>
|
||||||
{/* Band switcher */}
|
{/* Band switcher */}
|
||||||
@@ -72,10 +72,10 @@ export function TopBandBar() {
|
|||||||
<>
|
<>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 22, height: 22, borderRadius: 6, flexShrink: 0,
|
width: 22, height: 22, borderRadius: 6, flexShrink: 0,
|
||||||
background: "rgba(139,92,246,0.15)",
|
background: "rgba(20,184,166,0.15)",
|
||||||
border: "1px solid rgba(139,92,246,0.3)",
|
border: "1px solid rgba(20,184,166,0.3)",
|
||||||
display: "flex", alignItems: "center", justifyContent: "center",
|
display: "flex", alignItems: "center", justifyContent: "center",
|
||||||
fontSize: 9, fontWeight: 800, color: "#a78bfa",
|
fontSize: 9, fontWeight: 800, color: "#2dd4bf",
|
||||||
}}>
|
}}>
|
||||||
{getInitials(activeBand.name)}
|
{getInitials(activeBand.name)}
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +93,7 @@ export function TopBandBar() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: "absolute", top: "calc(100% + 6px)", left: 0,
|
position: "absolute", top: "calc(100% + 6px)", left: 0,
|
||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
background: "#1a1e30",
|
background: "#142420",
|
||||||
border: "1px solid rgba(255,255,255,0.1)",
|
border: "1px solid rgba(255,255,255,0.1)",
|
||||||
borderRadius: 10, padding: 6, zIndex: 100,
|
borderRadius: 10, padding: 6, zIndex: 100,
|
||||||
boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
|
boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
|
||||||
@@ -109,7 +109,7 @@ export function TopBandBar() {
|
|||||||
style={{
|
style={{
|
||||||
width: "100%", display: "flex", alignItems: "center", gap: 8,
|
width: "100%", display: "flex", alignItems: "center", gap: 8,
|
||||||
padding: "7px 9px", marginBottom: 1,
|
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,
|
border: "none", borderRadius: 6,
|
||||||
cursor: "pointer", color: "#e8e9f0",
|
cursor: "pointer", color: "#e8e9f0",
|
||||||
textAlign: "left", fontFamily: "inherit",
|
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)"; }}
|
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"; }}
|
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)}
|
{getInitials(band.name)}
|
||||||
</div>
|
</div>
|
||||||
<span style={{ flex: 1, fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
<span style={{ flex: 1, fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
{band.name}
|
{band.name}
|
||||||
</span>
|
</span>
|
||||||
{band.id === currentBandId && (
|
{band.id === currentBandId && (
|
||||||
<span style={{ fontSize: 10, color: "#a78bfa", flexShrink: 0 }}>✓</span>
|
<span style={{ fontSize: 10, color: "#2dd4bf", flexShrink: 0 }}>✓</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ input, textarea, button, select {
|
|||||||
|
|
||||||
/* ── Design system (dark only — no light mode in v1) ─────────────────────── */
|
/* ── Design system (dark only — no light mode in v1) ─────────────────────── */
|
||||||
:root {
|
:root {
|
||||||
/* v2 dark-space palette */
|
/* v2 dark-teal palette */
|
||||||
--bg: #0c0e1a;
|
--bg: #080f0d;
|
||||||
--bg-card: #10131f;
|
--bg-card: #0c1612;
|
||||||
--bg-raised: #151828;
|
--bg-raised: #101c18;
|
||||||
--bg-hover: #1a1e30;
|
--bg-hover: #142420;
|
||||||
--bg-subtle: rgba(255,255,255,0.025);
|
--bg-subtle: rgba(255,255,255,0.025);
|
||||||
--bg-inset: rgba(255,255,255,0.04);
|
--bg-inset: rgba(255,255,255,0.04);
|
||||||
--border: rgba(255,255,255,0.06);
|
--border: rgba(255,255,255,0.06);
|
||||||
@@ -29,12 +29,12 @@ input, textarea, button, select {
|
|||||||
--text: #e8e9f0;
|
--text: #e8e9f0;
|
||||||
--text-muted: rgba(232,233,240,0.55);
|
--text-muted: rgba(232,233,240,0.55);
|
||||||
--text-subtle: rgba(232,233,240,0.28);
|
--text-subtle: rgba(232,233,240,0.28);
|
||||||
/* Violet accent */
|
/* Teal accent */
|
||||||
--accent: #8b5cf6;
|
--accent: #14b8a6;
|
||||||
--accent-light: #a78bfa;
|
--accent-light: #2dd4bf;
|
||||||
--accent-hover: #9f70f8;
|
--accent-hover: #10a89a;
|
||||||
--accent-bg: rgba(139,92,246,0.12);
|
--accent-bg: rgba(20,184,166,0.12);
|
||||||
--accent-border: rgba(139,92,246,0.3);
|
--accent-border: rgba(20,184,166,0.3);
|
||||||
--accent-fg: #ffffff;
|
--accent-fg: #ffffff;
|
||||||
--teal: #34d399;
|
--teal: #34d399;
|
||||||
--teal-bg: rgba(52,211,153,0.1);
|
--teal-bg: rgba(52,211,153,0.1);
|
||||||
@@ -53,7 +53,7 @@ input, textarea, button, select {
|
|||||||
/* Bottom Navigation Bar */
|
/* Bottom Navigation Bar */
|
||||||
nav[style*="position: fixed"] {
|
nav[style*="position: fixed"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: #0b0b0e;
|
background: #060d0b;
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ function Input(props: React.InputHTMLAttributes<HTMLInputElement>) {
|
|||||||
onBlur={(e) => { setFocused(false); props.onBlur?.(e); }}
|
onBlur={(e) => { setFocused(false); props.onBlur?.(e); }}
|
||||||
style={{
|
style={{
|
||||||
width: "100%", padding: "8px 12px",
|
width: "100%", padding: "8px 12px",
|
||||||
background: "#151828",
|
background: "#101c18",
|
||||||
border: `1px solid ${focused ? "rgba(139,92,246,0.4)" : border}`,
|
border: `1px solid ${focused ? "rgba(20,184,166,0.4)" : border}`,
|
||||||
borderRadius: 8, color: "#e8e9f0",
|
borderRadius: 8, color: "#e8e9f0",
|
||||||
fontSize: 13, fontFamily: "inherit",
|
fontSize: 13, fontFamily: "inherit",
|
||||||
outline: "none", boxSizing: "border-box",
|
outline: "none", boxSizing: "border-box",
|
||||||
@@ -114,13 +114,13 @@ function SaveBtn({ pending, saved, onClick }: { pending: boolean; saved: boolean
|
|||||||
disabled={pending}
|
disabled={pending}
|
||||||
style={{
|
style={{
|
||||||
padding: "8px 20px", borderRadius: 8,
|
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",
|
border: saved ? "1px solid rgba(52,211,153,0.3)" : "none",
|
||||||
color: saved ? "#34d399" : "white",
|
color: saved ? "#34d399" : "white",
|
||||||
cursor: pending ? "default" : "pointer",
|
cursor: pending ? "default" : "pointer",
|
||||||
fontSize: 13, fontWeight: 600, fontFamily: "inherit",
|
fontSize: 13, fontWeight: 600, fontFamily: "inherit",
|
||||||
transition: "all 0.2s",
|
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"}
|
{pending ? "Saving…" : saved ? "Saved ✓" : "Save"}
|
||||||
@@ -211,7 +211,7 @@ function ProfileSection({ me }: { me: MemberRead }) {
|
|||||||
} finally { setUploading(false); }
|
} 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"}
|
{uploading ? "Uploading…" : "Upload"}
|
||||||
</label>
|
</label>
|
||||||
<button
|
<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" }} />
|
<Input value={folderInput} onChange={(e) => setFolderInput(e.target.value)} placeholder={defaultPath} style={{ fontFamily: "monospace" }} />
|
||||||
<div style={{ display: "flex", gap: 8, marginTop: 8 }}>
|
<div style={{ display: "flex", gap: 8, marginTop: 8 }}>
|
||||||
<button onClick={() => pathMutation.mutate(folderInput)} disabled={pathMutation.isPending}
|
<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"}
|
{pathMutation.isPending ? "Saving…" : "Save"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setEditingPath(false)}
|
<button onClick={() => setEditingPath(false)}
|
||||||
@@ -498,16 +498,16 @@ function MembersSection({ bandId, band, amAdmin, members, membersLoading }: { ba
|
|||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => inviteMutation.mutate()} disabled={inviteMutation.isPending}
|
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"}
|
{inviteMutation.isPending ? "Generating…" : "+ Generate invite link"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inviteLink && (
|
{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>
|
<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" }}>
|
<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
|
Dismiss
|
||||||
</button>
|
</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: 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 style={{ fontSize: 11, color: "rgba(232,233,240,0.28)", marginTop: 1 }}>{m.email}</div>
|
||||||
</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}
|
{m.role}
|
||||||
</span>
|
</span>
|
||||||
{amAdmin && m.role !== "admin" && (
|
{amAdmin && m.role !== "admin" && (
|
||||||
@@ -545,7 +545,7 @@ function MembersSection({ bandId, band, amAdmin, members, membersLoading }: { ba
|
|||||||
{/* Role legend */}
|
{/* Role legend */}
|
||||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginBottom: amAdmin ? 0 : 0 }}>
|
<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={{ 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 style={{ fontSize: 11, color: "rgba(232,233,240,0.28)", lineHeight: 1.55 }}>Upload, delete, manage members and storage</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: "10px 12px", background: "rgba(255,255,255,0.02)", border: `1px solid ${border}`, borderRadius: 8 }}>
|
<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>
|
<Label>Genre tags</Label>
|
||||||
<div style={{ display: "flex", gap: 5, flexWrap: "wrap", marginBottom: 8 }}>
|
<div style={{ display: "flex", gap: 5, flexWrap: "wrap", marginBottom: 8 }}>
|
||||||
{tags.map((t) => (
|
{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}
|
{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>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -671,13 +671,13 @@ function NavItem({ label, active, onClick }: { label: string; active: boolean; o
|
|||||||
padding: "8px 10px", borderRadius: 8,
|
padding: "8px 10px", borderRadius: 8,
|
||||||
border: "none", cursor: "pointer",
|
border: "none", cursor: "pointer",
|
||||||
fontSize: 13, fontFamily: "inherit", fontWeight: active ? 600 : 400,
|
fontSize: 13, fontFamily: "inherit", fontWeight: active ? 600 : 400,
|
||||||
background: active ? "rgba(139,92,246,0.1)" : hovered ? "rgba(255,255,255,0.04)" : "transparent",
|
background: active ? "rgba(20,184,166,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)",
|
color: active ? "#2dd4bf" : hovered ? "rgba(232,233,240,0.75)" : "rgba(232,233,240,0.45)",
|
||||||
transition: "all 0.12s",
|
transition: "all 0.12s",
|
||||||
position: "relative",
|
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}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -744,7 +744,7 @@ export function SettingsPage() {
|
|||||||
{section !== "profile" || searchParams.has("section") ? (
|
{section !== "profile" || searchParams.has("section") ? (
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => setSearchParams({})}
|
<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
|
← Settings
|
||||||
</button>
|
</button>
|
||||||
<div style={{ padding: "0 16px 24px" }}>
|
<div style={{ padding: "0 16px 24px" }}>
|
||||||
@@ -782,7 +782,7 @@ export function SettingsPage() {
|
|||||||
<div style={{ display: "flex", height: "100%", overflow: "hidden" }}>
|
<div style={{ display: "flex", height: "100%", overflow: "hidden" }}>
|
||||||
|
|
||||||
{/* Left nav */}
|
{/* 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>
|
<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" }} />
|
<div style={{ height: 1, background: border, margin: "12px 0" }} />
|
||||||
|
|
||||||
@@ -803,7 +803,7 @@ export function SettingsPage() {
|
|||||||
// Stay on same section if possible
|
// Stay on same section if possible
|
||||||
setSearchParams({ section }, { replace: true });
|
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) => (
|
{bands?.map((b) => (
|
||||||
<option key={b.id} value={b.id}>{b.name}</option>
|
<option key={b.id} value={b.id}>{b.name}</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user