Files
boocode/apps/web/src/styles/themes/effects/boocode-override-fx.css
indifferentketchup fc4fbb0b7e 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.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:16:59 +00:00

289 lines
13 KiB
CSS

/* BooCode Override — effects layer. Full neon cyberpunk: heavy bloom/glow,
chromatic-glitch wordmark, glitch-on-hover, strong scanlines, neon border
pulses. Magenta #ff2d78 + cyan #00e5ff + violet #9b5de5 on blue-black.
Scoping contract (immutable, same as boocode-classic-fx.css):
- EVERY rule is scoped under `.theme-boocode-override` so the other 22 themes
are byte-for-byte unaffected.
- EVERY continuous keyframe animation is *additionally* scoped under
`html.bc-anim-on.theme-boocode-override`. ThemeFx sets `bc-anim-on` only
when the Animated-background toggle is ON *and* prefers-reduced-motion is
not set — so toggling either off removes the class and freezes all motion.
@keyframes names are global (CSS can't scope the at-rule itself); they are
`bco-`-prefixed and only *referenced* under the gated selector.
Static vs. gated split:
- STATIC (always, under .theme-boocode-override): the neon palette, a bloom
vignette painted on the app-shell background, static neon rims/glows on the
primary action / cards / inputs, and a neon focus ring. This is the
"static tint" fallback shown when the toggle is OFF or reduced-motion is on.
- GATED (under html.bc-anim-on.theme-boocode-override): the NeonField canvas
(mounted by ThemeFx), the scanline texture + moving sweep, the wordmark
chromatic glitch, the hover glitch jitter, and the idle neon pulse.
AA: neon is used ONLY for accents/borders/glow and the (large display)
wordmark — never body text. Body/panel text keeps the light #cde0ff /
#8aa9cd tokens over the high-alpha translucent surfaces (see
boocode-override.css). Glitch uses transform / text-shadow / clip-path only,
so it never reflows layout. */
.theme-boocode-override {
--bco-magenta: #ff2d78;
--bco-cyan: #00e5ff;
--bco-violet: #9b5de5;
--bco-glow-magenta: 0 0 22px color-mix(in srgb, var(--bco-magenta) 42%, transparent);
--bco-glow-cyan: 0 0 22px color-mix(in srgb, var(--bco-cyan) 38%, transparent);
}
/* ── Bloom vignette on the app shell ──────────────────────────────────────
Painted as background-image on the AppShell root (`.h-dvh.bg-background`,
unique to App.tsx). The background-COLOR stays the translucent --background
token so the NeonField canvas (z-0, behind the z-10 root) still shows through
the panel gaps; these radial neon pools sit over the field for an atmospheric
bloom. Static → this is part of the reduced-motion / toggle-off fallback. */
.theme-boocode-override .h-dvh.bg-background {
background-color: var(--background);
background-image:
radial-gradient(125% 90% at 50% 8%,
color-mix(in oklab, var(--bco-magenta) 12%, transparent), transparent 46%),
radial-gradient(120% 85% at 6% 102%,
color-mix(in oklab, var(--bco-cyan) 11%, transparent), transparent 50%),
radial-gradient(120% 95% at 100% 100%,
color-mix(in oklab, var(--bco-violet) 12%, transparent), transparent 52%),
radial-gradient(140% 120% at 50% 50%,
transparent 60%, rgba(2, 4, 10, 0.55) 100%);
background-repeat: no-repeat;
}
/* ── Strong scanlines (gated) ─────────────────────────────────────────────
::before = a fine static CRT scanline texture; ::after = a bright neon band
that sweeps the viewport. Both are `position: fixed` (never create document
overflow), `pointer-events: none`, and use `mix-blend-mode: screen` so they
only LIGHTEN — text underneath never loses contrast. Both live ONLY under the
gate, so motion-off / reduced-motion removes scanlines entirely (the bloom
vignette above is the static fallback). z-index:2/3 overlays the static panels
inside the shell; portaled dialogs (separate stacking context) stay clean. */
html.bc-anim-on.theme-boocode-override .h-dvh.bg-background::before {
content: '';
position: fixed;
inset: 0;
z-index: 2;
pointer-events: none;
background-image: repeating-linear-gradient(
0deg,
color-mix(in srgb, var(--bco-cyan) 9%, transparent) 0px,
color-mix(in srgb, var(--bco-cyan) 9%, transparent) 1px,
transparent 1px,
transparent 3px
);
mix-blend-mode: screen;
opacity: 0.55;
}
html.bc-anim-on.theme-boocode-override .h-dvh.bg-background::after {
content: '';
position: fixed;
inset: 0;
z-index: 3;
pointer-events: none;
background: linear-gradient(
180deg,
transparent 44%,
color-mix(in srgb, var(--bco-cyan) 10%, transparent) 49%,
color-mix(in srgb, var(--bco-magenta) 12%, transparent) 51%,
transparent 56%
);
mix-blend-mode: screen;
animation: bco-scan-sweep 7s linear infinite;
will-change: transform;
}
/* ── Neon chromatic-glitch wordmark ───────────────────────────────────────
The "BooCode" display heading (Home.tsx, additive `boocode-display` class —
the same hook BooCode Classic restyles; only one theme is ever active).
Orbitron 800 (JS-imported in main.tsx). A near-white core with stacked cyan +
magenta glow gives the neon tube look; the gated glitch jitters the colour
split + a clip slice via transform/text-shadow only → zero layout shift. */
.theme-boocode-override .boocode-display {
font-family: 'Orbitron', var(--font-sans);
font-weight: 800;
letter-spacing: 0.08em;
color: #eafaff;
text-shadow:
0 0 2px color-mix(in srgb, var(--bco-cyan) 80%, transparent),
-0.02em 0 0 color-mix(in srgb, var(--bco-magenta) 70%, transparent),
0.02em 0 0 color-mix(in srgb, var(--bco-cyan) 70%, transparent),
0 0 18px color-mix(in srgb, var(--bco-cyan) 55%, transparent),
0 0 34px color-mix(in srgb, var(--bco-magenta) 40%, transparent);
}
html.bc-anim-on.theme-boocode-override .boocode-display {
animation: bco-glitch 5.5s steps(1, end) infinite;
}
/* ── Primary action: neon rim + idle pulse + hover bloom + press squash ────
The default-variant Button (Send / primary confirm). Static magenta rim +
drop glow (always); a gentle idle pulse and a hover glitch jitter under the
gate; box-shadow / transform / filter only → never reflows. */
.theme-boocode-override [data-slot="button"][data-variant="default"] {
box-shadow:
0 0 0 1px color-mix(in srgb, var(--bco-magenta) 55%, transparent),
0 4px 20px -6px color-mix(in srgb, var(--bco-magenta) 60%, transparent);
}
.theme-boocode-override [data-slot="button"][data-variant="default"]:not([disabled]):hover {
box-shadow:
0 0 0 1px color-mix(in srgb, var(--bco-magenta) 85%, transparent),
0 0 16px -2px color-mix(in srgb, var(--bco-magenta) 70%, transparent),
0 8px 30px -6px color-mix(in srgb, var(--bco-cyan) 55%, transparent);
filter: brightness(1.08);
}
html.bc-anim-on.theme-boocode-override [data-slot="button"][data-variant="default"] {
transition: box-shadow 200ms ease, transform 120ms ease, filter 160ms ease;
animation: bco-pulse 3.4s ease-in-out infinite;
}
html.bc-anim-on.theme-boocode-override [data-slot="button"][data-variant="default"]:not([disabled]):hover {
animation: bco-hover-glitch 320ms steps(1, end) 1;
}
html.bc-anim-on.theme-boocode-override [data-slot="button"][data-variant="default"]:not([disabled]):active {
transform: translateY(0.5px) scale(0.985);
}
/* ── Cards: neon edge + hover glow/lift ───────────────────────────────────
shadcn Card primitive (`data-slot="card"`). Static cyan hairline; on hover a
cyan/magenta rim glow + 1px lift (lift + transition gated). */
.theme-boocode-override [data-slot="card"] {
box-shadow: 0 0 0 1px color-mix(in srgb, var(--bco-cyan) 12%, transparent);
}
.theme-boocode-override [data-slot="card"]:hover {
border-color: color-mix(in srgb, var(--bco-cyan) 55%, transparent);
box-shadow:
0 0 0 1px color-mix(in srgb, var(--bco-cyan) 38%, transparent),
0 0 22px -6px color-mix(in srgb, var(--bco-magenta) 45%, transparent),
0 8px 24px -10px color-mix(in srgb, var(--bco-cyan) 50%, transparent);
}
html.bc-anim-on.theme-boocode-override [data-slot="card"] {
transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
}
html.bc-anim-on.theme-boocode-override [data-slot="card"]:hover {
transform: translateY(-1px);
}
/* ── Sidebar edge-light + neon focus ring ─────────────────────────────────
A static cyan inset hairline down the sidebar's trailing edge; a magenta neon
focus ring on inputs/buttons (focus-visible only, static — focus feedback is
acceptable under reduced motion). */
.theme-boocode-override .bg-sidebar {
box-shadow: inset -1px 0 0 0 color-mix(in srgb, var(--bco-cyan) 16%, transparent);
}
.theme-boocode-override [data-slot="input"]:focus-visible,
.theme-boocode-override [data-slot="button"]:focus-visible,
.theme-boocode-override [data-slot="textarea"]:focus-visible {
box-shadow:
0 0 0 1px color-mix(in srgb, var(--bco-magenta) 70%, transparent),
0 0 14px -2px color-mix(in srgb, var(--bco-magenta) 55%, transparent);
outline: none;
}
/* ── Links / accent text glow ─────────────────────────────────────────────
Anchors inside content get a soft cyan glow on hover — accent, not body. */
.theme-boocode-override a:hover {
text-shadow: 0 0 10px color-mix(in srgb, var(--bco-cyan) 55%, transparent);
}
/* ── Keyframes (global names, referenced only under the gate above) ───────── */
/* Scanline sweep: a bright neon band drifts top→bottom. */
@keyframes bco-scan-sweep {
0% { transform: translateY(-100%); }
100% { transform: translateY(100%); }
}
/* Idle neon pulse on the primary action: the glow swells and settles. */
@keyframes bco-pulse {
0%, 100% {
box-shadow:
0 0 0 1px color-mix(in srgb, var(--bco-magenta) 55%, transparent),
0 4px 20px -6px color-mix(in srgb, var(--bco-magenta) 55%, transparent);
}
50% {
box-shadow:
0 0 0 1px color-mix(in srgb, var(--bco-magenta) 80%, transparent),
0 0 18px -2px color-mix(in srgb, var(--bco-magenta) 60%, transparent),
0 6px 26px -6px color-mix(in srgb, var(--bco-cyan) 45%, transparent);
}
}
/* Wordmark chromatic glitch: mostly steady, with brief jitter bursts. Only
transform / text-shadow / clip-path change → no reflow. */
@keyframes bco-glitch {
0%, 88%, 100% {
transform: translate3d(0, 0, 0);
clip-path: none;
text-shadow:
0 0 2px color-mix(in srgb, var(--bco-cyan) 80%, transparent),
-0.02em 0 0 color-mix(in srgb, var(--bco-magenta) 70%, transparent),
0.02em 0 0 color-mix(in srgb, var(--bco-cyan) 70%, transparent),
0 0 18px color-mix(in srgb, var(--bco-cyan) 55%, transparent),
0 0 34px color-mix(in srgb, var(--bco-magenta) 40%, transparent);
}
90% {
transform: translate3d(-2px, 0, 0);
clip-path: inset(8% 0 62% 0);
text-shadow:
-0.08em 0 0 color-mix(in srgb, var(--bco-magenta) 90%, transparent),
0.08em 0 0 color-mix(in srgb, var(--bco-cyan) 90%, transparent),
0 0 22px color-mix(in srgb, var(--bco-cyan) 60%, transparent);
}
92% {
transform: translate3d(2px, 0, 0);
clip-path: inset(54% 0 18% 0);
text-shadow:
0.1em 0 0 color-mix(in srgb, var(--bco-magenta) 90%, transparent),
-0.06em 0 0 color-mix(in srgb, var(--bco-cyan) 85%, transparent),
0 0 22px color-mix(in srgb, var(--bco-magenta) 60%, transparent);
}
94% {
transform: translate3d(-1px, 0, 0);
clip-path: inset(34% 0 40% 0);
text-shadow:
-0.05em 0 0 color-mix(in srgb, var(--bco-cyan) 85%, transparent),
0.05em 0 0 color-mix(in srgb, var(--bco-magenta) 85%, transparent);
}
}
/* Hover glitch on the primary button: a single quick colour-split jitter. */
@keyframes bco-hover-glitch {
0% { transform: translate3d(0, 0, 0); }
25% { transform: translate3d(-1.5px, 0, 0); }
50% { transform: translate3d(1.5px, 0, 0); }
75% { transform: translate3d(-0.5px, 0, 0); }
100% { transform: translate3d(0, 0, 0); }
}
/* ── Reduced-motion belt-and-suspenders ───────────────────────────────────
bc-anim-on already excludes reduced-motion (ThemeFx), but if the class ever
lingered, this hard-stops every animation and the lift/jitter transforms. The
static neon rims, bloom vignette and focus ring remain (the "static tint"). */
@media (prefers-reduced-motion: reduce) {
.theme-boocode-override .boocode-display,
.theme-boocode-override .h-dvh.bg-background::before,
.theme-boocode-override .h-dvh.bg-background::after,
.theme-boocode-override [data-slot="button"][data-variant="default"],
.theme-boocode-override [data-slot="button"][data-variant="default"]:hover {
animation: none;
}
.theme-boocode-override .h-dvh.bg-background::after {
display: none;
}
.theme-boocode-override .boocode-display {
transform: none;
clip-path: none;
}
.theme-boocode-override [data-slot="card"],
.theme-boocode-override [data-slot="button"][data-variant="default"] {
transition: none;
}
.theme-boocode-override [data-slot="card"]:hover,
.theme-boocode-override [data-slot="button"][data-variant="default"]:active {
transform: none;
}
}