Add three opt-in dark themes (BooCode+, BooCode Classic, BooCode Override) plus an in-place Ember polish, on a class-scoped effects engine: matrix rain, a neon grid field, and frosted glass, all gated by a localStorage "Animated background" toggle and prefers-reduced- motion. Extend the server theme_id whitelist so the new ids persist, and replace the Home landing wordmark with the stacked mascot + wordmark banner.
206 lines
10 KiB
CSS
206 lines
10 KiB
CSS
/* BooCode+ — effects layer. Tasteful sci-fi: calm, premium, Linear-grade.
|
||
EVERY rule is scoped under `.theme-boocode-plus.dark` so (a) the other 22
|
||
themes are byte-for-byte unaffected and (b) the light variant stays a clean
|
||
flat slate — dark is the priority, so the glass/gradient/glow ship dark-only.
|
||
|
||
No canvas, no animation loop, no scanlines. Just: a faint STATIC ambient
|
||
gradient on the app background, frosted glass on CHROME ONLY (rails, menus,
|
||
dialogs, popovers, the composer, pane top-bars), a restrained indigo glow on
|
||
the primary action, and spring-eased transitions on chrome state changes.
|
||
|
||
iOS / quality guardrails honored:
|
||
- backdrop-blur capped at 8–12px; no nested blur-on-blur (the dialog
|
||
overlay's own backdrop-blur is neutralized so only the dialog CONTENT
|
||
frosts — one layer).
|
||
- opaque fallback: every glass rule lives inside @supports(backdrop-filter),
|
||
so a browser without it keeps the plain opaque Tailwind utility
|
||
(bg-sidebar/bg-popover/bg-card = solid token). prefers-reduced-transparency
|
||
forces the same opaque path.
|
||
- spring via cubic-bezier (NOT the linear() function — Safari <17.2 gap),
|
||
confined to box-shadow / transform / color / filter — never layout props.
|
||
- chat messages, tool-call cards and code blocks (bg-muted/30·/20, bg-card
|
||
bubbles) are deliberately NOT targeted — glass never sits behind reading
|
||
content; the ambient gradient behind it stays faint & opaque-based (AA).
|
||
|
||
Glass targets are stable, verified hooks: bg-sidebar (both rails), bg-popover
|
||
(all menus/dialogs/popovers/sheets), the composer's unique
|
||
focus-within:ring-primary/15 box, [data-slot=button][data-variant=default]
|
||
(the primary action), and the compound .border-b.bg-muted/30·/20 (pane
|
||
top-bars — distinct from message bubbles, which use .border.rounded-lg). */
|
||
|
||
.theme-boocode-plus.dark {
|
||
/* Spring curves. --bcp-spring overshoots a touch (press/glow bloom);
|
||
--bcp-ease is a smooth Linear-style decel for color/opacity. */
|
||
--bcp-spring: cubic-bezier(0.34, 1.42, 0.5, 1);
|
||
--bcp-ease: cubic-bezier(0.32, 0.72, 0, 1);
|
||
--bcp-blur: 10px;
|
||
}
|
||
|
||
/* ── Static ambient gradient ─────────────────────────────────────────────
|
||
Painted on the app-shell root (.h-dvh is unique to App.tsx). Opaque base
|
||
(var(--background)) + three faint indigo radials = depth without cost. The
|
||
MessageList scroll area is transparent, so the gradient reads faintly behind
|
||
chat — but it's static and opaque-based: the brightest stop (indigo @14% over
|
||
#0f1117) still gives ~10:1 for #e3e6f1 body text. The glass rails blur it for
|
||
a premium parallax-of-light feel. No background-attachment:fixed (iOS jank);
|
||
.h-dvh doesn't scroll, so the field is already viewport-stable. */
|
||
.theme-boocode-plus.dark .h-dvh {
|
||
background-color: var(--background);
|
||
background-image:
|
||
radial-gradient(1200px 760px at 8% -12%,
|
||
color-mix(in oklab, #5e6ad2 14%, transparent), transparent 58%),
|
||
radial-gradient(1000px 680px at 102% 4%,
|
||
color-mix(in oklab, #5a73d8 10%, transparent), transparent 54%),
|
||
radial-gradient(1100px 900px at 50% 128%,
|
||
color-mix(in oklab, #2c3270 16%, transparent), transparent 60%);
|
||
background-repeat: no-repeat;
|
||
}
|
||
|
||
/* ── Frosted glass on chrome ─────────────────────────────────────────────
|
||
Progressive enhancement: only inside @supports does the surface go
|
||
translucent + blur. Without backdrop-filter support the rules vanish and the
|
||
plain opaque utility (solid token bg) shows — that IS the opaque fallback.
|
||
Each background has an rgba() line first (covers the rare browser that has
|
||
backdrop-filter but not color-mix, e.g. Safari 15) then the color-mix line. */
|
||
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
|
||
/* Side rails (ProjectSidebar + RightRail). Blur the ambient gradient behind
|
||
them → the signature glass plane. Kept at 80% so the dense nav text stays
|
||
crisp; the blur + saturate sells the effect, not heavy transparency. */
|
||
.theme-boocode-plus.dark .bg-sidebar {
|
||
background-color: rgba(19, 21, 29, 0.80);
|
||
background-color: color-mix(in oklab, var(--sidebar) 80%, transparent);
|
||
-webkit-backdrop-filter: blur(var(--bcp-blur)) saturate(140%);
|
||
backdrop-filter: blur(var(--bcp-blur)) saturate(140%);
|
||
}
|
||
|
||
/* Every floating surface: dialogs, dropdown / context / sub menus, the
|
||
@-mention & slash pickers, the mobile bottom sheet, the message-actions
|
||
menu. All carry bg-popover and all float over content → one clean blur. */
|
||
.theme-boocode-plus.dark .bg-popover {
|
||
background-color: rgba(26, 29, 46, 0.84);
|
||
background-color: color-mix(in oklab, var(--popover) 84%, transparent);
|
||
-webkit-backdrop-filter: blur(12px) saturate(150%);
|
||
backdrop-filter: blur(12px) saturate(150%);
|
||
}
|
||
|
||
/* The composer message box (ChatInput) — unique focus-within ring hook.
|
||
Frosts the tail of the conversation as it scrolls beneath. 82% keeps the
|
||
textarea text readable even over a bright code block underneath. */
|
||
.theme-boocode-plus.dark .focus-within\:ring-primary\/15 {
|
||
background-color: rgba(26, 29, 46, 0.82);
|
||
background-color: color-mix(in oklab, var(--card) 82%, transparent);
|
||
-webkit-backdrop-filter: blur(var(--bcp-blur)) saturate(140%);
|
||
backdrop-filter: blur(var(--bcp-blur)) saturate(140%);
|
||
}
|
||
|
||
/* Pane top-bars (Coder/Workspace/terminal-hotkey/artifact headers). The
|
||
compound .border-b.bg-muted/N selector excludes chat bubbles & tool cards
|
||
(those use .border.rounded-lg). Lightest blur (8px) + an inset top hairline
|
||
for the edge-lit premium feel. */
|
||
.theme-boocode-plus.dark .border-b.bg-muted\/30,
|
||
.theme-boocode-plus.dark .border-b.bg-muted\/20 {
|
||
background-color: rgba(28, 32, 47, 0.62);
|
||
background-color: color-mix(in oklab, var(--muted) 62%, transparent);
|
||
-webkit-backdrop-filter: blur(8px) saturate(130%);
|
||
backdrop-filter: blur(8px) saturate(130%);
|
||
box-shadow: inset 0 1px 0 0 color-mix(in oklab, #aab2ff 8%, transparent);
|
||
}
|
||
}
|
||
|
||
/* Dialog scrim: deepen the dim (the stock bg-black/10 is too light to anchor a
|
||
frosted modal) and kill its own backdrop-blur so the only blur layer is the
|
||
dialog CONTENT above — no nested blur-on-blur. Unconditional: when blur is
|
||
unsupported the stock overlay had no blur anyway, and the deeper scrim is
|
||
harmless. */
|
||
.theme-boocode-plus.dark [data-slot="dialog-overlay"] {
|
||
background-color: rgba(7, 8, 14, 0.55);
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
}
|
||
|
||
/* ── Restrained indigo glow on the primary action ────────────────────────
|
||
The default-variant Button only (Send, primary confirm). Resting: a hairline
|
||
indigo rim + soft drop glow. Hover: the glow blooms with the spring curve.
|
||
Press: a composited squash. box-shadow/transform/filter only — no reflow. */
|
||
.theme-boocode-plus.dark [data-slot="button"][data-variant="default"] {
|
||
box-shadow:
|
||
0 0 0 1px color-mix(in oklab, #5e6ad2 38%, transparent),
|
||
0 4px 16px -6px color-mix(in oklab, #5e6ad2 50%, transparent);
|
||
transition:
|
||
box-shadow 0.28s var(--bcp-spring),
|
||
transform 0.2s var(--bcp-spring),
|
||
filter 0.2s var(--bcp-ease),
|
||
background-color 0.18s var(--bcp-ease);
|
||
}
|
||
.theme-boocode-plus.dark [data-slot="button"][data-variant="default"]:not([disabled]):hover {
|
||
box-shadow:
|
||
0 0 0 1px color-mix(in oklab, #5e6ad2 60%, transparent),
|
||
0 8px 26px -6px color-mix(in oklab, #5e6ad2 68%, transparent);
|
||
filter: brightness(1.06);
|
||
}
|
||
.theme-boocode-plus.dark [data-slot="button"][data-variant="default"]:not([disabled]):active {
|
||
transform: translateY(0.5px) scale(0.985);
|
||
}
|
||
|
||
/* ── Spring-eased state on the rest of the chrome ─────────────────────────
|
||
Smooth (non-overshoot) color/bg transitions on menu items and the composer
|
||
focus, so hovers and focus feel intentional, not instant. Color props only. */
|
||
.theme-boocode-plus.dark [data-slot="dropdown-menu-item"],
|
||
.theme-boocode-plus.dark [data-slot="context-menu-item"],
|
||
.theme-boocode-plus.dark [data-slot="dropdown-menu-sub-trigger"],
|
||
.theme-boocode-plus.dark [data-slot="context-menu-sub-trigger"] {
|
||
transition:
|
||
background-color 0.16s var(--bcp-ease),
|
||
color 0.16s var(--bcp-ease);
|
||
}
|
||
.theme-boocode-plus.dark .focus-within\:ring-primary\/15 {
|
||
transition:
|
||
border-color 0.22s var(--bcp-ease),
|
||
box-shadow 0.22s var(--bcp-spring),
|
||
background-color 0.22s var(--bcp-ease);
|
||
}
|
||
|
||
/* ── Accessibility fallbacks ──────────────────────────────────────────────
|
||
Reduced transparency: drop every glass surface to its solid token bg and
|
||
remove all blur. Declared after the @supports block so it wins. */
|
||
@media (prefers-reduced-transparency: reduce) {
|
||
.theme-boocode-plus.dark .bg-sidebar {
|
||
background-color: var(--sidebar);
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
}
|
||
.theme-boocode-plus.dark .bg-popover {
|
||
background-color: var(--popover);
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
}
|
||
.theme-boocode-plus.dark .focus-within\:ring-primary\/15 {
|
||
background-color: var(--card);
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
}
|
||
.theme-boocode-plus.dark .border-b.bg-muted\/30,
|
||
.theme-boocode-plus.dark .border-b.bg-muted\/20 {
|
||
background-color: var(--muted);
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
|
||
/* Reduced motion: no spring/transition, no press transform. The ambient
|
||
gradient is static, so it (correctly) stays. */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.theme-boocode-plus.dark [data-slot="button"][data-variant="default"],
|
||
.theme-boocode-plus.dark [data-slot="dropdown-menu-item"],
|
||
.theme-boocode-plus.dark [data-slot="context-menu-item"],
|
||
.theme-boocode-plus.dark [data-slot="dropdown-menu-sub-trigger"],
|
||
.theme-boocode-plus.dark [data-slot="context-menu-sub-trigger"],
|
||
.theme-boocode-plus.dark .focus-within\:ring-primary\/15 {
|
||
transition: none;
|
||
}
|
||
.theme-boocode-plus.dark [data-slot="button"][data-variant="default"]:not([disabled]):active {
|
||
transform: none;
|
||
}
|
||
}
|