diff --git a/design-mockups/01-command-deck.html b/design-mockups/01-command-deck.html new file mode 100644 index 0000000..cc2c265 --- /dev/null +++ b/design-mockups/01-command-deck.html @@ -0,0 +1,789 @@ + + + + + + Web App Launcher — Command Deck + + + + + + +
+ + + + +
+
+
SYSTEMS / OVERVIEW
+ +
+ + + + +
+
+ + + + +
+
+ +
+ +
+
+
Services Online
+
08 / 10
+
2 require attention
+
+
+
Avg Response
+
+ 142 ms +
+
p95 over 24h
+
+
+
Fleet Uptime
+
99.4%
+
rolling 30 days
+
+
+
UPS Load
+
61%
+
est. 38 min on battery
+
+
+ + +
+

Pinned Services

+ 8 ACTIVE +
+
+ +
+
+
🎬
+
+
Jellyfin
+
Media
+
+
+
+
+ + 99.9% 24h +
+
+ +
+
+
📷
+
+
Immich
+
Photos
+
+
+
+
+ + 100% 24h +
+
+ +
+
+
🌿
+
+
Gitea
+
Git
+
+
+
+
+ + 99.8% 24h +
+
+ +
+
+
🐳
+
+
Portainer
+
Containers
+
+
+
+
+ + 98.1% 24h +
+
+ +
+
+
🛡️
+
+
Pi-hole
+
DNS
+
+
+
+
+ + 100% 24h +
+
+ +
+
+
📋
+
+
Planka
+
Kanban
+
+
+
+
+ + 99.5% 24h +
+
+ +
+
+
⬇️
+
+
Deluge
+
Downloads
+
+
+
+
+ + OFFLINE +
+
+ +
+
+
🔀
+
+
Nginx Proxy Mgr
+
Network
+
+
+
+
+ + 99.9% 24h +
+
+
+ + +
+
+
+ + diff --git a/design-mockups/02-aurora-glass.html b/design-mockups/02-aurora-glass.html new file mode 100644 index 0000000..acf9ffe --- /dev/null +++ b/design-mockups/02-aurora-glass.html @@ -0,0 +1,915 @@ + + + + + + Web App Launcher — Aurora Glass + + + + + + +
+ + + + +
+
+
+
Good evening, Alexei
+
All systems nominal — 8 of 10 services responding
+
+
+ +
+ + + + +
+
+ + + + +
+
+
+ + +
+
+
+ + + +
+
8/10
+
Services online
+ +2 +
+
+
+ + + + +
+
142ms
+
Avg response
+ +18ms +
+
+
+ + + +
+
99.4%
+
Uptime · 30d
+ +0.2 +
+
+
+ + + +
+
61%
+
UPS load · 38m
+ batt +
+
+ +
+

Favorites

+ View all apps → +
+
+
+
+
🎬
+
+
Jellyfin
+
Media
+
+
Up
+
+
+ 99.9% uptime + + +
+
+
+
+
📷
+
+
Immich
+
Photos
+
+
Up
+
+
+ 100% uptime + + +
+
+
+
+
🌿
+
+
Gitea
+
Git server
+
+
Up
+
+
+ 99.8% uptime + + +
+
+
+
+
🐳
+
+
Portainer
+
Containers
+
+
Slow
+
+
+ 98.1% uptime + + +
+
+
+
+
🛡️
+
+
Pi-hole
+
DNS · Ads
+
+
Up
+
+
+ 100% uptime + + +
+
+
+
+
📋
+
+
Planka
+
Kanban
+
+
Up
+
+
+ 99.5% uptime + + +
+
+
+
+
⬇️
+
+
Deluge
+
Downloads
+
+
Down
+
+
+ offline · 4m + + +
+
+
+
+
🔀
+
+
Proxy Mgr
+
Network
+
+
Up
+
+
+ 99.9% uptime + + +
+
+
+ +
+ Accent (user-tunable): + + + + + + — try clicking; the whole UI + aurora retints live +
+
+
+ + diff --git a/design-mockups/03-editorial.html b/design-mockups/03-editorial.html new file mode 100644 index 0000000..2201e5a --- /dev/null +++ b/design-mockups/03-editorial.html @@ -0,0 +1,643 @@ + + + + + + Web App Launcher — Editorial + + + + + + +
+ +
+ + +
+ +
+ + + + +
+
+
+ + +
+
+
Tuesday · 27 May · 19:42
+

Your stack,
all in one place.

+

+ Ten services under one roof. Eight humming, two asking for attention. Everything + launches from here. +

+ +
+
+
+
8/10
+
Services onlineDeluge offline · Portainer slow to respond
+
+
+
99.4%
+
Fleet uptimeRolling 30-day average across all monitors
+
+
+
142ms
+
Median responsep95 latency over the last 24 hours
+
+
+
+ + +
+

Favorites

+
+
eight pinned
+
+
+
+
+
🎬
+
+
Jellyfin
+
Media server · the crown jewel
+
+ Online +
+

+ Streaming to 3 devices right now. Library scan completed 2 hours ago — 4,212 movies, 318 + shows indexed and healthy. +

+
+
99.9% uptime · 24h
+ + + +
+
+
+
+
📷
+
+
Immich
+
Photos
+
+
+
+
100% 24h
+ Up +
+
+
+
+
🌿
+
+
Gitea
+
Git
+
+
+
+
99.8% 24h
+ Up +
+
+
+
+
🐳
+
+
Portainer
+
Containers
+
+
+
+
98.1% 24h
+ Slow +
+
+
+
+
🛡️
+
+
Pi-hole
+
DNS
+
+
+
+
100% 24h
+ Up +
+
+
+
+
📋
+
+
Planka
+
Kanban
+
+
+
+
99.5% 24h
+ Up +
+
+
+
+
⬇️
+
+
Deluge
+
Downloads
+
+
+
+
+ Down +
+
+
+ +
+ Editorial — Bricolage Grotesque + Instrument Serifwarm paper · ink rules · hard shadows · asymmetric grid +
+
+ + diff --git a/design-mockups/04-cozy-home.html b/design-mockups/04-cozy-home.html new file mode 100644 index 0000000..58dba0b --- /dev/null +++ b/design-mockups/04-cozy-home.html @@ -0,0 +1,723 @@ + + + + + + Web App Launcher — Cozy Home + + + + + + +
+ + + + +
+
+ +
+ + + + +
+
+ + + + +
+
+ +

Hi Alexei 👋

+

It’s a calm evening — 8 of your 10 apps are happy and online.

+ +
+
+
+ + + +
+
+
8/10
+
Apps online
+
+
+
+
+ + + + +
+
+
142ms
+
Avg speed
+
+
+
+
+ + + +
+
+
99.4%
+
Uptime · 30d
+
+
+
+
+ + + +
+
+
38 min
+
Battery left
+
+
+
+ +
+

Your favorites

+ See all → +
+
+
+
+
🎬
+
Jellyfin
+
Movies & shows
+
+ Online99.9% +
+
+
+
+
📷
+
Immich
+
Photos
+
+ Online100% +
+
+
+
+
🌿
+
Gitea
+
Code
+
+ Online99.8% +
+
+
+
+
🐳
+
Portainer
+
Containers
+
+ A bit slow98.1% +
+
+
+
+
🛡️
+
Pi-hole
+
Ad blocker
+
+ Online100% +
+
+
+
+
📋
+
Planka
+
To-dos
+
+ Online99.5% +
+
+
+
+
⬇️
+
Deluge
+
Downloads
+
+ Asleep +
+
+
+
+
🔀
+
Proxy
+
Networking
+
+ Online99.9% +
+
+
+ +

