2026-04-25 22:51:36 +12:00
|
|
|
<script lang="ts">
|
2026-04-27 21:53:36 +12:00
|
|
|
import { invalidateAll } from '$app/navigation';
|
2026-04-25 22:51:36 +12:00
|
|
|
import { goto } from '$app/navigation';
|
|
|
|
|
import { page } from '$app/state';
|
2026-04-27 21:53:36 +12:00
|
|
|
import { clientSession, sessionHydrated } from '$lib/session';
|
2026-04-25 22:51:36 +12:00
|
|
|
import { onMount, tick } from 'svelte';
|
2026-04-27 21:53:36 +12:00
|
|
|
import packageInfo from '../../../package.json';
|
2026-04-25 22:51:36 +12:00
|
|
|
|
|
|
|
|
type SearchItem = {
|
|
|
|
|
href: string;
|
|
|
|
|
label: string;
|
|
|
|
|
description: string;
|
|
|
|
|
keywords: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const navigation = [
|
|
|
|
|
{ href: '/', label: 'Overview', shortLabel: 'OV' },
|
|
|
|
|
{ href: '/raw-materials', label: 'Raw Materials', shortLabel: 'RM' },
|
|
|
|
|
{ href: '/mixes', label: 'Mix Master', shortLabel: 'MM' },
|
|
|
|
|
{ href: '/products', label: 'Products', shortLabel: 'PR' },
|
|
|
|
|
{ href: '/scenarios', label: 'Scenarios', shortLabel: 'SC' }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const footerLinks = [
|
|
|
|
|
{ href: '/products', label: 'Delivered Pricing', shortLabel: 'DP' },
|
|
|
|
|
{ href: '/scenarios', label: 'Planning View', shortLabel: 'PV' }
|
|
|
|
|
];
|
2026-04-27 21:53:36 +12:00
|
|
|
const primaryBottomNavigation = navigation.slice(0, 4);
|
2026-04-25 22:51:36 +12:00
|
|
|
|
|
|
|
|
const searchItems: SearchItem[] = [
|
|
|
|
|
{
|
|
|
|
|
href: '/',
|
|
|
|
|
label: 'Open Hunter Overview',
|
|
|
|
|
description: 'Jump to the Hunter Premium Produce workspace summary.',
|
|
|
|
|
keywords: 'hunter premium produce overview dashboard workspace'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: '/raw-materials',
|
|
|
|
|
label: 'Open Raw Materials',
|
|
|
|
|
description: 'Review live input costs that feed the pricing model.',
|
|
|
|
|
keywords: 'raw materials pricing inputs costs supplier'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: '/mixes',
|
|
|
|
|
label: 'Open Mix Master',
|
|
|
|
|
description: 'Browse saved mixes and their costing outputs.',
|
|
|
|
|
keywords: 'mix master mixes recipes spreadsheet'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: '/mixes/new',
|
|
|
|
|
label: 'Create New Mix',
|
|
|
|
|
description: 'Start a new costing worksheet for Hunter Premium Produce.',
|
|
|
|
|
keywords: 'new mix create worksheet hunter premium produce formula'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: '/products',
|
|
|
|
|
label: 'Open Products',
|
|
|
|
|
description: 'Review delivered product pricing and margins.',
|
|
|
|
|
keywords: 'products pricing margins delivered outputs'
|
|
|
|
|
},
|
2026-04-27 21:53:36 +12:00
|
|
|
{
|
|
|
|
|
href: '/settings',
|
|
|
|
|
label: 'Open Workspace Settings',
|
|
|
|
|
description: 'Review account details and workspace preferences.',
|
|
|
|
|
keywords: 'settings account preferences profile workspace'
|
|
|
|
|
},
|
2026-04-25 22:51:36 +12:00
|
|
|
{
|
|
|
|
|
href: '/scenarios',
|
|
|
|
|
label: 'Open Scenarios',
|
|
|
|
|
description: 'Inspect planning scenarios and overrides.',
|
|
|
|
|
keywords: 'scenarios sandbox overrides compare planning'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let { children } = $props();
|
|
|
|
|
const isRootRoute = $derived(page.url.pathname === '/');
|
|
|
|
|
|
|
|
|
|
let paletteOpen = $state(false);
|
|
|
|
|
let paletteQuery = $state('');
|
|
|
|
|
let quickMenuOpen = $state(false);
|
2026-04-27 21:53:36 +12:00
|
|
|
let userMenuOpen = $state(false);
|
|
|
|
|
let navOpen = $state(false);
|
|
|
|
|
let showBottomNav = $state(false);
|
|
|
|
|
let isRestoringSession = $state(false);
|
|
|
|
|
let restoredToken = $state<string | null>(null);
|
2026-04-25 22:51:36 +12:00
|
|
|
let paletteInput: HTMLInputElement | null = $state(null);
|
2026-04-27 21:53:36 +12:00
|
|
|
const appVersion = `v${packageInfo.version}`;
|
|
|
|
|
const currentYear = new Date().getFullYear();
|
2026-04-25 22:51:36 +12:00
|
|
|
|
|
|
|
|
function matchesRoute(href: string, pathname: string) {
|
|
|
|
|
return href === '/' ? pathname === '/' : pathname.startsWith(href);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pageTitle(pathname: string) {
|
|
|
|
|
return navigation.find((item) => matchesRoute(item.href, pathname))?.label ?? 'Overview';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pageDescription(pathname: string) {
|
|
|
|
|
const descriptions: Record<string, string> = {
|
|
|
|
|
'/': 'Hunter Premium Produce client workspace',
|
|
|
|
|
'/raw-materials': 'Review source input costs and downstream exposure',
|
|
|
|
|
'/mixes': 'Browse saved mix worksheets and costing outputs',
|
|
|
|
|
'/mixes/new': 'Create a new mix worksheet for Hunter Premium Produce',
|
|
|
|
|
'/products': 'Track delivered product pricing and margin views',
|
2026-04-27 21:53:36 +12:00
|
|
|
'/settings': 'Review your workspace profile and application settings',
|
2026-04-25 22:51:36 +12:00
|
|
|
'/scenarios': 'Compare alternate pricing and production assumptions'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return descriptions[pathname] ?? 'Hunter Premium Produce client workspace';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openPalette(query = '') {
|
|
|
|
|
paletteQuery = query;
|
|
|
|
|
paletteOpen = true;
|
|
|
|
|
quickMenuOpen = false;
|
2026-04-27 21:53:36 +12:00
|
|
|
userMenuOpen = false;
|
|
|
|
|
navOpen = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function syncViewport() {
|
|
|
|
|
showBottomNav = window.innerWidth <= 1180;
|
|
|
|
|
|
|
|
|
|
if (!showBottomNav) {
|
|
|
|
|
navOpen = false;
|
|
|
|
|
}
|
2026-04-25 22:51:36 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runSearchItem(item: SearchItem) {
|
|
|
|
|
paletteOpen = false;
|
|
|
|
|
paletteQuery = '';
|
|
|
|
|
await goto(item.href);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
async function openSettings() {
|
|
|
|
|
quickMenuOpen = false;
|
|
|
|
|
userMenuOpen = false;
|
|
|
|
|
navOpen = false;
|
|
|
|
|
await goto('/settings');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
const filteredSearchItems = $derived(
|
|
|
|
|
searchItems.filter((item) => {
|
|
|
|
|
const haystack = `${item.label} ${item.description} ${item.keywords}`.toLowerCase();
|
|
|
|
|
return haystack.includes(paletteQuery.trim().toLowerCase());
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
page.url.pathname;
|
|
|
|
|
quickMenuOpen = false;
|
2026-04-27 21:53:36 +12:00
|
|
|
userMenuOpen = false;
|
2026-04-25 22:51:36 +12:00
|
|
|
paletteOpen = false;
|
|
|
|
|
paletteQuery = '';
|
2026-04-27 21:53:36 +12:00
|
|
|
navOpen = false;
|
2026-04-25 22:51:36 +12:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (paletteOpen) {
|
|
|
|
|
tick().then(() => paletteInput?.focus());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
$effect(() => {
|
|
|
|
|
const hydrated = $sessionHydrated;
|
|
|
|
|
const token = $clientSession?.token ?? null;
|
|
|
|
|
|
|
|
|
|
if (!hydrated) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
isRestoringSession = false;
|
|
|
|
|
restoredToken = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (restoredToken === token) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
restoredToken = token;
|
|
|
|
|
isRestoringSession = true;
|
|
|
|
|
|
|
|
|
|
invalidateAll().finally(() => {
|
|
|
|
|
if (restoredToken === token) {
|
|
|
|
|
isRestoringSession = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
onMount(() => {
|
2026-04-27 21:53:36 +12:00
|
|
|
syncViewport();
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
|
|
|
const target = event.target as HTMLElement | null;
|
|
|
|
|
const isTypingField =
|
|
|
|
|
target instanceof HTMLInputElement ||
|
|
|
|
|
target instanceof HTMLTextAreaElement ||
|
|
|
|
|
target instanceof HTMLSelectElement ||
|
|
|
|
|
target?.isContentEditable;
|
|
|
|
|
|
|
|
|
|
if ((event.key === 'k' && (event.metaKey || event.ctrlKey)) || (!isTypingField && event.key === '/')) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
openPalette();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.key === 'Escape') {
|
|
|
|
|
paletteOpen = false;
|
|
|
|
|
quickMenuOpen = false;
|
2026-04-27 21:53:36 +12:00
|
|
|
userMenuOpen = false;
|
|
|
|
|
navOpen = false;
|
2026-04-25 22:51:36 +12:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener('keydown', handleKeydown);
|
2026-04-27 21:53:36 +12:00
|
|
|
window.addEventListener('resize', syncViewport);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener('keydown', handleKeydown);
|
|
|
|
|
window.removeEventListener('resize', syncViewport);
|
|
|
|
|
};
|
2026-04-25 22:51:36 +12:00
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head>
|
|
|
|
|
<title>{pageTitle(page.url.pathname)} | Hunter Premium Produce</title>
|
|
|
|
|
</svelte:head>
|
|
|
|
|
|
|
|
|
|
<div class="app-shell">
|
2026-04-27 21:53:36 +12:00
|
|
|
{#if showBottomNav && navOpen}
|
|
|
|
|
<button aria-label="Close navigation" class="nav-backdrop" type="button" onclick={() => (navOpen = false)}></button>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if !showBottomNav}
|
|
|
|
|
<aside class="sidebar">
|
|
|
|
|
<div class="brand-row">
|
|
|
|
|
<a class="brand" href="/">
|
|
|
|
|
<span class="brand-mark">HP</span>
|
|
|
|
|
<span>Hunter Premium Produce</span>
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
2026-04-25 22:51:36 +12:00
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
<div class="sidebar-body">
|
|
|
|
|
<button class="search-box" type="button" aria-label="Search the workspace" onclick={() => openPalette()}>
|
|
|
|
|
<span class="search-icon"></span>
|
|
|
|
|
<span class="search-placeholder">Search the workspace...</span>
|
|
|
|
|
<kbd>/</kbd>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<nav class="nav-list" aria-label="Client navigation">
|
|
|
|
|
{#each navigation as item}
|
|
|
|
|
<a class:active={matchesRoute(item.href, page.url.pathname)} href={item.href}>
|
|
|
|
|
<span class="nav-icon">{item.shortLabel}</span>
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
</a>
|
|
|
|
|
{/each}
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<div class="sidebar-footer">
|
|
|
|
|
{#each footerLinks as item}
|
|
|
|
|
<a href={item.href}>
|
|
|
|
|
<span class="nav-icon muted">{item.shortLabel}</span>
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
</a>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
2026-04-25 22:51:36 +12:00
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
<div class="sidebar-meta">
|
|
|
|
|
<span>{appVersion}</span>
|
|
|
|
|
<small>© {currentYear} Hunter Premium Produce</small>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
{/if}
|
2026-04-25 22:51:36 +12:00
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
<div class:bottom-nav-layout={showBottomNav} class="main-shell">
|
2026-04-25 22:51:36 +12:00
|
|
|
<header class="topbar">
|
2026-04-27 21:53:36 +12:00
|
|
|
<div class="topbar-start">
|
|
|
|
|
<div class="topbar-copy">
|
|
|
|
|
<h1>{pageTitle(page.url.pathname)}</h1>
|
|
|
|
|
<p>{pageDescription(page.url.pathname)}</p>
|
|
|
|
|
</div>
|
2026-04-25 22:51:36 +12:00
|
|
|
</div>
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
<div class="topbar-middle">
|
|
|
|
|
<button class="search-box topbar-search" type="button" aria-label="Search the workspace" onclick={() => openPalette()}>
|
|
|
|
|
<span class="search-icon"></span>
|
|
|
|
|
<span class="search-placeholder">Search pages, mixes, products, and settings...</span>
|
|
|
|
|
<kbd>/</kbd>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-04-25 22:51:36 +12:00
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
<div class="topbar-actions">
|
2026-04-25 22:51:36 +12:00
|
|
|
<div class="menu-wrap">
|
2026-04-27 21:53:36 +12:00
|
|
|
<button
|
|
|
|
|
class="action-button"
|
|
|
|
|
type="button"
|
|
|
|
|
onclick={() => {
|
|
|
|
|
quickMenuOpen = !quickMenuOpen;
|
|
|
|
|
userMenuOpen = false;
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-25 22:51:36 +12:00
|
|
|
Quick Actions
|
|
|
|
|
<span class:open={quickMenuOpen} class="chevron"></span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{#if quickMenuOpen}
|
|
|
|
|
<div class="menu-panel">
|
|
|
|
|
<a href="/mixes">Open mix costing</a>
|
|
|
|
|
<a href="/mixes/new">Create mix worksheet</a>
|
|
|
|
|
<a href="/products">Review delivered pricing</a>
|
|
|
|
|
<button type="button" onclick={() => openPalette('')}>Search the workspace</button>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
2026-04-27 21:53:36 +12:00
|
|
|
|
|
|
|
|
<div class="menu-wrap user-menu-wrap">
|
|
|
|
|
<button
|
|
|
|
|
aria-expanded={userMenuOpen}
|
|
|
|
|
class="user-trigger"
|
|
|
|
|
type="button"
|
|
|
|
|
onclick={() => {
|
|
|
|
|
userMenuOpen = !userMenuOpen;
|
|
|
|
|
quickMenuOpen = false;
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span class={`user-status-dot ${$clientSession ? 'live' : 'idle'}`}></span>
|
|
|
|
|
<span class="user-trigger-copy">
|
|
|
|
|
<span class="workspace-label">{$sessionHydrated ? ($clientSession ? 'Signed in' : 'Signed out') : 'Checking session'}</span>
|
|
|
|
|
<strong>{$sessionHydrated ? ($clientSession ? $clientSession.email : 'Sign in required') : 'Restoring workspace access'}</strong>
|
|
|
|
|
</span>
|
|
|
|
|
<span class:open={userMenuOpen} class="chevron"></span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{#if userMenuOpen}
|
|
|
|
|
<div class="menu-panel user-menu-panel">
|
|
|
|
|
<div class="user-menu-summary">
|
|
|
|
|
<strong>
|
|
|
|
|
{$sessionHydrated
|
|
|
|
|
? $clientSession
|
|
|
|
|
? $clientSession.name || 'Client account'
|
|
|
|
|
: 'Client session inactive'
|
|
|
|
|
: 'Checking saved client session'}
|
|
|
|
|
</strong>
|
|
|
|
|
<span>
|
|
|
|
|
{$sessionHydrated
|
|
|
|
|
? $clientSession
|
|
|
|
|
? $clientSession.email
|
|
|
|
|
: 'Return to the overview page to sign in.'
|
|
|
|
|
: 'Waiting for the browser session check to complete.'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="button" onclick={openSettings}>Change settings</button>
|
|
|
|
|
{#if $clientSession}
|
|
|
|
|
<button type="button" onclick={() => clientSession.clear()}>Log out</button>
|
|
|
|
|
{:else if !$sessionHydrated}
|
|
|
|
|
<button type="button" disabled>Checking session...</button>
|
|
|
|
|
{:else}
|
|
|
|
|
<a href="/">Go to sign-in</a>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
2026-04-25 22:51:36 +12:00
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<main class="content">
|
2026-04-27 21:53:36 +12:00
|
|
|
{#if !isRootRoute && (!$sessionHydrated || isRestoringSession)}
|
|
|
|
|
<section class="locked-card loading-card">
|
|
|
|
|
<p class="workspace-label">Checking Session</p>
|
|
|
|
|
<h2>Restoring your client workspace.</h2>
|
|
|
|
|
<p>Refreshing the current page with the saved browser session before deciding whether sign-in is required.</p>
|
|
|
|
|
</section>
|
|
|
|
|
{:else if !isRootRoute && !$clientSession}
|
2026-04-25 22:51:36 +12:00
|
|
|
<section class="locked-card">
|
|
|
|
|
<p class="workspace-label">Client Sign-In Required</p>
|
|
|
|
|
<h2>Sign in on the Hunter Premium Produce home page to unlock workspace data.</h2>
|
|
|
|
|
<p>The client-facing routes stay empty until a valid client session is active.</p>
|
|
|
|
|
<a href="/">Return to sign-in</a>
|
|
|
|
|
</section>
|
|
|
|
|
{:else}
|
|
|
|
|
{@render children()}
|
|
|
|
|
{/if}
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
{#if showBottomNav}
|
|
|
|
|
<nav class="bottom-nav" aria-label="Tablet navigation">
|
|
|
|
|
{#each primaryBottomNavigation as item}
|
|
|
|
|
<a class:active={matchesRoute(item.href, page.url.pathname)} href={item.href}>
|
|
|
|
|
<span class="bottom-nav-icon">{item.shortLabel}</span>
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
</a>
|
|
|
|
|
{/each}
|
|
|
|
|
|
|
|
|
|
<button aria-expanded={navOpen} class:active={navOpen} type="button" onclick={() => (navOpen = !navOpen)}>
|
|
|
|
|
<span class="bottom-nav-icon muted">+</span>
|
|
|
|
|
<span>More</span>
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
{#if navOpen}
|
|
|
|
|
<section
|
|
|
|
|
aria-label="Tablet navigation drawer"
|
|
|
|
|
class="bottom-drawer"
|
|
|
|
|
role="dialog"
|
|
|
|
|
aria-modal="true"
|
|
|
|
|
onclick={(event) => event.stopPropagation()}
|
|
|
|
|
>
|
|
|
|
|
<div class="drawer-handle"></div>
|
|
|
|
|
|
|
|
|
|
<div class="drawer-header">
|
|
|
|
|
<div>
|
|
|
|
|
<p class="workspace-label">Workspace Drawer</p>
|
|
|
|
|
<strong>Hunter Premium Produce</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<button aria-label="Close drawer" class="nav-toggle" type="button" onclick={() => (navOpen = false)}>
|
|
|
|
|
<span></span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button class="search-box drawer-search" type="button" aria-label="Search the workspace" onclick={() => openPalette()}>
|
|
|
|
|
<span class="search-icon"></span>
|
|
|
|
|
<span class="search-placeholder">Search the workspace...</span>
|
|
|
|
|
<kbd>/</kbd>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<div class="drawer-grid">
|
|
|
|
|
<nav class="drawer-section" aria-label="All workspace pages">
|
|
|
|
|
{#each navigation as item}
|
|
|
|
|
<a class:active={matchesRoute(item.href, page.url.pathname)} href={item.href} onclick={() => (navOpen = false)}>
|
|
|
|
|
<span class="nav-icon">{item.shortLabel}</span>
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
</a>
|
|
|
|
|
{/each}
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<div class="drawer-section drawer-actions">
|
|
|
|
|
<a href="/mixes/new" onclick={() => (navOpen = false)}>
|
|
|
|
|
<span class="nav-icon">NW</span>
|
|
|
|
|
<span>Create mix worksheet</span>
|
|
|
|
|
</a>
|
|
|
|
|
<button type="button" onclick={openSettings}>
|
|
|
|
|
<span class="nav-icon muted">ST</span>
|
|
|
|
|
<span>Change settings</span>
|
|
|
|
|
</button>
|
|
|
|
|
<a href="/products" onclick={() => (navOpen = false)}>
|
|
|
|
|
<span class="nav-icon muted">DP</span>
|
|
|
|
|
<span>Review delivered pricing</span>
|
|
|
|
|
</a>
|
|
|
|
|
<button type="button" onclick={() => openPalette('')}>
|
|
|
|
|
<span class="nav-icon muted">SR</span>
|
|
|
|
|
<span>Search the workspace</span>
|
|
|
|
|
</button>
|
|
|
|
|
{#if $clientSession}
|
|
|
|
|
<button type="button" onclick={() => clientSession.clear()}>
|
|
|
|
|
<span class="nav-icon muted">SO</span>
|
|
|
|
|
<span>Sign out</span>
|
|
|
|
|
</button>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="drawer-footer">
|
|
|
|
|
{#each footerLinks as item}
|
|
|
|
|
<a href={item.href} onclick={() => (navOpen = false)}>
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
<small>{item.shortLabel}</small>
|
|
|
|
|
</a>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
{/if}
|
|
|
|
|
{/if}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
{#if paletteOpen}
|
|
|
|
|
<div class="palette-overlay" role="presentation" onclick={() => (paletteOpen = false)}>
|
|
|
|
|
<div
|
|
|
|
|
class="palette"
|
|
|
|
|
role="dialog"
|
|
|
|
|
aria-modal="true"
|
|
|
|
|
aria-label="Workspace search"
|
|
|
|
|
tabindex="-1"
|
|
|
|
|
onclick={(event) => event.stopPropagation()}
|
|
|
|
|
onkeydown={(event) => {
|
|
|
|
|
if (event.key === 'Escape') {
|
|
|
|
|
paletteOpen = false;
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div class="palette-input-row">
|
|
|
|
|
<span class="search-icon"></span>
|
|
|
|
|
<input bind:this={paletteInput} bind:value={paletteQuery} placeholder="Search pages, workflows, and pricing views..." />
|
|
|
|
|
<kbd>Esc</kbd>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="palette-results">
|
|
|
|
|
{#if filteredSearchItems.length}
|
|
|
|
|
{#each filteredSearchItems as item}
|
|
|
|
|
<button class="palette-item" type="button" onclick={() => runSearchItem(item)}>
|
|
|
|
|
<div>
|
|
|
|
|
<strong>{item.label}</strong>
|
|
|
|
|
<span>{item.description}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<small>{item.href}</small>
|
|
|
|
|
</button>
|
|
|
|
|
{/each}
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="palette-empty">
|
|
|
|
|
<strong>No results</strong>
|
|
|
|
|
<span>Try searching for mixes, products, scenarios, or pricing.</span>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
|
|
|
|
|
|
|
|
:global(:root) {
|
|
|
|
|
--bg: #f4f7f5;
|
|
|
|
|
--panel: #ffffff;
|
|
|
|
|
--panel-soft: #f8fbf9;
|
|
|
|
|
--line: #e5ece7;
|
|
|
|
|
--line-strong: #d9e4dd;
|
|
|
|
|
--text: #18231d;
|
|
|
|
|
--muted: #6d7d74;
|
|
|
|
|
--green: #22a95e;
|
|
|
|
|
--green-deep: #148249;
|
|
|
|
|
--green-soft: #eaf8ef;
|
|
|
|
|
--blue-soft: #eef7ff;
|
|
|
|
|
--shadow: 0 10px 30px rgba(15, 23, 17, 0.06);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(html, body) {
|
|
|
|
|
margin: 0;
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
background: var(--bg);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-family: Inter, "Segoe UI", sans-serif;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(*) {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(h1, h2, h3, h4, h5, h6) {
|
|
|
|
|
font-family: Inter, "Segoe UI", sans-serif;
|
|
|
|
|
letter-spacing: -0.03em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(button),
|
|
|
|
|
:global(input),
|
|
|
|
|
:global(select),
|
|
|
|
|
:global(textarea) {
|
|
|
|
|
font: inherit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(a) {
|
|
|
|
|
color: inherit;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app-shell {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 228px minmax(0, 1fr);
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.nav-backdrop {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.sidebar {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 1.1rem;
|
|
|
|
|
padding: 0.9rem;
|
|
|
|
|
background: var(--panel);
|
|
|
|
|
border-right: 1px solid var(--line);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.sidebar-body {
|
|
|
|
|
min-height: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex: 1;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 1.1rem;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.brand-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 0.68rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brand {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.68rem;
|
|
|
|
|
font-size: 1.08rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brand-mark,
|
|
|
|
|
.nav-icon {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
color: #fff;
|
|
|
|
|
background: linear-gradient(135deg, var(--green) 0%, var(--green-deep) 100%);
|
|
|
|
|
font-size: 0.68rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
letter-spacing: 0.04em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brand-mark {
|
|
|
|
|
width: 1.9rem;
|
|
|
|
|
height: 1.9rem;
|
|
|
|
|
border-radius: 0.68rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-toggle,
|
|
|
|
|
.action-button,
|
|
|
|
|
.menu-panel button {
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
background: var(--panel);
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-toggle {
|
|
|
|
|
width: 2.05rem;
|
|
|
|
|
height: 2.05rem;
|
|
|
|
|
border-radius: 0.68rem;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.topbar-nav-button {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.nav-toggle span,
|
|
|
|
|
.search-icon,
|
|
|
|
|
.chevron {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-toggle span,
|
|
|
|
|
.nav-toggle span::before,
|
|
|
|
|
.nav-toggle span::after {
|
|
|
|
|
width: 0.88rem;
|
|
|
|
|
height: 2px;
|
|
|
|
|
background: currentColor;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
content: '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-toggle span::before,
|
|
|
|
|
.nav-toggle span::after {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-toggle span::before {
|
|
|
|
|
top: -0.28rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-toggle span::after {
|
|
|
|
|
top: 0.28rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-box {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: auto 1fr auto;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.64rem;
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 0.72rem 0.82rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 0.82rem;
|
|
|
|
|
background: var(--panel-soft);
|
|
|
|
|
text-align: left;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-placeholder {
|
|
|
|
|
color: #93a098;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-icon {
|
|
|
|
|
width: 0.82rem;
|
|
|
|
|
height: 0.82rem;
|
|
|
|
|
border: 2px solid #98a59d;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-icon::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
right: -0.28rem;
|
|
|
|
|
bottom: -0.18rem;
|
|
|
|
|
width: 0.42rem;
|
|
|
|
|
height: 2px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: #98a59d;
|
|
|
|
|
transform: rotate(45deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kbd {
|
|
|
|
|
padding: 0.1rem 0.42rem;
|
|
|
|
|
border: 1px solid var(--line-strong);
|
|
|
|
|
border-radius: 0.42rem;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
background: #fff;
|
|
|
|
|
font-size: 0.76rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-list,
|
|
|
|
|
.sidebar-footer {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-list a,
|
|
|
|
|
.sidebar-footer a {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.68rem;
|
|
|
|
|
padding: 0.72rem 0.68rem;
|
|
|
|
|
border-radius: 0.82rem;
|
|
|
|
|
color: #304038;
|
|
|
|
|
transition: background-color 160ms ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-list a:hover,
|
|
|
|
|
.sidebar-footer a:hover,
|
|
|
|
|
.nav-list a.active {
|
|
|
|
|
background: var(--green-soft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-list a.active {
|
|
|
|
|
color: var(--green-deep);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-icon {
|
|
|
|
|
width: 1.56rem;
|
|
|
|
|
height: 1.56rem;
|
|
|
|
|
border-radius: 0.56rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-icon.muted {
|
|
|
|
|
background: linear-gradient(135deg, #95a39b 0%, #6e7c73 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-footer {
|
|
|
|
|
margin-top: auto;
|
|
|
|
|
padding-top: 0.6rem;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.sidebar-meta {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.2rem;
|
|
|
|
|
padding: 0.85rem 0.3rem 0;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-meta small {
|
|
|
|
|
font-size: 0.74rem;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.main-shell {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar {
|
2026-04-27 21:53:36 +12:00
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: minmax(0, 0.95fr) minmax(20rem, 1.1fr) auto;
|
2026-04-25 22:51:36 +12:00
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.9rem;
|
|
|
|
|
padding: 0.86rem 1.34rem;
|
|
|
|
|
background: var(--panel);
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.topbar-start {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
gap: 0.82rem;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.topbar-copy h1,
|
|
|
|
|
.topbar-copy p {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-copy h1 {
|
|
|
|
|
font-size: 1.62rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-copy p {
|
|
|
|
|
margin-top: 0.22rem;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
font-size: 0.92rem;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.topbar-middle {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-search {
|
|
|
|
|
min-height: 3rem;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.topbar-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.68rem;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workspace-chip {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.14rem;
|
|
|
|
|
padding: 0.65rem 0.85rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 0.92rem;
|
|
|
|
|
background: var(--panel-soft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.session-chip {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workspace-label {
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
font-size: 0.76rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
letter-spacing: 0.06em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workspace-chip strong {
|
|
|
|
|
font-size: 0.96rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-wrap {
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-button {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.62rem;
|
|
|
|
|
border-radius: 0.88rem;
|
|
|
|
|
padding: 0.68rem 0.84rem;
|
|
|
|
|
color: #304038;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.user-trigger {
|
|
|
|
|
min-width: 14rem;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.72rem;
|
|
|
|
|
padding: 0.64rem 0.84rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 0.96rem;
|
|
|
|
|
background: var(--panel-soft);
|
|
|
|
|
color: #304038;
|
|
|
|
|
text-align: left;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-status-dot {
|
|
|
|
|
width: 0.72rem;
|
|
|
|
|
height: 0.72rem;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: #b4c0ba;
|
|
|
|
|
box-shadow: 0 0 0 0.24rem rgba(180, 192, 186, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-status-dot.live {
|
|
|
|
|
background: var(--green);
|
|
|
|
|
box-shadow: 0 0 0 0.24rem rgba(34, 169, 94, 0.14);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-status-dot.idle {
|
|
|
|
|
background: #c08b3d;
|
|
|
|
|
box-shadow: 0 0 0 0.24rem rgba(192, 139, 61, 0.14);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-trigger-copy {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
display: grid;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-trigger-copy strong {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-menu-wrap {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-menu-panel {
|
|
|
|
|
min-width: 16rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-menu-summary {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.2rem;
|
|
|
|
|
padding: 0.72rem 0.78rem;
|
|
|
|
|
border-radius: 0.82rem;
|
|
|
|
|
background: var(--panel-soft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-menu-summary span {
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
font-size: 0.82rem;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.chevron {
|
|
|
|
|
width: 0.54rem;
|
|
|
|
|
height: 0.54rem;
|
|
|
|
|
border-right: 2px solid #7a8c82;
|
|
|
|
|
border-bottom: 2px solid #7a8c82;
|
|
|
|
|
transform: rotate(45deg);
|
|
|
|
|
transition: transform 140ms ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chevron.open {
|
|
|
|
|
transform: rotate(-135deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-panel {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: calc(100% + 0.45rem);
|
|
|
|
|
right: 0;
|
|
|
|
|
z-index: 20;
|
|
|
|
|
min-width: 13rem;
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.18rem;
|
|
|
|
|
padding: 0.4rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 0.96rem;
|
|
|
|
|
background: rgba(255, 255, 255, 0.98);
|
|
|
|
|
box-shadow: 0 18px 40px rgba(15, 23, 17, 0.1);
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-panel a,
|
|
|
|
|
.menu-panel button {
|
|
|
|
|
padding: 0.72rem 0.78rem;
|
|
|
|
|
border-radius: 0.78rem;
|
|
|
|
|
color: #304038;
|
|
|
|
|
text-align: left;
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-panel a:hover,
|
|
|
|
|
.menu-panel button:hover {
|
|
|
|
|
background: var(--panel-soft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
padding: 1.34rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.locked-card {
|
|
|
|
|
max-width: 42rem;
|
|
|
|
|
padding: 1.25rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 1.25rem;
|
|
|
|
|
background: var(--panel);
|
|
|
|
|
box-shadow: var(--shadow);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.loading-card {
|
|
|
|
|
min-height: 10rem;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.locked-card h2,
|
|
|
|
|
.locked-card p {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.locked-card h2 {
|
|
|
|
|
margin-top: 0.35rem;
|
|
|
|
|
font-size: clamp(1.7rem, 3vw, 2.2rem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.locked-card p:last-of-type {
|
|
|
|
|
margin-top: 0.45rem;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.locked-card a {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
padding: 0.78rem 0.92rem;
|
|
|
|
|
border-radius: 0.88rem;
|
|
|
|
|
background: linear-gradient(135deg, var(--green) 0%, var(--green-deep) 100%);
|
|
|
|
|
color: #fff;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-overlay {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
z-index: 40;
|
|
|
|
|
display: grid;
|
|
|
|
|
place-items: start center;
|
|
|
|
|
padding: 8vh 1rem 1rem;
|
|
|
|
|
background: rgba(11, 18, 14, 0.3);
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette {
|
|
|
|
|
width: min(44rem, 100%);
|
|
|
|
|
border: 1px solid rgba(217, 228, 221, 0.9);
|
|
|
|
|
border-radius: 1.2rem;
|
|
|
|
|
background: rgba(255, 255, 255, 0.98);
|
|
|
|
|
box-shadow: 0 24px 60px rgba(15, 23, 17, 0.16);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-input-row {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: auto 1fr auto;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.8rem;
|
|
|
|
|
padding: 0.95rem 1rem;
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-input-row input {
|
|
|
|
|
border: none;
|
|
|
|
|
outline: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-size: 0.98rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-results {
|
|
|
|
|
max-height: 26rem;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-item,
|
|
|
|
|
.palette-empty {
|
|
|
|
|
width: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
padding: 0.88rem 0.92rem;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 0.92rem;
|
|
|
|
|
text-align: left;
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-item {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-item:hover {
|
|
|
|
|
background: var(--panel-soft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-item strong,
|
|
|
|
|
.palette-empty strong {
|
|
|
|
|
display: block;
|
|
|
|
|
font-size: 0.96rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-item span,
|
|
|
|
|
.palette-empty span,
|
|
|
|
|
.palette-item small {
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-item span {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-top: 0.18rem;
|
|
|
|
|
font-size: 0.84rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-item small {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
font-size: 0.76rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.palette-empty {
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.bottom-nav,
|
|
|
|
|
.bottom-drawer {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-shell.bottom-nav-layout .content {
|
|
|
|
|
padding-bottom: 7.25rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1180px) {
|
2026-04-25 22:51:36 +12:00
|
|
|
.app-shell {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.nav-backdrop {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
z-index: 48;
|
|
|
|
|
display: block;
|
|
|
|
|
border: none;
|
|
|
|
|
background: rgba(11, 18, 14, 0.28);
|
|
|
|
|
backdrop-filter: blur(4px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav {
|
|
|
|
|
position: fixed;
|
|
|
|
|
left: max(0.8rem, env(safe-area-inset-left));
|
|
|
|
|
right: max(0.8rem, env(safe-area-inset-right));
|
|
|
|
|
bottom: max(0.8rem, env(safe-area-inset-bottom));
|
|
|
|
|
z-index: 45;
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
padding: 0.6rem;
|
|
|
|
|
border: 1px solid rgba(217, 228, 221, 0.92);
|
|
|
|
|
border-radius: 1.35rem;
|
|
|
|
|
background: rgba(255, 255, 255, 0.96);
|
|
|
|
|
box-shadow: 0 20px 40px rgba(15, 23, 17, 0.16);
|
|
|
|
|
backdrop-filter: blur(16px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav a,
|
|
|
|
|
.bottom-nav button {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
display: grid;
|
|
|
|
|
justify-items: center;
|
|
|
|
|
gap: 0.34rem;
|
|
|
|
|
padding: 0.62rem 0.38rem;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 1rem;
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: #51635a;
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-size: 0.74rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav a.active,
|
|
|
|
|
.bottom-nav button.active {
|
|
|
|
|
color: var(--green-deep);
|
|
|
|
|
background: linear-gradient(180deg, #f4fbf7 0%, #e8f6ee 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav-icon {
|
|
|
|
|
width: 2.1rem;
|
|
|
|
|
height: 2.1rem;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
border-radius: 0.78rem;
|
|
|
|
|
color: #fff;
|
|
|
|
|
background: linear-gradient(135deg, var(--green) 0%, var(--green-deep) 100%);
|
|
|
|
|
font-size: 0.66rem;
|
|
|
|
|
letter-spacing: 0.04em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav-icon.muted {
|
|
|
|
|
background: linear-gradient(135deg, #96a49c 0%, #718077 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-drawer {
|
|
|
|
|
position: fixed;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
z-index: 50;
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
padding: 0.85rem 1rem calc(6.6rem + env(safe-area-inset-bottom));
|
|
|
|
|
border-top: 1px solid var(--line);
|
|
|
|
|
border-radius: 1.6rem 1.6rem 0 0;
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(248, 251, 249, 0.98) 0%, rgba(255, 255, 255, 0.98) 100%);
|
|
|
|
|
box-shadow: 0 -20px 45px rgba(15, 23, 17, 0.16);
|
|
|
|
|
backdrop-filter: blur(16px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-handle {
|
|
|
|
|
width: 3.5rem;
|
|
|
|
|
height: 0.34rem;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: #c8d4ce;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 0.8rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-header strong {
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-search {
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.9rem;
|
|
|
|
|
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
|
2026-04-25 22:51:36 +12:00
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.drawer-section {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-section a,
|
|
|
|
|
.drawer-section button {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.72rem;
|
|
|
|
|
padding: 0.82rem 0.86rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 0.96rem;
|
|
|
|
|
background: rgba(255, 255, 255, 0.88);
|
|
|
|
|
color: #304038;
|
|
|
|
|
text-align: left;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-section a.active {
|
|
|
|
|
color: var(--green-deep);
|
|
|
|
|
background: var(--green-soft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-footer {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-footer a {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
padding: 0.82rem 0.9rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 0.96rem;
|
|
|
|
|
background: rgba(255, 255, 255, 0.88);
|
|
|
|
|
color: #304038;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-footer small {
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
letter-spacing: 0.05em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar {
|
|
|
|
|
position: sticky;
|
|
|
|
|
top: 0;
|
|
|
|
|
z-index: 30;
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
padding: 0.95rem 1rem;
|
|
|
|
|
background: rgba(255, 255, 255, 0.96);
|
|
|
|
|
backdrop-filter: blur(12px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-middle {
|
|
|
|
|
order: 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-actions {
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content {
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (min-width: 1181px) {
|
|
|
|
|
.bottom-nav-layout .content {
|
|
|
|
|
padding-bottom: 1.34rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (min-width: 1181px) {
|
|
|
|
|
.nav-toggle {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
.drawer-grid {
|
|
|
|
|
grid-template-columns: 1fr;
|
2026-04-25 22:51:36 +12:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 720px) {
|
|
|
|
|
.topbar,
|
2026-04-27 21:53:36 +12:00
|
|
|
.topbar-actions {
|
|
|
|
|
display: flex;
|
2026-04-25 22:51:36 +12:00
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:53:36 +12:00
|
|
|
.topbar-start {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-copy {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-copy h1 {
|
|
|
|
|
font-size: 1.34rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-middle {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav {
|
|
|
|
|
left: max(0.55rem, env(safe-area-inset-left));
|
|
|
|
|
right: max(0.55rem, env(safe-area-inset-right));
|
|
|
|
|
bottom: max(0.55rem, env(safe-area-inset-bottom));
|
|
|
|
|
gap: 0.32rem;
|
|
|
|
|
padding: 0.45rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav a,
|
|
|
|
|
.bottom-nav button {
|
|
|
|
|
padding: 0.55rem 0.2rem;
|
|
|
|
|
font-size: 0.68rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-nav-icon {
|
|
|
|
|
width: 1.9rem;
|
|
|
|
|
height: 1.9rem;
|
|
|
|
|
font-size: 0.6rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-drawer {
|
|
|
|
|
padding: 0.75rem 0.8rem calc(6.3rem + env(safe-area-inset-bottom));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-actions,
|
|
|
|
|
.workspace-chip,
|
|
|
|
|
.menu-wrap,
|
|
|
|
|
.action-button,
|
|
|
|
|
.user-trigger {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-button {
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-panel {
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-footer {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
.content {
|
|
|
|
|
padding: 0.92rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|