/** * @file index.css * @description Global styles and Tailwind CSS configuration for the application. * @author Nguyễn Ngọc Trí Vĩ */ @tailwind base; @tailwind components; @tailwind utilities; /* Palette sourced from Radix Colors (radix-ui.com/colors), an accessible * 12-step system built for UI, contrast-checked with APCA — chosen after * three rounds of hand-picked values that kept overshooting (too flat, then * too dark, then too glaring). Radix's own role guidance for a 12-step scale: * 1-2 app/chrome background 6-8 borders (6 subtle, 7-8 stronger) * 3-5 component background 9-10 solid (buttons, vibrant fills) * 11-12 text (11 secondary, 12 primary) * `slate` supplies surface/border/fg here; `blue` supplies accent; `green`/ * `red`/`amber` step 11 supplies the status tokens — the same step used for * fg-secondary, since 11 is Radix's guaranteed-readable-text step. Values are * the literal Radix hex constants (steps 1/2/3/4/5/6/7/9/11/12), converted to * RGB triplets for Tailwind's `` opacity support. */ :root { /* Light theme — the CSS default, so it applies with no class on . Radix `slate` (light), steps 1/2/3/4/5/6/7. */ --surface-0: 252 252 253; /* slate-1 #fcfcfd */ --surface-1: 249 249 251; /* slate-2 #f9f9fb */ --surface-2: 240 240 243; /* slate-3 #f0f0f3 */ --surface-3: 232 232 236; /* slate-4 #e8e8ec */ --surface-4: 224 225 230; /* slate-5 #e0e1e6 */ --surface-5: 217 217 224; /* slate-6 #d9d9e0 */ --border: 217 217 224; /* slate-6 #d9d9e0 */ --border-light: 205 206 214; /* slate-7 #cdced6 */ --accent: 0 144 255; /* blue-9 #0090ff */ --accent-hover: 5 136 240; /* blue-10 #0588f0 */ --fg-primary: 28 32 36; /* slate-12 #1c2024 */ --fg-secondary: 96 100 108; /* slate-11 #60646c */ --fg-muted: 139 141 152; /* slate-9 #8b8d98 */ --shadow-card: 0 1px 2px rgb(15 23 42 / 0.06), 0 1px 3px rgb(15 23 42 / 0.08); /* Status: green/red/amber step 11 (light) — the same "readable text" step as fg-secondary, so success/danger/warning sit at the same visual weight as ordinary secondary text, not shouting over it. */ --status-success: 33 131 88; /* green-11 #218358 */ --status-danger: 206 44 49; /* red-11 #ce2c31 */ --status-warning: 171 100 0; /* amber-11 #ab6400 */ } .dark { /* Radix `slate` (dark), steps 1/2/3/4/5/6/7. The scale is built in the opposite direction on purpose — step 6 is LIGHTER than step 3/4 here, which is what makes borders rise off a panel instead of sinking into it (the bug in the previous, hand-picked dark border). */ --surface-0: 17 17 19; /* slate-1 #111113 */ --surface-1: 24 25 27; /* slate-2 #18191b */ --surface-2: 33 34 37; /* slate-3 #212225 */ --surface-3: 39 42 45; /* slate-4 #272a2d */ --surface-4: 46 49 53; /* slate-5 #2e3135 */ --surface-5: 54 58 63; /* slate-6 #363a3f */ --border: 54 58 63; /* slate-6 #363a3f */ --border-light: 67 72 78; /* slate-7 #43484e */ --accent: 0 144 255; /* blue-9 #0090ff — identical value both themes, Radix's brand anchor */ --accent-hover: 59 158 255; /* blue-10 #3b9eff */ --fg-primary: 237 238 240; /* slate-12 #edeef0 */ --fg-secondary: 176 180 186; /* slate-11 #b0b4ba */ --fg-muted: 105 110 119; /* slate-9 #696e77 */ /* Dark surfaces already separate by lightness step; a shadow adds nothing visible against a dark page and reads as a stray dark smudge. */ --shadow-card: none; --status-success: 61 214 140; /* green-11 dark #3dd68c */ --status-danger: 255 149 146; /* red-11 dark #ff9592 */ --status-warning: 255 202 22; /* amber-11 dark #ffca16 */ } @layer base { * { scrollbar-width: thin; scrollbar-color: rgb(var(--border)) rgb(var(--surface-0)); } *::-webkit-scrollbar { width: 6px; height: 6px; } *::-webkit-scrollbar-track { background: rgb(var(--surface-0)); } *::-webkit-scrollbar-thumb { background: rgb(var(--border)); border-radius: 3px; } *::-webkit-scrollbar-thumb:hover { background: rgb(var(--border-light)); } ::selection { background: rgb(var(--accent) / 0.3); color: rgb(var(--fg-primary)); } ::-webkit-calendar-picker-indicator { filter: invert(1) brightness(0.8); cursor: pointer; opacity: 0.7; transition: opacity 0.2s; } ::-webkit-calendar-picker-indicator:hover { opacity: 1; } .time-input-custom::-webkit-calendar-picker-indicator { display: none; -webkit-appearance: none; } } @layer components { .card { @apply bg-surface-3 border border-border rounded-xl; box-shadow: var(--shadow-card); } .card-hover { @apply card transition-all duration-200 hover:border-border-light hover:bg-surface-4; } .btn-primary { @apply inline-flex items-center gap-2 px-4 py-2 bg-accent hover:bg-accent-hover text-white text-sm font-medium rounded-lg transition-colors duration-150; } .btn-ghost { @apply inline-flex items-center gap-2 px-3 py-1.5 text-sm text-fg-secondary hover:text-fg-primary hover:bg-surface-4 rounded-lg transition-colors duration-150; } .badge { @apply inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full border whitespace-nowrap shrink-0; } .input { @apply bg-surface-2 border border-border rounded-lg px-3 py-2 text-sm text-fg-primary placeholder-fg-muted focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30 transition-colors; } } @layer utilities { /* Hide the scrollbar on horizontally scrollable strips (e.g. the Settings TOC) where chevron affordances replace it. */ .no-scrollbar { scrollbar-width: none; } .no-scrollbar::-webkit-scrollbar { display: none; } /* Visually hidden until focused — first Tab lands here before the sidebar. */ .skip-to-content { position: absolute; left: 0.75rem; top: 0.75rem; z-index: 100; padding: 0.5rem 0.75rem; border-radius: 0.5rem; border: 1px solid rgb(var(--border-light)); background: rgb(var(--surface-2)); color: rgb(var(--fg-primary)); font-size: 0.8125rem; font-weight: 500; text-decoration: none; transform: translateY(-150%); transition: transform 0.15s ease; } .skip-to-content:focus { transform: translateY(0); outline: 2px solid rgb(var(--accent)); outline-offset: 2px; } } @keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } .animate-pulse-dot { animation: pulse-dot 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }