Files
platform/frontend-v2/src/app.css
Yusuf Suleman 4592e35732
All checks were successful
Security Checks / dependency-audit (push) Successful in 1m13s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 3s
feat: major platform expansion — Brain service, RSS reader, iOS app, AI assistants, Firefox extension
Brain Service:
- Playwright stealth crawler replacing browserless (og:image, Readability, Reddit JSON API)
- AI classification with tag definitions and folder assignment
- YouTube video download via yt-dlp
- Karakeep migration complete (96 items)
- Taxonomy management (folders with icons/colors, tags)
- Discovery shuffle, sort options, search (Meilisearch + pgvector)
- Item tag/folder editing, card color accents

RSS Reader Service:
- Custom FastAPI reader replacing Miniflux
- Feed management (add/delete/refresh), category support
- Full article extraction via Readability
- Background content fetching for new entries
- Mark all read with confirmation
- Infinite scroll, retention cleanup (30/60 day)
- 17 feeds migrated from Miniflux

iOS App (SwiftUI):
- Native iOS 17+ app with @Observable architecture
- Cookie-based auth, configurable gateway URL
- Dashboard with custom background photo + frosted glass widgets
- Full fitness module (today/templates/goals/food library)
- AI assistant chat (fitness + brain, raw JSON state management)
- 120fps ProMotion support

AI Assistants (Gateway):
- Unified dispatcher with fitness/brain domain detection
- Fitness: natural language food logging, photo analysis, multi-item splitting
- Brain: save/append/update/delete notes, search & answer, undo support
- Madiha user gets fitness-only (brain disabled)

Firefox Extension:
- One-click save to Brain from any page
- Login with platform credentials
- Right-click context menu (save page/link/image)
- Notes field for URL saves
- Signed and published on AMO

Other:
- Reader bookmark button routes to Brain (was Karakeep)
- Fitness food library with "Add" button + add-to-meal popup
- Kindle send file size check (25MB SMTP2GO limit)
- Atelier UI as default (useAtelierShell=true)
- Mobile upload box in nav drawer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:56:29 -05:00

