add extra colours in index.css and replace hex values
All checks were successful
CI / Maven unit tests (push) Successful in 14s
CI / Build and push images (push) Has been skipped
CI / Deploy to server (push) Has been skipped

This commit is contained in:
2026-08-16 16:13:51 +01:00
parent 78b90ebb26
commit f3056954c9
13 changed files with 77 additions and 58 deletions

View File

@@ -76,8 +76,8 @@ const Topbar = ({
{user.name}
{isStreaming && (
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#B5432E] opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-[#B5432E]" />
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-live opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-live" />
</span>
)}
</span>
@@ -86,7 +86,7 @@ const Topbar = ({
onClick={() => navigate("/profile")} />
<DropdownItem item="Logout"
onClick={() => handleLogout()}
className={"text-[#B5432E] font-medium"} />
className={"text-error font-medium"} />
</Dropdown>
</div>
) :

View File

@@ -5,7 +5,7 @@ type Props = React.ButtonHTMLAttributes<HTMLButtonElement>;
const PrimaryButton: React.FC<Props> = ({ className = "", ...props }) => {
return (
<button
className={`bg-[#A85A38] text-[#FDFBF7] font-body text-sm rounded-lg hover:bg-[#94492A] disabled:bg-[#C9AE9C] transition-colors duration-150 h-10 px-4 ${className}`}
className={`bg-terracotta text-on-accent font-body text-sm rounded-lg hover:bg-terracotta-hover disabled:bg-terracotta-disabled transition-colors duration-150 h-10 px-4 ${className}`}
{...props}
/>
);

View File

@@ -70,7 +70,7 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
className="w-full aspect-video object-cover group-hover:scale-105 transition-transform duration-200"
/>
<span className="absolute top-1.5 left-1.5 bg-[#2B2620]/75 text-[#FBF8F2] px-1.5 py-0.5 rounded text-xs font-data pointer-events-none">
<span className="absolute top-1.5 left-1.5 bg-text-primary/75 text-on-dark px-1.5 py-0.5 rounded text-xs font-data pointer-events-none">
{formatTime(duration)}
</span>
@@ -82,7 +82,7 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
>
<button
onClick={(e) => stopAndRun(e, () => { setMenuOpen(v => !v); setConfirmDelete(false); })}
className="bg-[#2B2620]/75 hover:bg-[#2B2620]/90 text-[#FBF8F2] rounded p-0.5 transition-colors"
className="bg-text-primary/75 hover:bg-text-primary/90 text-on-dark rounded p-0.5 transition-colors"
>
<MoreVertical size={14} />
</button>
@@ -101,7 +101,7 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
{onDelete && (
<button
onClick={(e) => stopAndRun(e, () => setConfirmDelete(true))}
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-[#B5432E] hover:bg-hover"
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-error hover:bg-hover"
>
<Trash2 size={13} /> Delete
</button>
@@ -113,7 +113,7 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
<div className="flex gap-1.5">
<button
onClick={(e) => stopAndRun(e, onDelete!)}
className="flex-1 text-xs bg-[#B5432E] text-[#FDFBF7] rounded px-2 py-1 hover:bg-[#94492A] transition-colors"
className="flex-1 text-xs bg-error text-on-accent rounded px-2 py-1 hover:bg-terracotta-hover transition-colors"
>
Delete
</button>

View File

@@ -19,9 +19,28 @@
--color-text-secondary: #6E6559;
--color-muted: #9A9084;
/* ── Accents ─────────────────────────────────────────────────────────── */
--color-terracotta: #A85A38;
--color-terracotta-hover: #94492A;
--color-terracotta-light: #C87A52;
--color-terracotta-disabled: #C9AE9C;
--color-olive: #6E7A50;
/* ── Status ──────────────────────────────────────────────────────────── */
--color-live: #B5432E;
--color-error: #B5432E;
/* ── Ink on dark / accent surfaces ───────────────────────────────────── */
--color-on-dark: #FBF8F2;
--color-on-accent: #FDFBF7;
/* ── Lines, chips, placeholders ──────────────────────────────────────── */
--color-hairline: #F0EBE2;
--color-hover: #E7E0D2;
--color-selected: #F6EFE6;
--color-ring: #E2DACB;
--color-inactive: #DED7CA;
--color-inactive-strong: #C4BAA9;
}
:root {
@@ -43,7 +62,7 @@ body {
@utility font-heading {
font-family: "Newsreader", serif;
font-weight: 400;
color: #2B2620;
color: var(--color-text-primary);
}
/* Clip trim slider (react-range-slider-input). Its own stylesheet ships blue
@@ -55,16 +74,16 @@ body {
}
#range-slider .range-slider__range {
background: #C87A52;
background: var(--color-terracotta-light);
}
#range-slider .range-slider__thumb {
width: 18px;
height: 18px;
background: #A85A38;
background: var(--color-terracotta);
box-shadow: 0 1px 2px rgba(90, 40, 20, 0.2);
}
#range-slider .range-slider__thumb:focus-visible {
box-shadow: 0 0 0 4px rgba(168, 90, 56, 0.35);
box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-terracotta) 35%, transparent);
}

View File

@@ -142,7 +142,7 @@ const ClipEdit = () => {
<Playbar
video={videoRef.current}
videoMetadata={metadata}
className={"w-full accent-[#A85A38]"}
className={"w-full accent-terracotta"}
/>
<PlaybackSlider
@@ -150,7 +150,7 @@ const ClipEdit = () => {
videoMetadata={metadata}
sliderValue={playbackValue}
setSliderValue={setPlaybackValue}
className={"w-full accent-[#A85A38]"}
className={"w-full accent-terracotta"}
/>
<ClipRangeSlider
@@ -162,7 +162,7 @@ const ClipEdit = () => {
/>
{error && (
<div className={"text-[#B5432E] text-center mt-2"}>
<div className={"text-error text-center mt-2"}>
{error}
</div>
)}

View File

@@ -84,7 +84,7 @@ const EditClip = () => {
/>
</div>
{error && <p className="text-sm text-[#B5432E]">{error}</p>}
{error && <p className="text-sm text-error">{error}</p>}
<div className="flex items-center gap-3 pt-1">
<PrimaryButton onClick={handleSave} disabled={saving} className="w-28">

View File

@@ -84,7 +84,7 @@ const EditVod = () => {
/>
</div>
{error && <p className="text-sm text-[#B5432E]">{error}</p>}
{error && <p className="text-sm text-error">{error}</p>}
<div className="flex items-center gap-3 pt-1">
<PrimaryButton onClick={handleSave} disabled={saving} className="w-28">

View File

@@ -59,7 +59,7 @@ const Home = () => {
className="bg-card border border-hairline rounded-xl p-5 flex flex-col gap-3 shadow-sm"
>
<div className="w-9 h-9 rounded-lg flex items-center justify-center shrink-0">
<Icon size={18} className="text-[#A85A38]" />
<Icon size={18} className="text-terracotta" />
</div>
<div>
<p className="font-body text-text-strong font-bold text-sm mb-1">{title}</p>

View File

@@ -233,7 +233,7 @@ const MediaPlayer = ({ noun, getMediaUrl, fetchDetails }: MediaPlayerProps) => {
{/* Player */}
<div
ref={containerRef}
className="relative bg-[#2B2620] rounded-xl overflow-hidden flex-1 min-h-0 select-none"
className="relative bg-text-primary rounded-xl overflow-hidden flex-1 min-h-0 select-none"
onMouseMove={showControls}
onMouseLeave={() => isPlaying && setControlsVisible(false)}
onClick={togglePlay}
@@ -265,7 +265,7 @@ const MediaPlayer = ({ noun, getMediaUrl, fetchDetails }: MediaPlayerProps) => {
{/* Buffering spinner */}
{isWaiting && mediaUrl && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="w-11 h-11 rounded-full border-[3px] border-[#FBF8F2]/20 border-t-[#FBF8F2] animate-spin" />
<div className="w-11 h-11 rounded-full border-[3px] border-on-dark/20 border-t-on-dark animate-spin" />
</div>
)}
@@ -282,20 +282,20 @@ const MediaPlayer = ({ noun, getMediaUrl, fetchDetails }: MediaPlayerProps) => {
{/* Scrub bar */}
<div
ref={progressRef}
className="relative h-1 rounded-full bg-[#FBF8F2]/25 cursor-pointer mb-3 group/prog hover:h-1.5 transition-[height] duration-150"
className="relative h-1 rounded-full bg-on-dark/25 cursor-pointer mb-3 group/prog hover:h-1.5 transition-[height] duration-150"
onMouseDown={(e) => { setIsDragging(true); seekTo(e.clientX); }}
>
<div className="absolute inset-y-0 left-0 bg-[#FBF8F2]/35 rounded-full" style={{ width: `${bufferedPct}%` }} />
<div className="absolute inset-y-0 left-0 bg-[#C87A52] rounded-full" style={{ width: `${progressPct}%` }} />
<div className="absolute inset-y-0 left-0 bg-on-dark/35 rounded-full" style={{ width: `${bufferedPct}%` }} />
<div className="absolute inset-y-0 left-0 bg-terracotta-light rounded-full" style={{ width: `${progressPct}%` }} />
<div
className="absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-[#FBF8F2] rounded-full shadow opacity-0 group-hover/prog:opacity-100 transition-opacity"
className="absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-on-dark rounded-full shadow opacity-0 group-hover/prog:opacity-100 transition-opacity"
style={{ left: `calc(${progressPct}% - 6px)` }}
/>
</div>
{/* Buttons row */}
<div className="flex items-center gap-3 text-[#FBF8F2]">
<button onClick={togglePlay} className="hover:text-[#FBF8F2]/70 transition-colors">
<div className="flex items-center gap-3 text-on-dark">
<button onClick={togglePlay} className="hover:text-on-dark/70 transition-colors">
{isPlaying
? <Pause size={20} fill="currentColor" />
: <Play size={20} fill="currentColor" />}
@@ -303,7 +303,7 @@ const MediaPlayer = ({ noun, getMediaUrl, fetchDetails }: MediaPlayerProps) => {
{/* Volume — slider expands on hover */}
<div className="flex items-center gap-1.5 group/vol">
<button onClick={toggleMute} className="hover:text-[#FBF8F2]/70 transition-colors">
<button onClick={toggleMute} className="hover:text-on-dark/70 transition-colors">
<VolumeIcon size={18} />
</button>
<div className="overflow-hidden w-0 group-hover/vol:w-16 transition-[width] duration-200">
@@ -312,18 +312,18 @@ const MediaPlayer = ({ noun, getMediaUrl, fetchDetails }: MediaPlayerProps) => {
min={0} max={1} step={0.02}
value={effectiveVolume}
onChange={handleVolumeChange}
className="w-16 accent-[#FBF8F2] cursor-pointer"
className="w-16 accent-on-dark cursor-pointer"
/>
</div>
</div>
<span className="text-xs font-data tabular-nums text-[#FBF8F2]/80">
<span className="text-xs font-data tabular-nums text-on-dark/80">
{formatTime(currentTime)} / {formatTime(duration)}
</span>
<div className="flex-1" />
<button onClick={toggleFullscreen} className="hover:text-[#FBF8F2]/70 transition-colors">
<button onClick={toggleFullscreen} className="hover:text-on-dark/70 transition-colors">
{isFullscreen ? <Minimize size={18} /> : <Maximize size={18} />}
</button>
</div>
@@ -331,10 +331,10 @@ const MediaPlayer = ({ noun, getMediaUrl, fetchDetails }: MediaPlayerProps) => {
</div>
</div>
{error && <div className="text-[#B5432E] text-sm mt-3">{error}</div>}
{error && <div className="text-error text-sm mt-3">{error}</div>}
{!mediaLoaded && !error && (
<div className="text-muted text-sm mt-3 flex items-center gap-2">
<div className="w-3 h-3 rounded-full border-2 border-[#DED7CA] border-t-muted animate-spin" />
<div className="w-3 h-3 rounded-full border-2 border-inactive border-t-muted animate-spin" />
Loading video...
</div>
)}

View File

@@ -49,7 +49,7 @@ const MyClips = () => {
</div>
)}
{error && <p className="text-sm text-[#B5432E] mt-4">{error.toString()}</p>}
{error && <p className="text-sm text-error mt-4">{error.toString()}</p>}
</div>
);
}

View File

@@ -52,7 +52,7 @@ const MyVods = () => {
</div>
)}
{error && <p className="text-sm text-[#B5432E] mt-4">{error}</p>}
{error && <p className="text-sm text-error mt-4">{error}</p>}
</div>
);
};

View File

@@ -75,7 +75,7 @@ const Profile = () => {
title="Copy key"
className="p-2 rounded-md bg-fields hover:bg-hover transition-colors duration-150 text-text-secondary"
>
{copied ? <Check size={16} className="text-[#6E7A50]" /> : <Copy size={16} />}
{copied ? <Check size={16} className="text-olive" /> : <Copy size={16} />}
</button>
</div>
</Box>

View File

@@ -24,8 +24,8 @@ type TrackedJob = {
const CardHeader = ({ icon: Icon, title, accent = "primary" }: { icon: React.ElementType; title: string; accent?: "primary" | "accent" }) => (
<div className="flex items-center gap-2.5">
<div className={clsx("w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0", accent === "primary" ? "bg-[#A85A38]/10" : "bg-[#6E7A50]/10")}>
<Icon size={16} className={accent === "primary" ? "text-[#A85A38]" : "text-[#6E7A50]"} />
<div className={clsx("w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0", accent === "primary" ? "bg-terracotta/10" : "bg-olive/10")}>
<Icon size={16} className={accent === "primary" ? "text-terracotta" : "text-olive"} />
</div>
<h2 className="text-xl font-heading text-text-primary">{title}</h2>
</div>
@@ -35,22 +35,22 @@ const JobStatus = ({ job }: { job: TrackedJob }) => {
if (job.state === 'FAILED') {
return (
<div className="flex items-start gap-2 text-sm">
<X size={15} className="text-[#B5432E] mt-0.5 flex-shrink-0" />
<p className="text-[#B5432E]">{job.label} failed: {job.errorOutput ?? "Unknown error"}</p>
<X size={15} className="text-error mt-0.5 flex-shrink-0" />
<p className="text-error">{job.label} failed: {job.errorOutput ?? "Unknown error"}</p>
</div>
);
}
if (job.state === 'SUCCEEDED') {
return (
<div className="flex items-center gap-2 text-sm">
<Check size={15} className="text-[#6E7A50] flex-shrink-0" />
<Check size={15} className="text-olive flex-shrink-0" />
<p className="text-text-body">{job.label} saved</p>
</div>
);
}
return (
<div className="flex items-center gap-2">
<Loader2 size={15} className="text-[#A85A38] animate-spin flex-shrink-0" />
<Loader2 size={15} className="text-terracotta animate-spin flex-shrink-0" />
<div className="flex-1">
<p className="text-sm text-text-secondary mb-1">{job.label}…</p>
<progress value={job.progress} className="w-full h-1.5 rounded bg-fields" />
@@ -246,12 +246,12 @@ const StreamControl = () => {
{/* Hero status bar */}
<Box className={clsx(
"p-6 flex items-center justify-between gap-6 border-l-4",
isStreaming ? "border-l-[#B5432E]" : "border-l-hairline"
isStreaming ? "border-l-live" : "border-l-hairline"
)}>
<div className="flex items-center gap-3.5">
<span className="relative flex h-3.5 w-3.5">
{isStreaming && <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#B5432E] opacity-75" />}
<span className={clsx("relative inline-flex rounded-full h-3.5 w-3.5", isStreaming ? "bg-[#B5432E]" : "bg-[#C4BAA9]")} />
{isStreaming && <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-live opacity-75" />}
<span className={clsx("relative inline-flex rounded-full h-3.5 w-3.5", isStreaming ? "bg-live" : "bg-inactive-strong")} />
</span>
<div>
<p className="text-2xl font-heading text-text-primary leading-tight">{isStreaming ? "Live" : "Offline"}</p>
@@ -269,7 +269,7 @@ const StreamControl = () => {
<p className={labelClass}>elapsed</p>
</div>
) : (
<Radio size={28} className="text-[#DED7CA]" />
<Radio size={28} className="text-inactive" />
)}
</Box>
@@ -278,7 +278,7 @@ const StreamControl = () => {
{/* Quick mark toolbar */}
<Box className="p-4 flex flex-col gap-3">
<div className="flex items-center gap-3">
<Bookmark size={18} className="text-[#6E7A50] flex-shrink-0" />
<Bookmark size={18} className="text-olive flex-shrink-0" />
<input
type="text"
value={markerMessage}
@@ -291,7 +291,7 @@ const StreamControl = () => {
Add Marker
</PrimaryButton>
</div>
{markerError && <p className="text-sm text-[#B5432E]">{markerError}</p>}
{markerError && <p className="text-sm text-error">{markerError}</p>}
{markers.length > 0 && (
<div className="flex gap-2 overflow-x-auto pb-0.5 -mx-1 px-1 pt-1.5">
@@ -304,22 +304,22 @@ const StreamControl = () => {
onClick={() => handleChipClick(m.id)}
className={clsx(
"flex flex-col items-start gap-0.5 rounded-lg border px-3 py-1.5 text-left transition-colors duration-150",
isStart && "border-[#A85A38] bg-[#F6EFE6] ring-1 ring-[#A85A38]",
isEnd && "border-[#6E7A50] bg-[#F6EFE6] ring-1 ring-[#6E7A50]",
!isStart && !isEnd && "border-hairline hover:border-[#E2DACB] hover:bg-hover"
isStart && "border-terracotta bg-selected ring-1 ring-terracotta",
isEnd && "border-olive bg-selected ring-1 ring-olive",
!isStart && !isEnd && "border-hairline hover:border-ring hover:bg-hover"
)}
>
<span className="text-xs font-medium text-text-strong max-w-40 truncate">{m.message}</span>
<span className="font-data text-[11px] text-muted">
{formatLocalDate(m.timestamp, { hour: "2-digit", minute: "2-digit", second: "2-digit" })}
{isStart && <span className="text-[#A85A38] font-medium"> · start</span>}
{isEnd && <span className="text-[#6E7A50] font-medium"> · end</span>}
{isStart && <span className="text-terracotta font-medium"> · start</span>}
{isEnd && <span className="text-olive font-medium"> · end</span>}
</span>
</button>
<button
onClick={(e) => { e.stopPropagation(); handleDeleteMarker(m.id); }}
title="Delete marker"
className="hidden group-hover:flex items-center justify-center absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-muted hover:bg-[#B5432E] text-[#FDFBF7] transition-colors duration-150"
className="hidden group-hover:flex items-center justify-center absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-muted hover:bg-error text-on-accent transition-colors duration-150"
>
<X size={10} />
</button>
@@ -346,7 +346,7 @@ const StreamControl = () => {
</div>
<input type="text" placeholder="Title" value={clipTitle} onChange={(e) => setClipTitle(e.target.value)} className={inputClass} />
<textarea placeholder="Description" value={clipDescription} onChange={(e) => setClipDescription(e.target.value)} rows={2} className={inputClass + " resize-none"} />
{clipError && <p className="text-sm text-[#B5432E]">{clipError}</p>}
{clipError && <p className="text-sm text-error">{clipError}</p>}
<PrimaryButton onClick={handleClip} className="self-start mt-auto">Clip</PrimaryButton>
</Box>
@@ -359,21 +359,21 @@ const StreamControl = () => {
<div className="flex items-center gap-2 flex-wrap">
<span className={clsx(
"text-xs font-medium px-2.5 py-1 rounded-full border",
startMarker ? "border-[#A85A38] text-[#A85A38] bg-[#F6EFE6]" : "border-dashed border-[#DED7CA] text-muted"
startMarker ? "border-terracotta text-terracotta bg-selected" : "border-dashed border-inactive text-muted"
)}>
{startMarker ? startMarker.message : "Tap a marker to set start"}
</span>
<span className="text-[#C4BAA9]">→</span>
<span className="text-inactive-strong">→</span>
<span className={clsx(
"text-xs font-medium px-2.5 py-1 rounded-full border",
endMarker ? "border-[#6E7A50] text-[#6E7A50] bg-[#F6EFE6]" : "border-dashed border-[#DED7CA] text-muted"
endMarker ? "border-olive text-olive bg-selected" : "border-dashed border-inactive text-muted"
)}>
{endMarker ? endMarker.message : "Tap a marker to set end"}
</span>
</div>
<input type="text" placeholder="Title" value={markerSaveTitle} onChange={(e) => setMarkerSaveTitle(e.target.value)} className={inputClass} />
<textarea placeholder="Description" value={markerSaveDescription} onChange={(e) => setMarkerSaveDescription(e.target.value)} rows={2} className={inputClass + " resize-none"} />
{markerSaveError && <p className="text-sm text-[#B5432E]">{markerSaveError}</p>}
{markerSaveError && <p className="text-sm text-error">{markerSaveError}</p>}
</>
)}
<PrimaryButton onClick={handleSaveByMarkers} disabled={markers.length < 2} className="self-start mt-auto">Save</PrimaryButton>