feat(design): apply Monolith Editorial design system to all Fase 1 screens
- Remove all 1px border separators (sidebar border-r, page header border-b, section border-t, member list divide-y, invite card border). Separation is now achieved via background color shifts and whitespace per the "No-Line" rule. - Fix sidebar: bg-surface-raised vs bg-background creates tonal separation. - Correct dark background to #020617 (slate-950) per spec. - Add surface-container-low, text-primary/secondary/muted, destructive tokens. - Fix CSS variable color space: all tokens are RGB triplets; switch Tailwind config and app.css from hsl() to rgb() to prevent yellow/warm color bleed. - Replace emoji nav icons with Lucide icons at 16px / 1.5 stroke-width. - Apply Masthead typography (uppercase Label-MD + Title-LG) to all page headers and section labels across lists, tasks, notes, search, settings, manage. - Destructive action (sign out) uses bg-destructive per spec. - Add `just serve` command: builds and runs the prod Node server at :3000 against the dev Docker stack for production build testing. - Add nav_collective i18n key (en + es). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import { logout } from '$lib/auth';
|
||||
import Avatar from '$lib/components/Avatar.svelte';
|
||||
import * as m from '$lib/paraglide/messages';
|
||||
import { ShoppingCart, CheckSquare, FileText, Search, Settings } from 'lucide-svelte';
|
||||
|
||||
// When auth resolves as unauthenticated, redirect to Keycloak.
|
||||
// This runs after onAuthStateChange fires (authLoading = false), so there
|
||||
@@ -20,10 +21,10 @@
|
||||
});
|
||||
|
||||
const navItems = [
|
||||
{ href: '/lists', label: () => m.nav_lists(), icon: '🛒' },
|
||||
{ href: '/tasks', label: () => m.nav_tasks(), icon: '✓' },
|
||||
{ href: '/notes', label: () => m.nav_notes(), icon: '📄' },
|
||||
{ href: '/search', label: () => m.nav_search(), icon: '🔍' }
|
||||
{ href: '/lists', label: () => m.nav_lists(), icon: ShoppingCart },
|
||||
{ href: '/tasks', label: () => m.nav_tasks(), icon: CheckSquare },
|
||||
{ href: '/notes', label: () => m.nav_notes(), icon: FileText },
|
||||
{ href: '/search', label: () => m.nav_search(), icon: Search }
|
||||
];
|
||||
|
||||
let collectiveSwitcherOpen = $state(false);
|
||||
@@ -46,14 +47,14 @@
|
||||
</div>
|
||||
{:else if $isAuthenticated}
|
||||
<div class="flex h-screen overflow-hidden bg-background">
|
||||
<!-- Sidebar -->
|
||||
<aside class="flex w-56 flex-shrink-0 flex-col border-r border-slate-200 bg-surface dark:border-slate-700">
|
||||
<!-- Sidebar — surface-raised bg creates the separation without a border line -->
|
||||
<aside class="flex w-56 flex-shrink-0 flex-col bg-surface-raised">
|
||||
|
||||
<!-- Collective header + switcher -->
|
||||
<!-- Collective header + switcher — bottom gap instead of border -->
|
||||
<div class="relative">
|
||||
<button
|
||||
class="flex h-14 w-full items-center gap-2 border-b border-slate-200 px-4 text-left
|
||||
hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800"
|
||||
class="flex h-14 w-full items-center gap-2 px-4 text-left
|
||||
hover:bg-black/5 dark:hover:bg-white/5"
|
||||
onclick={() => (collectiveSwitcherOpen = !collectiveSwitcherOpen)}
|
||||
aria-expanded={collectiveSwitcherOpen}
|
||||
>
|
||||
@@ -68,12 +69,12 @@
|
||||
|
||||
{#if collectiveSwitcherOpen && $userCollectives.length > 1}
|
||||
<div
|
||||
class="absolute left-0 right-0 top-full z-50 border-b border-slate-200 bg-surface shadow-md dark:border-slate-700"
|
||||
class="absolute left-0 right-0 top-full z-50 bg-surface-raised shadow-[0px_20px_40px_rgba(15,23,42,0.06)]"
|
||||
>
|
||||
{#each $userCollectives as c}
|
||||
<button
|
||||
class="flex w-full items-center gap-2 px-4 py-2.5 text-left text-sm
|
||||
hover:bg-slate-50 dark:hover:bg-slate-800
|
||||
hover:bg-black/5 dark:hover:bg-white/5
|
||||
{c.id === $currentCollective?.id ? 'font-semibold text-slate-900 dark:text-slate-50' : 'text-slate-600 dark:text-slate-400'}"
|
||||
onclick={() => switchCollective(c.id)}
|
||||
>
|
||||
@@ -87,22 +88,22 @@
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex-1 overflow-y-auto px-2 py-3">
|
||||
{#each navItems as item}
|
||||
{#each navItems as { href, label, icon: Icon }}
|
||||
<a
|
||||
href={item.href}
|
||||
{href}
|
||||
class="mb-0.5 flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors
|
||||
{activePath.startsWith(item.href)
|
||||
? 'bg-slate-100 text-slate-900 dark:bg-slate-700 dark:text-slate-50'
|
||||
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-50'}"
|
||||
{activePath.startsWith(href)
|
||||
? 'bg-black/10 text-slate-900 dark:bg-white/10 dark:text-slate-50'
|
||||
: 'text-slate-500 hover:bg-black/5 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-white/5 dark:hover:text-slate-50'}"
|
||||
>
|
||||
<span class="text-base">{item.icon}</span>
|
||||
{item.label()}
|
||||
<Icon size={16} strokeWidth={1.5} />
|
||||
{label()}
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<!-- User footer -->
|
||||
<div class="border-t border-slate-200 p-3 dark:border-slate-700">
|
||||
<!-- User footer — top gap instead of border -->
|
||||
<div class="p-3 pt-2">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<Avatar name={$displayName} size={32} />
|
||||
@@ -112,17 +113,18 @@
|
||||
</div>
|
||||
<a
|
||||
href="/settings"
|
||||
class="rounded-md p-1.5 text-slate-400 hover:bg-slate-100 hover:text-slate-600 dark:hover:bg-slate-700 dark:hover:text-slate-300"
|
||||
class="rounded-md p-1.5 text-slate-400 hover:bg-black/5 hover:text-slate-600
|
||||
dark:hover:bg-white/5 dark:hover:text-slate-300"
|
||||
aria-label={m.nav_settings()}
|
||||
>
|
||||
⚙
|
||||
<Settings size={16} strokeWidth={1.5} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="flex flex-1 flex-col overflow-hidden">
|
||||
<!-- Main content — bg-background creates the tonal separation from sidebar -->
|
||||
<main class="flex flex-1 flex-col overflow-hidden bg-background">
|
||||
{@render children()}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -136,25 +136,32 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<h1 class="mb-6 text-xl font-bold text-slate-900 dark:text-slate-50">
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="px-8 pb-4 pt-8">
|
||||
<p class="mb-1 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.nav_collective()}
|
||||
</p>
|
||||
<h1 class="text-[20px] font-semibold tracking-[-0.01em] text-slate-900 dark:text-slate-50">
|
||||
{m.manage_title()}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="px-8 py-6">
|
||||
|
||||
{#if error}
|
||||
<p class="mb-4 text-sm text-red-600">{error}</p>
|
||||
{/if}
|
||||
|
||||
<!-- Members list -->
|
||||
<!-- Members list — no dividers, vertical padding creates separation -->
|
||||
<section class="mb-8">
|
||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wide text-text-secondary">
|
||||
<h2 class="mb-4 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.manage_members()}
|
||||
</h2>
|
||||
|
||||
{#if loading}
|
||||
<p class="text-sm text-text-secondary">{m.loading()}</p>
|
||||
{:else}
|
||||
<ul class="divide-y divide-slate-100 dark:divide-slate-700">
|
||||
<ul>
|
||||
{#each members as member}
|
||||
<li class="flex items-center gap-3 py-3">
|
||||
<Avatar
|
||||
@@ -202,10 +209,10 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Invite link generator (admins only) -->
|
||||
<!-- Invite link generator — background shift instead of border -->
|
||||
{#if isAdmin}
|
||||
<section class="rounded-lg border border-slate-200 p-4 dark:border-slate-700">
|
||||
<h2 class="mb-3 text-sm font-semibold text-slate-900 dark:text-slate-50">
|
||||
<section class="rounded-lg bg-surface-container-low p-4 dark:bg-surface-raised">
|
||||
<h2 class="mb-3 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.manage_invite_title()}
|
||||
</h2>
|
||||
|
||||
@@ -232,8 +239,7 @@
|
||||
</button>
|
||||
|
||||
{#if generatedLink}
|
||||
<div class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 p-2
|
||||
dark:border-slate-700 dark:bg-slate-800">
|
||||
<div class="flex items-center gap-2 rounded-lg bg-surface p-2 dark:bg-surface-raised">
|
||||
<code class="min-w-0 flex-1 truncate text-xs text-slate-600 dark:text-slate-400">
|
||||
{generatedLink}
|
||||
</code>
|
||||
@@ -250,3 +256,5 @@
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<header class="flex h-14 items-center border-b border-slate-200 px-6 dark:border-slate-700">
|
||||
<h1 class="text-base font-semibold text-slate-900 dark:text-slate-50">{m.lists_title()}</h1>
|
||||
<header class="px-8 pb-4 pt-8">
|
||||
<p class="mb-1 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.nav_lists()}
|
||||
</p>
|
||||
<h1 class="text-[20px] font-semibold tracking-[-0.01em] text-slate-900 dark:text-slate-50">
|
||||
{m.lists_title()}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="flex-1 overflow-y-auto px-8 py-6">
|
||||
<!-- Fase 2a: shopping lists content -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<header class="flex h-14 items-center border-b border-slate-200 px-6 dark:border-slate-700">
|
||||
<h1 class="text-base font-semibold text-slate-900 dark:text-slate-50">{m.notes_title()}</h1>
|
||||
<header class="px-8 pb-4 pt-8">
|
||||
<p class="mb-1 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.nav_notes()}
|
||||
</p>
|
||||
<h1 class="text-[20px] font-semibold tracking-[-0.01em] text-slate-900 dark:text-slate-50">
|
||||
{m.notes_title()}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="flex-1 overflow-y-auto px-8 py-6">
|
||||
<!-- Fase 3: notes content -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<header class="flex h-14 items-center border-b border-slate-200 px-6 dark:border-slate-700">
|
||||
<h1 class="text-base font-semibold text-slate-900 dark:text-slate-50">{m.search_title()}</h1>
|
||||
<header class="px-8 pb-4 pt-8">
|
||||
<p class="mb-1 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.nav_search()}
|
||||
</p>
|
||||
<h1 class="text-[20px] font-semibold tracking-[-0.01em] text-slate-900 dark:text-slate-50">
|
||||
{m.search_title()}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="flex-1 overflow-y-auto px-8 py-6">
|
||||
<!-- Fase 4: search content -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -143,11 +143,16 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<header class="flex h-14 items-center border-b border-slate-200 px-6 dark:border-slate-700">
|
||||
<h1 class="text-base font-semibold text-slate-900 dark:text-slate-50">{m.settings_title()}</h1>
|
||||
<header class="px-8 pb-4 pt-8">
|
||||
<p class="mb-1 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.nav_settings()}
|
||||
</p>
|
||||
<h1 class="text-[20px] font-semibold tracking-[-0.01em] text-slate-900 dark:text-slate-50">
|
||||
{m.settings_title()}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="flex-1 overflow-y-auto px-8 py-6">
|
||||
<div class="mx-auto max-w-md space-y-8">
|
||||
|
||||
<!-- Avatar preview -->
|
||||
@@ -163,7 +168,7 @@
|
||||
|
||||
<!-- Display name -->
|
||||
<section>
|
||||
<label for="display-name" class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
<label for="display-name" class="mb-1 block text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.settings_display_name()}
|
||||
</label>
|
||||
<div class="relative">
|
||||
@@ -185,7 +190,7 @@
|
||||
|
||||
<!-- Avatar -->
|
||||
<section>
|
||||
<p class="mb-3 text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
<p class="mb-3 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.settings_avatar()}
|
||||
</p>
|
||||
|
||||
@@ -229,7 +234,7 @@
|
||||
|
||||
<!-- Language -->
|
||||
<section>
|
||||
<p class="mb-3 text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
<p class="mb-3 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.settings_language()}
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
@@ -247,9 +252,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Account -->
|
||||
<section class="border-t border-slate-200 pt-6 dark:border-slate-700">
|
||||
<p class="mb-3 text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
<!-- Account — spacing creates the separation, not a border line -->
|
||||
<section class="pt-8">
|
||||
<p class="mb-3 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.settings_account()}
|
||||
</p>
|
||||
<a
|
||||
@@ -262,8 +267,7 @@
|
||||
</a>
|
||||
<button
|
||||
onclick={logout}
|
||||
class="rounded-lg border border-red-200 px-4 py-2 text-sm font-medium text-red-600
|
||||
hover:bg-red-50 dark:border-red-800 dark:text-red-400 dark:hover:bg-red-900/20"
|
||||
class="rounded-md bg-destructive px-4 py-2 text-sm font-semibold text-white hover:opacity-90"
|
||||
>
|
||||
{m.settings_logout()}
|
||||
</button>
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<header class="flex h-14 items-center border-b border-slate-200 px-6 dark:border-slate-700">
|
||||
<h1 class="text-base font-semibold text-slate-900 dark:text-slate-50">{m.tasks_title()}</h1>
|
||||
<header class="px-8 pb-4 pt-8">
|
||||
<p class="mb-1 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
|
||||
{m.nav_tasks()}
|
||||
</p>
|
||||
<h1 class="text-[20px] font-semibold tracking-[-0.01em] text-slate-900 dark:text-slate-50">
|
||||
{m.tasks_title()}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="flex-1 overflow-y-auto px-8 py-6">
|
||||
<!-- Fase 3: tasks content -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user