/** * @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) * The DARK theme follows it literally: `slate` supplies surface/border/fg and * `blue` supplies accent. The LIGHT theme keeps the structure (same roles, same * rising-lightness ramp) with IntelliJ Light's neutrals and blue 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, [data-mode="light"] { /* Light theme — the CSS default, so it applies with no class on . `[data-mode="light"]` lets a light theme preview re-scope inside a dark page (the Settings appearance cards). IntelliJ Light's cool neutrals, ordered so lightness rises with the index exactly as it does in dark: the page is the DARKEST step and a card is brighter than the page it sits on, so every `hover:bg-*` lifts. */ --surface-0: 236 238 242; /* #eceef2 — page background */ --surface-1: 241 242 245; /* #f1f2f5 — sub-headers inside a card */ --surface-2: 247 248 250; /* #f7f8fa — inputs, chips, toolbars */ --surface-3: 253 253 254; /* #fdfdfe — `.card` */ --surface-4: 255 255 255; /* #ffffff — hover / active lift */ --surface-5: 255 255 255; /* #ffffff — topmost, rare */ --border: 211 213 219; /* #d3d5db */ --border-light: 228 230 235; /* #e4e6eb */ --accent: 40 90 192; /* #285ac0 */ --accent-hover: 33 74 158; /* #214a9e */ /* The FILL behind white text (`.btn-primary`, a Switch's on-track, a ticked Checkbox, `bg-accent-solid`). In light themes the text accent is already dark enough to carry white, so it simply IS the accent — resolved per scope, like `--tint-base`. Dark themes declare their own: there the text accent must be LIGHT to read on a near-black card, which makes it a fill white text cannot sit on (3.3:1). */ --accent-solid: var(--accent); --accent-solid-hover: var(--accent-hover); --fg-primary: 30 31 34; /* #1e1f22 */ --fg-secondary: 73 75 87; /* #494b57 */ /* Carries real content (timestamps, counts), so it is held above 4.5:1 on a card and still sits a clear step below fg-secondary. */ --fg-muted: 103 106 120; /* #676a78 */ /* The SHADOW carries "raised" here, not the quiet border. */ --shadow-card: 0 1px 2px rgb(30 31 34 / 0.05), 0 2px 6px rgb(30 31 34 / 0.06); /* Status labels clear 4.5:1 on the card AND on their own 20% chip. */ --status-success: 23 106 45; /* #176a2d */ --status-danger: 173 28 38; /* #ad1c26 */ --status-warning: 124 82 0; /* #7c5200 */ /* What every status/accent FILL is pre-mixed against (see `tint()` in tailwind.config.js) — the card surface, because that is where chips and alert strips overwhelmingly sit. */ /* Re-declared by every scope that sets a theme (this block, `.dark`), so it resolves against THAT scope's surface-3; a new scope must re-declare it too. */ --tint-base: var(--surface-3); /* Syntax colours for `lib/highlight.ts` — IntelliJ Light's editor scheme. */ --syn-comment: 112 112 112; /* #707070 */ --syn-string: 6 125 23; /* #067d17 */ --syn-number: 23 80 235; /* #1750eb */ --syn-keyword: 0 51 179; /* #0033b3 */ --syn-builtin: 0 0 128; /* #000080 */ --syn-function: 0 98 122; /* #00627a */ --syn-operator: 75 75 75; /* #4b4b4b */ --syn-punctuation: 75 75 75; /* #4b4b4b */ --syn-property: 135 16 148; /* #871094 */ --syn-tag: 0 51 179; /* #0033b3 */ --syn-plain: var(--fg-primary); --syn-add: 6 125 23; /* #067d17 */ --syn-del: 192 31 42; /* #c01f2a */ /* A diff line edited in place (not wholly added or removed). */ --diff-mod: 29 78 216; /* blue-700 #1d4ed8 */ /* An unversioned (untracked) file in the Changes panel, JetBrains' orange. */ --kept: 194 65 12; /* orange-700 #c2410c */ } .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 */ /* Text/indicator accent is Radix blue-11 (its text step): blue-9 #0090ff was 4.4:1 on a card and 3.4:1 on its own selected chip. The solid blue-9 fill lives on as the hue of `--accent-solid`, darkened until white text on it clears 4.5:1 (it was 3.3:1). */ --accent: 112 184 255; /* blue-11 #70b8ff */ --accent-hover: 158 206 255; /* #9eceff */ --accent-solid: 0 118 209; /* #0076d1 — white 4.8:1 */ --accent-solid-hover: 0 102 181; /* #0066b5 */ --fg-primary: 237 238 240; /* slate-12 #edeef0 */ --fg-secondary: 176 180 186; /* slate-11 #b0b4ba */ /* slate-9 #696e77 was 2.8:1 on a card. Raised until it clears 4.5:1 on the hover lift (surface-4) too, still a clear step below fg-secondary. */ --fg-muted: 150 154 160; /* #969aa0 */ /* 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 */ --tint-base: var(--surface-3); /* Unchanged from the pastels this file has always drawn code in. */ --syn-comment: 107 114 128; /* gray-500 #6b7280 */ --syn-string: 110 231 183; /* emerald-300 #6ee7b7 */ --syn-number: 253 186 116; /* orange-300 #fdba74 */ --syn-keyword: 196 181 253; /* violet-300 #c4b5fd */ --syn-builtin: 125 211 252; /* sky-300 #7dd3fc */ --syn-function: 254 240 138; /* yellow-200 #fef08a */ --syn-operator: 249 168 212; /* pink-300 #f9a8d4 */ --syn-punctuation: 156 163 175; /* gray-400 #9ca3af */ --syn-property: 103 232 249; /* cyan-300 #67e8f9 */ --syn-tag: 253 164 175; /* rose-300 #fda4af */ --syn-plain: 229 231 235; /* gray-200 #e5e7eb — kept off fg-primary so Default is unchanged */ --syn-add: 110 231 183; /* emerald-300 */ --syn-del: 252 165 165; /* red-300 #fca5a5 */ --diff-mod: 96 165 250; /* blue-400 #60a5fa */ --kept: 251 146 60; /* orange-400 #fb923c */ } /* Named themes (`data-theme` on , set by hooks/useTheme.ts, picked in * Settings → Appearance). Each overrides only what differs from its mode's * default block above; `--tint-base` and light `--syn-plain` are derived there. * Dark ones pair with `.dark`, light ones with `[data-mode="light"]`, so a * theme can never paint over the wrong mode whatever order the attributes * land in. Contrast is held to the defaults' own bar: fg-muted ≥4.5:1 on a * light card, status labels ≥4.5:1 on their own /20 chip in light themes. */ .dark[data-theme="darcula"] { --surface-0: 30 31 32; /* #1e1f20 */ --surface-1: 35 36 37; /* #232425 */ --surface-2: 39 41 42; /* #27292a */ --surface-3: 43 43 43; /* #2b2b2b */ --surface-4: 51 53 55; /* #333537 */ --surface-5: 60 63 65; /* #3c3f41 */ --border: 63 66 68; /* #3f4244 */ --border-light: 77 81 83; /* #4d5153 */ --accent: 132 189 219; /* #84bddb */ --accent-hover: 168 209 230; /* #a8d1e6 */ --accent-solid: 45 124 167; /* #2d7ca7 */ --accent-solid-hover: 38 106 143; /* #266a8f */ --fg-primary: 220 220 220; /* #dcdcdc */ --fg-secondary: 169 183 198; /* #a9b7c6 */ --fg-muted: 154 159 163; /* #9a9fa3 */ --status-success: 132 200 141; /* #84c88d */ --status-danger: 255 153 150; /* #ff9996 */ --status-warning: 232 191 106; /* #e8bf6a */ --syn-comment: 128 128 128; /* #808080 */ --syn-string: 127 159 105; /* #7f9f69 */ --syn-number: 104 151 187; /* #6897bb */ --syn-keyword: 204 120 50; /* #cc7832 */ --syn-builtin: 136 136 198; /* #8888c6 */ --syn-function: 255 198 109; /* #ffc66d */ --syn-operator: 169 183 198; /* #a9b7c6 */ --syn-punctuation: 169 183 198; /* #a9b7c6 */ --syn-property: 168 139 192; /* #a88bc0 */ --syn-tag: 232 191 106; /* #e8bf6a */ --syn-add: 106 171 115; /* #6aab73 */ --syn-del: 255 107 104; /* #ff6b68 */ --syn-plain: 169 183 198; /* #a9b7c6 */ } .dark[data-theme="apple-dark"] { --surface-0: 22 22 24; /* #161618 */ --surface-1: 28 28 30; /* #1c1c1e */ --surface-2: 36 36 38; /* #242426 */ --surface-3: 44 44 46; /* #2c2c2e */ --surface-4: 54 54 56; /* #363638 */ --surface-5: 58 58 60; /* #3a3a3c */ --border: 56 56 58; /* #38383a */ --border-light: 72 72 74; /* #48484a */ --accent: 123 189 255; /* #7bbdff */ --accent-hover: 163 209 255; /* #a3d1ff */ --accent-solid: 9 115 222; /* #0973de */ --accent-solid-hover: 7 98 190; /* #0762be */ --fg-primary: 245 245 247; /* #f5f5f7 */ /* Lifted with fg-muted so the two stay a visible step apart. */ --fg-secondary: 199 199 204; /* #c7c7cc */ --fg-muted: 160 160 164; /* #a0a0a4 */ --status-success: 48 209 88; /* #30d158 */ --status-danger: 255 155 150; /* #ff9b96 */ --status-warning: 255 214 10; /* #ffd60a */ --syn-comment: 127 140 152; /* #7f8c98 */ --syn-string: 252 106 93; /* #fc6a5d */ --syn-number: 208 191 105; /* #d0bf69 */ --syn-keyword: 252 95 163; /* #fc5fa3 */ --syn-builtin: 93 216 255; /* #5dd8ff */ --syn-function: 103 183 164; /* #67b7a4 */ --syn-operator: 161 161 166; /* #a1a1a6 */ --syn-punctuation: 161 161 166; /* #a1a1a6 */ --syn-property: 181 140 240; /* #b58cf0 */ --syn-tag: 252 95 163; /* #fc5fa3 */ --syn-add: 48 209 88; /* #30d158 */ --syn-del: 255 105 97; /* #ff6961 */ --syn-plain: 229 229 234; /* #e5e5ea */ } /* Orca Dark — Orca's neutral palette (src/renderer/src/assets/main.css `.dark`). * Accent is NOT Orca's #e5e5e5 primary: AgentHub puts white text on `bg-accent` * (.btn-primary), which would read 1.3:1 there. Text accent and the solid fill * behind white text (`--accent-solid`) are two shades of #3b82f6, each held at * 4.5:1. Syntax colours are Apple Dark's. */ .dark[data-theme="orca-dark"] { --surface-0: 10 10 10; /* #0a0a0a */ --surface-1: 17 17 17; /* #111111 */ --surface-2: 23 23 23; /* #171717 */ --surface-3: 28 28 28; /* #1c1c1c */ --surface-4: 38 38 38; /* #262626 */ --surface-5: 64 64 64; /* #404040 */ --border: 31 31 31; /* #1f1f1f */ --border-light: 46 46 46; /* #2e2e2e */ --accent: 106 160 248; /* #6aa0f8 */ --accent-hover: 147 187 250; /* #93bbfa */ --accent-solid: 52 114 216; /* #3472d8 */ --accent-solid-hover: 44 97 184; /* #2c61b8 */ --fg-primary: 250 250 250; /* #fafafa */ --fg-secondary: 212 212 212; /* #d4d4d4 */ --fg-muted: 161 161 161; /* #a1a1a1 */ --status-success: 134 239 172; /* #86efac */ --status-danger: 255 112 115; /* #ff7073 */ --status-warning: 234 179 8; /* #eab308 */ --syn-comment: 127 140 152; /* #7f8c98 */ --syn-string: 252 106 93; /* #fc6a5d */ --syn-number: 208 191 105; /* #d0bf69 */ --syn-keyword: 252 95 163; /* #fc5fa3 */ --syn-builtin: 93 216 255; /* #5dd8ff */ --syn-function: 103 183 164; /* #67b7a4 */ --syn-operator: 161 161 166; /* #a1a1a6 */ --syn-punctuation: 161 161 166; /* #a1a1a6 */ --syn-property: 181 140 240; /* #b58cf0 */ --syn-tag: 252 95 163; /* #fc5fa3 */ --syn-add: 48 209 88; /* #30d158 */ --syn-del: 255 105 97; /* #ff6961 */ --syn-plain: 229 229 234; /* #e5e5ea */ } [data-mode="light"][data-theme="apple-light"] { --surface-0: 233 233 238; /* #e9e9ee */ --surface-1: 239 239 244; /* #efeff4 */ --surface-2: 245 245 248; /* #f5f5f8 */ --surface-3: 251 251 253; /* #fbfbfd */ --surface-4: 253 253 254; /* #fdfdfe */ --surface-5: 255 255 255; /* #ffffff */ --border: 209 209 214; /* #d1d1d6 */ --border-light: 229 229 234; /* #e5e5ea */ --accent: 0 92 184; /* #005cb8 */ --accent-hover: 0 76 153; /* #004c99 */ --fg-primary: 29 29 31; /* #1d1d1f */ --fg-secondary: 81 81 84; /* #515154 */ --fg-muted: 103 103 108; /* #67676c */ --shadow-card: 0 1px 2px rgb(0 0 0 / 0.05), 0 2px 6px rgb(0 0 0 / 0.06); --status-success: 23 107 46; /* #176b2e */ --status-danger: 179 0 14; /* #b3000e */ --status-warning: 122 79 0; /* #7a4f00 */ --syn-comment: 93 108 121; /* #5d6c79 */ --syn-string: 196 26 22; /* #c41a16 */ --syn-number: 28 0 207; /* #1c00cf */ --syn-keyword: 155 35 147; /* #9b2393 */ --syn-builtin: 11 79 121; /* #0b4f79 */ --syn-function: 50 109 116; /* #326d74 */ --syn-operator: 75 75 80; /* #4b4b50 */ --syn-punctuation: 75 75 80; /* #4b4b50 */ --syn-property: 57 0 160; /* #3900a0 */ --syn-tag: 155 35 147; /* #9b2393 */ --syn-add: 31 122 54; /* #1f7a36 */ --syn-del: 197 0 15; /* #c5000f */ } [data-mode="light"][data-theme="canvas"] { --surface-0: 227 220 205; /* #e3dccd */ --surface-1: 232 226 212; /* #e8e2d4 */ --surface-2: 237 232 220; /* #ede8dc */ --surface-3: 243 239 230; /* #f3efe6 */ --surface-4: 247 244 237; /* #f7f4ed */ --surface-5: 250 248 243; /* #faf8f3 */ --border: 203 191 166; /* #cbbfa6 */ --border-light: 220 211 192; /* #dcd3c0 */ --accent: 119 80 47; /* #77502f */ --accent-hover: 102 63 34; /* #663f22 */ --fg-primary: 43 38 32; /* #2b2620 */ --fg-secondary: 94 85 74; /* #5e554a */ --fg-muted: 104 94 82; /* #685e52 */ --status-success: 54 93 36; /* #365d24 */ --status-danger: 147 41 31; /* #93291f */ --status-warning: 109 76 0; /* #6d4c00 */ --syn-comment: 125 114 98; /* #7d7262 */ --syn-string: 77 107 31; /* #4d6b1f */ --syn-number: 154 91 0; /* #9a5b00 */ --syn-keyword: 138 59 18; /* #8a3b12 */ --syn-builtin: 47 94 110; /* #2f5e6e */ --syn-function: 122 90 0; /* #7a5a00 */ --syn-operator: 138 48 80; /* #8a3050 */ --syn-punctuation: 107 97 85; /* #6b6155 */ --syn-property: 59 102 96; /* #3b6660 */ --syn-tag: 142 47 47; /* #8e2f2f */ --syn-add: 77 107 31; /* #4d6b1f */ --syn-del: 159 47 35; /* #9f2f23 */ } /* Orca Light — Orca's neutral palette (`:root`). The surfaces rise with their * index, so the page sits below a #fafafa card instead of Orca's white-on-white; * status colours are two steps darker than Orca's so each label clears 4.5:1 on * its own /20 chip. Syntax colours are Apple Light's. */ [data-mode="light"][data-theme="orca-light"] { --surface-0: 235 235 235; /* #ebebeb */ --surface-1: 240 240 240; /* #f0f0f0 */ --surface-2: 245 245 245; /* #f5f5f5 */ --surface-3: 250 250 250; /* #fafafa */ --surface-4: 253 253 253; /* #fdfdfd */ --surface-5: 255 255 255; /* #ffffff */ --border: 229 229 229; /* #e5e5e5 */ --border-light: 212 212 212; /* #d4d4d4 */ --accent: 23 23 23; /* #171717 */ --accent-hover: 38 38 38; /* #262626 */ --fg-primary: 10 10 10; /* #0a0a0a */ --fg-secondary: 64 64 64; /* #404040 */ --fg-muted: 105 105 105; /* #696969 */ --status-success: 22 101 52; /* #166534 */ --status-danger: 168 0 15; /* #a8000f */ --status-warning: 133 77 14; /* #854d0e */ --syn-comment: 93 108 121; /* #5d6c79 */ --syn-string: 196 26 22; /* #c41a16 */ --syn-number: 28 0 207; /* #1c00cf */ --syn-keyword: 155 35 147; /* #9b2393 */ --syn-builtin: 11 79 121; /* #0b4f79 */ --syn-function: 50 109 116; /* #326d74 */ --syn-operator: 75 75 80; /* #4b4b50 */ --syn-punctuation: 75 75 80; /* #4b4b50 */ --syn-property: 57 0 160; /* #3900a0 */ --syn-tag: 155 35 147; /* #9b2393 */ --syn-add: 31 122 54; /* #1f7a36 */ --syn-del: 197 0 15; /* #c5000f */ } @layer base { body { font-weight: 500; } * { scrollbar-width: thin; /* Claude's: a faint thumb on no track. */ scrollbar-color: rgb(var(--fg-muted) / 0.35) transparent; } *::-webkit-scrollbar { width: 6px; height: 6px; } *::-webkit-scrollbar-track { background: transparent; } *::-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 { cursor: pointer; opacity: 0.7; transition: opacity 0.2s; } /* The browser draws this glyph near-black. Inverting it is a DARK-mode fix; applied globally it painted a white icon onto a white field. */ .dark ::-webkit-calendar-picker-indicator { filter: invert(1) brightness(0.8); } ::-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; } /* Buttons. Every variant shares one keyboard focus ring (`focus-visible` only, so a mouse click draws none). `.btn-primary` fills with `accent-solid`, the shade white text clears 4.5:1 on in every theme — never `bg-accent` behind white text. */ .btn-primary { @apply inline-flex items-center gap-2 px-4 py-2 bg-accent-solid hover:bg-accent-solid-hover text-white text-sm font-medium rounded-lg transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-50; } .btn-secondary { @apply inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-fg-primary bg-surface-2 border border-border-light hover:bg-surface-4 rounded-lg transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/60 disabled:cursor-not-allowed disabled:opacity-50; } .btn-danger { @apply inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-status-danger bg-status-danger/10 border border-status-danger/40 hover:bg-status-danger/20 rounded-lg transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-status-danger/60 disabled:cursor-not-allowed disabled:opacity-50; } .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 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/60 disabled:cursor-not-allowed disabled:opacity-50; } /* An icon-only button: a 28px target (WCAG 2.5.8 asks ≥24px). It has no text, so give it an `aria-label` (its `title` becomes the tooltip). */ .btn-icon { @apply inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-fg-secondary hover:text-fg-primary hover:bg-surface-4 transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/60 disabled:cursor-not-allowed disabled:opacity-50; } .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; } /* A native and the date picker follow the mode. */ :root, [data-mode="light"] { color-scheme: light; } .dark { color-scheme: dark; } /* Vestibular safety (WCAG 2.3.3): with the OS asking for reduced motion, every animation and transition collapses to an instant change — the pulsing status dots, `animate-ping`, fade/slide-in, spinners included (a spinner still shows; it just stops turning). Outside any layer so it beats the utilities. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } @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; } } /* A few pulses when the dot appears, then it rests lit. Status dots sit in always-visible chrome; an infinite animation there makes the browser draw a frame 60 times a second for as long as the page is open, which made the whole desktop stutter. A state change re-adds the class and pulses again. */ .animate-pulse-dot { animation: pulse-dot 2s cubic-bezier(0.4, 0, 0.6, 1) 3; } /* The chat's live step, like Claude Code's: a light sweeping over the words — a few sweeps, then it rests on the muted colour (a background-position animation repaints the text every frame; the spinner beside it keeps saying the step runs). Reduced motion stops it at once. */ .text-shimmer { background: linear-gradient( 90deg, rgb(var(--fg-muted)) 40%, rgb(var(--fg-primary)) 50%, rgb(var(--fg-muted)) 60% ) 0 0 / 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; animation: text-shimmer 2.5s linear 3; } @keyframes text-shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } } /* Rendered markdown (MarkdownPreview.tsx): theme tokens only, so every theme reads it; no typography plugin. */ .md-preview { color: rgb(var(--fg-secondary)); font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; } .md-preview > :first-child { margin-top: 0; } .md-preview h1, .md-preview h2, .md-preview h3, .md-preview h4, .md-preview h5, .md-preview h6 { color: rgb(var(--fg-primary)); font-weight: 600; line-height: 1.3; margin: 1.4em 0 0.6em; } .md-preview h1 { font-size: 1.6em; padding-bottom: 0.3em; border-bottom: 1px solid rgb(var(--border)); } .md-preview h2 { font-size: 1.3em; padding-bottom: 0.25em; border-bottom: 1px solid rgb(var(--border)); } .md-preview h3 { font-size: 1.12em; } .md-preview h4, .md-preview h5, .md-preview h6 { font-size: 1em; } .md-preview p, .md-preview ul, .md-preview ol, .md-preview blockquote, .md-preview pre, .md-preview table { margin: 0 0 0.9em; } .md-preview ul { list-style: disc; padding-left: 1.5em; } .md-preview ol { list-style: decimal; padding-left: 1.5em; } .md-preview li + li { margin-top: 0.2em; } .md-preview li > ul, .md-preview li > ol { margin: 0.2em 0 0; } .md-preview li.task-list-item { list-style: none; margin-left: -1.3em; } .md-preview li.task-list-item input { margin-right: 0.4em; vertical-align: middle; } .md-preview a { color: rgb(var(--accent)); text-decoration: underline; text-underline-offset: 2px; } .md-preview strong { color: rgb(var(--fg-primary)); font-weight: 600; } .md-preview code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.88em; padding: 0.1em 0.35em; border-radius: 4px; background: rgb(var(--surface-3)); color: rgb(var(--fg-primary)); } .md-preview pre, .md-preview .md-frontmatter { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.5; padding: 0.75em 1em; border-radius: 6px; border: 1px solid rgb(var(--border)); background: rgb(var(--surface-0)); overflow-x: auto; white-space: pre; } .md-preview .md-frontmatter { margin: 0 0 1em; color: rgb(var(--fg-muted)); } .md-preview pre code { padding: 0; background: none; font-size: inherit; } .md-preview blockquote { padding: 0 1em; border-left: 3px solid rgb(var(--border)); color: rgb(var(--fg-muted)); } .md-preview hr { margin: 1.5em 0; border: 0; border-top: 1px solid rgb(var(--border)); } .md-preview table { display: block; max-width: 100%; overflow-x: auto; border-collapse: collapse; } .md-preview th, .md-preview td { padding: 0.35em 0.75em; border: 1px solid rgb(var(--border)); } .md-preview th { color: rgb(var(--fg-primary)); font-weight: 600; background: rgb(var(--surface-2)); } .md-preview img { max-width: 100%; } /* The run console's terminal attaches to tmux, which repaints the whole pane on every frame — xterm's own scrollback stays empty forever, so its viewport scrollbar renders as a groove with no thumb that never moves. Scrolling there goes through mouse reports to the pane's program, not the DOM, so hide the dead affordance. Deliberately OUTSIDE `@layer utilities`: Tailwind purges a layered rule whose class never appears in the source, and xterm creates `.xterm-viewport` at runtime. */ .xterm-viewport { scrollbar-width: none; } .xterm-viewport::-webkit-scrollbar { display: none; }