+ Cozy Home — Fraunces + Figtree · warm cream · soft pastel rooms · gentle motion +

+
+
+ + diff --git a/design-mockups/cozy-system.html b/design-mockups/cozy-system.html new file mode 100644 index 0000000..4f5c5a6 --- /dev/null +++ b/design-mockups/cozy-system.html @@ -0,0 +1,639 @@ + + + + + + Cozy Home — Design System Reference + + + + + + +
+

Cozy Home — Design System

+

+ The component pattern sheet for the migration. Every phase styles its components to match + these primitives. Tokens here mirror what now lives in + src/app.css — change them there and the whole app follows. +

+ +
+
Color tokens
+
+
+
+
backgroundcream #fdf8f2
+
+
+
+
card#fffdfa
+
+
+
+
primaryterracotta · tunable
+
+
+
+
foregroundwarm ink
+
+
+
+
muted#f3ecde
+
+
+
+
border#ece2d3
+
+
+
+
+
+
room · terra
+
+
+
+
room · peach
+
+
+
+
room · butter
+
+
+
+
room · sage
+
+
+
+
room · sky
+
+
+
+
room · lav
+
+
+
+ +
+
Typography — Fraunces (display) · Figtree (body)
+

Good evening, Alexei

+

Your favorites

+

Jellyfin

+

+ Body copy is Figtree — friendly, legible, rounded. It carries descriptions, hints, and + plain-language status like “a bit slow” or “asleep”. +

+
+ +
+
Buttons
+
+ + + + +
+
+ +
+
Form fields
+
+
+ Shown on the card and in search. +
+
+
+
+ +
+
Status pills & room tags
+
+ Online + A bit slow + Asleep + Media + Network + Git +
+
+ +
+
Tabs
+
+
Overview
+
Activity
+
Settings
+
+
+ +
+
App card
+
+
+
+ 🎬 +
+

Jellyfin

+
Movies & shows
+
+ Online99.9% +
+
+
+ +
+
Dialog
+
+

Remove Deluge?

+

This deletes the app and its uptime history. This can’t be undone.

+
+ +
+
+
+ +
+
Table (admin)
+ + + + + + + + + + + + + + + + + + + + + + + +
UserRoleStatusLast seen
AlexeiAdmin + Active + just now
GuestViewer + Idle + 2h ago
+
+ +
+
Empty state
+
+
+ + + + +
+

No apps yet

+

Add your first service and it’ll show up here with live status.

+ +
+
+ +

+ Cozy Home design system · mirrors src/app.css · use as the pattern for every migrated + component +

+
+ + diff --git a/design-mockups/index.html b/design-mockups/index.html new file mode 100644 index 0000000..752657e --- /dev/null +++ b/design-mockups/index.html @@ -0,0 +1,160 @@ + + + + + + Launcher — Redesign Mockups + + + + +
+

Web App Launcher — Redesign Directions

+

+ Four aesthetic directions for the same launcher, built as theme presets of one modernized + design system. Open each, resize, hover the cards, and try the live accent swatches in + Aurora Glass. Pick the one that fits — or mix and match. +

+
+ + 01 · Dark · Power-user +
Command Deck
+
+ Mission-control / terminal. Dense, glanceable telemetry, LED status, monospace data. + Saira + JetBrains Mono. +
+
+ +
+
+ + 02 · Dark · Premium +
Aurora Glass
+
+ Frosted glass over a living gradient mesh. Soft glows, generous space, fully retintable + accent. Outfit + Manrope. +
+
+ +
+
+ + 03 · Light · Bold +
Editorial
+
+ Magazine masthead, big display type, ink rules, hard shadows, asymmetric grid. Bricolage + Grotesque + Instrument Serif. +
+
+ +
+
+ + 04 · Light · Friendly +
Cozy Home
+
+ Warm cream, soft rounded cards, pastel “rooms”, gentle motion. Friendly for the whole + household. Fraunces + Figtree. +
+
+ +
+
+
+
+ + diff --git a/src/app.css b/src/app.css index 96983bd..bc0b6d0 100644 --- a/src/app.css +++ b/src/app.css @@ -4,83 +4,140 @@ @custom-variant dark (&:is(.dark *)); -:root { - /* HSL-based primary color (overridden by theme store via JS) */ - --primary-h: 220; - --primary-s: 70%; - --primary-l: 50%; +/* ===================================================================== + COZY HOME design system + --------------------------------------------------------------------- + Tokens are intentionally organised as a single swappable "bundle": + the neutral ramp + accent + shape + type live here in :root / .dark. + Swapping these blocks for another set (e.g. Command Deck / Aurora / + Editorial) is all a future theme-preset system needs to do — no + component edits required, because the whole app reads these vars. + Accent stays user-tunable via --primary-h / --primary-s. + ===================================================================== */ - --background: hsl(0 0% 100%); - --foreground: hsl(240 10% 3.9%); - --muted: hsl(240 4.8% 95.9%); - --muted-foreground: hsl(240 3.8% 46.1%); - --popover: hsl(0 0% 100%); - --popover-foreground: hsl(240 10% 3.9%); - --card: hsl(0 0% 100%); - --card-foreground: hsl(240 10% 3.9%); - --border: hsl(240 5.9% 90%); - --input: hsl(240 5.9% 90%); +:root { + /* Accent — terracotta by default, still user-tunable from settings */ + --primary-h: 16; + --primary-s: 72%; + --primary-l: 56%; + + /* Neutrals — warm cream "paper" ramp */ + --background: hsl(35 56% 97%); /* #fdf8f2 warm cream */ + --foreground: hsl(33 18% 18%); /* #3a322b warm ink */ + --muted: hsl(36 42% 93%); /* #f3ecde */ + --muted-foreground: hsl(34 12% 47%); /* #857a6d */ + --popover: hsl(40 60% 99%); + --popover-foreground: hsl(33 18% 18%); + --card: hsl(40 60% 99%); /* #fffdfa */ + --card-foreground: hsl(33 18% 18%); + --border: hsl(36 35% 88%); /* #ece2d3 */ + --input: hsl(36 35% 88%); --primary: hsl(var(--primary-h) var(--primary-s) var(--primary-l)); - --primary-foreground: hsl(0 0% 98%); - --secondary: hsl(240 4.8% 95.9%); - --secondary-foreground: hsl(240 5.9% 10%); - --accent: hsl(240 4.8% 95.9%); - --accent-foreground: hsl(240 5.9% 10%); - --destructive: hsl(0 72.2% 50.6%); - --destructive-foreground: hsl(0 0% 98%); + --primary-foreground: hsl(40 60% 99%); + --secondary: hsl(36 42% 93%); + --secondary-foreground: hsl(33 18% 22%); + --accent: hsl(34 44% 90%); /* hover wash */ + --accent-foreground: hsl(33 18% 20%); + --destructive: hsl(6 68% 56%); + --destructive-foreground: hsl(40 60% 99%); --ring: hsl(var(--primary-h) var(--primary-s) var(--primary-l)); - --status-online: #22c55e; - --status-offline: #ef4444; - --status-degraded: #eab308; - --status-unknown: #6b7280; - --radius: 0.5rem; - --sidebar: hsl(0 0% 98%); - --sidebar-foreground: hsl(240 5.3% 26.1%); + + /* Status — vivid values for dots / bars / rings / sparklines */ + --status-online: #5fa86c; + --status-offline: #e0685f; + --status-degraded: #d99a2b; + --status-unknown: #b3a899; + /* Status "ink" — darker, AA-legible as small text on cream + tinted washes */ + --status-online-ink: #2c723f; + --status-offline-ink: #bd382e; + --status-degraded-ink: #785406; + --status-unknown-ink: #6b5f50; + + /* Pastel "rooms" — category / board accents */ + --room-sage: #7fb069; + --room-sky: #6ca9d6; + --room-butter: #f3c969; + --room-lav: #b09fd6; + --room-peach: #ff9a76; + --room-terra: #e8754f; + + /* Shape — cozy rounding */ + --radius: 1rem; + + /* Soft warm shadows */ + --shadow-soft: 0 10px 26px -20px rgba(80, 50, 20, 0.45); + --shadow-lift: 0 22px 40px -22px rgba(80, 50, 20, 0.4); + + /* Typography */ + --font-sans: 'Figtree', system-ui, -apple-system, sans-serif; + --font-display: 'Fraunces', 'Figtree', Georgia, serif; + + /* Sidebar */ + --sidebar: hsl(36 48% 95%); + --sidebar-foreground: hsl(34 14% 32%); --sidebar-primary: hsl(var(--primary-h) var(--primary-s) var(--primary-l)); - --sidebar-primary-foreground: hsl(0 0% 98%); - --sidebar-accent: hsl(240 4.8% 95.9%); - --sidebar-accent-foreground: hsl(240 5.9% 10%); - --sidebar-border: hsl(220 13% 91%); + --sidebar-primary-foreground: hsl(40 60% 99%); + --sidebar-accent: hsl(34 44% 90%); + --sidebar-accent-foreground: hsl(33 18% 20%); + --sidebar-border: hsl(36 35% 87%); --sidebar-ring: hsl(var(--primary-h) calc(var(--primary-s) * 1.2) 60%); } .dark { - --primary-l: 60%; + /* "Dusk" — warm charcoal, not cold black */ + --primary-l: 62%; - --background: hsl(240 10% 3.9%); - --foreground: hsl(0 0% 98%); - --muted: hsl(240 3.7% 15.9%); - --muted-foreground: hsl(240 5% 64.9%); - --popover: hsl(240 10% 3.9%); - --popover-foreground: hsl(0 0% 98%); - --card: hsl(240 6% 7%); - --card-foreground: hsl(0 0% 98%); - --border: hsl(240 3.7% 15.9%); - --input: hsl(240 3.7% 15.9%); + --background: hsl(30 14% 9%); /* #1a1714 */ + --foreground: hsl(35 30% 90%); /* #f0e9df */ + --muted: hsl(30 14% 16%); /* #2b2520 */ + --muted-foreground: hsl(35 14% 64%); /* #b3a899 */ + --popover: hsl(30 16% 12%); + --popover-foreground: hsl(35 30% 90%); + --card: hsl(30 16% 13%); /* #262019 */ + --card-foreground: hsl(35 30% 90%); + --border: hsl(31 16% 19%); /* #352d24 */ + --input: hsl(31 16% 19%); --primary: hsl(var(--primary-h) var(--primary-s) var(--primary-l)); - --primary-foreground: hsl(240 5.9% 10%); - --secondary: hsl(240 3.7% 15.9%); - --secondary-foreground: hsl(0 0% 98%); - --accent: hsl(240 3.7% 15.9%); - --accent-foreground: hsl(0 0% 98%); - --destructive: hsl(0 62.8% 30.6%); - --destructive-foreground: hsl(0 0% 98%); + --primary-foreground: hsl(30 18% 10%); + --secondary: hsl(30 14% 16%); + --secondary-foreground: hsl(35 30% 90%); + --accent: hsl(30 14% 18%); + --accent-foreground: hsl(35 30% 90%); + --destructive: hsl(6 58% 46%); + --destructive-foreground: hsl(40 60% 99%); --ring: hsl(var(--primary-h) var(--primary-s) var(--primary-l)); - --sidebar: hsl(240 5.9% 6%); - --sidebar-foreground: hsl(240 4.8% 95.9%); + + --status-online: #6dba79; + --status-offline: #ea7a72; + --status-degraded: #e3ab4a; + --status-unknown: #9a8f80; + /* On dusk charcoal the vivid values already clear AA — ink == vivid */ + --status-online-ink: #6dba79; + --status-offline-ink: #ea7a72; + --status-degraded-ink: #e3ab4a; + --status-unknown-ink: #9a8f80; + + --shadow-soft: 0 12px 30px -20px rgba(0, 0, 0, 0.65); + --shadow-lift: 0 26px 46px -22px rgba(0, 0, 0, 0.6); + + --sidebar: hsl(30 16% 11%); + --sidebar-foreground: hsl(35 22% 82%); --sidebar-primary: hsl(var(--primary-h) var(--primary-s) var(--primary-l)); - --sidebar-primary-foreground: hsl(0 0% 100%); - --sidebar-accent: hsl(240 3.7% 15.9%); - --sidebar-accent-foreground: hsl(240 4.8% 95.9%); - --sidebar-border: hsl(240 3.7% 15.9%); + --sidebar-primary-foreground: hsl(30 18% 10%); + --sidebar-accent: hsl(30 14% 18%); + --sidebar-accent-foreground: hsl(35 30% 90%); + --sidebar-border: hsl(31 16% 19%); --sidebar-ring: hsl(var(--primary-h) calc(var(--primary-s) * 1.2) 60%); } @theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) - 8px); + --radius-md: calc(var(--radius) - 4px); --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); + --radius-xl: calc(var(--radius) + 6px); + + --font-sans: var(--font-sans); + --font-display: var(--font-display); --color-background: var(--background); --color-foreground: var(--foreground); @@ -101,6 +158,23 @@ --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-ring: var(--ring); + + --color-status-online: var(--status-online); + --color-status-offline: var(--status-offline); + --color-status-degraded: var(--status-degraded); + --color-status-unknown: var(--status-unknown); + --color-status-online-ink: var(--status-online-ink); + --color-status-offline-ink: var(--status-offline-ink); + --color-status-degraded-ink: var(--status-degraded-ink); + --color-status-unknown-ink: var(--status-unknown-ink); + + --color-room-sage: var(--room-sage); + --color-room-sky: var(--room-sky); + --color-room-butter: var(--room-butter); + --color-room-lav: var(--room-lav); + --color-room-peach: var(--room-peach); + --color-room-terra: var(--room-terra); + --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); @@ -117,10 +191,21 @@ } body { @apply bg-background text-foreground; + font-family: var(--font-sans); + font-feature-settings: 'ss01', 'cv01'; transition: background-color 0.3s ease, color 0.3s ease; } + /* Display face for headings — gives the cozy/editorial warmth */ + h1, + h2, + h3, + .font-display { + font-family: var(--font-display); + font-optical-sizing: auto; + letter-spacing: -0.01em; + } } /* ===== Status Indicator Pulse ===== */ @@ -138,27 +223,27 @@ .status-online { animation: status-pulse 2s ease-in-out infinite; - color: hsl(142 71% 45%); + color: var(--status-online); } /* ===== Card Style Variants ===== */ .card-solid { background: var(--card); border: 1px solid var(--border); + box-shadow: var(--shadow-soft); } .card-glass { backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); - background: color-mix(in srgb, var(--card) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.05); + background: color-mix(in srgb, var(--card) 70%, transparent); + border: 1px solid color-mix(in srgb, var(--border) 50%, transparent); + box-shadow: var(--shadow-soft); } .dark .card-glass { - background: color-mix(in srgb, var(--card) 50%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 25%, transparent); - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.15); + background: color-mix(in srgb, var(--card) 55%, transparent); + border: 1px solid color-mix(in srgb, var(--border) 35%, transparent); } .card-outline { @@ -170,24 +255,17 @@ border-color: var(--border); } -/* ===== Card Hover Effects ===== */ +/* ===== Card Hover Effects — gentle cozy lift + micro-tilt ===== */ .card-hover { transition: - transform 0.2s ease, - box-shadow 0.2s ease; + transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 0.2s ease, + border-color 0.2s ease; } .card-hover:hover { - transform: scale(1.02); - box-shadow: - 0 10px 25px -5px rgba(0, 0, 0, 0.15), - 0 4px 10px -5px rgba(0, 0, 0, 0.1); -} - -.dark .card-hover:hover { - box-shadow: - 0 10px 25px -5px rgba(0, 0, 0, 0.4), - 0 4px 10px -5px rgba(0, 0, 0, 0.3); + transform: translateY(-5px) rotate(-0.35deg); + box-shadow: var(--shadow-lift); } /* ===== Skeleton Loading ===== */ @@ -201,14 +279,14 @@ } .skeleton { - background: linear-gradient(90deg, var(--muted) 25%, hsl(240 4.8% 85%) 50%, var(--muted) 75%); + background: linear-gradient(90deg, var(--muted) 25%, hsl(36 30% 86%) 50%, var(--muted) 75%); background-size: 200% 100%; animation: shimmer 1.5s ease-in-out infinite; border-radius: var(--radius); } .dark .skeleton { - background: linear-gradient(90deg, var(--muted) 25%, hsl(240 3.7% 22%) 50%, var(--muted) 75%); + background: linear-gradient(90deg, var(--muted) 25%, hsl(30 12% 22%) 50%, var(--muted) 75%); background-size: 200% 100%; } @@ -236,7 +314,7 @@ [data-keyboard-selected='true'] { outline: 2px solid hsl(var(--primary-h) var(--primary-s) var(--primary-l)); outline-offset: 2px; - border-radius: var(--radius, 0.5rem); + border-radius: var(--radius, 1rem); } /* ===== Aurora Keyframes ===== */ @@ -251,3 +329,40 @@ background-position: 0% 50%; } } + +/* ===== Cozy greeting wave ===== */ +@keyframes cozy-wave { + 0%, + 60%, + 100% { + transform: rotate(0); + } + 10% { + transform: rotate(16deg); + } + 20% { + transform: rotate(-8deg); + } + 30% { + transform: rotate(14deg); + } + 40% { + transform: rotate(-4deg); + } +} + +.cozy-wave { + display: inline-block; + transform-origin: 70% 70%; + animation: cozy-wave 2.6s ease-in-out infinite; +} + +@media (prefers-reduced-motion: reduce) { + .cozy-wave, + .status-online { + animation: none; + } + .card-hover:hover { + transform: none; + } +} diff --git a/src/app.html b/src/app.html index af21d81..4f9d99d 100644 --- a/src/app.html +++ b/src/app.html @@ -5,11 +5,14 @@ - + + + -
+
diff --git a/src/lib/components/admin/PermissionEditor.svelte b/src/lib/components/admin/PermissionEditor.svelte index 9f538a7..1e49808 100644 --- a/src/lib/components/admin/PermissionEditor.svelte +++ b/src/lib/components/admin/PermissionEditor.svelte @@ -198,7 +198,7 @@ {#if permissions.length > 0} -
+
diff --git a/src/lib/components/admin/SettingsForm.svelte b/src/lib/components/admin/SettingsForm.svelte index 78dd096..9fa387b 100644 --- a/src/lib/components/admin/SettingsForm.svelte +++ b/src/lib/components/admin/SettingsForm.svelte @@ -55,7 +55,7 @@ id="authMode" name="authMode" bind:value={$form.authMode} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" > @@ -92,7 +92,7 @@ name="oauthClientId" type="text" bind:value={$form.oauthClientId} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" placeholder={$t('admin.oauth_client_id_placeholder')} /> @@ -103,7 +103,7 @@ name="oauthClientSecret" type="password" bind:value={$form.oauthClientSecret} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" placeholder={$t('admin.oauth_client_secret_placeholder')} /> @@ -114,7 +114,7 @@ name="oauthDiscoveryUrl" type="url" bind:value={$form.oauthDiscoveryUrl} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" placeholder={$t('admin.oauth_discovery_url_placeholder')} /> {#if $errors.oauthDiscoveryUrl}{$errors.oauthDiscoveryUrl}{/if} @@ -124,12 +124,12 @@ type="button" onclick={testOAuthConnection} disabled={oauthTesting} - class="rounded-md border border-border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50" + class="rounded-md border border-border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 disabled:cursor-not-allowed disabled:opacity-50" > {oauthTesting ? $t('admin.oauth_testing') : $t('admin.oauth_test')} {#if oauthTestResult} - + {oauthTestResult} {/if} @@ -147,7 +147,7 @@ id="defaultTheme" name="defaultTheme" bind:value={$form.defaultTheme} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" > @@ -161,8 +161,8 @@ name="defaultPrimaryColor" type="text" bind:value={$form.defaultPrimaryColor} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" - placeholder="#6366f1" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" + placeholder="#e8754f" pattern="^#[0-9a-fA-F]{6}$" /> {#if $form.defaultPrimaryColor} @@ -188,7 +188,7 @@ name="healthcheckDefaults" bind:value={$form.healthcheckDefaults} rows="4" - class="w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 font-mono text-sm text-foreground" placeholder={'{"interval": 300, "timeout": 5000, "method": "GET"}'} > {#if $errors.healthcheckDefaults}{$errors.healthcheckDefaults}{/if} @@ -206,7 +206,7 @@ id="dockerSocketPath" type="text" bind:value={dockerSocketPath} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" placeholder="/var/run/docker.sock" />

{$t('admin.discovery_docker_socket_hint')}

@@ -217,7 +217,7 @@ id="traefikApiUrl" type="url" bind:value={traefikApiUrl} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" placeholder="http://traefik:8080" />

{$t('admin.discovery_traefik_url_hint')}

@@ -244,7 +244,7 @@
@@ -141,7 +141,7 @@ type="text" bind:value={newName} placeholder="Tag name" - class="rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" required />
@@ -159,7 +159,7 @@ diff --git a/src/lib/components/admin/UserTable.svelte b/src/lib/components/admin/UserTable.svelte index 2be630f..0f09182 100644 --- a/src/lib/components/admin/UserTable.svelte +++ b/src/lib/components/admin/UserTable.svelte @@ -37,7 +37,7 @@ let selectedGroupId = $state(''); -
+
diff --git a/src/lib/components/app/AnimatedStatusRing.svelte b/src/lib/components/app/AnimatedStatusRing.svelte index bd77feb..b201c14 100644 --- a/src/lib/components/app/AnimatedStatusRing.svelte +++ b/src/lib/components/app/AnimatedStatusRing.svelte @@ -126,4 +126,13 @@ .status-ring-unknown { animation: ring-rotate-dash 8s linear infinite; } + + @media (prefers-reduced-motion: reduce) { + .status-ring-online, + .status-ring-offline, + .status-ring-degraded, + .status-ring-unknown { + animation: none; + } + } diff --git a/src/lib/components/app/AppCard.svelte b/src/lib/components/app/AppCard.svelte index d63f965..aeb6e49 100644 --- a/src/lib/components/app/AppCard.svelte +++ b/src/lib/components/app/AppCard.svelte @@ -58,6 +58,14 @@ }); } + // Cozy "room" pastel tint — stable per app, derived from its name + const roomTints = ['var(--room-terra)', 'var(--room-sky)', 'var(--room-sage)', 'var(--room-butter)', 'var(--room-lav)', 'var(--room-peach)']; + const tint = $derived.by(() => { + let h = 0; + for (const ch of app.name) h = (h * 31 + ch.charCodeAt(0)) >>> 0; + return roomTints[h % roomTints.length]; + }); + const iconDisplay = $derived.by(() => { if (!app.icon) return null; @@ -82,32 +90,39 @@ tabindex="0" onclick={() => window.open(app.url, '_blank', 'noopener,noreferrer')} onkeydown={(e: KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') window.open(app.url, '_blank', 'noopener,noreferrer'); }} - class="card-hover group flex cursor-pointer flex-col rounded-xl border border-border bg-card p-4 transition-colors hover:border-primary/50" + class="card-hover group relative flex cursor-pointer flex-col overflow-hidden rounded-[1.4rem] border border-border bg-card p-5 shadow-[var(--shadow-soft)]" title={app.description ?? app.name} > + +
{#if iconDisplay?.kind === 'emoji'} - {iconDisplay.value} + {iconDisplay.value} {:else if iconDisplay?.kind === 'image'} {app.name} icon {:else if iconDisplay?.kind === 'text'} - {iconDisplay.value} + {iconDisplay.value} {:else} - {app.name.charAt(0).toUpperCase()} + {app.name.charAt(0).toUpperCase()} {/if}
-

+

{app.name}

{#if app.description} -

{app.description}

+

{app.description}

{/if} @@ -143,14 +158,15 @@
{#if uptimePercent !== null} - {uptimePercent}% {$t('app.uptime')} + {uptimePercent}% {$t('app.uptime')} {/if}
{/if} {#if app.category} {app.category} diff --git a/src/lib/components/app/AppForm.svelte b/src/lib/components/app/AppForm.svelte index 0e9a780..14b1258 100644 --- a/src/lib/components/app/AppForm.svelte +++ b/src/lib/components/app/AppForm.svelte @@ -121,7 +121,7 @@ name="name" type="text" bind:value={$form.name} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder={$t('app.name_placeholder')} /> {#if $errors.name} @@ -138,7 +138,7 @@ name="url" type="url" bind:value={$form.url} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder={$t('app.url_placeholder')} /> {#if $errors.url} @@ -170,7 +170,7 @@ name="description" type="text" bind:value={$form.description} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder={$t('app.description_placeholder')} />
@@ -186,7 +186,7 @@ bind:value={$form.category} suggestions={categorySuggestions} placeholder={$t('app.category_placeholder')} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" /> @@ -200,7 +200,7 @@ bind:value={$form.tags} suggestions={tagSuggestions} placeholder={$t('app.tags_placeholder')} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" /> @@ -269,7 +269,7 @@ name="healthcheckExpectedStatus" type="number" bind:value={$form.healthcheckExpectedStatus} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" min="100" max="599" /> @@ -287,7 +287,7 @@ name="healthcheckTimeout" type="number" bind:value={$form.healthcheckTimeout} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" min="1000" max="30000" step="1000" @@ -307,7 +307,7 @@ name="healthcheckInterval" type="number" bind:value={$form.healthcheckInterval} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" min="30" max="86400" /> @@ -349,7 +349,7 @@ id="integrationType" name="integrationType" bind:value={$form.integrationType} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" > {#each availableIntegrations as integration (integration.id)} @@ -395,7 +395,7 @@ {testingConnection ? 'Testing...' : 'Test Connection'} {#if testResult} - + {testResult.message} {/if} @@ -412,7 +412,7 @@ @@ -196,7 +196,7 @@ type="button" onclick={saveLinks} disabled={saving} - class="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50" + class="rounded-xl bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50" > {saving ? 'Saving...' : 'Save Links'} diff --git a/src/lib/components/app/AppUrlPreview.svelte b/src/lib/components/app/AppUrlPreview.svelte index c4dd3c9..462ce2e 100644 --- a/src/lib/components/app/AppUrlPreview.svelte +++ b/src/lib/components/app/AppUrlPreview.svelte @@ -21,8 +21,8 @@ const statusColor = $derived(() => { if (!result) return ''; if (result.error) return 'text-destructive'; - if (result.status >= 200 && result.status < 300) return 'text-green-500'; - if (result.status >= 300 && result.status < 400) return 'text-yellow-500'; + if (result.status >= 200 && result.status < 300) return 'text-status-online-ink'; + if (result.status >= 300 && result.status < 400) return 'text-status-degraded-ink'; return 'text-destructive'; }); diff --git a/src/lib/components/app/IntegrationConfigFields.svelte b/src/lib/components/app/IntegrationConfigFields.svelte index 3fdf345..6a07656 100644 --- a/src/lib/components/app/IntegrationConfigFields.svelte +++ b/src/lib/components/app/IntegrationConfigFields.svelte @@ -10,7 +10,7 @@ let { fields, values, onchange, idPrefix = 'int' }: Props = $props(); - const inputClass = 'w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30'; + const inputClass = 'w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30';
diff --git a/src/lib/components/app/SparklineChart.svelte b/src/lib/components/app/SparklineChart.svelte index 2977be8..124aa13 100644 --- a/src/lib/components/app/SparklineChart.svelte +++ b/src/lib/components/app/SparklineChart.svelte @@ -13,10 +13,10 @@ let { data, width = 80, height = 20 }: Props = $props(); const STATUS_COLORS: Record = { - online: '#22c55e', - offline: '#ef4444', - degraded: '#eab308', - unknown: '#6b7280' + online: 'var(--status-online)', + offline: 'var(--status-offline)', + degraded: 'var(--status-degraded)', + unknown: 'var(--status-unknown)' }; const barWidth = $derived(data.length > 0 ? Math.max(1, (width - 2) / data.length) : 1); diff --git a/src/lib/components/background/AmbientBackground.svelte b/src/lib/components/background/AmbientBackground.svelte index 74c4968..bd6aadf 100644 --- a/src/lib/components/background/AmbientBackground.svelte +++ b/src/lib/components/background/AmbientBackground.svelte @@ -1,5 +1,6 @@ -
+
{ icon = v; }} size="sm" /> diff --git a/src/lib/components/board/BoardAccessControl.svelte b/src/lib/components/board/BoardAccessControl.svelte index 7cc5135..bcc51eb 100644 --- a/src/lib/components/board/BoardAccessControl.svelte +++ b/src/lib/components/board/BoardAccessControl.svelte @@ -131,7 +131,7 @@ class="w-full rounded border border-input bg-background px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground" /> {#if searchQuery.length > 0 && filteredTargetOptions.length > 0} -
+
{#each filteredTargetOptions as option (option.id)}
diff --git a/src/lib/components/board/BoardCard.svelte b/src/lib/components/board/BoardCard.svelte index e2c97d7..97cc6de 100644 --- a/src/lib/components/board/BoardCard.svelte +++ b/src/lib/components/board/BoardCard.svelte @@ -20,32 +20,53 @@ let { board }: Props = $props(); const sectionCount = $derived(board._count?.sections ?? 0); + + // Stable per-board pastel "room" tint derived from the name + const roomTints = ['var(--room-terra)', 'var(--room-sky)', 'var(--room-sage)', 'var(--room-butter)', 'var(--room-lav)', 'var(--room-peach)']; + const tint = $derived.by(() => { + let h = 0; + for (const ch of board.name) h = (h * 31 + ch.charCodeAt(0)) >>> 0; + return roomTints[h % roomTints.length]; + }); -
+ +
{#if board.icon} - + + + {:else} - - B + + {board.name.charAt(0).toUpperCase()} {/if}
-

+

{board.name}

{#if board.isDefault} - + {$t('board.default')} {/if} {#if board.isGuestAccessible} - + @@ -54,7 +75,7 @@ {$t('board.guest')} {:else} - + @@ -62,7 +83,7 @@ {/if} {#if board.hasSharedPermissions} - + diff --git a/src/lib/components/board/BoardHeader.svelte b/src/lib/components/board/BoardHeader.svelte index dd3419e..fbd4dbb 100644 --- a/src/lib/components/board/BoardHeader.svelte +++ b/src/lib/components/board/BoardHeader.svelte @@ -29,13 +29,18 @@ } -
-
+
+
{#if icon} - + + + {/if}
-

{name}

+

{name}

{#if description}

{description}

{/if} @@ -45,7 +50,7 @@
{$t('board.all_boards')} @@ -53,7 +58,7 @@ diff --git a/src/lib/components/board/BoardShareDialog.svelte b/src/lib/components/board/BoardShareDialog.svelte index 8a9591c..2fc0e6e 100644 --- a/src/lib/components/board/BoardShareDialog.svelte +++ b/src/lib/components/board/BoardShareDialog.svelte @@ -153,7 +153,7 @@ onclick={handleBackdropClick} role="presentation" > - @@ -187,7 +187,7 @@ type="text" bind:value={telegramChatId} placeholder="-1001234567890" - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" required />
@@ -201,7 +201,7 @@ type="url" bind:value={httpUrl} placeholder="https://example.com/webhook" - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" required />
@@ -212,7 +212,7 @@ @@ -104,7 +104,7 @@

No notifications found

{:else} -
+
diff --git a/src/lib/components/onboarding/OnboardingWizard.svelte b/src/lib/components/onboarding/OnboardingWizard.svelte index df3bb39..594257c 100644 --- a/src/lib/components/onboarding/OnboardingWizard.svelte +++ b/src/lib/components/onboarding/OnboardingWizard.svelte @@ -20,7 +20,7 @@ // Theme form let defaultTheme = $state<'dark' | 'light'>('dark'); - let defaultPrimaryColor = $state('#6366f1'); + let defaultPrimaryColor = $state('#e8754f'); // Board form let boardName = $state('My Dashboard'); @@ -169,6 +169,7 @@ } const primaryColorOptions = [ + { label: 'Terracotta', value: '#e8754f' }, { label: 'Indigo', value: '#6366f1' }, { label: 'Blue', value: '#3b82f6' }, { label: 'Emerald', value: '#10b981' }, @@ -182,7 +183,7 @@
@@ -227,7 +228,7 @@ {:else if currentStep === 'admin'}

Create Admin Account

{#if adminCreated} -
+
Admin account created successfully. You can proceed to the next step.
{:else} @@ -238,7 +239,7 @@ id="ob-display-name" type="text" bind:value={adminDisplayName} - class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder="Admin" />
@@ -248,7 +249,7 @@ id="ob-email" type="email" bind:value={adminEmail} - class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder="admin@example.com" />
@@ -258,7 +259,7 @@ id="ob-password" type="password" bind:value={adminPassword} - class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder="Min. 6 characters" />
@@ -298,19 +299,19 @@
@@ -369,7 +370,7 @@ id="ob-board-name" type="text" bind:value={boardName} - class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder="My Dashboard" /> @@ -417,7 +418,7 @@ type="button" onclick={handleNext} disabled={loading} - class="rounded-lg bg-primary px-6 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50" + class="rounded-xl bg-primary px-6 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50" > {#if loading} Processing... diff --git a/src/lib/components/search/SearchDialog.svelte b/src/lib/components/search/SearchDialog.svelte index 52df990..146d77d 100644 --- a/src/lib/components/search/SearchDialog.svelte +++ b/src/lib/components/search/SearchDialog.svelte @@ -76,7 +76,7 @@ >
diff --git a/src/lib/components/settings/BookmarkletGenerator.svelte b/src/lib/components/settings/BookmarkletGenerator.svelte index 33a60a6..73b457d 100644 --- a/src/lib/components/settings/BookmarkletGenerator.svelte +++ b/src/lib/components/settings/BookmarkletGenerator.svelte @@ -20,7 +20,7 @@ }); -
+

{$t('settings.bookmarklet_title')}

diff --git a/src/lib/components/settings/CustomCssEditor.svelte b/src/lib/components/settings/CustomCssEditor.svelte index 31c5f6f..681501d 100644 --- a/src/lib/components/settings/CustomCssEditor.svelte +++ b/src/lib/components/settings/CustomCssEditor.svelte @@ -88,7 +88,7 @@ value={localValue} oninput={handleInput} rows="8" - class="w-full rounded-lg border border-input bg-background px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30" + class="w-full rounded-xl border border-input bg-background px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder={`/* Custom CSS */\n.custom-css-scope .my-widget {\n background: #333;\n}`} spellcheck="false" > diff --git a/src/lib/components/settings/ThemeCustomizer.svelte b/src/lib/components/settings/ThemeCustomizer.svelte index 55c398a..ccaf07e 100644 --- a/src/lib/components/settings/ThemeCustomizer.svelte +++ b/src/lib/components/settings/ThemeCustomizer.svelte @@ -128,7 +128,7 @@ type="button" onclick={() => setMode(opt.value)} class="flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors {theme.mode === opt.value - ? 'bg-background text-foreground shadow-sm' + ? 'bg-background text-foreground shadow-[var(--shadow-soft)]' : 'text-muted-foreground hover:text-foreground'}" > {$t(opt.labelKey)} @@ -167,7 +167,7 @@ max="360" step="1" bind:value={theme.primaryHue} - class="absolute inset-0 h-3 w-full cursor-pointer appearance-none bg-transparent [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-white [&::-moz-range-thumb]:bg-current [&::-moz-range-thumb]:shadow-md [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:bg-current [&::-webkit-slider-thumb]:shadow-md" + class="absolute inset-0 h-3 w-full cursor-pointer appearance-none bg-transparent [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-white [&::-moz-range-thumb]:bg-current [&::-moz-range-thumb]:shadow-[var(--shadow-soft)] [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:bg-current [&::-webkit-slider-thumb]:shadow-[var(--shadow-soft)]" style="color: {previewColor};" />
@@ -188,7 +188,7 @@ max="100" step="1" bind:value={theme.primarySaturation} - class="absolute inset-0 h-3 w-full cursor-pointer appearance-none bg-transparent [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-white [&::-moz-range-thumb]:bg-current [&::-moz-range-thumb]:shadow-md [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:bg-current [&::-webkit-slider-thumb]:shadow-md" + class="absolute inset-0 h-3 w-full cursor-pointer appearance-none bg-transparent [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-white [&::-moz-range-thumb]:bg-current [&::-moz-range-thumb]:shadow-[var(--shadow-soft)] [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:bg-current [&::-webkit-slider-thumb]:shadow-[var(--shadow-soft)]" style="color: {previewColor};" />
@@ -204,7 +204,7 @@ type="button" onclick={() => setBackground(opt.value)} class="flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors {theme.backgroundType === opt.value - ? 'bg-background text-foreground shadow-sm' + ? 'bg-background text-foreground shadow-[var(--shadow-soft)]' : 'text-muted-foreground hover:text-foreground'}" > {$t(opt.labelKey)} @@ -222,7 +222,7 @@ type="button" onclick={() => setCardStyle(opt.value)} class="flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors {theme.cardStyle === opt.value - ? 'bg-background text-foreground shadow-sm' + ? 'bg-background text-foreground shadow-[var(--shadow-soft)]' : 'text-muted-foreground hover:text-foreground'}" > {$t(opt.labelKey) ?? opt.value} @@ -240,7 +240,7 @@ type="button" onclick={() => setLocale(opt.value)} class="flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors {$i18nLocale === opt.value - ? 'bg-background text-foreground shadow-sm' + ? 'bg-background text-foreground shadow-[var(--shadow-soft)]' : 'text-muted-foreground hover:text-foreground'}" > {opt.label} @@ -255,12 +255,12 @@ type="button" onclick={savePreferences} disabled={saving} - class="rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50" + class="rounded-xl bg-primary px-6 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50" > {saving ? $t('settings.saving') : $t('settings.save')} {#if saved} - {$t('settings.saved')} + {$t('settings.saved')} {/if} {#if errorMessage} {errorMessage} diff --git a/src/lib/components/ui/AutocompleteInput.svelte b/src/lib/components/ui/AutocompleteInput.svelte index 7ba9aed..f9163ee 100644 --- a/src/lib/components/ui/AutocompleteInput.svelte +++ b/src/lib/components/ui/AutocompleteInput.svelte @@ -91,7 +91,7 @@ /> {#if open && filtered.length > 0} -
+
{#each filtered as item, i (item)}
diff --git a/src/lib/components/widget/WidgetCreationForm.svelte b/src/lib/components/widget/WidgetCreationForm.svelte index 6af89cd..a87c8e6 100644 --- a/src/lib/components/widget/WidgetCreationForm.svelte +++ b/src/lib/components/widget/WidgetCreationForm.svelte @@ -60,7 +60,7 @@ // Calendar fields let calendarUrls = $state>([ - { url: '', color: '#6366f1', label: '' } + { url: '', color: '#e8754f', label: '' } ]); let calendarDaysAhead = $state(7); @@ -198,7 +198,7 @@ rssFeedUrl = ''; rssMaxItems = 10; rssShowSummary = true; - calendarUrls = [{ url: '', color: '#6366f1', label: '' }]; + calendarUrls = [{ url: '', color: '#e8754f', label: '' }]; calendarDaysAhead = 7; markdownContent = ''; metricLabel = ''; @@ -350,7 +350,7 @@ } function addCalendarUrl() { - calendarUrls = [...calendarUrls, { url: '', color: '#6366f1', label: '' }]; + calendarUrls = [...calendarUrls, { url: '', color: '#e8754f', label: '' }]; } function removeCalendarUrl(index: number) { @@ -367,7 +367,7 @@ // Input CSS class for reuse const inputClass = - 'w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30'; + 'w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30';
@@ -505,7 +505,7 @@
Select Apps -
+
{#each apps as app (app.id)}
@@ -123,13 +123,13 @@ type="text" readonly value={issuedLink.url} - class="flex-1 rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs" + class="flex-1 rounded-xl border border-input bg-background px-2 py-1.5 font-mono text-xs" onclick={(e) => (e.currentTarget as HTMLInputElement).select()} /> diff --git a/src/routes/admin/users/+page.svelte b/src/routes/admin/users/+page.svelte index 7473510..6c515d8 100644 --- a/src/routes/admin/users/+page.svelte +++ b/src/routes/admin/users/+page.svelte @@ -28,7 +28,7 @@ @@ -46,7 +46,7 @@ name="email" type="email" bind:value={$form.email} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" required /> {#if $errors.email}{$errors.email}{/if} @@ -58,7 +58,7 @@ name="displayName" type="text" bind:value={$form.displayName} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" required /> {#if $errors.displayName}{$errors.displayName}{/if} @@ -70,7 +70,7 @@ name="password" type="password" bind:value={$form.password} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" /> {#if $errors.password}{$errors.password}{/if} @@ -80,7 +80,7 @@ id="role" name="role" bind:value={$form.role} - class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground" > @@ -92,7 +92,7 @@ {/if} diff --git a/src/routes/api/onboarding/+server.ts b/src/routes/api/onboarding/+server.ts index cefa60c..3bcec67 100644 --- a/src/routes/api/onboarding/+server.ts +++ b/src/routes/api/onboarding/+server.ts @@ -167,7 +167,7 @@ export const POST: RequestHandler = async (event) => { create: { id: DEFAULTS.SYSTEM_SETTINGS_ID, defaultTheme: themeData.data.defaultTheme ?? 'dark', - defaultPrimaryColor: themeData.data.defaultPrimaryColor ?? '#6366f1' + defaultPrimaryColor: themeData.data.defaultPrimaryColor ?? '#e8754f' } }); diff --git a/src/routes/apps/+page.svelte b/src/routes/apps/+page.svelte index 0d7332c..1f89de6 100644 --- a/src/routes/apps/+page.svelte +++ b/src/routes/apps/+page.svelte @@ -36,14 +36,14 @@ {#if showForm} -
+

{$t('app.new')}

@@ -53,14 +53,14 @@
{$t('app.all_categories')} {#each data.categories as category (category)} {category} @@ -69,8 +69,8 @@ {/if} {#if data.apps.length === 0} -
-
+
+
(showForm = true)} - class="mt-4 inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" + class="mt-4 inline-flex items-center gap-2 rounded-xl bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground shadow-[var(--shadow-soft)] transition-all hover:-translate-y-0.5 hover:shadow-[var(--shadow-lift)]" > diff --git a/src/routes/apps/[id]/edit/+page.svelte b/src/routes/apps/[id]/edit/+page.svelte index b205dbc..0be53d0 100644 --- a/src/routes/apps/[id]/edit/+page.svelte +++ b/src/routes/apps/[id]/edit/+page.svelte @@ -19,13 +19,13 @@
{$t('common.cancel')}
-
+
diff --git a/src/routes/apps/quick-add/+page.svelte b/src/routes/apps/quick-add/+page.svelte index f579c05..bffe2d1 100644 --- a/src/routes/apps/quick-add/+page.svelte +++ b/src/routes/apps/quick-add/+page.svelte @@ -30,14 +30,14 @@

{$t('app.quick_add_description')}

{#if created} -
-

+

+

{$t('app.quick_add_success')}

{:else} -
+
{/if} diff --git a/src/routes/boards/+error.svelte b/src/routes/boards/+error.svelte index 1802427..85cdc76 100644 --- a/src/routes/boards/+error.svelte +++ b/src/routes/boards/+error.svelte @@ -27,7 +27,7 @@ {#snippet actions()} {$t('nav.home') ?? 'Home'} diff --git a/src/routes/boards/+page.svelte b/src/routes/boards/+page.svelte index ec79365..09962c9 100644 --- a/src/routes/boards/+page.svelte +++ b/src/routes/boards/+page.svelte @@ -23,7 +23,7 @@ {#if !data.isGuest && data.user?.role === 'admin'} {$t('board.new')} @@ -31,8 +31,8 @@
{#if data.boards.length === 0} -
-
+
+ @@ -62,7 +62,7 @@ name="icon" type="text" bind:value={$form.icon} - class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder="layout-dashboard" />
@@ -90,7 +90,7 @@ diff --git a/src/routes/forgot-password/+page.svelte b/src/routes/forgot-password/+page.svelte index 637df37..093024b 100644 --- a/src/routes/forgot-password/+page.svelte +++ b/src/routes/forgot-password/+page.svelte @@ -24,10 +24,10 @@ class="relative z-10 flex min-h-screen items-center justify-center bg-background/80 p-4 text-foreground" >
-
+

@@ -72,7 +72,7 @@ type="email" required autocomplete="email" - class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-ring/30" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" /> @@ -83,7 +83,7 @@ diff --git a/src/routes/invite/+page.svelte b/src/routes/invite/+page.svelte index 9bfb1d2..28f9d4a 100644 --- a/src/routes/invite/+page.svelte +++ b/src/routes/invite/+page.svelte @@ -21,9 +21,9 @@
-
+
-
+

{$t('auth.invite_title') ?? 'Redeem invite'}

@@ -54,7 +54,7 @@ autocomplete="off" spellcheck="false" placeholder="inv_…" - class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-ring/30" + class="w-full rounded-xl border border-input bg-background px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" /> @@ -65,7 +65,7 @@ diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index 4a938e4..49020dd 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -19,11 +19,11 @@
-
+
-
+
@@ -79,7 +79,7 @@ type="email" autocomplete="email" bind:value={$form.email} - class="w-full rounded-lg border border-input bg-background px-3 py-2.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30" + class="w-full rounded-xl border border-input bg-background px-3 py-2.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder={$t('auth.email_placeholder')} /> {#if $errors.email} @@ -97,7 +97,7 @@ type="password" autocomplete="current-password" bind:value={$form.password} - class="w-full rounded-lg border border-input bg-background px-3 py-2.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30" + class="w-full rounded-xl border border-input bg-background px-3 py-2.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30" placeholder={$t('auth.password_placeholder')} /> {#if $errors.password} @@ -111,7 +111,7 @@ type="checkbox" name="rememberMe" bind:checked={$form.rememberMe} - class="h-4 w-4 rounded border-input text-primary focus:ring-2 focus:ring-ring/30" + class="h-4 w-4 rounded border-input text-primary focus-visible:ring-2 focus-visible:ring-primary/30" /> {$t('auth.remember_me')} @@ -123,7 +123,7 @@ diff --git a/src/routes/settings/api-tokens/+page.svelte b/src/routes/settings/api-tokens/+page.svelte index d91c39c..d0047c5 100644 --- a/src/routes/settings/api-tokens/+page.svelte +++ b/src/routes/settings/api-tokens/+page.svelte @@ -43,7 +43,7 @@ @@ -52,19 +52,19 @@ {#if createdToken} -
+

Token Created

Copy this token now. It will not be shown again.

- + {createdToken} diff --git a/src/routes/settings/notifications/+page.svelte b/src/routes/settings/notifications/+page.svelte index 2eb62ed..9ee75be 100644 --- a/src/routes/settings/notifications/+page.svelte +++ b/src/routes/settings/notifications/+page.svelte @@ -146,7 +146,7 @@ diff --git a/src/routes/status/+page.svelte b/src/routes/status/+page.svelte index d363de2..2f541da 100644 --- a/src/routes/status/+page.svelte +++ b/src/routes/status/+page.svelte @@ -19,39 +19,39 @@ function statusColor(status: string): string { switch (status) { case 'online': - return 'text-green-500'; + return 'text-status-online-ink'; case 'offline': - return 'text-red-500'; + return 'text-status-offline-ink'; case 'degraded': - return 'text-yellow-500'; + return 'text-status-degraded-ink'; default: - return 'text-gray-400'; + return 'text-status-unknown-ink'; } } function statusDotColor(status: string): string { switch (status) { case 'online': - return 'bg-green-500'; + return 'bg-status-online'; case 'offline': - return 'bg-red-500'; + return 'bg-status-offline'; case 'degraded': - return 'bg-yellow-500'; + return 'bg-status-degraded'; default: - return 'bg-gray-400'; + return 'bg-status-unknown'; } } function statusBorderColor(status: string): string { switch (status) { case 'online': - return 'border-l-green-500'; + return 'border-l-status-online'; case 'offline': - return 'border-l-red-500'; + return 'border-l-status-offline'; case 'degraded': - return 'border-l-yellow-500'; + return 'border-l-status-degraded'; default: - return 'border-l-gray-400'; + return 'border-l-status-unknown'; } } @@ -88,7 +88,7 @@

Services Online

-

{data.summary.appsOnline}

+

{data.summary.appsOnline}

Overall Uptime

diff --git a/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxC9TeA.woff2 b/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxC9TeA.woff2 new file mode 100644 index 0000000..cb295bf Binary files /dev/null and b/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxC9TeA.woff2 differ diff --git a/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCBTeO-U.woff2 b/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCBTeO-U.woff2 new file mode 100644 index 0000000..43126c2 Binary files /dev/null and b/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCBTeO-U.woff2 differ diff --git a/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCFTeO-U.woff2 b/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCFTeO-U.woff2 new file mode 100644 index 0000000..b6e3e3a Binary files /dev/null and b/static/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCFTeO-U.woff2 differ diff --git a/static/fonts/_Xms-HUzqDCFdgfMm4S9DQ.woff2 b/static/fonts/_Xms-HUzqDCFdgfMm4S9DQ.woff2 new file mode 100644 index 0000000..05ce657 Binary files /dev/null and b/static/fonts/_Xms-HUzqDCFdgfMm4S9DQ.woff2 differ diff --git a/static/fonts/_Xms-HUzqDCFdgfMm4q9DbZs.woff2 b/static/fonts/_Xms-HUzqDCFdgfMm4q9DbZs.woff2 new file mode 100644 index 0000000..a8a1123 Binary files /dev/null and b/static/fonts/_Xms-HUzqDCFdgfMm4q9DbZs.woff2 differ diff --git a/static/fonts/fonts.css b/static/fonts/fonts.css new file mode 100644 index 0000000..84b5b37 --- /dev/null +++ b/static/fonts/fonts.css @@ -0,0 +1,200 @@ +/* latin-ext */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4q9DbZs.woff2) format('woff2'); + unicode-range: + U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, + U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4S9DQ.woff2) format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4q9DbZs.woff2) format('woff2'); + unicode-range: + U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, + U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4S9DQ.woff2) format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4q9DbZs.woff2) format('woff2'); + unicode-range: + U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, + U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4S9DQ.woff2) format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4q9DbZs.woff2) format('woff2'); + unicode-range: + U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, + U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(/fonts/_Xms-HUzqDCFdgfMm4S9DQ.woff2) format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* vietnamese */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCBTeO-U.woff2) + format('woff2'); + unicode-range: + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, + U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCFTeO-U.woff2) + format('woff2'); + unicode-range: + U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, + U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxC9TeA.woff2) format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* vietnamese */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCBTeO-U.woff2) + format('woff2'); + unicode-range: + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, + U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCFTeO-U.woff2) + format('woff2'); + unicode-range: + U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, + U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxC9TeA.woff2) format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* vietnamese */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCBTeO-U.woff2) + format('woff2'); + unicode-range: + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, + U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxCFTeO-U.woff2) + format('woff2'); + unicode-range: + U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, + U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(/fonts/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxC9TeA.woff2) format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +}