/* === Bundled Fonts — offline-first === These @font-face declarations are mirrored in src/design-system/colors_and_type.css for the buildless preview pages (which load that file directly without going through Tailwind/Vite). The two copies must keep font-family, font-weight, font-style, and font-display in sync. The src URLs intentionally differ: this file uses root-absolute paths served by Vite, the preview file uses relative paths so the static HTML pages resolve fonts/ alongside. */ @font-face { font-family: 'Lexend'; src: url('/fonts/lexend-variable.woff2') format('woff2'); font-weight: 100 900; font-style: normal; font-display: swap; } @font-face { font-family: 'Instrument Serif'; src: url('/fonts/instrument-serif-italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; } @font-face { font-family: 'JetBrains Mono'; src: url('/fonts/jetbrains-mono.woff2') format('woff2'); font-weight: 100 800; font-style: normal; font-display: swap; } @font-face { font-family: 'Atkinson Hyperlegible Next'; src: url('/fonts/atkinson-hyperlegible-next.woff2') format('woff2'); font-weight: 200 800; font-style: normal; font-display: swap; } @font-face { font-family: 'OpenDyslexic'; src: url('/fonts/opendyslexic.woff2') format('woff2'); font-weight: 400 700; font-style: normal; font-display: swap; } @import "tailwindcss"; /* === Lumotia Design Tokens === */ @theme { /* Surfaces — warm layered depth */ --color-bg: #0f0e0c; --color-bg-elevated: #171614; --color-bg-card: #1b1a17; --color-bg-input: #151412; --color-border: #2c2923; --color-border-subtle: #221f1b; /* Text — warm hierarchy */ --color-text: #f0ece4; --color-text-secondary: #9a9486; /* Phase 10a a11y P1: lifted from #716b60 to #8c8678 to clear AA 4.5:1 on the dark surfaces this token is used on. Used as both decorative and body-grade label, so the token has to meet AA. */ --color-text-tertiary: #8c8678; /* Accent — warm amber/copper. Phase 10a a11y P2: dark accent darkened from #e8a87c to #c98555 so the "Browse Files" filled button (white text on accent fill) clears AA in dark theme. Phase 10b: chroma bumped to #d68450 (same lightness band, ~OKLCH C 0.12 vs prior 0.08) so the brand reads warmer without losing the AA Large headroom on filled buttons. Subtle / hover / glow recomputed off the new base. */ --color-accent: #d68450; --color-accent-hover: #c47340; --color-accent-subtle: #d6845010; --color-accent-glow: #d6845025; /* Amber shadow scale. Hard-coded rgba shadows in components were tied to the dark-theme accent and stayed off-hue in light mode. These tokens theme-switch with the accent (overrides in :root[data-theme="light"]). Shapes correspond to existing usage sites: sm = subtle button/toggle glow (Toggle on-state) md = lifted card / button drop (ModelDownloader card) lg = hero glow under record button (DictationPage record) glow = stronger 0.4-alpha emission (progress-bar glows) pill = small selected-pill lift (SegmentedButton, pill rows) raised = soft raised tile (FilesPage drop tile) */ --shadow-accent-sm: 0 0 8px rgba(214, 132, 80, 0.25); --shadow-accent-md: 0 4px 16px rgba(214, 132, 80, 0.3); --shadow-accent-lg: 0 4px 20px rgba(214, 132, 80, 0.3); --shadow-accent-glow: 0 0 8px rgba(214, 132, 80, 0.4); --shadow-accent-pill: 0 1px 4px rgba(214, 132, 80, 0.3); --shadow-accent-raised: 0 2px 8px rgba(214, 132, 80, 0.2); /* Semantic — Phase 10b: chroma bumped on all three so success/danger/ warning read as signals, not as cream-tinted body text. AA on bg-card (#1b1a17) verified mentally for each. */ --color-success: #5fc28a; --color-danger: #e85f5f; --color-warning: #e8be4a; /* Overlays — used by modal scrims. Derived from --color-bg #0f0e0c at 0.7 alpha so the dim sits on the brand neutral, not pure black. */ --color-overlay-dim: rgba(15, 14, 12, 0.7); /* Layout */ --color-sidebar: #13120f; --color-nav-active: #201e1a; --color-hover: #1e1c18; /* === Layout Tokens === */ --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --title-size: 26px; /* Typography */ --font-family-body: 'Lexend', system-ui, sans-serif; --font-family-display: 'Instrument Serif', serif; --font-family-mono: 'JetBrains Mono', monospace; --font-size-body: 16px; --line-height-body: 1.5; --letter-spacing-body: 0; /* Motion */ --duration-ui: 150ms; --duration-decorative: 300ms; /* Window resize hit zones — consumed by ResizeHandles.svelte. One source of truth so every Lumotia window feels identical. */ --lumotia-resize-edge: 12px; --lumotia-resize-corner: 20px; } /* === Button Component Classes === */ .btn-sm { @apply px-2.5 py-1 text-[11px]; } .btn-md { @apply px-3 py-1.5 text-[12px]; } .btn-lg { @apply px-4 py-2 text-[13px]; } /* === Light theme overrides === */ :root[data-theme="light"] { --color-bg: #faf8f5; --color-bg-elevated: #f3f0eb; --color-bg-card: #ffffff; --color-bg-input: #f0ede8; --color-border: #ddd8d0; --color-border-subtle: #e6e2da; --color-text: #1a1816; --color-text-secondary: #5c574d; /* Phase 10a a11y P1: darkened from #8a8578 to #6b6557 to clear AA 4.5:1 on cream/card surfaces in light theme. */ --color-text-tertiary: #6b6557; /* Phase 10a a11y P2: darkened from #b87a4a to #a06a3e so white text on accent fill (selected pills, primary buttons) clears AA. Phase 10b: chroma bumped to #a3683a — same lightness band, more saturation. AA on cream surfaces re-verified. */ --color-accent: #a3683a; --color-accent-hover: #8a542b; --color-accent-subtle: #a3683a10; --color-accent-glow: #a3683a20; /* Light-theme amber shadows. Mirror the dark-theme scale but use the light-theme accent rgba so glows stay on-hue in cream surfaces. */ --shadow-accent-sm: 0 0 8px rgba(163, 104, 58, 0.25); --shadow-accent-md: 0 4px 16px rgba(163, 104, 58, 0.3); --shadow-accent-lg: 0 4px 20px rgba(163, 104, 58, 0.3); --shadow-accent-glow: 0 0 8px rgba(163, 104, 58, 0.4); --shadow-accent-pill: 0 1px 4px rgba(163, 104, 58, 0.3); --shadow-accent-raised: 0 2px 8px rgba(163, 104, 58, 0.2); /* Phase 10a a11y G3: darkened success from #3d8a5a to #2f7549 so success text on light surfaces clears AA. Phase 10a a11y FR3 / D4: darkened danger from #c44d4d to #a83838 for the same reason. Phase 10b: chroma bumped on all three, lightness preserved or slightly darkened to keep AA on cream backgrounds. */ --color-success: #1f7344; --color-danger: #b32626; --color-warning: #a08a1f; --color-sidebar: #f5f2ed; --color-nav-active: #eae6e0; --color-hover: #ede9e3; /* Warm dim for modal scrims in light theme. Lower alpha so the cream surface still reads through softly. */ --color-overlay-dim: rgba(26, 24, 22, 0.55); } :root[data-theme="light"] ::-webkit-scrollbar-thumb { background: #d0ccc4; } :root[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: #b8b3aa; } /* === Sensory Zones — Dark Theme === */ /* Zones tint surfaces only. Accent tokens (amber) stay constant across all zones — the brand colour is the constant, the environment is the variable. Phase 10b: zone tints pushed further apart so each zone feels emotionally distinct (cool blue-grey / warm red-orange / cool green) rather than three nearly-identical near-blacks. AA verified for body text (--color-text #f0ece4) on every Card surface. */ :root[data-zone="cave"] { --color-bg: #0a1218; --color-bg-elevated: #0f1a22; --color-bg-card: #14222b; --color-sidebar: #080f14; } :root[data-zone="energy"] { --color-bg: #160e09; --color-bg-elevated: #20140e; --color-bg-card: #261913; --color-sidebar: #100806; } :root[data-zone="reset"] { --color-bg: #0c130c; --color-bg-elevated: #111a11; --color-bg-card: #161f15; --color-sidebar: #0a0e0a; } /* === Sensory Zones — Light Theme === */ /* Phase 10b: light zones likewise pushed further from default cream so the environment shift is felt, not merely intuited. AA verified for body text (--color-text #1a1816) on every Card surface. */ :root[data-theme="light"][data-zone="cave"] { --color-bg: #eef4f6; --color-bg-elevated: #e2eef0; --color-bg-card: #f5fafc; --color-sidebar: #e8f0f2; } :root[data-theme="light"][data-zone="energy"] { --color-bg: #fdf3eb; --color-bg-elevated: #f8e8d8; --color-bg-card: #fff8ef; --color-sidebar: #f5e8d4; } :root[data-theme="light"][data-zone="reset"] { --color-bg: #eff6ed; --color-bg-elevated: #e4f0e0; --color-bg-card: #f7fcf5; --color-sidebar: #e8f3e4; } /* === Base === */ html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background: var(--color-bg); color: var(--color-text); font-family: var(--font-family-body); font-size: var(--font-size-body); line-height: var(--line-height-body); letter-spacing: var(--letter-spacing-body); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* Selection allowed on content by default. Users need to copy error strings, model paths, profile names, transcripts, etc. Chrome elements opt out below. */ user-select: text; } /* Chrome elements opt out of selection so a click on a button label doesn't drag-select. data-no-select is an escape hatch for app surfaces (titlebar, drag handles) that should never select. */ button, summary, [role="button"], [role="switch"], [role="tab"], [role="menuitem"], [data-no-select] { user-select: none; } /* === Display font === */ .font-display { font-family: var(--font-family-display); } /* === Transcript font — optimised for reading === */ .font-transcript { font-family: var(--font-family-body); font-size: var(--text-transcript, 16px); line-height: 1.85; letter-spacing: 0.01em; font-feature-settings: "liga" 1, "kern" 1; } /* === Grain texture overlay === */ .grain::after { content: ""; position: fixed; inset: 0; pointer-events: none; opacity: 0.025; background-image: url('/textures/grain.png'); background-repeat: repeat; background-size: 200px 200px; z-index: 50; } :root[data-theme="light"] .grain::after { opacity: 0.015; } /* === Scrollbar — thin, appears on hover === */ ::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #2c2923; border-radius: 999px; } ::-webkit-scrollbar-thumb:hover { background: #3d3830; } /* === Smooth transitions (global) === */ * { transition-property: background-color, border-color, color, opacity, box-shadow; transition-duration: var(--duration-ui); transition-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1); } /* Opt out for inputs */ textarea, input, [data-no-transition] { transition: none; } /* === Focus ring — consistent, accessible === */ :focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; border-radius: var(--radius-md); } /* === Selection colour === */ ::selection { background: var(--color-accent-glow); color: var(--color-text); } /* === Animations === */ @keyframes fade-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } @keyframes pulse-soft { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } @keyframes pulse-warm { 0%, 100% { box-shadow: 0 0 24px rgba(232, 168, 124, 0.25), 0 0 48px rgba(232, 168, 124, 0.08); } 50% { box-shadow: 0 0 32px rgba(232, 168, 124, 0.4), 0 0 64px rgba(232, 168, 124, 0.15); } } @keyframes slide-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } @keyframes breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.04); } } .animate-fade-in { animation: fade-in var(--duration-decorative) ease-out; } .animate-pulse-soft { animation: pulse-soft 1.5s ease-in-out infinite; } .animate-pulse-warm { animation: pulse-warm 2s ease-in-out infinite; } .animate-slide-up { animation: slide-up var(--duration-decorative) ease-out; } .animate-breathe { animation: breathe 3s ease-in-out infinite; } /* === Task sidebar slide === */ @keyframes slide-in-right { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slide-out-right { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } /* Task extraction highlight */ @keyframes highlight-warm { 0% { border-left-color: var(--color-accent); } 100% { border-left-color: transparent; } } /* Checkbox pop */ @keyframes scale-pop { 0% { transform: scale(0.95); } 100% { transform: scale(1); } } .animate-slide-in-right { animation: slide-in-right var(--duration-decorative) ease-out; } .animate-highlight-warm { border-left: 3px solid transparent; animation: highlight-warm 2s ease-out; } .animate-scale-pop { animation: scale-pop 50ms ease-out; } /* Window focus glow */ @keyframes glow-pulse { 0% { box-shadow: 0 0 0 0 rgba(232, 168, 124, 0); } 30% { box-shadow: 0 0 20px 4px rgba(232, 168, 124, 0.35), inset 0 0 12px rgba(232, 168, 124, 0.08); } 100% { box-shadow: 0 0 0 0 rgba(232, 168, 124, 0); } } .animate-glow-pulse { animation: glow-pulse 700ms ease-out; } :root[data-theme="light"] .animate-glow-pulse { animation: glow-pulse-light 700ms ease-out; } @keyframes glow-pulse-light { 0% { box-shadow: 0 0 0 0 rgba(184, 122, 74, 0); } 30% { box-shadow: 0 0 20px 4px rgba(184, 122, 74, 0.3), inset 0 0 12px rgba(184, 122, 74, 0.06); } 100% { box-shadow: 0 0 0 0 rgba(184, 122, 74, 0); } } /* Float window entrance */ @keyframes float-enter { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: scale(1); } } .animate-float-enter { animation: float-enter var(--duration-decorative) ease-out; } /* === Custom titlebar buttons === */ .titlebar-btn { display: flex; align-items: center; justify-content: center; width: 46px; height: 32px; color: var(--color-text-secondary); } /* Sinhala character spin (decorative, during recording) */ @keyframes sinhala-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .animate-sinhala-spin { animation: sinhala-spin 2s linear infinite; } /* === Reduced Motion === */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } /* === Data-attribute reduce-motion support === */ :root[data-reduce-motion="true"], :root[data-reduce-motion="true"] *, :root[data-reduce-motion="true"] *::before, :root[data-reduce-motion="true"] *::after { --duration-ui: 0ms; --duration-decorative: 0ms; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } /* === Window resize handles (see ResizeHandles.svelte) === Lives in the global sheet so @tailwindcss/vite does not try to parse it as a per-component virtual CSS module, which triggers an "Invalid declaration" error on plain CSS inputs in some Tailwind v4 + Svelte 5 combinations. */ .lumotia-rh { position: fixed; z-index: 2147483646; background: transparent; pointer-events: auto; touch-action: none; user-select: none; -webkit-user-select: none; } .lumotia-rh-nw, .lumotia-rh-ne, .lumotia-rh-sw, .lumotia-rh-se { z-index: 2147483647; } .lumotia-rh-n { top: 0; left: var(--lumotia-resize-corner); right: var(--lumotia-resize-corner); height: var(--lumotia-resize-edge); cursor: n-resize; } .lumotia-rh-s { bottom: 0; left: var(--lumotia-resize-corner); right: var(--lumotia-resize-corner); height: var(--lumotia-resize-edge); cursor: s-resize; } .lumotia-rh-w { top: var(--lumotia-resize-corner); bottom: var(--lumotia-resize-corner); left: 0; width: var(--lumotia-resize-edge); cursor: w-resize; } .lumotia-rh-e { top: var(--lumotia-resize-corner); bottom: var(--lumotia-resize-corner); right: 0; width: var(--lumotia-resize-edge); cursor: e-resize; } .lumotia-rh-nw { top: 0; left: 0; width: var(--lumotia-resize-corner); height: var(--lumotia-resize-corner); cursor: nw-resize; } .lumotia-rh-ne { top: 0; right: 0; width: var(--lumotia-resize-corner); height: var(--lumotia-resize-corner); cursor: ne-resize; } .lumotia-rh-sw { bottom: 0; left: 0; width: var(--lumotia-resize-corner); height: var(--lumotia-resize-corner); cursor: sw-resize; } .lumotia-rh-se { bottom: 0; right: 0; width: var(--lumotia-resize-corner); height: var(--lumotia-resize-corner); cursor: se-resize; }