Merge design/overhaul-tasteskill: Flame Blue accent, home recomposition, motion layer

UI overhaul:
- Flame Blue (#1025A1) accent — official Wild Dragon brand standard
- Home: compact left-aligned header, primary ops + secondary 4-col rows
- Typography: hyphen fixes, sidebar version removed, topbar 48px
- Motion: staggered tile entry, button scale press, nav transitions
- Jobs stats: semantic card variants (active/failed/total)

🤖 Generated with Claude Code
This commit is contained in:
Claude 2026-06-02 13:28:54 +00:00
commit f8eda7fc37
11 changed files with 197 additions and 138 deletions

1
.claude/launch.json Normal file
View file

@ -0,0 +1 @@
{"version":"0.0.1","configurations":[{"name":"web-ui","runtimeExecutable":"npx","runtimeArgs":["serve","services/web-ui/public","--listen","47434","--no-clipboard"],"port":47434}]}

View file

@ -1,6 +1,6 @@
// app.jsx - main shell
const ACCENT = '#5B7CFA';
const ACCENT = '#1025A1';
function App() {
const [route, setRoute] = React.useState('home');
@ -41,8 +41,8 @@ function App() {
document.documentElement.style.setProperty('--accent', ACCENT);
document.documentElement.style.setProperty('--accent-soft', hexToRgba(ACCENT, 0.14));
document.documentElement.style.setProperty('--accent-soft-2', hexToRgba(ACCENT, 0.22));
document.documentElement.style.setProperty('--accent-text', lighten(ACCENT, 0.25));
document.documentElement.style.setProperty('--accent-hover', lighten(ACCENT, 0.08));
document.documentElement.style.setProperty('--accent-text', '#C7CFEA'); // Halo readable on dark
document.documentElement.style.setProperty('--accent-hover', '#1830B8');
}, []);
React.useEffect(() => {

View file

@ -436,10 +436,10 @@ function NewRecorderModal({ open, onClose }) {
<select className="field-input" value={growingOn ? 'h264_growing' : recCodec}
onChange={e => setRecCodec(e.target.value)} disabled={growingOn}
style={{ appearance: 'auto', opacity: growingOn ? 0.6 : 1 }}>
{growingOn && <option value="h264_growing">XDCAM HD422 (MXF OP1a) growing</option>}
<option value="hevc_nvenc">All-Intra HEVC (NVENC) GPU, growing</option>
<option value="h264_nvenc">H.264 (NVENC) GPU</option>
<option value="prores_hq">ProRes 422 HQ 4:2:2 CPU</option>
{growingOn && <option value="h264_growing">XDCAM HD422 (MXF OP1a, growing)</option>}
<option value="hevc_nvenc">All-Intra HEVC (NVENC, GPU, growing)</option>
<option value="h264_nvenc">H.264 (NVENC, GPU)</option>
<option value="prores_hq">ProRes 422 HQ (4:2:2, CPU)</option>
<option value="prores">ProRes 422</option>
<option value="prores_lt">ProRes 422 LT</option>
<option value="prores_proxy">ProRes 422 Proxy</option>
@ -461,8 +461,8 @@ function NewRecorderModal({ open, onClose }) {
) : (
<Field label="Bitrate" value="Quality-based (profile)" select />
)}
<Field label="Resolution" value="Auto — from source" select />
<Field label="Framerate" value="Auto — from source" select />
<Field label="Resolution" value="Auto (from source)" select />
<Field label="Framerate" value="Auto (from source)" select />
{/* #3: warn when the configured bitrate exceeds the probed source
bitrate re-encoding above source adds storage, not quality. */}
{codecUsesBitrate && (() => {

View file

@ -439,7 +439,7 @@ function UserPolicyRow({ user: u, expanded, onToggle, onChangeRole, onResetTotp
{!loading && !accessErr && (u.role === 'admin') && (
<div style={{ padding: '12px 0', fontSize: 12.5, color: 'var(--text-3)', display: 'flex', alignItems: 'center', gap: 6 }}>
<Icon name="check" size={12} style={{ color: 'var(--success)' }} />
Admin full access to every project.
Admin: full access to every project.
</div>
)}
{!loading && !accessErr && u.role !== 'admin' && (
@ -1883,7 +1883,7 @@ function AddNodeModal({ onClose }) {
<React.Fragment>
<div style={{ marginBottom: 10, padding: 10, border: '1px solid var(--accent)', background: 'var(--accent-soft)', borderRadius: 6 }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--accent-text)' }}>
This token is shown only once copy the command now.
This token is shown only once. Copy the command now.
</div>
</div>
<code className="mono" style={{ display: 'block', background: 'var(--bg-2)', padding: 12, borderRadius: 6, fontSize: 11.5, lineHeight: 1.5, wordBreak: 'break-all', whiteSpace: 'pre-wrap' }}>{command}</code>
@ -2060,7 +2060,7 @@ function TotpSection() {
</div>
<div>
<div style={{ fontSize: 12, color: 'var(--text-2)', marginBottom: 8 }}>
Scan the QR with Google Authenticator, Authy, or 1Password or enter this secret manually:
Scan the QR with Google Authenticator, Authy, or 1Password, or enter this secret manually:
</div>
<div className="mono" style={{ fontSize: 12, background: 'var(--bg-2)', padding: '6px 10px', borderRadius: 5, wordBreak: 'break-all', marginBottom: 12 }}>{enroll.secret}</div>
<div className="field">
@ -2264,7 +2264,7 @@ function StorageWarningBanner() {
}}>
<Icon name="alert" size={20} style={{ color: 'var(--danger)', flexShrink: 0, marginTop: 1 }} />
<div style={{ fontSize: 13, fontWeight: 700, letterSpacing: '0.02em', lineHeight: 1.5, color: 'var(--text-1)' }}>
WARNING THESE SETTINGS ARE MEANT TO BE SET ONCE AT INITIAL DEPLOYMENT.
WARNING: THESE SETTINGS ARE MEANT TO BE SET ONCE AT INITIAL DEPLOYMENT.
CHANGING STORAGE PATHS MID-CYCLE CAN CAUSE DATABASE CORRUPTION AND FILE LOSS.
PLEASE USE WITH CAUTION.
</div>

View file

@ -210,7 +210,7 @@
// An expired/used ticket means the user must start over.
if (r.status === 401 && /ticket/.test(body.error || '')) {
setTicket(null); setCode(''); setPassword('');
setError('Session timed out — please sign in again.');
setError('Session timed out. Please sign in again.');
} else {
setError(body.error || ('Verification failed: ' + r.status));
}

View file

@ -134,7 +134,6 @@ function Home({ navigate }) {
<div className="launcher-inner">
<div className="launcher-hero">
<span className="launcher-logo-wrap">
<span className="launcher-logo-pulse" aria-hidden="true" />
<img
className="launcher-logo"
src="img/dragon-logo.png"
@ -142,15 +141,17 @@ function Home({ navigate }) {
draggable="false"
/>
</span>
<h1 className="launcher-wordmark">DRAGONFLIGHT</h1>
<p className="launcher-kicker">Let's Create</p>
<p className="launcher-tagline">
Media Asset Management &amp; Production Platform
</p>
<div className="launcher-hero-text">
<h1 className="launcher-wordmark">DRAGONFLIGHT</h1>
<p className="launcher-tagline">
Media Asset Management &amp; Production Platform
</p>
</div>
</div>
{/* Primary ops: Library, Recorders, Playout */}
<div className="launcher-grid">
{tiles.map(t => (
{tiles.slice(0, 3).map(t => (
<button
key={t.id}
className={'launcher-tile tone-' + t.tone}
@ -167,38 +168,55 @@ function Home({ navigate }) {
</span>
</button>
))}
</div>
{/* Secondary: Downloads, Jobs, Dashboard, Settings — smaller tiles */}
<div className="launcher-settings-row">
{tiles.slice(3).map(t => (
<button
key={t.id}
className={'launcher-tile tone-' + t.tone + ' launcher-tile-secondary'}
onClick={() => t.id === '__downloads' ? setShowDownloads(true) : navigate(t.id)}
>
<span className="launcher-tile-icon">
<Icon name={t.icon} size={20} />
</span>
<span className="launcher-tile-label">{t.label}</span>
<span className="launcher-tile-sub">{t.sub}</span>
<span className="launcher-tile-desc">{t.desc}</span>
<span className="launcher-tile-arrow">
<Icon name="arrowRight" size={13} />
</span>
</button>
))}
<button
className="launcher-tile tone-ghost launcher-tile-secondary"
onClick={() => navigate('dashboard')}
>
<span className="launcher-tile-icon">
<Icon name="layout" size={22} />
<Icon name="layout" size={20} />
</span>
<span className="launcher-tile-label">Dashboard</span>
<span className="launcher-tile-sub">Operations view</span>
<span className="launcher-tile-desc">
Recent activity, job queue, cluster health.
Live recorders, job queue, cluster health.
</span>
<span className="launcher-tile-arrow">
<Icon name="arrowRight" size={14} />
<Icon name="arrowRight" size={13} />
</span>
</button>
</div>
<div className="launcher-settings-row">
<button
className={'launcher-tile tone-' + settingsTile.tone + ' launcher-tile-settings'}
className={'launcher-tile tone-' + settingsTile.tone + ' launcher-tile-secondary launcher-tile-settings'}
onClick={() => navigate(settingsTile.id)}
>
<span className="launcher-tile-icon">
<Icon name={settingsTile.icon} size={26} />
<Icon name={settingsTile.icon} size={20} />
</span>
<span className="launcher-tile-label">{settingsTile.label}</span>
<span className="launcher-tile-sub">{settingsTile.sub}</span>
<span className="launcher-tile-desc">{settingsTile.desc}</span>
<span className="launcher-tile-arrow">
<Icon name="arrowRight" size={14} />
<Icon name="arrowRight" size={13} />
</span>
</button>
</div>
@ -340,7 +358,7 @@ function DownloadsModal({ onClose }) {
<span className="btn primary sm" aria-disabled="true" style={{ opacity: 0.5, pointerEvents: 'none' }}>
<Icon name="download" />Teams ISO (.exe)
</span>
<span style={{ fontSize: 11.5, color: 'var(--text-3)' }}>coming soon file pending</span>
<span style={{ fontSize: 11.5, color: 'var(--text-3)' }}>coming soon, file pending</span>
</>
)}
</div>
@ -540,7 +558,7 @@ function Dashboard({ navigate }) {
const alerts = [
...erroredRecorders.map(r => ({
key: 'r-' + r.id, sev: 'danger',
title: r.name + ' recorder error',
title: r.name + ': recorder error',
meta: r.error_message || r.url || 'signal lost',
action: 'Reconnect', to: 'recorders',
})),
@ -795,7 +813,7 @@ function OnAirEmpty({ sources, onStart }) {
<span className="onair-empty-icon"><Icon name="record" size={18} /></span>
<div className="onair-empty-copy">
<div className="onair-empty-title">Nothing on air</div>
<div className="onair-empty-sub">All recorders are idle start a source to begin capturing.</div>
<div className="onair-empty-sub">All recorders are idle. Start a source to begin capturing.</div>
</div>
<button className="btn primary" onClick={onStart}><Icon name="plus" size={14} />Start a recorder</button>
</div>

View file

@ -172,26 +172,26 @@ function Jobs({ navigate }) {
<div className="page-body">
<div className="jobs-stats">
<div className="stat-card">
<div className={'stat-card' + (counts.running > 0 ? ' stat-card--active' : '')}>
<div className="label">Running</div>
<div className="value">{counts.running}</div>
<div className="delta">{counts.queued} queued</div>
</div>
<div className="stat-card">
<div className="label">Completed</div>
<div className="value">{counts.done}</div>
<div className="value stat-value--muted">{counts.done}</div>
<div className="delta">Total done</div>
</div>
<div className="stat-card">
<div className={'stat-card' + (counts.failed > 0 ? ' stat-card--failed' : '')}>
<div className="label">Failed</div>
<div className="value">{counts.failed}</div>
<div className={'value' + (counts.failed > 0 ? ' stat-value--danger' : ' stat-value--muted')}>{counts.failed}</div>
<div className={'delta' + (counts.failed > 0 ? ' delta-warn' : '')}>
{counts.failed > 0 ? 'Needs attention' : 'All clear'}
</div>
</div>
<div className="stat-card">
<div className="stat-card stat-card--quiet">
<div className="label">Total jobs</div>
<div className="value">{counts.all}</div>
<div className="value stat-value--muted">{counts.all}</div>
<div className="delta muted delta-tiny">Updated {Math.round((Date.now()-lastFetch)/1000)}s ago</div>
</div>
</div>

View file

@ -166,7 +166,6 @@ function Sidebar({ active, onNavigate, me, collapsed, onToggle }) {
>
<img className="brand-logo" src="img/dragon-logo.png" alt="Dragonflight" draggable="false" />
<div className="brand-name">Dragonflight</div>
<div className="brand-sub">v1.2.0</div>
</div>
<button
className="icon-btn sidebar-toggle"

View file

@ -253,10 +253,10 @@
/* Convert the dark logo to white so it pops on the dark sidebar.
brightness(0) collapses everything to black, invert(1) flips to white.
Works on both the original dark PNG and any transparent white PNG. */
filter: brightness(0) invert(1) drop-shadow(0 0 6px rgba(91, 124, 250, 0.25));
filter: brightness(0) invert(1) drop-shadow(0 0 6px rgba(16, 37, 161, 0.35));
}
.sidebar-header:hover .brand-logo {
filter: brightness(0) invert(1) drop-shadow(0 0 10px rgba(91, 124, 250, 0.45));
filter: brightness(0) invert(1) drop-shadow(0 0 10px rgba(16, 37, 161, 0.55));
}
/* ============================================================
@ -268,139 +268,132 @@
overflow-y: auto;
overflow-x: hidden;
background:
radial-gradient(1100px 600px at 50% 0%, rgba(91, 124, 250, 0.10), transparent 65%),
radial-gradient(900px 600px at 50% 100%, rgba(181, 124, 250, 0.06), transparent 60%),
radial-gradient(1100px 500px at 50% -10%, rgba(16, 37, 161, 0.08), transparent 60%),
var(--bg-0);
display: flex;
align-items: flex-start;
justify-content: center;
padding: 48px 32px 64px;
padding: 40px 32px 64px;
}
.launcher-inner {
width: 100%;
max-width: 1160px;
display: flex;
flex-direction: column;
align-items: center;
gap: 40px;
align-items: stretch;
gap: 32px;
}
.launcher-hero {
display: flex;
flex-direction: column;
flex-direction: row;
align-items: center;
gap: 14px;
text-align: center;
margin-top: 8px;
gap: 18px;
text-align: left;
padding-bottom: 24px;
border-bottom: 1px solid var(--border);
}
/* Logo wrapper holds the animated pulse halo behind the image. */
/* Logo wrapper — compact, left-aligned mark. */
.launcher-logo-wrap {
position: relative;
display: inline-grid;
place-items: center;
width: 180px;
height: 180px;
}
.launcher-logo-pulse {
position: absolute;
left: 50%;
top: 50%;
width: 200px;
height: 200px;
transform: translate(-50%, -50%) scale(0.85);
border-radius: 50%;
pointer-events: none;
z-index: 0;
background: radial-gradient(
circle at center,
color-mix(in srgb, var(--accent) 55%, transparent) 0%,
color-mix(in srgb, var(--accent) 22%, transparent) 38%,
transparent 68%
);
filter: blur(2px);
animation: launcherLogoPulse 3.4s ease-in-out infinite;
}
@keyframes launcherLogoPulse {
0%, 100% { transform: translate(-50%, -50%) scale(0.82); opacity: 0.45; }
50% { transform: translate(-50%, -50%) scale(1.08); opacity: 0.9; }
width: 52px;
height: 52px;
flex-shrink: 0;
}
.launcher-logo-pulse { display: none; }
.launcher-logo {
position: relative;
z-index: 1;
width: 180px;
height: 180px;
width: 52px;
height: 52px;
object-fit: contain;
/* Convert to white - same approach as .brand-logo. */
filter:
brightness(0) invert(1)
drop-shadow(0 0 24px rgba(91, 124, 250, 0.28))
drop-shadow(0 0 48px rgba(91, 124, 250, 0.18));
animation: launcherLogoIn 600ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
drop-shadow(0 0 8px rgba(16, 37, 161, 0.35));
animation: launcherLogoIn 400ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
@keyframes launcherLogoIn {
from { opacity: 0; transform: translateY(8px) scale(0.96); }
to { opacity: 1; transform: translateY(0) scale(1); }
from { opacity: 0; transform: scale(0.88); }
to { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.launcher-logo-pulse { animation: none; opacity: 0.5; }
.launcher-logo { animation: none; }
}
.launcher-hero-text {
display: flex;
flex-direction: column;
gap: 3px;
flex: 1;
min-width: 0;
}
.launcher-wordmark {
margin: 0;
font-size: 44px;
font-size: 22px;
font-weight: 700;
letter-spacing: 0.12em;
letter-spacing: 0.08em;
line-height: 1;
color: var(--text-1);
text-shadow: 0 0 32px rgba(91, 124, 250, 0.15);
}
.launcher-kicker {
margin: 2px 0 0;
color: var(--accent);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.22em;
text-transform: uppercase;
text-shadow: none;
}
.launcher-kicker { display: none; }
.launcher-tagline {
margin: 0;
color: var(--text-3);
font-size: 13.5px;
letter-spacing: 0.02em;
white-space: nowrap;
}
@media (max-width: 480px) {
.launcher-tagline { font-size: 11.5px; letter-spacing: 0; }
}
.launcher-tagline-motto {
margin-top: 6px;
color: var(--accent);
font-style: italic;
font-size: 15px;
letter-spacing: 0.04em;
font-size: 12.5px;
letter-spacing: 0.01em;
}
.launcher-tagline-motto { display: none; }
.launcher-grid {
width: 100%;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
gap: 12px;
}
@media (max-width: 960px) { .launcher-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .launcher-grid { grid-template-columns: 1fr; } }
/* Settings sits on its own centered row beneath the main grid. */
/* Secondary tiles: smaller vertical footprint, quieter treatment. */
.launcher-tile-secondary {
min-height: 120px;
padding: 16px 18px 18px;
}
.launcher-tile-secondary .launcher-tile-icon {
width: 34px;
height: 34px;
border-radius: 8px;
margin-bottom: 4px;
}
.launcher-tile-secondary .launcher-tile-icon svg {
width: 17px;
height: 17px;
}
.launcher-tile-secondary .launcher-tile-label {
font-size: 15px;
}
.launcher-tile-secondary .launcher-tile-desc {
font-size: 12px;
}
/* Settings row: holds the 4 secondary tiles. */
.launcher-settings-row {
width: 100%;
display: flex;
justify-content: center;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.launcher-tile-settings {
width: 100%;
max-width: calc((100% - 28px) / 3);
max-width: 100%;
}
@media (max-width: 960px) {
.launcher-settings-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
.launcher-settings-row { grid-template-columns: 1fr; }
}
@media (max-width: 960px) { .launcher-tile-settings { max-width: calc((100% - 14px) / 2); } }
@media (max-width: 620px) { .launcher-tile-settings { max-width: 100%; } }
.launcher-tile {
position: relative;
@ -507,10 +500,10 @@
/* Tone variants - colour the icon tile + halo, leave the body text
neutral so the tile reads as a button, not a banner. */
.launcher-tile.tone-accent {
--tile-tint: rgba(91, 124, 250, 0.18);
--tile-tint: rgba(16, 37, 161, 0.18);
--tile-icon-bg: var(--accent-soft);
--tile-icon-fg: var(--accent-text);
--tile-icon-border: rgba(91, 124, 250, 0.30);
--tile-icon-border: rgba(16, 37, 161, 0.30);
}
.launcher-tile.tone-live {
--tile-tint: rgba(255, 59, 48, 0.18);
@ -557,7 +550,7 @@
gap: 16px;
align-items: center;
flex-wrap: wrap;
justify-content: center;
justify-content: flex-start;
margin-top: 4px;
}
.launcher-status-pip {
@ -585,12 +578,46 @@
.launcher-footer {
margin-top: 20px;
text-align: center;
text-align: left;
font-size: 11px;
letter-spacing: 0.04em;
color: var(--text-4);
}
/* ============================================================
Motion layer entry stagger for launcher tiles.
Respects prefers-reduced-motion.
============================================================ */
@media (prefers-reduced-motion: no-preference) {
.launcher-tile {
animation: tileEnter 360ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.launcher-grid .launcher-tile:nth-child(1) { animation-delay: 60ms; }
.launcher-grid .launcher-tile:nth-child(2) { animation-delay: 110ms; }
.launcher-grid .launcher-tile:nth-child(3) { animation-delay: 160ms; }
.launcher-grid .launcher-tile:nth-child(4) { animation-delay: 210ms; }
.launcher-grid .launcher-tile:nth-child(5) { animation-delay: 250ms; }
.launcher-grid .launcher-tile:nth-child(6) { animation-delay: 290ms; }
.launcher-settings-row .launcher-tile { animation-delay: 320ms; }
@keyframes tileEnter {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
}
/* Tactile press feedback on high-stakes operational buttons. */
.btn-record:active,
button.btn.primary:active {
transform: scale(0.97);
transition: transform 80ms ease-out;
}
/* Smooth active-item transition in sidebar nav. */
.nav-item {
transition: background 150ms ease-out, color 150ms ease-out;
}
/* ============================================================
Recorder row - signal indicator with a pulsing dot when
actually receiving frames. Closes part of #2.
@ -640,7 +667,7 @@
stroke-width: 1.5;
}
.bmd-card-trace {
stroke: rgba(91, 124, 250, 0.12);
stroke: rgba(16, 37, 161, 0.12);
stroke-width: 0.5;
fill: none;
}

View file

@ -171,7 +171,7 @@
gap: 20px;
align-items: stretch;
background:
radial-gradient(ellipse at top, rgba(91,124,250,0.05), transparent 60%),
radial-gradient(ellipse at top, rgba(16,37,161,0.05), transparent 60%),
var(--bg-0);
}
.decklink-card-face {
@ -971,7 +971,7 @@
border-bottom: 1px solid var(--border);
}
.epg-week-day:last-child { border-bottom: 0; }
.epg-week-day.today { background: linear-gradient(180deg, rgba(91,124,250,0.04) 0%, transparent 80%); }
.epg-week-day.today { background: linear-gradient(180deg, rgba(16,37,161,0.04) 0%, transparent 80%); }
.epg-week-dayhead {
display: flex; align-items: baseline; gap: 10px;
padding: 10px 20px 6px;
@ -1060,6 +1060,20 @@
line-height: 1.1;
}
/* Stat card semantic variants */
.stat-card--active {
border-color: var(--accent-soft-2);
background: linear-gradient(180deg, var(--accent-soft) 0%, var(--bg-1) 60%);
}
.stat-card--active .value { color: var(--accent-text); }
.stat-card--failed {
border-color: rgba(255,91,91,0.25);
background: linear-gradient(180deg, var(--danger-soft) 0%, var(--bg-1) 60%);
}
.stat-card--quiet { opacity: 0.75; }
.stat-value--muted { color: var(--text-2); }
.stat-value--danger { color: var(--danger); }
/* ========== Settings ========== */
.settings-nav {
display: flex; flex-direction: column; gap: 2px;
@ -1222,7 +1236,7 @@
.token-tier.popular {
border-color: var(--accent);
background: linear-gradient(180deg, var(--accent-soft) 0%, var(--bg-1) 40%);
box-shadow: 0 0 24px rgba(91,124,250,0.10);
box-shadow: 0 0 24px rgba(16,37,161,0.12);
}
.token-tier-badge {
position: absolute;
@ -1345,7 +1359,7 @@
border-radius: 99px;
flex-shrink: 0;
}
.search-result-kind.kind-asset { color: #B4C3FF; border-color: rgba(91,124,250,0.25); }
.search-result-kind.kind-asset { color: #C7CFEA; border-color: rgba(16,37,161,0.30); }
.search-result-kind.kind-project { color: #FFD89B; border-color: rgba(245,166,35,0.25); }
.search-result-kind.kind-recorder { color: #FFAFAF; border-color: rgba(255,91,91,0.25); }
.search-result-kind.kind-job { color: #9EE7D2; border-color: rgba(45,212,168,0.25); }

View file

@ -18,12 +18,12 @@
--text-3: #8B92A0; /* WCAG AA (#133) - was #6B7280, 4.06:1 vs --bg-0; now ~7.5:1 */
--text-4: #6B7280;
/* accent (blue, frame.io-ish) */
--accent: #5B7CFA;
--accent-hover: #6E8BFF;
--accent-soft: rgba(91, 124, 250, 0.14);
--accent-soft-2: rgba(91, 124, 250, 0.22);
--accent-text: #B4C3FF;
/* accent — Flame Blue (Wild Dragon brand, Pantone 286 C) */
--accent: #1025A1;
--accent-hover: #1830B8;
--accent-soft: rgba(16, 37, 161, 0.14);
--accent-soft-2: rgba(16, 37, 161, 0.22);
--accent-text: #C7CFEA;
/* status */
--success: #2DD4A8;
@ -51,7 +51,7 @@
/* sizing (density tweakable) */
--sidebar-w: 232px;
--row-h: 44px;
--topbar-h: 56px;
--topbar-h: 48px;
--gap: 16px;
/* fonts */
@ -244,10 +244,10 @@ a { color: inherit; text-decoration: none; }
.avatar {
width: 28px; height: 28px;
border-radius: 50%;
background: linear-gradient(135deg, #5B7CFA, #B57CFA);
background: var(--accent);
display: grid; place-items: center;
font-weight: 600; font-size: 11px;
color: white;
font-weight: 700; font-size: 11px;
color: #fff;
flex-shrink: 0;
}
.user-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
@ -350,7 +350,7 @@ a { color: inherit; text-decoration: none; }
transition: background 80ms, border 80ms, color 80ms;
white-space: nowrap;
}
.btn.primary { background: var(--accent); color: white; }
.btn.primary { background: var(--accent); color: #fff; }
.btn.primary:hover { background: var(--accent-hover); }
.btn.ghost { background: transparent; color: var(--text-2); }
.btn.ghost:hover { background: var(--hover); color: var(--text-1); }