style: full redesign — Zinc/Emerald palette, Outfit font, bento dashboard
Dashboard rebuilt from scratch to match React mockup: - Asymmetric bento grid (2fr/1fr, 1fr/1fr, 7fr/3fr) - Big hero numbers on bento cards - Task pill trigger with breathing dot animation - Inline fitness card with animated progress bar - Emerald accent replaces indigo across all pages - Outfit font replaces DM Sans - Zinc-tinted shadows - 16px card radius - Staggered card reveal animations All pages verified working: tasks, fitness, budget, inventory, settings, trips. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import DashboardActionCard from '$lib/components/dashboard/DashboardActionCard.svelte';
|
|
||||||
import BudgetModule from '$lib/components/dashboard/BudgetModule.svelte';
|
import BudgetModule from '$lib/components/dashboard/BudgetModule.svelte';
|
||||||
import FitnessModule from '$lib/components/dashboard/FitnessModule.svelte';
|
import FitnessModule from '$lib/components/dashboard/FitnessModule.svelte';
|
||||||
import IssuesModule from '$lib/components/dashboard/IssuesModule.svelte';
|
import IssuesModule from '$lib/components/dashboard/IssuesModule.svelte';
|
||||||
@@ -27,8 +26,13 @@
|
|||||||
let budgetIncome = $state('');
|
let budgetIncome = $state('');
|
||||||
let fitnessCalRemaining = $state(0);
|
let fitnessCalRemaining = $state(0);
|
||||||
let fitnessCalLogged = $state(0);
|
let fitnessCalLogged = $state(0);
|
||||||
|
let fitnessCalGoal = $state(2000);
|
||||||
let fitnessProtein = $state(0);
|
let fitnessProtein = $state(0);
|
||||||
let fitnessCarbs = $state(0);
|
let fitnessCarbs = $state(0);
|
||||||
|
let fitnessProteinGoal = $state(150);
|
||||||
|
let fitnessCarbsGoal = $state(200);
|
||||||
|
let fitnessFat = $state(0);
|
||||||
|
let fitnessFatGoal = $state(65);
|
||||||
let headerTasks = $state<QuickTask[]>([]);
|
let headerTasks = $state<QuickTask[]>([]);
|
||||||
let headerOverdue = $state(0);
|
let headerOverdue = $state(0);
|
||||||
let headerTotalCount = $state(0);
|
let headerTotalCount = $state(0);
|
||||||
@@ -60,6 +64,8 @@
|
|||||||
} catch { return ''; }
|
} catch { return ''; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fitPercent = $derived(fitnessCalGoal > 0 ? Math.min(100, Math.round((fitnessCalLogged / fitnessCalGoal) * 100)) : 0);
|
||||||
|
|
||||||
async function loadTasks() {
|
async function loadTasks() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/tasks/today', { credentials: 'include' });
|
const res = await fetch('/api/tasks/today', { credentials: 'include' });
|
||||||
@@ -69,7 +75,6 @@
|
|||||||
const today = t.today || [];
|
const today = t.today || [];
|
||||||
const overdue = t.overdue || [];
|
const overdue = t.overdue || [];
|
||||||
headerTotalCount = today.length + overdue.length;
|
headerTotalCount = today.length + overdue.length;
|
||||||
// First task for preview
|
|
||||||
headerTasks = [...overdue, ...today].slice(0, 1);
|
headerTasks = [...overdue, ...today].slice(0, 1);
|
||||||
}
|
}
|
||||||
} catch { /* silent */ }
|
} catch { /* silent */ }
|
||||||
@@ -106,10 +111,15 @@
|
|||||||
fitnessCalLogged = Math.round(t.total_calories || 0);
|
fitnessCalLogged = Math.round(t.total_calories || 0);
|
||||||
fitnessProtein = Math.round(t.total_protein || 0);
|
fitnessProtein = Math.round(t.total_protein || 0);
|
||||||
fitnessCarbs = Math.round(t.total_carbs || 0);
|
fitnessCarbs = Math.round(t.total_carbs || 0);
|
||||||
|
fitnessFat = Math.round(t.total_fat || 0);
|
||||||
}
|
}
|
||||||
if (fitGoalsRes.ok) {
|
if (fitGoalsRes.ok) {
|
||||||
const g = await fitGoalsRes.json();
|
const g = await fitGoalsRes.json();
|
||||||
fitnessCalRemaining = Math.max(0, (g.calories || 2000) - fitnessCalLogged);
|
fitnessCalGoal = g.calories || 2000;
|
||||||
|
fitnessProteinGoal = g.protein || 150;
|
||||||
|
fitnessCarbsGoal = g.carbs || 200;
|
||||||
|
fitnessFatGoal = g.fat || 65;
|
||||||
|
fitnessCalRemaining = Math.max(0, fitnessCalGoal - fitnessCalLogged);
|
||||||
}
|
}
|
||||||
} catch { /* silent */ }
|
} catch { /* silent */ }
|
||||||
|
|
||||||
@@ -118,217 +128,295 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="app-surface">
|
<div class="db-surface">
|
||||||
<!-- Dashboard header with task trigger -->
|
|
||||||
<div class="dash-header">
|
<!-- ═══ HEADER ═══ -->
|
||||||
<div class="dash-left">
|
<div class="db-header">
|
||||||
<div class="dash-date">{getDateString()}</div>
|
<div>
|
||||||
<h1 class="dash-greeting">{getGreeting()}, <strong>{userName}</strong></h1>
|
<div class="db-date">{getDateString()}</div>
|
||||||
|
<h1 class="db-greeting">{getGreeting()}, <strong>{userName}</strong></h1>
|
||||||
</div>
|
</div>
|
||||||
<button class="task-trigger" onclick={() => taskPanelOpen = true}>
|
<button class="task-pill" onclick={() => taskPanelOpen = true}>
|
||||||
<div class="task-trigger-icon" class:has-overdue={headerOverdue > 0}>
|
<span class="tp-dot" class:tp-overdue={headerOverdue > 0}></span>
|
||||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
|
<span class="tp-text"><b>{headerTotalCount} task{headerTotalCount !== 1 ? 's' : ''}</b>
|
||||||
<rect x="2" y="2" width="14" height="14" rx="3"/>
|
{#if headerTasks[0]} · Next: {headerTasks[0].title}{/if}
|
||||||
<path d="M6 9l2 2 4-4"/>
|
</span>
|
||||||
</svg>
|
<svg class="tp-arrow" width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 3l3.5 3.5L5 10"/></svg>
|
||||||
{#if headerOverdue > 0}
|
|
||||||
<span class="task-trigger-dot"></span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="task-trigger-text">
|
|
||||||
<span class="task-trigger-count">
|
|
||||||
{headerTotalCount} task{headerTotalCount !== 1 ? 's' : ''}
|
|
||||||
{#if headerOverdue > 0}
|
|
||||||
<span class="task-trigger-overdue">· {headerOverdue} overdue</span>
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
{#if headerTasks[0]}
|
|
||||||
<span class="task-trigger-next">
|
|
||||||
Next: {headerTasks[0].title}
|
|
||||||
{#if formatTaskTime(headerTasks[0])} · {formatTaskTime(headerTasks[0])}{/if}
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<span class="task-trigger-next">All clear for today</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<svg class="task-trigger-arrow" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M6 4l4 4-4 4"/></svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TaskSlideOver bind:open={taskPanelOpen} onclose={() => { taskPanelOpen = false; loadTasks(); }} />
|
<TaskSlideOver bind:open={taskPanelOpen} onclose={() => { taskPanelOpen = false; loadTasks(); }} />
|
||||||
|
|
||||||
<!-- Bento row 1: Budget (wide) + Inventory (narrow) -->
|
<!-- ═══ BENTO ROW 1: Budget (2fr) + Inventory (1fr) ═══ -->
|
||||||
<div class="bento-row stagger">
|
<div class="bento stagger">
|
||||||
<DashboardActionCard
|
<a href="/budget" class="bc bc-emerald">
|
||||||
title="{budgetUncatCount} uncategorized transactions"
|
<div class="bc-top">
|
||||||
description="{budgetSpending} spent · {budgetIncome} income"
|
<span class="bc-label">Budget</span>
|
||||||
action="Review"
|
<div class="bc-icon emerald">
|
||||||
variant="budget"
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
|
||||||
size="primary"
|
</div>
|
||||||
href="/budget"
|
</div>
|
||||||
/>
|
<div class="bc-value">{budgetUncatCount}</div>
|
||||||
<DashboardActionCard
|
<div class="bc-desc">uncategorized transactions<br><strong>{budgetSpending} spent</strong> · {budgetIncome} income</div>
|
||||||
title="{inventoryIssueCount} issue{inventoryIssueCount !== 1 ? 's' : ''} · {inventoryReviewCount} needs review"
|
<div class="bc-action">Review <svg width="11" height="11" viewBox="0 0 11 11" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 2l3.5 3.5L4 9"/></svg></div>
|
||||||
description="{inventoryIssueCount + inventoryReviewCount} items need attention"
|
</a>
|
||||||
action="View"
|
|
||||||
variant="inventory"
|
<a href="/inventory" class="bc bc-rose">
|
||||||
href="/inventory"
|
<div class="bc-top">
|
||||||
/>
|
<span class="bc-label">Inventory</span>
|
||||||
|
<div class="bc-icon rose">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bc-value-sm">{inventoryIssueCount} issues</div>
|
||||||
|
<div class="bc-desc">{inventoryReviewCount} needs review<br>{inventoryIssueCount + inventoryReviewCount} items need attention</div>
|
||||||
|
<div class="bc-action">View <svg width="11" height="11" viewBox="0 0 11 11" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 2l3.5 3.5L4 9"/></svg></div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bento row 2: Calories + Fitness detail -->
|
<!-- ═══ BENTO ROW 2: Calories (1fr) + Fitness detail (1fr) ═══ -->
|
||||||
<div class="bento-row-2 stagger">
|
<div class="bento-row2 stagger">
|
||||||
<DashboardActionCard
|
<a href="/fitness" class="bc bc-emerald">
|
||||||
title="{fitnessCalRemaining.toLocaleString()} calories remaining today"
|
<div class="bc-top">
|
||||||
description="{fitnessCalLogged.toLocaleString()} cal logged · {fitnessProtein}g protein · {fitnessCarbs}g carbs"
|
<span class="bc-label">Calories</span>
|
||||||
action="Log food"
|
<div class="bc-icon emerald">
|
||||||
variant="fitness"
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
||||||
href="/fitness"
|
</div>
|
||||||
/>
|
</div>
|
||||||
<FitnessModule />
|
<div class="bc-value">{fitnessCalRemaining.toLocaleString()}</div>
|
||||||
|
<div class="bc-desc">remaining today<br><strong>{fitnessCalLogged.toLocaleString()} logged</strong> · {fitnessProtein}g protein · {fitnessCarbs}g carbs</div>
|
||||||
|
<div class="bc-action">Log food <svg width="11" height="11" viewBox="0 0 11 11" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 2l3.5 3.5L4 9"/></svg></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="bc">
|
||||||
|
<div class="fit-header">
|
||||||
|
<div class="fit-av">Y</div>
|
||||||
|
<div>
|
||||||
|
<div class="fit-name">{userName}</div>
|
||||||
|
<div class="fit-sub">{fitnessCalLogged.toLocaleString()} cal · {fitnessCalRemaining.toLocaleString()} left</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fit-bar"><div class="fit-fill" style="width: {fitPercent}%"></div></div>
|
||||||
|
<div class="fit-macros">
|
||||||
|
<div><span class="fm-v">{fitnessProtein}<span class="fm-u">/{fitnessProteinGoal}g</span></span><div class="fm-l">protein</div></div>
|
||||||
|
<div><span class="fm-v">{fitnessCarbs}<span class="fm-u">/{fitnessCarbsGoal}g</span></span><div class="fm-l">carbs</div></div>
|
||||||
|
<div><span class="fm-v">{fitnessFat}<span class="fm-u">/{fitnessFatGoal}g</span></span><div class="fm-l">fat</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modules: Budget detail (wide) + Issues (narrow) -->
|
<!-- ═══ MODULES: Budget detail (7fr) + Issues (3fr) ═══ -->
|
||||||
<div class="modules-grid">
|
<div class="db-modules">
|
||||||
<BudgetModule />
|
<BudgetModule />
|
||||||
<IssuesModule />
|
<IssuesModule />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* ── Dashboard header ── */
|
/* ═══ Dashboard — matches React mockup exactly ═══ */
|
||||||
.dash-header {
|
|
||||||
display: flex;
|
.db-surface {
|
||||||
align-items: flex-start;
|
max-width: 1280px;
|
||||||
justify-content: space-between;
|
width: 100%;
|
||||||
gap: var(--sp-6);
|
margin: 0 auto;
|
||||||
margin-bottom: var(--section-gap);
|
padding: 0 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dash-date {
|
/* ── Header ── */
|
||||||
|
.db-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 32px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-date {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.1em;
|
||||||
margin-bottom: var(--sp-1);
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dash-greeting {
|
.db-greeting {
|
||||||
font-size: var(--text-2xl);
|
font-size: 28px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
line-height: 1.1;
|
||||||
color: var(--text-1);
|
color: var(--text-1);
|
||||||
line-height: var(--leading-tight);
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.dash-greeting strong { font-weight: 700; }
|
.db-greeting strong { font-weight: 800; }
|
||||||
|
|
||||||
/* ── Task trigger button ── */
|
/* ── Task pill ── */
|
||||||
.task-trigger {
|
.task-pill {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--sp-3);
|
gap: 10px;
|
||||||
padding: var(--sp-3) var(--sp-4);
|
padding: 8px 14px 8px 10px;
|
||||||
border-radius: var(--radius);
|
border-radius: 999px;
|
||||||
background: var(--card);
|
background: var(--card);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
|
||||||
text-align: left;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
max-width: 320px;
|
|
||||||
}
|
|
||||||
.task-trigger:hover {
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
border-color: var(--accent-border);
|
|
||||||
}
|
|
||||||
.task-trigger:active { transform: scale(0.98); }
|
|
||||||
|
|
||||||
.task-trigger-icon {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: var(--accent-dim);
|
|
||||||
color: var(--accent);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.task-trigger-icon.has-overdue {
|
|
||||||
background: var(--error-dim);
|
|
||||||
color: var(--error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-trigger-dot {
|
|
||||||
position: absolute;
|
|
||||||
top: -2px;
|
|
||||||
right: -2px;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: var(--radius-full);
|
|
||||||
background: var(--error);
|
|
||||||
border: 2px solid var(--card);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-trigger-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-trigger-count {
|
|
||||||
display: block;
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-trigger-overdue {
|
|
||||||
color: var(--error);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-trigger-next {
|
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-3);
|
|
||||||
margin-top: 1px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
transition: all 0.25s cubic-bezier(0.16,1,0.3,1);
|
||||||
text-overflow: ellipsis;
|
font-family: var(--font);
|
||||||
|
}
|
||||||
|
.task-pill:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
|
||||||
|
.task-pill:active { transform: scale(0.97); }
|
||||||
|
|
||||||
|
.tp-dot {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
flex-shrink: 0;
|
||||||
|
animation: breathe 2.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.tp-dot.tp-overdue { background: var(--error); }
|
||||||
|
|
||||||
|
@keyframes breathe {
|
||||||
|
0%, 100% { opacity: 1; transform: scale(1); }
|
||||||
|
50% { opacity: 0.4; transform: scale(0.85); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-trigger-arrow {
|
.tp-text { font-size: 12px; font-weight: 500; color: var(--text-3); }
|
||||||
color: var(--text-4);
|
.tp-text b { color: var(--text-1); font-weight: 600; }
|
||||||
flex-shrink: 0;
|
|
||||||
transition: transform 0.2s;
|
.tp-arrow { color: var(--text-4); transition: all 0.2s; }
|
||||||
}
|
.task-pill:hover .tp-arrow { color: var(--accent); transform: translateX(2px); }
|
||||||
.task-trigger:hover .task-trigger-arrow {
|
|
||||||
transform: translateX(2px);
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Bento grids ── */
|
/* ── Bento grids ── */
|
||||||
.bento-row {
|
.bento {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2fr 1fr;
|
grid-template-columns: 2fr 1fr;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.bento-row2 {
|
||||||
.bento-row-2 {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Modules grid ── */
|
/* ── Bento card ── */
|
||||||
.modules-grid {
|
.bc {
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 22px 24px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-top: 2px solid transparent;
|
||||||
|
transition: all 0.3s cubic-bezier(0.16,1,0.3,1);
|
||||||
|
}
|
||||||
|
.bc:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
.bc:active { transform: scale(0.985); }
|
||||||
|
.bc.bc-emerald:hover { border-top-color: var(--accent); }
|
||||||
|
.bc.bc-rose:hover { border-top-color: var(--error); }
|
||||||
|
|
||||||
|
.bc-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.bc-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: var(--text-4);
|
||||||
|
}
|
||||||
|
.bc-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.bc-icon.emerald { background: var(--accent-dim); color: var(--accent); }
|
||||||
|
.bc-icon.rose { background: var(--error-dim); color: var(--error); }
|
||||||
|
|
||||||
|
.bc-value {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--mono);
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--text-1);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.bc-value-sm {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--mono);
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
color: var(--text-1);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.bc-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-3);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.bc-desc :global(strong) { color: var(--text-2); font-weight: 500; }
|
||||||
|
|
||||||
|
.bc-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
margin-top: 10px;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
transition: gap 0.2s;
|
||||||
|
}
|
||||||
|
.bc:hover .bc-action { gap: 7px; }
|
||||||
|
|
||||||
|
/* ── Fitness card (inline) ── */
|
||||||
|
.fit-header { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
|
||||||
|
.fit-av {
|
||||||
|
width: 34px; height: 34px; border-radius: 50%;
|
||||||
|
background: var(--accent-dim); color: var(--accent);
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 12px; font-weight: 700;
|
||||||
|
}
|
||||||
|
.fit-name { font-size: 13px; font-weight: 600; color: var(--text-1); }
|
||||||
|
.fit-sub { font-size: 11px; color: var(--text-4); margin-top: 1px; }
|
||||||
|
.fit-bar {
|
||||||
|
height: 4px; background: var(--card-hover); border-radius: 2px;
|
||||||
|
margin-bottom: 14px; overflow: hidden;
|
||||||
|
}
|
||||||
|
.fit-fill {
|
||||||
|
height: 100%; border-radius: 2px; background: var(--accent);
|
||||||
|
transition: width 1s cubic-bezier(0.16,1,0.3,1);
|
||||||
|
}
|
||||||
|
.fit-macros { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; }
|
||||||
|
.fm-v { font-size: 14px; font-weight: 600; font-family: var(--mono); color: var(--text-1); }
|
||||||
|
.fm-u { font-size: 10px; color: var(--text-4); }
|
||||||
|
.fm-l { font-size: 10px; color: var(--text-4); margin-top: 1px; }
|
||||||
|
|
||||||
|
/* ── Modules ── */
|
||||||
|
.db-modules {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 7fr 3fr;
|
grid-template-columns: 7fr 3fr;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -337,30 +425,15 @@
|
|||||||
|
|
||||||
/* ── Mobile ── */
|
/* ── Mobile ── */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.dash-header {
|
.db-header { flex-direction: column; align-items: flex-start; gap: 12px; }
|
||||||
flex-direction: column;
|
.db-greeting { font-size: 22px; }
|
||||||
gap: var(--sp-3);
|
.task-pill { width: 100%; }
|
||||||
}
|
|
||||||
.dash-greeting {
|
|
||||||
font-size: var(--text-xl);
|
|
||||||
}
|
|
||||||
.task-trigger {
|
|
||||||
max-width: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.bento-row,
|
.db-surface { padding: 0 16px; }
|
||||||
.bento-row-2,
|
.bento, .bento-row2, .db-modules { grid-template-columns: 1fr; }
|
||||||
.modules-grid {
|
.bento > *, .bento-row2 > *, .db-modules > :global(*) { min-width: 0; max-width: 100%; }
|
||||||
grid-template-columns: 1fr;
|
.bc-value { font-size: 24px; }
|
||||||
}
|
|
||||||
.bento-row > :global(*),
|
|
||||||
.bento-row-2 > :global(*),
|
|
||||||
.modules-grid > :global(*) {
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user