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>
289 lines
13 KiB
CSS
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;
|
|
}
|
|
}
|