513 lines
14 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import 'tailwindcss';
/* ═══════════════════════════════════════════════
DESIGN SYSTEM — Single source of truth
RULES:
1. No raw px in component <style> blocks — use tokens
2. No raw rgba/hex colors — use semantic tokens
3. No copy-pasting base component styles — use globals
4. Surface hierarchy: canvas → surface → card
5. Text hierarchy: text-1 → text-2 → text-3 → text-4
═══════════════════════════════════════════════ */
@layer base {
html { background-color: #f5efe6; }
body { overflow-x: clip; }
body { padding-bottom: env(safe-area-inset-bottom); }
:root {
/* ── Fonts ── */
--font: 'Outfit', -apple-system, system-ui, sans-serif;
--mono: 'JetBrains Mono', ui-monospace, monospace;
--transition: 150ms ease;
/* ── Spacing scale (4px grid) ──
* Use these everywhere: padding, margin, gap.
* Naming: --sp-{n} where value = n × 4px
*/
--sp-0: 0px;
--sp-px: 1px;
--sp-0.5: 2px;
--sp-1: 4px;
--sp-1.5: 6px;
--sp-2: 8px;
--sp-3: 12px;
--sp-4: 16px;
--sp-5: 20px;
--sp-6: 24px;
--sp-7: 28px;
--sp-8: 32px;
--sp-10: 40px;
--sp-12: 48px;
--sp-16: 64px;
--sp-20: 80px;
/* Semantic spacing aliases */
--section-gap: var(--sp-7);
--card-pad: var(--sp-5);
--card-pad-primary: var(--sp-7);
--card-pad-secondary: var(--sp-4);
--row-gap: var(--sp-3);
--module-gap: var(--sp-5);
--row-pad-y: 14px;
--row-pad-x: var(--sp-4);
--inner-gap: var(--sp-3);
/* ── Radius scale ── */
--radius-xs: 4px;
--radius-sm: 6px;
--radius-md: 8px;
--radius: 16px;
--radius-lg: 20px;
--radius-full: 9999px;
/* ── Elevation scale ──
* xs: barely visible (row hover, inner elements)
* sm: subtle lift (secondary cards, inputs)
* md: standard card elevation
* lg: elevated (dropdowns, popovers, hero cards)
* xl: overlay (modals, slide-out panels)
*/
/* Zinc-tinted shadows */
--shadow-xs: 0 1px 2px rgba(24,24,27,0.04);
--shadow-sm: 0 1px 2px rgba(24,24,27,0.04), 0 4px 8px rgba(24,24,27,0.02);
--shadow-md: 0 2px 8px rgba(24,24,27,0.05), 0 12px 24px rgba(24,24,27,0.04);
--shadow-lg: 0 4px 16px rgba(24,24,27,0.06), 0 24px 48px rgba(24,24,27,0.06);
--shadow-xl: 0 8px 24px rgba(24,24,27,0.08), 0 32px 64px rgba(24,24,27,0.1);
/* Legacy aliases */
--card-shadow: var(--shadow-md);
--card-shadow-sm: var(--shadow-sm);
/* ── Typography scale ──
* xs: badges, pills, tiny counters
* sm: labels, meta, captions, button text
* base: body text, list items, inputs
* md: card titles, important rows (16px avoids iOS zoom)
* lg: section headers, modal titles
* xl: page titles
* 2xl: hero headings
* 3xl: large hero numbers
*/
--text-xs: 11px;
--text-sm: 13px;
--text-base: 14px;
--text-md: 15px;
--text-lg: 17px;
--text-xl: 22px;
--text-2xl: 28px;
--text-3xl: 36px;
/* Line heights */
--leading-tight: 1.2;
--leading-snug: 1.35;
--leading-normal: 1.5;
--leading-relaxed: 1.65;
--leading-loose: 1.8;
}
/* ── LIGHT MODE — Zinc + Emerald ── */
:root {
--canvas: #f5efe6;
--surface: #FFFFFF;
--surface-secondary: #F4F4F5;
--card: #FFFFFF;
--card-secondary: #F4F4F5;
--card-hover: #F0F0F2;
--border: rgba(0,0,0,0.06);
--border-strong: rgba(0,0,0,0.10);
--text-1: #18181B;
--text-2: #3F3F46;
--text-3: #71717A;
--text-4: #A1A1AA;
/* Accent — Emerald (single, desaturated) */
--accent: #059669;
--accent-bg: #ECFDF5;
--accent-dim: rgba(5,150,105,0.07);
--accent-border: rgba(5,150,105,0.14);
--accent-focus: rgba(5,150,105,0.16);
--success: #059669;
--success-bg: #ECFDF5;
--success-dim: rgba(5,150,105,0.07);
--error: #DC2626;
--error-bg: #FEF2F2;
--error-dim: rgba(220,38,38,0.06);
--warning: #D97706;
--warning-bg: rgba(217,119,6,0.07);
--overlay: rgba(0,0,0,0.2);
--overlay-strong: rgba(0,0,0,0.4);
--nav-bg: rgba(250,250,250,0.8);
}
/* ── DARK MODE — Zinc + Emerald ── */
.dark {
--canvas: #09090B;
--surface: #111113;
--surface-secondary: #18181B;
--card: #1C1C1F;
--card-secondary: #18181B;
--card-hover: #232326;
--border: rgba(255,255,255,0.07);
--border-strong: rgba(255,255,255,0.12);
--text-1: #FAFAFA;
--text-2: #A1A1AA;
--text-3: #71717A;
--text-4: #3F3F46;
--accent: #34D399;
--accent-bg: rgba(52,211,153,0.1);
--accent-dim: rgba(52,211,153,0.08);
--accent-border: rgba(52,211,153,0.14);
--accent-focus: rgba(52,211,153,0.16);
--success: #22c55e;
--success-bg: rgba(34,197,94,0.1);
--success-dim: rgba(34,197,94,0.08);
--error: #ef4444;
--error-bg: rgba(239,68,68,0.1);
--error-dim: rgba(239,68,68,0.08);
--warning: #f59e0b;
--warning-bg: rgba(245,158,11,0.1);
--overlay: rgba(0,0,0,0.6);
--overlay-strong: rgba(0,0,0,0.75);
--nav-bg: rgba(15,15,18,0.9);
/* Dark shadows need higher opacity */
--shadow-xs: 0 1px 2px rgba(0,0,0,0.1);
--shadow-sm: 0 1px 3px rgba(0,0,0,0.15), 0 4px 12px rgba(0,0,0,0.1);
--shadow-md: 0 2px 6px rgba(0,0,0,0.15), 0 8px 24px rgba(0,0,0,0.12);
--shadow-lg: 0 4px 12px rgba(0,0,0,0.2), 0 16px 40px rgba(0,0,0,0.2);
--shadow-xl: 0 8px 24px rgba(0,0,0,0.25), 0 24px 60px rgba(0,0,0,0.3);
}
/* ── Base resets ── */
html, body {
font-family: var(--font);
background: var(--canvas);
color: var(--text-1);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
transition: background var(--transition), color var(--transition);
-webkit-font-smoothing: antialiased;
}
* { border-color: var(--border); }
a { color: inherit; text-decoration: none; }
button { font-family: var(--font); cursor: pointer; }
input { font-family: var(--font); }
}
/* ═══════════════════════════════════════════════
GLOBAL COMPONENT CLASSES
Use these in any component without re-declaring
═══════════════════════════════════════════════ */
/* ── Skeleton loader ── */
.skeleton {
background: linear-gradient(90deg, var(--card) 25%, var(--card-hover) 50%, var(--card) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: var(--radius-xs);
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* ── Staggered reveal ──
* Add .stagger to a container and children auto-cascade.
* Or use .reveal on individual elements.
*/
@keyframes cardIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal {
animation: cardIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.stagger > * {
animation: cardIn 0.45s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.stagger > *:nth-child(1) { animation-delay: 0ms; }
.stagger > *:nth-child(2) { animation-delay: 60ms; }
.stagger > *:nth-child(3) { animation-delay: 120ms; }
.stagger > *:nth-child(4) { animation-delay: 180ms; }
.stagger > *:nth-child(5) { animation-delay: 240ms; }
.stagger > *:nth-child(6) { animation-delay: 300ms; }
.stagger > *:nth-child(7) { animation-delay: 360ms; }
.stagger > *:nth-child(8) { animation-delay: 420ms; }
/* ── Buttons ── */
.btn-primary {
padding: var(--sp-2) var(--sp-4);
border-radius: var(--radius-md);
background: var(--accent);
color: white;
border: none;
font-size: var(--text-sm);
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
letter-spacing: 0.01em;
}
.btn-primary:hover { filter: brightness(1.1); transform: translateY(-1px); box-shadow: 0 4px 12px var(--accent-dim); }
.btn-primary:active { transform: scale(0.97); filter: none; }
.btn-primary.full {
width: 100%;
padding: var(--sp-3) var(--sp-4);
font-size: var(--text-md);
font-weight: 600;
border-radius: var(--radius);
}
.btn-secondary {
padding: var(--sp-2) var(--sp-4);
border-radius: var(--radius-md);
background: var(--card-secondary);
color: var(--text-2);
border: 1px solid var(--border);
font-size: var(--text-sm);
font-weight: 500;
cursor: pointer;
transition: all var(--transition);
}
.btn-secondary:hover { background: var(--card-hover); color: var(--text-1); }
.btn-secondary:active { transform: scale(0.97); }
.btn-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
background: var(--card-secondary);
border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--text-3);
transition: all var(--transition);
flex-shrink: 0;
}
.btn-icon:hover { color: var(--text-1); background: var(--card-hover); }
.btn-icon svg { width: var(--sp-4); height: var(--sp-4); }
/* ── Inputs ── */
.input {
width: 100%;
padding: 10px 14px;
border-radius: var(--radius-md);
background: var(--surface-secondary);
border: 1px solid var(--border);
color: var(--text-1);
font-size: var(--text-base);
font-family: var(--font);
outline: none;
transition: border-color var(--transition);
}
.input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
.input::placeholder { color: var(--text-4); }
/* ── Module (card container) ──
* Use for any content block: dashboard widgets, data tables, etc.
* Variants: .primary (hero, more padding + elevation), .flush (no padding)
*/
.module {
background: var(--card);
border-radius: var(--radius);
border: 1px solid var(--border);
box-shadow: var(--shadow-sm);
padding: var(--card-pad);
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.module:hover {
box-shadow: var(--shadow-md);
}
.module.primary {
padding: var(--card-pad-primary);
box-shadow: var(--shadow-md);
}
.module.primary:hover {
box-shadow: var(--shadow-lg);
}
.module.flush { padding: 0; }
.module-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--sp-5);
}
.module-title {
font-size: 11px;
font-weight: 700;
color: var(--text-3);
text-transform: uppercase;
letter-spacing: 0.1em;
}
.module-action {
font-size: var(--text-sm);
color: var(--accent);
font-weight: 500;
cursor: pointer;
text-decoration: none;
}
.module-action:hover { text-decoration: underline; }
/* ── Data row ──
* Standard list item pattern: name + meta on left, value/badge on right.
* Use in transactions, inventory items, feed entries, etc.
*/
.data-row {
display: flex;
align-items: center;
gap: var(--inner-gap);
padding: var(--row-pad-y) var(--row-pad-x);
transition: background 0.2s ease, transform 0.2s ease;
border-radius: var(--radius-sm);
}
.data-row:hover { background: var(--card-hover); }
.data-row + .data-row { border-top: 1px solid var(--border); }
.data-row:active { transform: scale(0.995); }
/* ── Badges ──
* Semantic status badges. Variants: error, success, warning, accent, muted.
*/
.badge {
font-size: 10px;
font-weight: 600;
padding: 2px 8px;
border-radius: var(--radius-xs);
flex-shrink: 0;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.badge.error { background: var(--error-dim); color: var(--error); }
.badge.success { background: var(--success-dim); color: var(--success); }
.badge.warning { background: var(--warning-bg); color: var(--warning); }
.badge.accent { background: var(--accent-dim); color: var(--accent); }
.badge.muted { background: var(--card-hover); color: var(--text-4); }
/* ── Tabs ──
* Pill-style tab bar.
*/
.tab-bar { display: flex; gap: var(--sp-1); }
.tab {
padding: var(--sp-2) 14px;
border-radius: var(--radius-md);
font-size: var(--text-base);
font-weight: 500;
color: var(--text-3);
background: none;
border: none;
cursor: pointer;
transition: all var(--transition);
font-family: var(--font);
display: flex;
align-items: center;
gap: var(--sp-1.5);
}
.tab:hover { color: var(--text-1); background: var(--card-hover); }
.tab.active { color: var(--accent); background: var(--accent-dim); font-weight: 600; }
.tab-badge {
font-size: var(--text-xs);
font-family: var(--mono);
background: var(--accent-dim);
color: var(--accent);
padding: 1px 6px;
border-radius: var(--radius-xs);
margin-left: var(--sp-1);
}
/* ── Section header ──
* Uppercase label above a group of content.
*/
.section-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-3);
margin-bottom: var(--sp-2);
}
/* ── Page wrapper ── */
.page {
padding: var(--sp-8) 0 var(--sp-20);
animation: pageIn 0.4s ease both;
}
@keyframes pageIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.page-header { margin-bottom: var(--section-gap); }
.page-title {
font-size: var(--text-xl);
font-weight: 600;
color: var(--text-1);
letter-spacing: -0.02em;
line-height: var(--leading-tight);
}
.page-subtitle {
font-size: 11px;
font-weight: 600;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: var(--sp-1);
}
.page-greeting {
font-size: var(--text-2xl);
font-weight: 400;
color: var(--text-1);
line-height: var(--leading-tight);
letter-spacing: -0.02em;
}
.page-greeting strong { font-weight: 700; }
/* ── App surface (centered container) ── */
.app-surface {
max-width: 1280px;
width: 100%;
margin: 0 auto;
padding: 0 var(--sp-6);
}
/* ── Responsive ── */
@media (max-width: 768px) {
:root {
--text-xs: 12px;
--text-sm: 15px;
--text-base: 16px;
--text-md: 17px;
--text-lg: 18px;
--text-xl: 22px;
--text-2xl: 26px;
--text-3xl: 32px;
--card-pad: var(--sp-4);
--card-pad-primary: var(--sp-5);
--row-pad-y: var(--sp-4);
--section-gap: var(--sp-5);
}
.page-greeting { font-size: var(--text-xl); }
.page { padding: var(--sp-5) 0 var(--sp-20); }
.app-surface { padding: 0 var(--sp-5); max-width: 100vw; box-sizing: border-box; }
.module, .module.primary, .module.flush { box-sizing: border-box; max-width: 100%; overflow: hidden; }
}