Files
musicshare/apps/web/src/app/globals.css
T
chrisandClaude Opus 5.5 1f0cdb68f5
CI / check (push) Canceled after 0s
Password reset by email
"Forgot your password?" on web and mobile sends a one-time link (1 hour)
to the account's email; the website's reset page sets the new password,
signs out every other session and signs in. The request responds the same
whether or not the email has an account, and the email goes out in the
background so timing doesn't reveal it either. Only a hash of the link
token is stored. Sends through SMTP_* settings; without them, emails are
logged (and captured in tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
2026-10-06 01:05:30 -04:00

262 lines
15 KiB
CSS

:root {
--bg: #07070b;
--surface: rgba(255, 255, 255, 0.045);
--surface-solid: #12121a;
--surface-2: rgba(255, 255, 255, 0.08);
--border: rgba(255, 255, 255, 0.09);
--border-strong: rgba(255, 255, 255, 0.16);
--text: #f4f3fa;
--muted: #9b99ae;
/* Brand: the logo's purple-to-blue. */
--accent: #7c4dff;
--accent-2: #2f8cff;
--grad: linear-gradient(135deg, #7b2ff7 0%, #5b4cf0 50%, #2f8cff 100%);
--accent-text: #fff;
--live: #34d399;
--pin: #fbbf24;
--danger: #fb7185;
--radius: 20px;
--font-display: var(--font-space-grotesk), ui-sans-serif, system-ui, sans-serif;
--font-body: var(--font-inter), ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--spring: cubic-bezier(0.34, 1.56, 0.64, 1);
color-scheme: dark;
}
* { box-sizing: border-box; }
html { background: var(--bg); }
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text);
font: 15px/1.55 var(--font-body);
-webkit-font-smoothing: antialiased;
}
/* Slowly drifting colour behind everything, so the app never feels static. */
.ambient { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.ambient::before, .ambient::after {
content: ''; position: absolute; width: 60vmax; height: 60vmax; border-radius: 50%;
filter: blur(90px); opacity: 0.34;
}
.ambient::before { background: #6d28d9; top: -25vmax; left: -15vmax; animation: drift-a 28s ease-in-out infinite alternate; }
.ambient::after { background: #1d6fe0; bottom: -30vmax; right: -20vmax; animation: drift-b 34s ease-in-out infinite alternate; }
@keyframes drift-a { to { transform: translate(18vmax, 12vmax) scale(1.15); } }
@keyframes drift-b { to { transform: translate(-16vmax, -10vmax) scale(0.9); } }
a { color: inherit; }
h1, h2, .brand, .display { font-family: var(--font-display); letter-spacing: -0.02em; }
h1 { font-size: 1.85rem; margin: 0; line-height: 1.15; }
h2 { font-size: 1.1rem; margin: 0 0 0.5rem; }
input, textarea, select {
width: 100%;
padding: 0.7rem 0.85rem;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--surface-2);
color: var(--text);
font: inherit;
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
}
input:focus, textarea:focus, select:focus {
outline: none; border-color: var(--accent); background: rgba(124, 77, 255, 0.08);
box-shadow: 0 0 0 4px rgba(124, 77, 255, 0.2);
}
.container { max-width: 680px; margin: 0 auto; padding: 1.75rem 1rem 5rem; }
.stack { display: flex; flex-direction: column; gap: 1rem; }
.row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.service-name { display: flex; align-items: center; gap: 0.6rem; font-family: var(--font-display); font-size: 1.12rem; font-weight: 700; margin-bottom: 0.2rem; }
.service-badge { width: 1.7rem; height: 1.7rem; border-radius: 0.5rem; display: grid; place-items: center; color: #fff; font-size: 0.95rem; border: 1px solid rgba(255, 255, 255, 0.25); flex-shrink: 0; }
.avatar-picker { display: flex; gap: 1rem; align-items: center; }
.avatar-picker-actions { display: flex; flex-direction: column; gap: 0.4rem; }
.grow { flex: 1; min-width: 0; }
.muted { color: var(--muted); }
.error { color: var(--danger); margin: 0; }
.notice {
background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 0.85rem 1rem; margin: 0;
backdrop-filter: blur(16px);
}
.section-label { margin: 0; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); font-weight: 700; }
.narrow { max-width: 420px; margin: 2.5rem auto; }
.nav {
display: flex; justify-content: space-between; align-items: center;
padding: 0.9rem 1.25rem; border-bottom: 1px solid var(--border);
position: sticky; top: 0; background: rgba(7, 7, 11, 0.65); backdrop-filter: blur(18px) saturate(1.4); z-index: 10;
}
.brand { font-weight: 700; text-decoration: none; font-size: 1.2rem; display: inline-flex; align-items: center; gap: 0.5rem; }
.brand-logo { width: 30px; height: 30px; border-radius: 8px; box-shadow: 0 4px 14px -4px rgba(47, 140, 255, 0.6); }
.nav-links { display: flex; gap: 1.1rem; align-items: center; }
.nav-links a { text-decoration: none; color: var(--muted); transition: color 0.2s; }
.nav-links a:hover { color: var(--text); }
.nav-links a.btn-primary { color: var(--accent-text); }
.nav-links a.support-link { color: #f9a8d4; }
@media (max-width: 480px) { .nav-links a.support-link { display: none; } }
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
padding: 0.55rem 1.05rem; border-radius: 999px; border: 1px solid var(--border-strong);
background: var(--surface-2); color: var(--text); font: inherit; font-weight: 600;
text-decoration: none; cursor: pointer; white-space: nowrap;
transition: transform 0.25s var(--spring), border-color 0.2s, background 0.2s, box-shadow 0.25s, filter 0.2s;
}
.btn:hover { border-color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.12); transform: translateY(-1px); }
.btn:active { transform: scale(0.96); }
.btn:disabled { opacity: 0.55; cursor: default; transform: none; }
.btn-primary {
background: var(--grad); border-color: transparent; color: var(--accent-text);
box-shadow: 0 6px 22px -6px rgba(91, 76, 240, 0.6);
}
.btn-primary:hover { background: var(--grad); filter: brightness(1.08); box-shadow: 0 10px 28px -6px rgba(47, 140, 255, 0.65); }
.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-small { padding: 0.3rem 0.75rem; font-size: 0.82rem; }
.link-button { background: none; border: 0; color: var(--muted); font: inherit; cursor: pointer; padding: 0; transition: color 0.2s; }
.link-button:hover { color: var(--text); }
.card {
position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
padding: 1.1rem; backdrop-filter: blur(20px) saturate(1.3); overflow: hidden;
}
.card-header { position: relative; display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; margin-bottom: 0.85rem; }
.card-header .user-chip { flex-shrink: 0; }
.card-header > .row { flex-wrap: nowrap; min-width: 0; justify-content: flex-end; margin-left: auto; }
.card-header .status { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; display: inline-block; }
.card-header .status .eq { vertical-align: -1px; margin-right: 0.4rem; }
.card-header .pin { flex-shrink: 0; }
.form { display: flex; flex-direction: column; gap: 0.8rem; }
.form label { display: flex; flex-direction: column; gap: 0.35rem; font-weight: 600; font-size: 0.9rem; }
.form label.checkbox { flex-direction: row; align-items: center; gap: 0.5rem; font-weight: 500; }
/* Now-playing cards take their colour from the album art. */
.np-card { border-color: rgba(255, 255, 255, 0.11); }
.np-card.pinned { border-color: rgba(251, 191, 36, 0.45); box-shadow: 0 0 0 1px rgba(251, 191, 36, 0.15), 0 18px 40px -20px rgba(251, 191, 36, 0.35); }
.np-backdrop {
position: absolute; inset: -40%; z-index: 0; background-size: cover; background-position: center;
filter: blur(40px) saturate(2.2) brightness(1.1); opacity: 0.6; transform: scale(1.1); transition: opacity 0.6s;
}
.np-card.playing .np-backdrop { animation: breathe 9s ease-in-out infinite alternate; }
@keyframes breathe { to { transform: scale(1.3) rotate(8deg); opacity: 0.75; } }
.np-card::after {
content: ''; position: absolute; inset: 0; z-index: 0;
background: linear-gradient(180deg, rgba(7, 7, 11, 0.05), rgba(7, 7, 11, 0.45));
}
.np-card > *:not(.np-backdrop) { position: relative; z-index: 1; }
.pin {
background: none; border: 0; cursor: pointer; font-size: 1.25rem; line-height: 1; color: var(--muted);
padding: 0.15rem 0.25rem; transition: color 0.2s;
}
.pin:hover, .pin.pinned { color: var(--pin); }
.pin.pinned { text-shadow: 0 0 14px rgba(251, 191, 36, 0.6); }
.user-chip { display: inline-flex; align-items: center; gap: 0.55rem; text-decoration: none; font-weight: 600; }
.user-chip:hover .user-name { text-decoration: underline; }
.avatar { border-radius: 50%; object-fit: cover; flex-shrink: 0; display: block; }
.avatar-empty { display: grid; place-items: center; background: linear-gradient(135deg, #4a1278, #1872cf); color: #fff; font-weight: 700; font-family: var(--font-display); }
/* A spinning gradient ring while someone is listening. */
.avatar-ring { position: relative; display: inline-grid; place-items: center; border-radius: 50%; padding: 2px; flex-shrink: 0; }
.avatar-ring.live::before {
content: ''; position: absolute; inset: 0; border-radius: 50%;
background: conic-gradient(from 0deg, #7b2ff7, #2f8cff, #34d399, #7b2ff7); animation: spin 3.5s linear infinite;
}
.avatar-ring > * { position: relative; border: 2px solid var(--bg); border-radius: 50%; }
@keyframes spin { to { transform: rotate(360deg); } }
.status { font-size: 0.85rem; color: var(--muted); display: inline-flex; align-items: center; gap: 0.45rem; }
.status-live { color: var(--live); font-weight: 600; }
/* Equalizer bars: dancing while playing, resting when paused. */
.eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; }
.eq span { width: 3px; height: 100%; border-radius: 2px; background: currentColor; transform-origin: bottom; transform: scaleY(0.3); }
.eq.playing span { animation: eq 1s ease-in-out infinite alternate; }
.eq.playing span:nth-child(2) { animation-duration: 0.7s; animation-delay: -0.3s; }
.eq.playing span:nth-child(3) { animation-duration: 1.2s; animation-delay: -0.6s; }
.eq.playing span:nth-child(4) { animation-duration: 0.85s; animation-delay: -0.15s; }
@keyframes eq { 0% { transform: scaleY(0.25); } 50% { transform: scaleY(1); } 100% { transform: scaleY(0.45); } }
.track { display: flex; gap: 1rem; align-items: flex-start; }
.artwork {
width: 112px; height: 112px; border-radius: 14px; object-fit: cover; flex-shrink: 0; background: var(--surface-2);
box-shadow: 0 14px 30px -12px rgba(0, 0, 0, 0.75), 0 0 0 1px rgba(255, 255, 255, 0.08);
transition: transform 0.4s var(--spring);
}
.track:hover .artwork { transform: scale(1.04) rotate(-1deg); }
.artwork-empty { display: grid; place-items: center; font-size: 2rem; color: rgba(255, 255, 255, 0.85); background: linear-gradient(135deg, #4a1278, #1872cf); }
.artwork-sm { width: 44px; height: 44px; border-radius: 9px; object-fit: cover; flex-shrink: 0; box-shadow: 0 6px 14px -8px rgba(0, 0, 0, 0.8); }
.track-body { min-width: 0; flex: 1; }
.track-title { font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.track-artist { color: var(--text); opacity: 0.82; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.track-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-top: 0.8rem; }
.service-link {
font-size: 0.76rem; color: var(--text); text-decoration: none; padding: 0.2rem 0.55rem; border-radius: 999px;
background: rgba(255, 255, 255, 0.07); border: 1px solid rgba(255, 255, 255, 0.08); transition: background 0.2s, transform 0.25s var(--spring);
}
.service-link:hover { background: rgba(255, 255, 255, 0.16); transform: translateY(-1px); }
.service-link.inexact { opacity: 0.55; }
.profile-header { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.75rem; padding: 0.5rem 0 0.75rem; }
.profile-header h1 { font-size: 2.1rem; }
.profile-header .bio { margin: 0; max-width: 30rem; }
.stats { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
.stats span { padding: 0.3rem 0.8rem; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border); color: var(--muted); font-size: 0.88rem; }
.stats strong { color: var(--text); font-family: var(--font-display); }
.featured blockquote {
margin: 0.9rem 0 0; padding: 0.6rem 0.9rem; border-left: 3px solid transparent; font-style: italic;
border-image: var(--grad) 1; background: rgba(255, 255, 255, 0.04);
}
.search { position: relative; }
.search-results { list-style: none; margin: 0.5rem 0 0; padding: 0.4rem; background: var(--surface-solid); border: 1px solid var(--border); border-radius: 14px; }
.search-results li { padding: 0.4rem 0.45rem; border-radius: 10px; transition: background 0.15s; }
.search-results li:hover { background: var(--surface-2); }
.result { display: flex; gap: 0.6rem; align-items: center; background: none; border: 0; color: inherit; font: inherit; cursor: pointer; width: 100%; text-align: left; padding: 0; }
.recent { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.recent-row { display: flex; align-items: center; gap: 0.8rem; padding: 0.5rem 0.4rem; text-decoration: none; border-radius: 12px; transition: background 0.15s, transform 0.25s var(--spring); }
a.recent-row:hover { background: var(--surface-2); transform: translateX(3px); }
.recent li + li { border-top: 1px solid var(--border); }
.recent-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.recent-text > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recent-title { font-weight: 600; }
.recent-time { font-size: 0.8rem; white-space: nowrap; }
.artwork-sm.artwork-empty { display: grid; place-items: center; background: var(--surface-2); color: var(--muted); }
.qr-card { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; text-align: center; }
.qr { border-radius: 14px; box-shadow: 0 20px 50px -20px rgba(47, 140, 255, 0.6); }
.credit { margin: 0.6rem 0 0; font-size: 0.8rem; }
.picked { display: flex; gap: 0.75rem; align-items: center; }
.connections { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.5rem; }
.connection { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: 0.85rem 0; border-top: 1px solid var(--border); flex-wrap: wrap; }
.connection form input { width: 160px; }
.connection.disabled { opacity: 0.55; }
.steps { margin: 0.5rem 0; padding-left: 1.2rem; display: flex; flex-direction: column; gap: 0.35rem; }
.jellyfin-form { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-top: 0.6rem; }
.jellyfin-form input[name='serverUrl'] { grid-column: 1 / -1; }
.jellyfin-form .btn { justify-self: start; }
.webhook { margin-top: 0.4rem; font-size: 0.85rem; }
.webhook summary { cursor: pointer; color: var(--muted); }
.webhook p { margin: 0.35rem 0; }
.webhook code { font-size: 0.75rem; background: var(--surface-2); padding: 0.3rem 0.5rem; border-radius: 6px; word-break: break-all; flex: 1; min-width: 0; }
@media (max-width: 480px) { .jellyfin-form { grid-template-columns: 1fr; } }
.hero { padding: 4.5rem 0 3rem; display: flex; flex-direction: column; gap: 1.25rem; }
.hero h1 { font-size: 3.2rem; letter-spacing: -0.04em; line-height: 1.02; }
.gradient-text {
background: linear-gradient(90deg, #a78bfa, #7dd3fc, #60a5fa, #a78bfa); background-size: 300% 100%;
-webkit-background-clip: text; background-clip: text; color: transparent; animation: shimmer 8s linear infinite;
}
@keyframes shimmer { to { background-position: 300% 0; } }
.hero p { color: var(--muted); font-size: 1.15rem; margin: 0; max-width: 34rem; }
.hero .eq { height: 42px; gap: 5px; color: #8b9dff; }
.hero .eq span { width: 8px; border-radius: 4px; }
@media (max-width: 480px) {
.artwork { width: 84px; height: 84px; }
.nav-links { gap: 0.75rem; font-size: 0.9rem; }
.hero h1 { font-size: 2.3rem; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}