feat: futuristic theme ladder + stacked landing banner

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.
This commit is contained in:
2026-06-03 14:16:59 +00:00
parent 41f93f5d8e
commit f42c673881
21 changed files with 1822 additions and 30 deletions

View File

@@ -0,0 +1,205 @@
/* 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 812px; 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;
}
}