fix: improve light theme onboarding controls
Make the onboarding tour theme-aware across the app themes, add the first-run theme picker, and keep fake media previews on the dark media surface. Update light-mode secondary controls in onboarding, settings, dropdowns, toolbar controls, and duplicate actions so they no longer render as dark buttons on subtle-light.
This commit is contained in:
@@ -193,7 +193,7 @@ export function DuplicateFinder() {
|
|||||||
{/* Batch select — only shown when there are groups and nothing is selected yet */}
|
{/* Batch select — only shown when there are groups and nothing is selected yet */}
|
||||||
{hasResults && selectedCount === 0 && !deleting && (
|
{hasResults && selectedCount === 0 && !deleting && (
|
||||||
<button
|
<button
|
||||||
className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-gray-400 transition-colors hover:bg-white/[0.07] hover:text-white"
|
className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-gray-400 transition-colors hover:bg-white/[0.07] hover:text-white light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={selectKeepFirstAllGroups}
|
onClick={selectKeepFirstAllGroups}
|
||||||
title={`Mark ${totalDuplicateImages} duplicate${totalDuplicateImages === 1 ? "" : "s"} for deletion across all groups (keeps first in each)`}
|
title={`Mark ${totalDuplicateImages} duplicate${totalDuplicateImages === 1 ? "" : "s"} for deletion across all groups (keeps first in each)`}
|
||||||
>
|
>
|
||||||
@@ -204,7 +204,7 @@ export function DuplicateFinder() {
|
|||||||
<>
|
<>
|
||||||
<span className="text-[11px] text-white/40">{selectedCount} marked for deletion</span>
|
<span className="text-[11px] text-white/40">{selectedCount} marked for deletion</span>
|
||||||
<button
|
<button
|
||||||
className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-gray-400 transition-colors hover:bg-white/[0.07] hover:text-white disabled:opacity-40"
|
className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-gray-400 transition-colors hover:bg-white/[0.07] hover:text-white disabled:opacity-40 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={clearDuplicateSelection}
|
onClick={clearDuplicateSelection}
|
||||||
disabled={deleting}
|
disabled={deleting}
|
||||||
>
|
>
|
||||||
@@ -220,7 +220,7 @@ export function DuplicateFinder() {
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<button
|
<button
|
||||||
className="rounded-lg border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-40"
|
className="rounded-lg border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-40 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => { setDeleteResult(null); void scanDuplicates(selectedFolderId); }}
|
onClick={() => { setDeleteResult(null); void scanDuplicates(selectedFolderId); }}
|
||||||
disabled={duplicateScanning}
|
disabled={duplicateScanning}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ export function FolderScopeDropdown() {
|
|||||||
onClick={() => setOpen((v) => !v)}
|
onClick={() => setOpen((v) => !v)}
|
||||||
className={`flex max-w-56 items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs transition-colors ${
|
className={`flex max-w-56 items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs transition-colors ${
|
||||||
open
|
open
|
||||||
? "border-white/15 bg-white/8 text-white"
|
? "border-white/15 bg-white/8 text-white light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white"
|
||||||
: "border-white/8 bg-transparent text-gray-400 hover:border-white/15 hover:text-gray-200"
|
: "border-white/8 bg-transparent text-gray-400 hover:border-white/15 hover:text-gray-200 light-theme:border-gray-700/40 light-theme:text-gray-600 light-theme:hover:border-gray-700 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
title="Change folder scope"
|
title="Change folder scope"
|
||||||
>
|
>
|
||||||
@@ -55,10 +55,10 @@ export function FolderScopeDropdown() {
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{open ? (
|
{open ? (
|
||||||
<div className="absolute right-0 top-full z-30 mt-1.5 max-h-80 min-w-52 overflow-y-auto rounded-xl border border-white/10 bg-gray-950/98 p-1 shadow-2xl backdrop-blur">
|
<div className="absolute right-0 top-full z-30 mt-1.5 max-h-80 min-w-52 overflow-y-auto rounded-xl border border-white/10 bg-gray-950/98 p-1 shadow-2xl backdrop-blur light-theme:border-gray-700/50">
|
||||||
<button
|
<button
|
||||||
className={`flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors ${
|
className={`flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors ${
|
||||||
selectedFolderId === null ? "bg-white/6 text-white" : "text-gray-400 hover:bg-white/5 hover:text-white"
|
selectedFolderId === null ? "bg-white/6 text-white light-theme:bg-gray-900 light-theme:text-white" : "text-gray-400 hover:bg-white/5 hover:text-white light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => select(null)}
|
onClick={() => select(null)}
|
||||||
>
|
>
|
||||||
@@ -75,7 +75,7 @@ export function FolderScopeDropdown() {
|
|||||||
<button
|
<button
|
||||||
key={folder.id}
|
key={folder.id}
|
||||||
className={`flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors ${
|
className={`flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors ${
|
||||||
active ? "bg-white/6 text-white" : "text-gray-400 hover:bg-white/5 hover:text-white"
|
active ? "bg-white/6 text-white light-theme:bg-gray-900 light-theme:text-white" : "text-gray-400 hover:bg-white/5 hover:text-white light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => select(folder.id)}
|
onClick={() => select(folder.id)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function ScopeButton({ scope, current, onSelect, children }: {
|
|||||||
className={`rounded-md border px-3 py-1.5 text-xs transition-colors ${
|
className={`rounded-md border px-3 py-1.5 text-xs transition-colors ${
|
||||||
active
|
active
|
||||||
? "border-emerald-400/35 bg-emerald-500/15 text-emerald-200 light-theme:border-emerald-600/50 light-theme:bg-emerald-100 light-theme:text-emerald-700"
|
? "border-emerald-400/35 bg-emerald-500/15 text-emerald-200 light-theme:border-emerald-600/50 light-theme:bg-emerald-100 light-theme:text-emerald-700"
|
||||||
: "border-transparent text-gray-500 hover:bg-white/[0.06] hover:text-gray-200 light-theme:text-gray-600 light-theme:hover:bg-black/[0.06] light-theme:hover:text-gray-900"
|
: "border-transparent text-gray-500 hover:bg-white/[0.06] hover:text-gray-200 light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onSelect(scope)}
|
onClick={() => onSelect(scope)}
|
||||||
>
|
>
|
||||||
@@ -112,7 +112,7 @@ function TaggerAccelerationButton({ acceleration, current, onSelect, children }:
|
|||||||
className={`rounded-md border px-3 py-1.5 text-xs transition-colors ${
|
className={`rounded-md border px-3 py-1.5 text-xs transition-colors ${
|
||||||
active
|
active
|
||||||
? "border-emerald-400/35 bg-emerald-500/15 text-emerald-200 light-theme:border-emerald-600/50 light-theme:bg-emerald-100 light-theme:text-emerald-700"
|
? "border-emerald-400/35 bg-emerald-500/15 text-emerald-200 light-theme:border-emerald-600/50 light-theme:bg-emerald-100 light-theme:text-emerald-700"
|
||||||
: "border-transparent text-gray-500 hover:bg-white/[0.06] hover:text-gray-200 light-theme:text-gray-600 light-theme:hover:bg-black/[0.06] light-theme:hover:text-gray-900"
|
: "border-transparent text-gray-500 hover:bg-white/[0.06] hover:text-gray-200 light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onSelect(acceleration)}
|
onClick={() => onSelect(acceleration)}
|
||||||
>
|
>
|
||||||
@@ -371,7 +371,7 @@ export function SettingsModal() {
|
|||||||
{taggerReady ? (
|
{taggerReady ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => void probeTaggerRuntime()}
|
onClick={() => void probeTaggerRuntime()}
|
||||||
disabled={taggerRuntimeChecking}
|
disabled={taggerRuntimeChecking}
|
||||||
>
|
>
|
||||||
@@ -387,7 +387,7 @@ export function SettingsModal() {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="relative overflow-hidden rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="relative overflow-hidden rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => void prepareTaggerModel()}
|
onClick={() => void prepareTaggerModel()}
|
||||||
disabled={taggerModelPreparing}
|
disabled={taggerModelPreparing}
|
||||||
>
|
>
|
||||||
@@ -536,14 +536,14 @@ export function SettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.045] px-2.5 py-1 text-[11px] text-gray-400 transition-colors hover:bg-white/[0.075] hover:text-white disabled:opacity-40 disabled:cursor-not-allowed light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.045] px-2.5 py-1 text-[11px] text-gray-400 transition-colors hover:bg-white/[0.075] hover:text-white disabled:opacity-40 disabled:cursor-not-allowed light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => setTaggingQueueFolderIds(folders.map((folder) => folder.id))}
|
onClick={() => setTaggingQueueFolderIds(folders.map((folder) => folder.id))}
|
||||||
disabled={taggingQueueScope === "all" || folders.length === 0}
|
disabled={taggingQueueScope === "all" || folders.length === 0}
|
||||||
>
|
>
|
||||||
Select all
|
Select all
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.045] px-2.5 py-1 text-[11px] text-gray-400 transition-colors hover:bg-white/[0.075] hover:text-white disabled:opacity-40 disabled:cursor-not-allowed light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.045] px-2.5 py-1 text-[11px] text-gray-400 transition-colors hover:bg-white/[0.075] hover:text-white disabled:opacity-40 disabled:cursor-not-allowed light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => setTaggingQueueFolderIds([])}
|
onClick={() => setTaggingQueueFolderIds([])}
|
||||||
disabled={taggingQueueScope === "all" || taggingQueueFolderIds.length === 0}
|
disabled={taggingQueueScope === "all" || taggingQueueFolderIds.length === 0}
|
||||||
>
|
>
|
||||||
@@ -582,7 +582,7 @@ export function SettingsModal() {
|
|||||||
<SettingsItem label="Queue tagging jobs" description="Generate missing AI tags for the current target. Results flow back into the library as the background worker finishes.">
|
<SettingsItem label="Queue tagging jobs" description="Generate missing AI tags for the current target. Results flow back into the library as the background worker finishes.">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => runQueueAction("queue")}
|
onClick={() => runQueueAction("queue")}
|
||||||
disabled={!taggerReady || taggerQueueing || taggerClearing || (taggingQueueScope === "selected" && taggingQueueFolderIds.length === 0)}
|
disabled={!taggerReady || taggerQueueing || taggerClearing || (taggingQueueScope === "selected" && taggingQueueFolderIds.length === 0)}
|
||||||
>
|
>
|
||||||
@@ -649,7 +649,7 @@ export function SettingsModal() {
|
|||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => void checkForUpdates()}
|
onClick={() => void checkForUpdates()}
|
||||||
disabled={updateStatus === "checking" || updateStatus === "downloading" || updateStatus === "installing"}
|
disabled={updateStatus === "checking" || updateStatus === "downloading" || updateStatus === "installing"}
|
||||||
>
|
>
|
||||||
@@ -666,7 +666,7 @@ export function SettingsModal() {
|
|||||||
description="Replay the guided first-run tour — library setup, the background pipeline, search modes, and AI features."
|
description="Replay the guided first-run tour — library setup, the background pipeline, search modes, and AI features."
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSettingsOpen(false);
|
setSettingsOpen(false);
|
||||||
openOnboarding();
|
openOnboarding();
|
||||||
@@ -683,7 +683,7 @@ export function SettingsModal() {
|
|||||||
description="Open the folder in Explorer to inspect or back up the database, thumbnails, and models."
|
description="Open the folder in Explorer to inspect or back up the database, thumbnails, and models."
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpeningDataFolder(true);
|
setOpeningDataFolder(true);
|
||||||
void openAppDataFolder().finally(() => setOpeningDataFolder(false));
|
void openAppDataFolder().finally(() => setOpeningDataFolder(false));
|
||||||
@@ -748,7 +748,7 @@ export function SettingsModal() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVacuuming(true);
|
setVacuuming(true);
|
||||||
setVacuumResult(null);
|
setVacuumResult(null);
|
||||||
@@ -809,7 +809,7 @@ export function SettingsModal() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-300/70 light-theme:bg-gray-100 light-theme:text-gray-700 light-theme:hover:bg-gray-200 light-theme:hover:text-gray-900"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCleaningThumbnails(true);
|
setCleaningThumbnails(true);
|
||||||
cleanupOrphanedThumbnails()
|
cleanupOrphanedThumbnails()
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ export function ThemedDropdown({
|
|||||||
onClick={() => setOpen((currentOpen) => !currentOpen)}
|
onClick={() => setOpen((currentOpen) => !currentOpen)}
|
||||||
className={`flex items-center justify-between gap-2 rounded-md border transition-colors ${
|
className={`flex items-center justify-between gap-2 rounded-md border transition-colors ${
|
||||||
compact
|
compact
|
||||||
? "border-white/10 bg-white/[0.04] px-1.5 py-0.5 text-[10px] font-medium"
|
? "border-white/10 bg-white/[0.04] px-1.5 py-0.5 text-[10px] font-medium light-theme:border-gray-700/50 light-theme:bg-gray-900"
|
||||||
: "min-w-40 border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs"
|
: "min-w-40 border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs light-theme:border-gray-700/50 light-theme:bg-gray-900"
|
||||||
} ${open ? "border-white/20 text-white" : "text-gray-400 hover:border-white/15 hover:text-gray-200"}`}
|
} ${open ? "border-white/20 text-white light-theme:border-gray-700 light-theme:text-white" : "text-gray-400 hover:border-white/15 hover:text-gray-200 light-theme:text-white light-theme:hover:border-gray-700 light-theme:hover:bg-gray-800 light-theme:hover:text-white"}`}
|
||||||
>
|
>
|
||||||
<span>{current?.label}</span>
|
<span>{current?.label}</span>
|
||||||
<svg
|
<svg
|
||||||
@@ -68,7 +68,7 @@ export function ThemedDropdown({
|
|||||||
<div
|
<div
|
||||||
role="listbox"
|
role="listbox"
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
className={`absolute top-full z-50 mt-1.5 min-w-full rounded-xl border border-white/10 bg-gray-950/98 p-1 shadow-2xl shadow-black/30 backdrop-blur-xl ${
|
className={`absolute top-full z-50 mt-1.5 min-w-full rounded-xl border border-white/10 bg-gray-950/98 p-1 shadow-2xl shadow-black/30 backdrop-blur-xl light-theme:border-gray-700/50 ${
|
||||||
align === "right" ? "right-0" : "left-0"
|
align === "right" ? "right-0" : "left-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -82,8 +82,8 @@ export function ThemedDropdown({
|
|||||||
aria-selected={selected}
|
aria-selected={selected}
|
||||||
className={`flex w-full items-center justify-between gap-4 whitespace-nowrap rounded-lg px-3 py-2 text-left text-xs transition-colors ${
|
className={`flex w-full items-center justify-between gap-4 whitespace-nowrap rounded-lg px-3 py-2 text-left text-xs transition-colors ${
|
||||||
selected
|
selected
|
||||||
? "bg-white/[0.08] text-white"
|
? "bg-white/[0.08] text-white light-theme:bg-gray-900 light-theme:text-white"
|
||||||
: "text-gray-400 hover:bg-white/[0.055] hover:text-gray-200"
|
: "text-gray-400 hover:bg-white/[0.055] hover:text-gray-200 light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onChange(option.value);
|
onChange(option.value);
|
||||||
|
|||||||
+11
-11
@@ -55,8 +55,8 @@ function SortDropdown({
|
|||||||
onClick={() => setOpen((v) => !v)}
|
onClick={() => setOpen((v) => !v)}
|
||||||
className={`flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs transition-colors ${
|
className={`flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs transition-colors ${
|
||||||
open
|
open
|
||||||
? "border-white/15 bg-white/8 text-white"
|
? "border-white/15 bg-white/8 text-white light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white"
|
||||||
: "border-white/8 bg-transparent text-gray-400 hover:border-white/15 hover:text-gray-200"
|
: "border-white/8 bg-transparent text-gray-400 hover:border-white/15 hover:text-gray-200 light-theme:border-gray-700/40 light-theme:text-gray-600 light-theme:hover:border-gray-700 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{current?.label ?? "Sort"}</span>
|
<span>{current?.label ?? "Sort"}</span>
|
||||||
@@ -68,14 +68,14 @@ function SortDropdown({
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{open ? (
|
{open ? (
|
||||||
<div className="absolute right-0 top-full z-30 mt-1.5 min-w-44 rounded-xl border border-white/10 bg-gray-950/98 p-1 shadow-2xl backdrop-blur">
|
<div className="absolute right-0 top-full z-30 mt-1.5 min-w-44 rounded-xl border border-white/10 bg-gray-950/98 p-1 shadow-2xl backdrop-blur light-theme:border-gray-700/50">
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<button
|
<button
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm transition-colors ${
|
className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm transition-colors ${
|
||||||
option.value === value
|
option.value === value
|
||||||
? "bg-white/6 text-white"
|
? "bg-white/6 text-white light-theme:bg-gray-900 light-theme:text-white"
|
||||||
: "text-gray-400 hover:bg-white/5 hover:text-white"
|
: "text-gray-400 hover:bg-white/5 hover:text-white light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => { onChange(option.value); setOpen(false); }}
|
onClick={() => { onChange(option.value); setOpen(false); }}
|
||||||
>
|
>
|
||||||
@@ -106,14 +106,14 @@ function FilterPill({
|
|||||||
}) {
|
}) {
|
||||||
const activeClass =
|
const activeClass =
|
||||||
variant === "amber"
|
variant === "amber"
|
||||||
? "bg-amber-500/15 text-amber-300 border border-amber-500/30"
|
? "bg-amber-500/15 text-amber-300 border border-amber-500/30 light-theme:bg-amber-100 light-theme:text-amber-700 light-theme:border-amber-500/50"
|
||||||
: "bg-white/10 text-white";
|
: "bg-white/10 text-white light-theme:bg-gray-900 light-theme:text-white";
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
|
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
|
||||||
active
|
active
|
||||||
? activeClass
|
? activeClass
|
||||||
: "text-gray-500 hover:text-gray-200 hover:bg-white/5"
|
: "text-gray-500 hover:text-gray-200 hover:bg-white/5 light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
@@ -419,7 +419,7 @@ export function Toolbar() {
|
|||||||
<div className="h-4 w-px bg-white/10 shrink-0" />
|
<div className="h-4 w-px bg-white/10 shrink-0" />
|
||||||
|
|
||||||
{/* Zoom */}
|
{/* Zoom */}
|
||||||
<div className="flex items-center rounded-lg border border-white/8 overflow-hidden">
|
<div className="flex items-center rounded-lg border border-white/8 overflow-hidden light-theme:border-gray-700/40">
|
||||||
{(["compact", "comfortable", "detail"] as const).map((preset, i) => (
|
{(["compact", "comfortable", "detail"] as const).map((preset, i) => (
|
||||||
<button
|
<button
|
||||||
key={preset}
|
key={preset}
|
||||||
@@ -427,8 +427,8 @@ export function Toolbar() {
|
|||||||
i > 0 ? "border-l border-white/8" : ""
|
i > 0 ? "border-l border-white/8" : ""
|
||||||
} ${
|
} ${
|
||||||
zoomPreset === preset
|
zoomPreset === preset
|
||||||
? "bg-white/10 text-white"
|
? "bg-white/10 text-white light-theme:bg-gray-900 light-theme:text-white"
|
||||||
: "text-gray-500 hover:text-gray-200 hover:bg-white/5"
|
: "text-gray-500 hover:text-gray-200 hover:bg-white/5 light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
title={`${tileSize}px tiles`}
|
title={`${tileSize}px tiles`}
|
||||||
onClick={() => setZoomPreset(preset)}
|
onClick={() => setZoomPreset(preset)}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ export function OnboardingOverlay() {
|
|||||||
const isLast = onboardingStep >= STEPS.length - 1;
|
const isLast = onboardingStep >= STEPS.length - 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/65 backdrop-blur-sm">
|
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/65 backdrop-blur-sm light-theme:bg-black/40">
|
||||||
<div className="flex max-h-[min(85vh,760px)] w-[min(90vw,720px)] flex-col rounded-lg border border-white/10 bg-[#07080f] shadow-2xl shadow-black/60">
|
<div className="flex max-h-[min(85vh,760px)] w-[min(90vw,720px)] flex-col rounded-lg border border-white/10 bg-gray-950 shadow-2xl shadow-black/60 light-theme:border-gray-300/70">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-white/[0.07] px-7 py-4">
|
<div className="flex items-center justify-between border-b border-white/[0.07] px-7 py-4 light-theme:border-gray-300/70">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[11px] uppercase tracking-[0.14em] text-gray-600">
|
<p className="text-[11px] uppercase tracking-[0.14em] text-gray-600">
|
||||||
Step {onboardingStep + 1} of {STEPS.length}
|
Step {onboardingStep + 1} of {STEPS.length}
|
||||||
@@ -59,7 +59,11 @@ export function OnboardingOverlay() {
|
|||||||
key={s.id}
|
key={s.id}
|
||||||
aria-label={s.title}
|
aria-label={s.title}
|
||||||
className={`h-1.5 rounded-full transition-all ${
|
className={`h-1.5 rounded-full transition-all ${
|
||||||
i === onboardingStep ? "w-5 bg-white/70" : i < onboardingStep ? "w-1.5 bg-white/35" : "w-1.5 bg-white/15"
|
i === onboardingStep
|
||||||
|
? "w-5 bg-white/70 light-theme:bg-gray-700"
|
||||||
|
: i < onboardingStep
|
||||||
|
? "w-1.5 bg-white/35 light-theme:bg-gray-400"
|
||||||
|
: "w-1.5 bg-white/15 light-theme:bg-gray-300"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setOnboardingStep(i)}
|
onClick={() => setOnboardingStep(i)}
|
||||||
/>
|
/>
|
||||||
@@ -83,23 +87,23 @@ export function OnboardingOverlay() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-between border-t border-white/[0.07] px-7 py-4">
|
<div className="flex items-center justify-between border-t border-white/[0.07] px-7 py-4 light-theme:border-gray-300/70">
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-transparent px-3 py-1.5 text-xs text-gray-500 transition-colors hover:bg-white/[0.06] hover:text-gray-200"
|
className="rounded-md border border-transparent px-3 py-1.5 text-xs text-gray-500 transition-colors hover:bg-white/[0.06] hover:text-gray-200 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={completeOnboarding}
|
onClick={completeOnboarding}
|
||||||
>
|
>
|
||||||
Skip tour
|
Skip tour
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => setOnboardingStep(onboardingStep - 1)}
|
onClick={() => setOnboardingStep(onboardingStep - 1)}
|
||||||
disabled={isFirst}
|
disabled={isFirst}
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-emerald-400/35 bg-emerald-500/15 px-4 py-1.5 text-xs text-emerald-200 transition-colors hover:bg-emerald-500/25"
|
className="rounded-md border border-emerald-400/35 bg-emerald-500/15 px-4 py-1.5 text-xs text-emerald-200 transition-colors hover:bg-emerald-500/25 light-theme:border-emerald-600/50 light-theme:bg-emerald-100 light-theme:text-emerald-700 light-theme:hover:bg-emerald-200"
|
||||||
onClick={() => (isLast ? completeOnboarding() : setOnboardingStep(onboardingStep + 1))}
|
onClick={() => (isLast ? completeOnboarding() : setOnboardingStep(onboardingStep + 1))}
|
||||||
>
|
>
|
||||||
{isLast ? "Finish" : "Next"}
|
{isLast ? "Finish" : "Next"}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function StepAddLibrary() {
|
|||||||
added, renamed, or removed. Nothing is moved or copied.
|
added, renamed, or removed. Nothing is moved or copied.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-5 flex items-center justify-between gap-6 border-y border-white/[0.05] py-4">
|
<div className="mt-5 flex items-center justify-between gap-6 border-y border-white/[0.05] py-4 light-theme:border-gray-300/70">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
{folders.length > 0 ? (
|
{folders.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
@@ -49,10 +49,10 @@ export function StepAddLibrary() {
|
|||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{addError ? <p className="mt-1.5 text-xs text-amber-300/90">{addError}</p> : null}
|
{addError ? <p className="mt-1.5 text-xs text-amber-300/90 light-theme:text-amber-700">{addError}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="shrink-0 rounded-md border border-emerald-400/35 bg-emerald-500/15 px-3 py-1.5 text-xs text-emerald-200 transition-colors hover:bg-emerald-500/25 disabled:cursor-not-allowed disabled:opacity-45"
|
className="shrink-0 rounded-md border border-emerald-400/35 bg-emerald-500/15 px-3 py-1.5 text-xs text-emerald-200 transition-colors hover:bg-emerald-500/25 disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-emerald-600/50 light-theme:bg-emerald-100 light-theme:text-emerald-700 light-theme:hover:bg-emerald-200"
|
||||||
onClick={() => void handlePick()}
|
onClick={() => void handlePick()}
|
||||||
disabled={adding}
|
disabled={adding}
|
||||||
>
|
>
|
||||||
@@ -64,7 +64,7 @@ export function StepAddLibrary() {
|
|||||||
As indexing runs, the gallery fills in roughly like this — tiles appear immediately and sharpen
|
As indexing runs, the gallery fills in roughly like this — tiles appear immediately and sharpen
|
||||||
as thumbnails are generated:
|
as thumbnails are generated:
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3 grid grid-cols-6 gap-1.5">
|
<div className="media-dark-surface mt-3 grid grid-cols-6 gap-1.5">
|
||||||
<FakeTile index={0} />
|
<FakeTile index={0} />
|
||||||
<FakeTile index={1} favorite />
|
<FakeTile index={1} favorite />
|
||||||
<FakeTile index={2} duration="0:42" />
|
<FakeTile index={2} duration="0:42" />
|
||||||
|
|||||||
@@ -37,18 +37,18 @@ export function StepAiFeatures() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h4 className="mt-6 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">AI tagging — optional</h4>
|
<h4 className="mt-6 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">AI tagging — optional</h4>
|
||||||
<div className="mt-1 divide-y divide-white/[0.05]">
|
<div className="mt-1 divide-y divide-white/[0.05] light-theme:divide-gray-300/70">
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<div className="flex items-start justify-between gap-6">
|
<div className="flex items-start justify-between gap-6">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-sm text-white">Automatic tags for every image</p>
|
<p className="text-sm text-white">Automatic tags for every image</p>
|
||||||
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
||||||
The WD tagger model (~1.3 GB download) labels images so you can search with{" "}
|
The WD tagger model (~1.3 GB download) labels images so you can search with{" "}
|
||||||
<code className="rounded bg-white/[0.07] px-1 py-0.5 text-[11px] text-gray-200">/t</code> — tags look like:
|
<code className="rounded bg-gray-900 px-1 py-0.5 text-[11px] text-gray-200 light-theme:bg-gray-800 light-theme:text-gray-100">/t</code> — tags look like:
|
||||||
</p>
|
</p>
|
||||||
<span className="mt-2 flex flex-wrap gap-1.5">
|
<span className="mt-2 flex flex-wrap gap-1.5">
|
||||||
{FAKE_TAGS.map((tag) => (
|
{FAKE_TAGS.map((tag) => (
|
||||||
<span key={tag} className="rounded-md border border-white/10 bg-white/[0.04] px-2 py-0.5 text-[11px] text-gray-400">
|
<span key={tag} className="rounded-md border border-white/10 bg-gray-900/50 px-2 py-0.5 text-[11px] text-gray-400 light-theme:border-gray-300/70 light-theme:bg-gray-900 light-theme:text-gray-600">
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@@ -56,12 +56,12 @@ export function StepAiFeatures() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
{taggerReady ? (
|
{taggerReady ? (
|
||||||
<span className="inline-flex rounded-md border border-emerald-400/25 bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-300">
|
<span className="inline-flex rounded-md border border-emerald-400/25 bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-300 light-theme:border-emerald-600/40 light-theme:bg-emerald-100 light-theme:text-emerald-700">
|
||||||
Installed
|
Installed
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45"
|
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => void prepareTaggerModel()}
|
onClick={() => void prepareTaggerModel()}
|
||||||
disabled={taggerModelPreparing}
|
disabled={taggerModelPreparing}
|
||||||
>
|
>
|
||||||
@@ -84,7 +84,7 @@ export function StepAiFeatures() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{!taggerModelPreparing && taggerModelError ? (
|
{!taggerModelPreparing && taggerModelError ? (
|
||||||
<p className="mt-2 text-xs leading-relaxed text-amber-300/90">
|
<p className="mt-2 text-xs leading-relaxed text-amber-300/90 light-theme:text-amber-700">
|
||||||
Download failed: {taggerModelError}
|
Download failed: {taggerModelError}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -92,11 +92,11 @@ export function StepAiFeatures() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 className="mt-6 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">Semantic search & similarity — built in</h4>
|
<h4 className="mt-6 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">Semantic search & similarity — built in</h4>
|
||||||
<div className="mt-1 divide-y divide-white/[0.05]">
|
<div className="mt-1 divide-y divide-white/[0.05] light-theme:divide-gray-300/70">
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<p className="text-sm text-white">Search by meaning, find look-alikes</p>
|
<p className="text-sm text-white">Search by meaning, find look-alikes</p>
|
||||||
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
||||||
Powers <code className="rounded bg-white/[0.07] px-1 py-0.5 text-[11px] text-gray-200">/s</code> search,
|
Powers <code className="rounded bg-gray-900 px-1 py-0.5 text-[11px] text-gray-200 light-theme:bg-gray-800 light-theme:text-gray-100">/s</code> search,
|
||||||
"find similar", and the Explore view, so it's part of the standard pipeline: the CLIP model
|
"find similar", and the Explore view, so it's part of the standard pipeline: the CLIP model
|
||||||
(~580 MB) downloads automatically the first time embeddings run. Nothing to do — you'll see it
|
(~580 MB) downloads automatically the first time embeddings run. Nothing to do — you'll see it
|
||||||
in the background-tasks bar.
|
in the background-tasks bar.
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ export function StepGalleryPreview() {
|
|||||||
carry your favorites, star ratings, and video durations; hover for filename and quick actions.
|
carry your favorites, star ratings, and video durations; hover for filename and quick actions.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-5 grid grid-cols-4 gap-1.5">
|
<div className="media-dark-surface mt-5 grid grid-cols-4 gap-1.5">
|
||||||
{TILE_PROPS.map((props, i) => (
|
{TILE_PROPS.map((props, i) => (
|
||||||
<FakeTile key={i} index={i} loaded={i < revealed} {...props} />
|
<FakeTile key={i} index={i} loaded={i < revealed} {...props} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex items-start justify-between gap-4">
|
<div className="mt-6 flex items-start justify-between gap-4">
|
||||||
<div className="divide-y divide-white/[0.05] text-xs leading-relaxed text-gray-500">
|
<div className="divide-y divide-white/[0.05] text-xs leading-relaxed text-gray-500 light-theme:divide-gray-300/70">
|
||||||
<p className="pb-2.5">
|
<p className="pb-2.5">
|
||||||
<span className="text-gray-300">Click any tile</span> to open the lightbox — keyboard navigation,
|
<span className="text-gray-300">Click any tile</span> to open the lightbox — keyboard navigation,
|
||||||
zoom, inline tag editing, ratings, and a full video player.
|
zoom, inline tag editing, ratings, and a full video player.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function StepPipeline() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Fake BackgroundTasks slim bar */}
|
{/* Fake BackgroundTasks slim bar */}
|
||||||
<div className="mt-3 rounded-lg border border-white/[0.07] bg-white/[0.02] px-4 py-3">
|
<div className="mt-3 rounded-lg border border-white/[0.07] bg-gray-900/30 px-4 py-3 light-theme:border-gray-300/70 light-theme:bg-gray-900">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="relative flex h-1.5 w-1.5 shrink-0">
|
<span className="relative flex h-1.5 w-1.5 shrink-0">
|
||||||
{!finished ? (
|
{!finished ? (
|
||||||
@@ -57,7 +57,7 @@ export function StepPipeline() {
|
|||||||
) : null}
|
) : null}
|
||||||
<span className={`relative inline-flex h-1.5 w-1.5 rounded-full ${finished ? "bg-emerald-400" : "bg-blue-400"}`} />
|
<span className={`relative inline-flex h-1.5 w-1.5 rounded-full ${finished ? "bg-emerald-400" : "bg-blue-400"}`} />
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[13px] font-medium text-white/60">Holiday Photos</span>
|
<span className="text-[13px] font-medium text-white/60 light-theme:text-gray-500">Holiday Photos</span>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
{STAGES.map((stage, i) => (
|
{STAGES.map((stage, i) => (
|
||||||
<FakeStageTag
|
<FakeStageTag
|
||||||
@@ -72,7 +72,7 @@ export function StepPipeline() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex items-start justify-between gap-4">
|
<div className="mt-6 flex items-start justify-between gap-4">
|
||||||
<div className="divide-y divide-white/[0.05] text-xs leading-relaxed text-gray-500">
|
<div className="divide-y divide-white/[0.05] text-xs leading-relaxed text-gray-500 light-theme:divide-gray-300/70">
|
||||||
<p className="pb-2.5">
|
<p className="pb-2.5">
|
||||||
<span className="text-gray-300">It's all interruptible.</span> Close the app whenever you like —
|
<span className="text-gray-300">It's all interruptible.</span> Close the app whenever you like —
|
||||||
the queue picks up where it left off next launch.
|
the queue picks up where it left off next launch.
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ export function StepSearchDemo() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm leading-relaxed text-gray-300">
|
<p className="text-sm leading-relaxed text-gray-300">
|
||||||
One search bar, three modes — picked by prefix. No prefix searches filenames, <code className="rounded bg-white/[0.07] px-1 py-0.5 text-[11px] text-gray-200">/s</code> searches
|
One search bar, three modes — picked by prefix. No prefix searches filenames, <code className="rounded bg-gray-900 px-1 py-0.5 text-[11px] text-gray-200 light-theme:bg-gray-800 light-theme:text-gray-100">/s</code> searches
|
||||||
by meaning, <code className="rounded bg-white/[0.07] px-1 py-0.5 text-[11px] text-gray-200">/t</code> searches tags.
|
by meaning, <code className="rounded bg-gray-900 px-1 py-0.5 text-[11px] text-gray-200 light-theme:bg-gray-800 light-theme:text-gray-100">/t</code> searches tags.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Mock search bar */}
|
{/* Mock search bar */}
|
||||||
<div className="mt-5 flex items-center gap-2.5 rounded-lg border border-white/10 bg-white/[0.04] px-3.5 py-2.5">
|
<div className="mt-5 flex items-center gap-2.5 rounded-lg border border-white/10 bg-gray-900/50 px-3.5 py-2.5 light-theme:border-gray-300/70 light-theme:bg-gray-900">
|
||||||
<svg className="h-4 w-4 shrink-0 text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
<svg className="h-4 w-4 shrink-0 text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -77,7 +77,7 @@ export function StepSearchDemo() {
|
|||||||
{demo.query.slice(0, typed)}
|
{demo.query.slice(0, typed)}
|
||||||
{!finished ? <span className="animate-pulse text-gray-500">|</span> : null}
|
{!finished ? <span className="animate-pulse text-gray-500">|</span> : null}
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-auto shrink-0 rounded-md border border-sky-400/25 bg-sky-500/10 px-2 py-0.5 text-[11px] font-medium text-sky-300">
|
<span className="ml-auto shrink-0 rounded-md border border-sky-400/25 bg-sky-500/10 px-2 py-0.5 text-[11px] font-medium text-sky-300 light-theme:border-sky-600/40 light-theme:bg-sky-100 light-theme:text-sky-700">
|
||||||
{demo.mode}
|
{demo.mode}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +90,7 @@ export function StepSearchDemo() {
|
|||||||
demo's visible state. */}
|
demo's visible state. */}
|
||||||
<div
|
<div
|
||||||
key={demoIndex}
|
key={demoIndex}
|
||||||
className={`mt-3 flex flex-wrap justify-center gap-1.5 transition-opacity duration-300 ${fullyTyped ? "opacity-100" : "opacity-0"}`}
|
className={`media-dark-surface mt-3 flex flex-wrap justify-center gap-1.5 transition-opacity duration-300 ${fullyTyped ? "opacity-100" : "opacity-0"}`}
|
||||||
>
|
>
|
||||||
{demo.results.map((src, i) => (
|
{demo.results.map((src, i) => (
|
||||||
<div key={i} className="aspect-square w-36 overflow-hidden rounded-xl bg-white/[0.04]">
|
<div key={i} className="aspect-square w-36 overflow-hidden rounded-xl bg-white/[0.04]">
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ export function StepUpdates() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Mini app-window mockup: the lit mark shown in a real title bar. */}
|
{/* Mini app-window mockup: the lit mark shown in a real title bar. */}
|
||||||
<div className="mt-3 overflow-hidden rounded-lg border border-white/10 bg-gray-950 shadow-lg">
|
<div className="mt-3 overflow-hidden rounded-lg border border-white/10 bg-gray-950 shadow-lg light-theme:border-gray-300/70">
|
||||||
<div className="flex items-center gap-2 border-b border-white/[0.06] px-3 py-2">
|
<div className="flex items-center gap-2 border-b border-white/[0.06] px-3 py-2 light-theme:border-gray-300/70">
|
||||||
<div className="relative flex h-6 w-6 items-center justify-center rounded-md bg-white/[0.08] text-gray-300">
|
<div className="relative flex h-6 w-6 items-center justify-center rounded-md bg-gray-900 text-gray-300 light-theme:bg-gray-800">
|
||||||
<span className="pointer-events-none absolute left-1/2 top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-amber-400/30 blur-[3px]" />
|
<span className="pointer-events-none absolute left-1/2 top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-amber-400/30 blur-[3px]" />
|
||||||
<span className="pointer-events-none absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-amber-400/55 animate-ping" />
|
<span className="pointer-events-none absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-amber-400/55 animate-ping" />
|
||||||
<PhokusMark className="relative h-[18px] w-[18px]" dotClassName="fill-amber-400" />
|
<PhokusMark className="relative h-[18px] w-[18px]" dotClassName="fill-amber-400" />
|
||||||
@@ -48,16 +48,16 @@ export function StepUpdates() {
|
|||||||
|
|
||||||
{/* Ghosted window body, just enough to read as the app. */}
|
{/* Ghosted window body, just enough to read as the app. */}
|
||||||
<div className="flex h-[72px] bg-gray-900/30">
|
<div className="flex h-[72px] bg-gray-900/30">
|
||||||
<div className="w-14 shrink-0 border-r border-white/[0.05] p-2.5">
|
<div className="w-14 shrink-0 border-r border-white/[0.05] p-2.5 light-theme:border-gray-300/70">
|
||||||
<div className="h-1.5 w-full rounded bg-white/[0.07]" />
|
<div className="h-1.5 w-full rounded bg-gray-800" />
|
||||||
<div className="mt-2 h-1.5 w-3/4 rounded bg-white/[0.04]" />
|
<div className="mt-2 h-1.5 w-3/4 rounded bg-gray-900" />
|
||||||
<div className="mt-2 h-1.5 w-3/4 rounded bg-white/[0.04]" />
|
<div className="mt-2 h-1.5 w-3/4 rounded bg-gray-900" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 p-2.5">
|
<div className="flex-1 p-2.5">
|
||||||
<div className="h-2 w-20 rounded bg-white/[0.06]" />
|
<div className="h-2 w-20 rounded bg-gray-800" />
|
||||||
<div className="mt-2.5 grid grid-cols-5 gap-1.5">
|
<div className="mt-2.5 grid grid-cols-5 gap-1.5">
|
||||||
{Array.from({ length: 5 }).map((_, i) => (
|
{Array.from({ length: 5 }).map((_, i) => (
|
||||||
<div key={i} className="h-7 rounded bg-white/[0.04]" />
|
<div key={i} className="h-7 rounded bg-gray-900" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function ExplorePreview() {
|
|||||||
{ size: 16, x: 70, y: 44, i: 6 },
|
{ size: 16, x: 70, y: 44, i: 6 },
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<div className="relative h-[72px] w-32 overflow-hidden rounded-lg border border-white/[0.07] bg-white/[0.02]">
|
<div className="media-dark-surface relative h-[72px] w-32 overflow-hidden rounded-lg border border-white/[0.07] bg-white/[0.02]">
|
||||||
{blobs.map((blob, idx) => (
|
{blobs.map((blob, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
@@ -36,7 +36,7 @@ function ExplorePreview() {
|
|||||||
|
|
||||||
function TimelinePreview() {
|
function TimelinePreview() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[72px] w-32 flex-col justify-center gap-2 rounded-lg border border-white/[0.07] bg-white/[0.02] px-3">
|
<div className="media-dark-surface flex h-[72px] w-32 flex-col justify-center gap-2 rounded-lg border border-white/[0.07] bg-white/[0.02] px-3">
|
||||||
{[2024, 2023].map((year, row) => (
|
{[2024, 2023].map((year, row) => (
|
||||||
<div key={year} className="flex items-center gap-1.5">
|
<div key={year} className="flex items-center gap-1.5">
|
||||||
<span className="w-7 text-[9px] tabular-nums text-gray-600">{year}</span>
|
<span className="w-7 text-[9px] tabular-nums text-gray-600">{year}</span>
|
||||||
@@ -51,7 +51,7 @@ function TimelinePreview() {
|
|||||||
|
|
||||||
function DuplicatesPreview() {
|
function DuplicatesPreview() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[72px] w-32 items-center justify-center gap-1.5 rounded-lg border border-white/[0.07] bg-white/[0.02]">
|
<div className="media-dark-surface flex h-[72px] w-32 items-center justify-center gap-1.5 rounded-lg border border-white/[0.07] bg-white/[0.02]">
|
||||||
<div className="w-10">
|
<div className="w-10">
|
||||||
<FakeTile index={3} className="rounded-md" />
|
<FakeTile index={3} className="rounded-md" />
|
||||||
</div>
|
</div>
|
||||||
@@ -69,7 +69,7 @@ export function StepViews() {
|
|||||||
<p className="text-sm leading-relaxed text-gray-300">
|
<p className="text-sm leading-relaxed text-gray-300">
|
||||||
Beyond the main grid, the sidebar switches between three more ways to look at your library:
|
Beyond the main grid, the sidebar switches between three more ways to look at your library:
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3 divide-y divide-white/[0.05]">
|
<div className="mt-3 divide-y divide-white/[0.05] light-theme:divide-gray-300/70">
|
||||||
<ViewRow
|
<ViewRow
|
||||||
title="Explore"
|
title="Explore"
|
||||||
description="A visual cluster map and tag cloud — browse by theme instead of folder, and jump into any cluster."
|
description="A visual cluster map and tag cloud — browse by theme instead of folder, and jump into any cluster."
|
||||||
|
|||||||
@@ -1,6 +1,52 @@
|
|||||||
import { useGalleryStore } from "../../store";
|
import { AppTheme, useGalleryStore } from "../../store";
|
||||||
import { FakeProgressBar, formatBytes } from "./fakes";
|
import { FakeProgressBar, formatBytes } from "./fakes";
|
||||||
|
|
||||||
|
const THEME_OPTIONS: {
|
||||||
|
value: AppTheme;
|
||||||
|
name: string;
|
||||||
|
colors: {
|
||||||
|
background: string;
|
||||||
|
surface: string;
|
||||||
|
text: string;
|
||||||
|
muted: string;
|
||||||
|
accent: string;
|
||||||
|
};
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
value: "phokus",
|
||||||
|
name: "Phokus",
|
||||||
|
colors: {
|
||||||
|
background: "#030712",
|
||||||
|
surface: "#111827",
|
||||||
|
text: "#f9fafb",
|
||||||
|
muted: "#6b7280",
|
||||||
|
accent: "#10b981",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "conventional-dark",
|
||||||
|
name: "Conventional Dark",
|
||||||
|
colors: {
|
||||||
|
background: "#1f1f1f",
|
||||||
|
surface: "#303030",
|
||||||
|
text: "#a3a3a3",
|
||||||
|
muted: "#666666",
|
||||||
|
accent: "#10b981",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "subtle-light",
|
||||||
|
name: "Subtle Light",
|
||||||
|
colors: {
|
||||||
|
background: "#e9e7e2",
|
||||||
|
surface: "#dedbd4",
|
||||||
|
text: "#18202c",
|
||||||
|
muted: "#817b72",
|
||||||
|
accent: "#059669",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function FfmpegStatusRow() {
|
export function FfmpegStatusRow() {
|
||||||
const ffmpegStatus = useGalleryStore((s) => s.ffmpegStatus);
|
const ffmpegStatus = useGalleryStore((s) => s.ffmpegStatus);
|
||||||
const ffmpegProgress = useGalleryStore((s) => s.ffmpegProgress);
|
const ffmpegProgress = useGalleryStore((s) => s.ffmpegProgress);
|
||||||
@@ -10,7 +56,7 @@ export function FfmpegStatusRow() {
|
|||||||
if (ffmpegStatus === "installed") {
|
if (ffmpegStatus === "installed") {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2.5 py-3">
|
<div className="flex items-center gap-2.5 py-3">
|
||||||
<svg className="h-4 w-4 shrink-0 text-emerald-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
<svg className="h-4 w-4 shrink-0 text-emerald-400 light-theme:text-emerald-700" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
@@ -27,14 +73,14 @@ export function FfmpegStatusRow() {
|
|||||||
<div className="flex items-start justify-between gap-6">
|
<div className="flex items-start justify-between gap-6">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-sm text-white">Media engine download failed</p>
|
<p className="text-sm text-white">Media engine download failed</p>
|
||||||
<p className="mt-1 text-xs leading-relaxed text-amber-300/90">{ffmpegError}</p>
|
<p className="mt-1 text-xs leading-relaxed text-amber-300/90 light-theme:text-amber-700">{ffmpegError}</p>
|
||||||
<p className="mt-1.5 text-xs leading-relaxed text-gray-500">
|
<p className="mt-1.5 text-xs leading-relaxed text-gray-500">
|
||||||
You can keep going — photos work without it. Video thumbnails and metadata will start
|
You can keep going — photos work without it. Video thumbnails and metadata will start
|
||||||
automatically once the download succeeds.
|
automatically once the download succeeds.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="shrink-0 rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white"
|
className="shrink-0 rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
onClick={() => void retryFfmpegDownload()}
|
onClick={() => void retryFfmpegDownload()}
|
||||||
>
|
>
|
||||||
Retry download
|
Retry download
|
||||||
@@ -71,6 +117,9 @@ export function FfmpegStatusRow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function StepWelcome() {
|
export function StepWelcome() {
|
||||||
|
const theme = useGalleryStore((s) => s.theme);
|
||||||
|
const setTheme = useGalleryStore((s) => s.setTheme);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm leading-relaxed text-gray-300">
|
<p className="text-sm leading-relaxed text-gray-300">
|
||||||
@@ -83,8 +132,40 @@ export function StepWelcome() {
|
|||||||
from Settings.
|
from Settings.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h4 className="mt-7 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">Choose your look</h4>
|
||||||
|
<div className="mt-3 grid grid-cols-3 gap-2">
|
||||||
|
{THEME_OPTIONS.map((option) => {
|
||||||
|
const selected = option.value === theme;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
type="button"
|
||||||
|
className={`rounded-md border p-2 text-left transition-colors ${
|
||||||
|
selected
|
||||||
|
? "border-emerald-400/35 bg-emerald-500/15 text-emerald-200 ring-1 ring-emerald-400/40 light-theme:border-emerald-600/50 light-theme:bg-emerald-100 light-theme:text-emerald-700 light-theme:hover:bg-emerald-200"
|
||||||
|
: "border-white/10 bg-white/[0.055] text-gray-300 hover:bg-white/10 hover:text-white light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
|
}`}
|
||||||
|
onClick={() => setTheme(option.value)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="block overflow-hidden rounded border border-black/20 p-1.5"
|
||||||
|
style={{ backgroundColor: option.colors.background }}
|
||||||
|
>
|
||||||
|
<span className="mb-1 block h-2 w-8 rounded-sm" style={{ backgroundColor: option.colors.accent }} />
|
||||||
|
<span className="block rounded-sm p-1.5" style={{ backgroundColor: option.colors.surface }}>
|
||||||
|
<span className="block h-1.5 w-9 rounded-sm" style={{ backgroundColor: option.colors.text }} />
|
||||||
|
<span className="mt-1 block h-1.5 w-14 rounded-sm" style={{ backgroundColor: option.colors.muted }} />
|
||||||
|
<span className="mt-1 block h-1.5 w-10 rounded-sm" style={{ backgroundColor: option.colors.muted }} />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span className="mt-2 block text-[11px] font-medium">{option.name}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 className="mt-7 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">First-time setup</h4>
|
<h4 className="mt-7 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">First-time setup</h4>
|
||||||
<div className="mt-1 divide-y divide-white/[0.05]">
|
<div className="mt-1 divide-y divide-white/[0.05] light-theme:divide-gray-300/70">
|
||||||
<FfmpegStatusRow />
|
<FfmpegStatusRow />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function FakeTile({
|
|||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={`group relative aspect-square overflow-hidden rounded-xl bg-white/[0.04] ${className}`}>
|
<div className={`media-dark-surface group relative aspect-square overflow-hidden rounded-xl bg-white/[0.04] ${className}`}>
|
||||||
{loaded ? (
|
{loaded ? (
|
||||||
<img src={fakeImage(index)} alt="" loading="lazy" className="absolute inset-0 h-full w-full object-cover" />
|
<img src={fakeImage(index)} alt="" loading="lazy" className="absolute inset-0 h-full w-full object-cover" />
|
||||||
) : (
|
) : (
|
||||||
@@ -116,16 +116,16 @@ export function FakeTile({
|
|||||||
export function FakeStageTag({ label, state }: { label: string; state: "active" | "done" | "waiting" }) {
|
export function FakeStageTag({ label, state }: { label: string; state: "active" | "done" | "waiting" }) {
|
||||||
const className =
|
const className =
|
||||||
state === "active"
|
state === "active"
|
||||||
? "bg-white/5 text-gray-300"
|
? "bg-gray-900 text-gray-300 light-theme:bg-gray-900 light-theme:text-gray-300"
|
||||||
: state === "done"
|
: state === "done"
|
||||||
? "bg-emerald-500/10 text-emerald-400"
|
? "bg-emerald-500/10 text-emerald-400 light-theme:bg-emerald-100 light-theme:text-emerald-700"
|
||||||
: "bg-white/4 text-gray-600";
|
: "bg-gray-900/60 text-gray-600 light-theme:bg-gray-800 light-theme:text-gray-500";
|
||||||
return <span className={`rounded-md px-2 py-0.5 text-[11px] ${className}`}>{label}</span>;
|
return <span className={`rounded-md px-2 py-0.5 text-[11px] ${className}`}>{label}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FakeProgressBar({ fraction, className = "" }: { fraction: number | null; className?: string }) {
|
export function FakeProgressBar({ fraction, className = "" }: { fraction: number | null; className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div className={`h-px overflow-hidden rounded-full bg-white/8 ${className}`}>
|
<div className={`h-px overflow-hidden rounded-full bg-gray-200/60 light-theme:bg-gray-300 ${className}`}>
|
||||||
{fraction === null ? (
|
{fraction === null ? (
|
||||||
<div className="h-full w-full animate-pulse bg-blue-500/40" />
|
<div className="h-full w-full animate-pulse bg-blue-500/40" />
|
||||||
) : (
|
) : (
|
||||||
@@ -143,7 +143,7 @@ export function ReplayButton({ onClick, label = "Replay" }: { onClick: () => voi
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="inline-flex items-center gap-1.5 rounded-md border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-200"
|
className="inline-flex items-center gap-1.5 rounded-md border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] text-gray-400 transition-colors hover:bg-white/10 hover:text-gray-200 light-theme:border-gray-700/50 light-theme:bg-gray-900 light-theme:text-white light-theme:hover:bg-gray-800 light-theme:hover:text-white"
|
||||||
>
|
>
|
||||||
<svg className="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
<svg className="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12a7.5 7.5 0 0012.9 5.3M19.5 12A7.5 7.5 0 006.6 6.7M4.5 6.5v3h3M19.5 17.5v-3h-3" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12a7.5 7.5 0 0012.9 5.3M19.5 12A7.5 7.5 0 006.6 6.7M4.5 6.5v3h3M19.5 17.5v-3h-3" />
|
||||||
|
|||||||
Reference in New Issue
Block a user