feat: Added light mode plus various bug fixes
- Added click handler for Easter egg that enables music inspiration feature. - Updated credits page to include new icons and handle click events. - Enhanced inspiration page to fetch and display Spotify track embed HTML. - Refactored inspiration loading logic to include track data. - Introduced theme selection in settings with light and dark modes. - Updated settings page to reflect new theme options and improve toast messages. - Refined layout styles across various pages for consistent theming. - Bumped application version to 1.5.0 in installer script.
This commit is contained in:
@@ -3,8 +3,7 @@
|
||||
import { page, navigating } from "$app/state";
|
||||
import { beforeNavigate, goto } from "$app/navigation";
|
||||
import { locales, localizeHref } from "$lib/paraglide/runtime";
|
||||
import { unsavedChanges, sidebarOpen, bugReportDialogOpen } from "$lib/stores/app";
|
||||
import "../layout.css";
|
||||
import { unsavedChanges, sidebarOpen, bugReportDialogOpen, dangerZoneEnabled } from "$lib/stores/app";
|
||||
import { onMount } from "svelte";
|
||||
import * as m from "$lib/paraglide/messages.js";
|
||||
import type { utils } from "$lib/wailsjs/go/models";
|
||||
@@ -100,6 +99,7 @@
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if(dev) dangerZoneEnabled.set(true);
|
||||
if (browser && isDebbugerProtectionOn) {
|
||||
detectDebugging();
|
||||
setInterval(detectDebugging, 1000);
|
||||
@@ -391,15 +391,17 @@
|
||||
style="cursor: pointer; opacity: 0.7;"
|
||||
class="hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
<Music
|
||||
size="16"
|
||||
onclick={() => {
|
||||
if (page.url.pathname !== "/inspiration" && page.url.pathname !== "/inspiration/")
|
||||
goto("/inspiration");
|
||||
}}
|
||||
style="cursor: pointer; opacity: 0.7;"
|
||||
class="hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
{#if settingsStore.settings.musicInspirationEnabled}
|
||||
<Music
|
||||
size="16"
|
||||
onclick={() => {
|
||||
if (page.url.pathname !== "/inspiration" && page.url.pathname !== "/inspiration/")
|
||||
goto("/inspiration");
|
||||
}}
|
||||
style="cursor: pointer; opacity: 0.7;"
|
||||
class="hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<a
|
||||
data-sveltekit-reload
|
||||
@@ -568,8 +570,6 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: oklch(0 0 0);
|
||||
color: #eaeaea;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
@@ -578,11 +578,13 @@
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 32px;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -592,11 +594,12 @@
|
||||
flex: 0 0 32px;
|
||||
z-index: 50;
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.footerbar {
|
||||
height: 32px;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@@ -604,28 +607,28 @@
|
||||
padding: 0 12px;
|
||||
user-select: none;
|
||||
flex: 0 0 32px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
color: gray;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.title bold {
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.title version {
|
||||
color: rgb(228, 221, 221);
|
||||
opacity: 0.4;
|
||||
color: var(--muted-foreground);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.title version debug {
|
||||
color: #e11d48;
|
||||
color: var(--destructive);
|
||||
opacity: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -642,8 +645,9 @@
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: #111;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: var(--popover);
|
||||
color: var(--popover-foreground);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
z-index: 1000;
|
||||
@@ -673,16 +677,16 @@
|
||||
}
|
||||
|
||||
.tooltip-item .label {
|
||||
color: #9ca3af;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.tooltip-item .value {
|
||||
color: #f3f4f6;
|
||||
color: var(--foreground);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.tooltip-item .channel {
|
||||
color: #6b7280;
|
||||
color: var(--muted-foreground);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@@ -697,20 +701,20 @@
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
@@ -721,7 +725,7 @@
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
@@ -758,12 +762,12 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@@ -779,14 +783,14 @@
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: rgba(255, 255, 255, 0.8);
|
||||
border: 2px solid var(--border);
|
||||
border-top-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
@@ -807,12 +811,12 @@
|
||||
}
|
||||
|
||||
:global(.custom-scrollbar::-webkit-scrollbar-thumb) {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:global(.custom-scrollbar::-webkit-scrollbar-thumb:hover) {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
:global(.custom-scrollbar::-webkit-scrollbar-corner) {
|
||||
|
||||
@@ -1,15 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { goto, preloadData } from "$app/navigation";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import { ChevronLeft, Heart, Code, Package, Globe, Github, Mail, BadgeInfo } from "@lucide/svelte";
|
||||
import { ChevronLeft, Heart, Code, Package, Globe, Github, Mail, BadgeInfo, Music, PartyPopper } from "@lucide/svelte";
|
||||
import * as m from "$lib/paraglide/messages";
|
||||
import { OpenURLInBrowser } from "$lib/wailsjs/go/main/App";
|
||||
import { dangerZoneEnabled } from "$lib/stores/app";
|
||||
import { settingsStore } from "$lib/stores/settings.svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
let { data } = $props();
|
||||
let config = $derived(data.config);
|
||||
|
||||
// Easter Egg State
|
||||
const REQUIRED_CLICKS = 10;
|
||||
const CLICK_WINDOW_MS = 4000;
|
||||
let recentClicks: number[] = [];
|
||||
|
||||
function handleEasterEggClick(_event: MouseEvent) {
|
||||
console.log("clicked")
|
||||
// Only proceed if danger zone is already enabled
|
||||
if (!$dangerZoneEnabled) return;
|
||||
|
||||
// If already enabled, do nothing to avoid spam
|
||||
if (settingsStore.settings.musicInspirationEnabled) return;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// Clean old clicks
|
||||
recentClicks = recentClicks.filter(t => now - t < CLICK_WINDOW_MS);
|
||||
recentClicks.push(now);
|
||||
|
||||
if (recentClicks.length >= REQUIRED_CLICKS) {
|
||||
recentClicks = [];
|
||||
try {
|
||||
settingsStore.update({ musicInspirationEnabled: true });
|
||||
preloadData("/inspiration");
|
||||
} catch (e) {
|
||||
console.error("Failed to enable music inspiration:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open external URL in default browser
|
||||
async function openUrl(url: string) {
|
||||
try {
|
||||
@@ -67,7 +100,7 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="min-h-[calc(100vh-1rem)] from-background to-muted/30">
|
||||
<div class="min-h-[calc(100vh-1rem)] bg-gradient-to-b from-background to-muted/30">
|
||||
<div
|
||||
class="mx-auto flex max-w-3xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||
>
|
||||
@@ -132,19 +165,29 @@
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
{#each team as member}
|
||||
<div class="flex items-start gap-4 rounded-lg border bg-card p-4">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="flex items-start gap-4 rounded-lg border bg-card p-4 relative overflow-hidden"
|
||||
onclick={member.username === "FOISX" ? handleEasterEggClick : undefined}
|
||||
>
|
||||
<!-- Selectable trigger area overlay for cleaner interaction -->
|
||||
{#if member.username === "FOISX" && $dangerZoneEnabled && !settingsStore.settings.musicInspirationEnabled}
|
||||
<div class="absolute inset-0 cursor-pointer z-10 opacity-0 bg-transparent"></div>
|
||||
{/if}
|
||||
|
||||
<img
|
||||
src={gravatarUrls[member.email]}
|
||||
alt={member.name}
|
||||
class="h-14 w-14 rounded-full border-2 border-primary/20"
|
||||
class="h-14 w-14 rounded-full border-2 border-primary/20 z-0 select-none"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="flex-1 z-0">
|
||||
<div class="font-medium">{member.username} ({member.name})</div>
|
||||
<div class="text-sm text-primary/80">{member.role}</div>
|
||||
<div class="text-sm text-muted-foreground mt-1">{member.description}</div>
|
||||
<a
|
||||
href="mailto:{member.email}"
|
||||
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-primary mt-2 transition-colors"
|
||||
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-primary mt-2 transition-colors relative z-20"
|
||||
>
|
||||
<Mail class="size-3" />
|
||||
{member.email}
|
||||
|
||||
@@ -6,69 +6,11 @@
|
||||
import { ChevronLeft, Music, ExternalLink } from "@lucide/svelte";
|
||||
import * as m from "$lib/paraglide/messages";
|
||||
import { OpenURLInBrowser } from "$lib/wailsjs/go/main/App";
|
||||
import type { SpotifyTrack } from "./+page";
|
||||
|
||||
let { data } = $props();
|
||||
let config = $derived(data.config);
|
||||
|
||||
interface SpotifyTrack {
|
||||
name: string;
|
||||
artist: string;
|
||||
albumArt?: string;
|
||||
spotifyUrl: string;
|
||||
embedUrl: string;
|
||||
}
|
||||
|
||||
// Music that inspired this project
|
||||
const inspirationTracks: SpotifyTrack[] = [
|
||||
{
|
||||
name: "Strays",
|
||||
artist: "Ivycomb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/1aXATIo34e5ZZvFcavePpy",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1aXATIo34e5ZZvFcavePpy?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Headlock",
|
||||
artist: "Imogen Heap",
|
||||
spotifyUrl: "https://open.spotify.com/track/63Pi2NAx5yCgeLhCTOrEou",
|
||||
embedUrl: "https://open.spotify.com/embed/track/63Pi2NAx5yCgeLhCTOrEou?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "I Still Create",
|
||||
artist: "YonKaGor",
|
||||
spotifyUrl: "https://open.spotify.com/track/0IqTgwWU2syiSYbdBEromt",
|
||||
embedUrl: "https://open.spotify.com/embed/track/0IqTgwWU2syiSYbdBEromt?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Raised by Aliens",
|
||||
artist: "ivy comb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/5ezyCaoc5XiVdkpRYWeyG5",
|
||||
embedUrl: "https://open.spotify.com/embed/track/5ezyCaoc5XiVdkpRYWeyG5?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "VENOMOUS",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/4rPKifkzrhIYAsl1njwmjd",
|
||||
embedUrl: "https://open.spotify.com/embed/track/4rPKifkzrhIYAsl1njwmjd?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "PREY",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/510m8qwFCHgzi4zsQnjLUX",
|
||||
embedUrl: "https://open.spotify.com/embed/track/510m8qwFCHgzi4zsQnjLUX?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Dracula",
|
||||
artist: "Tame Impala",
|
||||
spotifyUrl: "https://open.spotify.com/track/1NXbNEAcPvY5G1xvfN57aA",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1NXbNEAcPvY5G1xvfN57aA?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Electric love",
|
||||
artist: "When Snakes Sing",
|
||||
spotifyUrl: "https://open.spotify.com/track/1nDkT2Cn13qDnFegF93UHi",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1nDkT2Cn13qDnFegF93UHi?utm_source=generator"
|
||||
}
|
||||
];
|
||||
let tracks: SpotifyTrack[] = $derived(data.tracks ?? []);
|
||||
|
||||
// Open external URL in default browser
|
||||
async function openUrl(url: string) {
|
||||
@@ -80,7 +22,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-[calc(100vh-1rem)] from-background to-muted/30">
|
||||
<div class="min-h-[calc(100vh-1rem)] bg-gradient-to-b from-background to-muted/30">
|
||||
<div
|
||||
class="mx-auto flex max-w-4xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||
>
|
||||
@@ -119,37 +61,25 @@
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-2">
|
||||
{#each inspirationTracks as track}
|
||||
{#each tracks as track}
|
||||
<div class="group relative">
|
||||
<div class="overflow-hidden rounded-lg bg-muted" style="height: 352px;">
|
||||
<iframe
|
||||
src={track.embedUrl}
|
||||
width="100%"
|
||||
height="352"
|
||||
frameborder="0"
|
||||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||||
loading="lazy"
|
||||
title={`${track.artist} - ${track.name}`}
|
||||
class="rounded-lg"
|
||||
></iframe>
|
||||
</div>
|
||||
<div class="mt-2 flex items-start justify-between gap-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium">{track.name}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{track.artist}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-8 shrink-0 opacity-70 hover:opacity-100"
|
||||
onclick={() => openUrl(track.spotifyUrl)}
|
||||
title="Open in Spotify"
|
||||
>
|
||||
<ExternalLink class="size-4" />
|
||||
</Button>
|
||||
<div class="overflow-hidden rounded-lg bg-muted">
|
||||
{#if track.embedHtml}
|
||||
{@html track.embedHtml}
|
||||
{:else}
|
||||
<iframe
|
||||
src={track.embedUrl}
|
||||
width="100%"
|
||||
height="352"
|
||||
frameborder="0"
|
||||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||||
loading="lazy"
|
||||
title={`${track.artist} - ${track.name}`}
|
||||
class="rounded-lg"
|
||||
></iframe>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -184,10 +114,6 @@
|
||||
and
|
||||
<span class="text-red-500">♥</span>
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
GUI: {config ? `v${config.GUISemver}` : "N/A"} •
|
||||
SDK: {config ? `v${config.SDKDecoderSemver}` : "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,18 +2,98 @@ import type { PageLoad } from './$types';
|
||||
import { GetConfig } from "$lib/wailsjs/go/main/App";
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export const load = (async () => {
|
||||
if (!browser) return { config: null };
|
||||
export interface SpotifyTrack {
|
||||
name: string;
|
||||
artist: string;
|
||||
spotifyUrl: string;
|
||||
embedUrl: string;
|
||||
embedHtml?: string;
|
||||
}
|
||||
|
||||
// Music that inspired this project
|
||||
const inspirationTracks: SpotifyTrack[] = [
|
||||
{
|
||||
name: "Strays",
|
||||
artist: "Ivycomb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/1aXATIo34e5ZZvFcavePpy",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1aXATIo34e5ZZvFcavePpy?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Headlock",
|
||||
artist: "Imogen Heap",
|
||||
spotifyUrl: "https://open.spotify.com/track/63Pi2NAx5yCgeLhCTOrEou",
|
||||
embedUrl: "https://open.spotify.com/embed/track/63Pi2NAx5yCgeLhCTOrEou?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "I Still Create",
|
||||
artist: "YonKaGor",
|
||||
spotifyUrl: "https://open.spotify.com/track/0IqTgwWU2syiSYbdBEromt",
|
||||
embedUrl: "https://open.spotify.com/embed/track/0IqTgwWU2syiSYbdBEromt?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Raised by Aliens",
|
||||
artist: "ivy comb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/5ezyCaoc5XiVdkpRYWeyG5",
|
||||
embedUrl: "https://open.spotify.com/embed/track/5ezyCaoc5XiVdkpRYWeyG5?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "VENOMOUS",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/4rPKifkzrhIYAsl1njwmjd",
|
||||
embedUrl: "https://open.spotify.com/embed/track/4rPKifkzrhIYAsl1njwmjd?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "PREY",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/510m8qwFCHgzi4zsQnjLUX",
|
||||
embedUrl: "https://open.spotify.com/embed/track/510m8qwFCHgzi4zsQnjLUX?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Dracula",
|
||||
artist: "Tame Impala",
|
||||
spotifyUrl: "https://open.spotify.com/track/1NXbNEAcPvY5G1xvfN57aA",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1NXbNEAcPvY5G1xvfN57aA?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Electric love",
|
||||
artist: "When Snakes Sing",
|
||||
spotifyUrl: "https://open.spotify.com/track/1nDkT2Cn13qDnFegF93UHi",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1nDkT2Cn13qDnFegF93UHi?utm_source=generator"
|
||||
}
|
||||
];
|
||||
|
||||
async function fetchEmbedHtml(track: SpotifyTrack, fetch: typeof globalThis.fetch): Promise<SpotifyTrack> {
|
||||
try {
|
||||
const oEmbedUrl = `https://open.spotify.com/oembed?url=${encodeURIComponent(track.spotifyUrl)}`;
|
||||
const res = await fetch(oEmbedUrl);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
return { ...track, embedHtml: data.html };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to fetch oEmbed for ${track.name}:`, e);
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
export const load = (async ({fetch}) => {
|
||||
if (!browser) return { config: null, tracks: inspirationTracks };
|
||||
|
||||
try {
|
||||
const configRoot = await GetConfig();
|
||||
const [configRoot, ...tracks] = await Promise.all([
|
||||
GetConfig(),
|
||||
...inspirationTracks.map(t => fetchEmbedHtml(t, fetch))
|
||||
]);
|
||||
|
||||
return {
|
||||
config: configRoot.EMLy
|
||||
config: configRoot.EMLy,
|
||||
tracks
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Failed to load config for inspiration", e);
|
||||
console.error("Failed to load data for inspiration", e);
|
||||
return {
|
||||
config: null
|
||||
config: null,
|
||||
tracks: inspirationTracks
|
||||
};
|
||||
}
|
||||
}) satisfies PageLoad;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import { Switch } from "$lib/components/ui/switch";
|
||||
import { ChevronLeft, Flame, Download, Upload, RefreshCw, CheckCircle2, AlertCircle } from "@lucide/svelte";
|
||||
import { ChevronLeft, Flame, Download, Upload, RefreshCw, CheckCircle2, AlertCircle, Sun, Moon } from "@lucide/svelte";
|
||||
import type { EMLy_GUI_Settings } from "$lib/types";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { It, Us } from "svelte-flags";
|
||||
@@ -40,7 +40,8 @@
|
||||
previewFileSupportedTypes: ["jpg", "jpeg", "png"],
|
||||
enableAttachedDebuggerProtection: true,
|
||||
useDarkEmailViewer: true,
|
||||
enableUpdateChecker: true,
|
||||
enableUpdateChecker: false,
|
||||
theme: "dark",
|
||||
};
|
||||
|
||||
async function setLanguage(
|
||||
@@ -73,8 +74,10 @@
|
||||
s.enableAttachedDebuggerProtection ?? defaults.enableAttachedDebuggerProtection ?? true,
|
||||
useDarkEmailViewer:
|
||||
s.useDarkEmailViewer ?? defaults.useDarkEmailViewer ?? true,
|
||||
enableUpdateChecker:
|
||||
s.enableUpdateChecker ?? defaults.enableUpdateChecker ?? true,
|
||||
enableUpdateChecker: runningInDevMode
|
||||
? false
|
||||
: (s.enableUpdateChecker ?? defaults.enableUpdateChecker ?? true),
|
||||
theme: s.theme || defaults.theme || "light",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,6 +89,7 @@
|
||||
!!a.enableAttachedDebuggerProtection === !!b.enableAttachedDebuggerProtection &&
|
||||
!!a.useDarkEmailViewer === !!b.useDarkEmailViewer &&
|
||||
!!a.enableUpdateChecker === !!b.enableUpdateChecker &&
|
||||
(a.theme ?? "light") === (b.theme ?? "light") &&
|
||||
JSON.stringify(a.previewFileSupportedTypes?.sort()) ===
|
||||
JSON.stringify(b.previewFileSupportedTypes?.sort())
|
||||
);
|
||||
@@ -278,25 +282,25 @@
|
||||
updateStatus = status;
|
||||
|
||||
if (status.updateAvailable) {
|
||||
toast.success(`Update available: ${status.availableVersion}`);
|
||||
toast.success(m.settings_toast_update_available({ version: status.availableVersion }));
|
||||
} else if (!status.errorMessage) {
|
||||
toast.info("You're on the latest version");
|
||||
toast.info(m.settings_toast_latest_version());
|
||||
} else {
|
||||
toast.error(status.errorMessage);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check for updates:", err);
|
||||
toast.error("Failed to check for updates");
|
||||
toast.error(m.settings_toast_check_failed());
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadUpdate() {
|
||||
try {
|
||||
await DownloadUpdate();
|
||||
toast.success("Update downloaded successfully");
|
||||
toast.success(m.settings_toast_download_success());
|
||||
} catch (err) {
|
||||
console.error("Failed to download update:", err);
|
||||
toast.error("Failed to download update");
|
||||
toast.error(m.settings_toast_download_failed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +310,7 @@
|
||||
// App will quit, so no toast needed
|
||||
} catch (err) {
|
||||
console.error("Failed to install update:", err);
|
||||
toast.error("Failed to launch installer");
|
||||
toast.error(m.settings_toast_install_failed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +328,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-[calc(100vh-1rem)] from-background to-muted/30">
|
||||
<div class="min-h-[calc(100vh-1rem)] bg-gradient-to-b from-background to-muted/30">
|
||||
<div
|
||||
class="mx-auto flex max-w-3xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||
>
|
||||
@@ -393,6 +397,52 @@
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title>{m.settings_appearance_title()}</Card.Title>
|
||||
<Card.Description>{m.settings_appearance_description()}</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<RadioGroup.Root
|
||||
bind:value={form.theme}
|
||||
class="flex flex-col gap-3"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item
|
||||
value="light"
|
||||
id="theme-light"
|
||||
class="cursor-pointer hover:cursor-pointer"
|
||||
/>
|
||||
<Label
|
||||
for="theme-light"
|
||||
class="flex items-center gap-2 cursor-pointer hover:cursor-pointer"
|
||||
>
|
||||
<Sun class="size-4" />
|
||||
{m.settings_theme_light()}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item
|
||||
value="dark"
|
||||
id="theme-dark"
|
||||
class="cursor-pointer hover:cursor-pointer"
|
||||
/>
|
||||
<Label
|
||||
for="theme-dark"
|
||||
class="flex items-center gap-2 cursor-pointer hover:cursor-pointer"
|
||||
>
|
||||
<Moon class="size-4" />
|
||||
{m.settings_theme_dark()}
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
<div class="text-xs text-muted-foreground mt-4">
|
||||
<strong>Info:</strong>
|
||||
{m.settings_theme_hint()}
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title>{m.settings_export_import_title()}</Card.Title>
|
||||
@@ -592,14 +642,14 @@
|
||||
{#if form.enableUpdateChecker}
|
||||
<Card.Root>
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title>Updates</Card.Title>
|
||||
<Card.Description>Check for and install application updates from your network share</Card.Description>
|
||||
<Card.Title>{m.settings_updates_title()}</Card.Title>
|
||||
<Card.Description>{m.settings_updates_description()}</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<!-- Current Version -->
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4">
|
||||
<div>
|
||||
<div class="font-medium">Current Version</div>
|
||||
<div class="font-medium">{m.settings_updates_current_version()}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{updateStatus.currentVersion} ({config?.GUIReleaseChannel || "stable"})
|
||||
</div>
|
||||
@@ -607,17 +657,17 @@
|
||||
{#if updateStatus.updateAvailable}
|
||||
<div class="flex items-center gap-2 text-sm font-medium text-green-600 dark:text-green-400">
|
||||
<AlertCircle class="size-4" />
|
||||
Update Available
|
||||
{m.settings_updates_available()}
|
||||
</div>
|
||||
{:else if updateStatus.errorMessage && updateStatus.lastCheckTime}
|
||||
<div class="flex items-center gap-2 text-sm text-destructive">
|
||||
<AlertCircle class="size-4" />
|
||||
Check failed
|
||||
{m.settings_updates_check_failed()}
|
||||
</div>
|
||||
{:else if updateStatus.lastCheckTime}
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<CheckCircle2 class="size-4" />
|
||||
No updates found
|
||||
{m.settings_updates_no_updates()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -627,12 +677,12 @@
|
||||
<!-- Check for Updates -->
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4">
|
||||
<div>
|
||||
<div class="font-medium">Check for Updates</div>
|
||||
<div class="font-medium">{m.settings_updates_check_label()}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{#if updateStatus.lastCheckTime}
|
||||
Last checked: {updateStatus.lastCheckTime}
|
||||
{m.settings_updates_last_checked({ time: updateStatus.lastCheckTime })}
|
||||
{:else}
|
||||
Click to check for available updates
|
||||
{m.settings_updates_click_check()}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -643,7 +693,7 @@
|
||||
disabled={updateStatus.checking || updateStatus.downloading}
|
||||
>
|
||||
<RefreshCw class="size-4 mr-2 {updateStatus.checking ? 'animate-spin' : ''}" />
|
||||
{updateStatus.checking ? "Checking..." : "Check Now"}
|
||||
{updateStatus.checking ? m.settings_updates_checking() : m.settings_updates_check_now()}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -652,12 +702,12 @@
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border border-blue-500/30 bg-blue-500/10 p-4">
|
||||
<div>
|
||||
<div class="font-medium">Version {updateStatus.availableVersion} Available</div>
|
||||
<div class="font-medium">{m.settings_updates_version_available({ version: updateStatus.availableVersion })}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{#if updateStatus.downloading}
|
||||
Downloading... {updateStatus.downloadProgress}%
|
||||
{m.settings_updates_downloading({ progress: updateStatus.downloadProgress })}
|
||||
{:else}
|
||||
Click to download the update
|
||||
{m.settings_updates_click_download()}
|
||||
{/if}
|
||||
</div>
|
||||
{#if updateStatus.releaseNotes}
|
||||
@@ -673,7 +723,7 @@
|
||||
disabled={updateStatus.downloading}
|
||||
>
|
||||
<Download class="size-4 mr-2" />
|
||||
{updateStatus.downloading ? `${updateStatus.downloadProgress}%` : "Download"}
|
||||
{updateStatus.downloading ? `${updateStatus.downloadProgress}%` : m.settings_updates_download_button()}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -683,9 +733,9 @@
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border border-green-500/30 bg-green-500/10 p-4">
|
||||
<div>
|
||||
<div class="font-medium">Update Ready to Install</div>
|
||||
<div class="font-medium">{m.settings_updates_ready_title()}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
Version {updateStatus.availableVersion} has been downloaded and verified
|
||||
{m.settings_updates_ready_ref({ version: updateStatus.availableVersion })}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@@ -694,7 +744,7 @@
|
||||
onclick={installUpdate}
|
||||
>
|
||||
<CheckCircle2 class="size-4 mr-2" />
|
||||
Install Now
|
||||
{m.settings_updates_install_button()}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -713,11 +763,11 @@
|
||||
|
||||
<!-- Info about update path -->
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<strong>Info:</strong> Updates are checked from your configured network share path.
|
||||
<strong>Info:</strong> {m.settings_updates_info_message()}
|
||||
{#if (config as any)?.UpdatePath}
|
||||
Current path: <code class="text-xs bg-muted px-1 py-0.5 rounded">{(config as any).UpdatePath}</code>
|
||||
{m.settings_updates_current_path()} <code class="text-xs bg-muted px-1 py-0.5 rounded">{(config as any).UpdatePath}</code>
|
||||
{:else}
|
||||
<span class="text-amber-600 dark:text-amber-400">No update path configured</span>
|
||||
<span class="text-amber-600 dark:text-amber-400">{m.settings_updates_no_path()}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -843,18 +893,19 @@
|
||||
class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4 border-destructive/30"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<Label class="text-sm">Enable Update Checker</Label>
|
||||
<Label class="text-sm">{m.settings_danger_update_checker_label()}</Label>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
Check for application updates from network share
|
||||
{m.settings_danger_update_checker_hint()}
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
bind:checked={form.enableUpdateChecker}
|
||||
class="cursor-pointer hover:cursor-pointer"
|
||||
disabled={runningInDevMode}
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<strong>Info:</strong> When enabled, the app will check for updates from your configured network share. Disable this if you manage updates manually or don't have network access.
|
||||
{m.settings_danger_update_checker_info()}
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
@@ -871,6 +922,7 @@
|
||||
</Card.Root>
|
||||
{/if}
|
||||
|
||||
{#if !runningInDevMode}
|
||||
<AlertDialog.Root bind:open={dangerWarningOpen}>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
@@ -878,7 +930,11 @@
|
||||
>{m.settings_danger_alert_title()}</AlertDialog.Title
|
||||
>
|
||||
<AlertDialog.Description>
|
||||
{m.settings_danger_alert_description()}
|
||||
{m.settings_danger_alert_description_part1()}
|
||||
<br />
|
||||
{m.settings_danger_alert_description_part2()}
|
||||
<br />
|
||||
{m.settings_danger_alert_description_part3()}
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
<AlertDialog.Footer>
|
||||
@@ -888,5 +944,6 @@
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { setupConsoleLogger } from '$lib/utils/logger-hook';
|
||||
import "./layout.css";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -81,11 +82,13 @@
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 32px;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -94,13 +97,13 @@
|
||||
user-select: none;
|
||||
flex: 0 0 32px;
|
||||
z-index: 50;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -114,7 +117,7 @@
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
@@ -124,16 +127,18 @@
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
background: #e81123;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--background);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.page-container {
|
||||
@@ -185,15 +185,15 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
color: white;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
height: 50px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -221,7 +221,7 @@
|
||||
.separator {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
background: var(--border);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
@@ -233,21 +233,21 @@
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--muted);
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
.image-area {
|
||||
flex: 1;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: var(--muted);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -275,16 +275,16 @@
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: var(--muted-foreground);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #f87171;
|
||||
background: rgba(248, 113, 113, 0.1);
|
||||
color: var(--destructive);
|
||||
background: var(--destructive-foreground);
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(248, 113, 113, 0.2);
|
||||
border: 1px solid var(--destructive);
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -79,7 +79,8 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -88,11 +89,13 @@
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 32px;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -101,13 +104,14 @@
|
||||
user-select: none;
|
||||
flex: 0 0 32px;
|
||||
z-index: 50;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
color: white;
|
||||
color: var(--muted-foreground);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -121,24 +125,25 @@
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
background: #e81123;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -147,6 +152,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
background: var(--background);
|
||||
}
|
||||
</style>
|
||||
@@ -233,9 +233,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #1e1e1e;
|
||||
background: var(--background);
|
||||
position: relative;
|
||||
user-select: none;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
@@ -247,15 +248,15 @@
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #111;
|
||||
color: white;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: rgba(255, 255, 255, 0.8);
|
||||
border: 2px solid var(--border);
|
||||
border-top-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
@@ -273,14 +274,14 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #111;
|
||||
color: #ef4444;
|
||||
background: var(--background);
|
||||
color: var(--destructive);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
height: 50px;
|
||||
background: #000;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -308,7 +309,7 @@
|
||||
.separator {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
background: var(--border);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
@@ -320,22 +321,16 @@
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--muted);
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
@@ -345,7 +340,7 @@
|
||||
justify-content: center;
|
||||
align-items: flex-start; /* scroll from top */
|
||||
padding: 20px;
|
||||
background: #333; /* Dark background for contrast */
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
canvas {
|
||||
@@ -363,12 +358,12 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
|
||||
Reference in New Issue
Block a user