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.
269 lines
11 KiB
CSS
269 lines
11 KiB
CSS
/* BooCode Classic — effects layer. Faithful revival of /opt/boolab
|
|
data-mode='boocode' terminal-HUD chrome.
|
|
|
|
Scoping contract (immutable):
|
|
- EVERY rule is scoped under `.theme-boocode-classic` so the other 22 themes
|
|
are byte-for-byte unaffected.
|
|
- EVERY continuous keyframe animation is *additionally* scoped under
|
|
`html.bc-anim-on.theme-boocode-classic`. 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
|
|
`bc-`-prefixed and only *referenced* under the gated selector, so no
|
|
animation runs unless the gate is on.
|
|
|
|
The matrix-rain canvas lives in components/fx/MatrixRain.tsx (mounted by
|
|
ThemeFx). This sheet adds: an Orbitron display wordmark + blinking caret, an
|
|
app-wide scanline sweep, orange card hover glow + lift, terminal-frame chrome
|
|
on the rails, and the ported boolab `.bc-*` / `.boocode-*` design-system
|
|
classes (dormant until a HUD component consumes them). */
|
|
|
|
.theme-boocode-classic {
|
|
--bc-orange: #f97316;
|
|
--bc-amber: #fbbf24;
|
|
--bc-rust: #c2410c;
|
|
--bc-glow-orange: 0 0 24px color-mix(in srgb, var(--bc-orange) 32%, transparent);
|
|
--bc-glow-soft: 0 0 40px color-mix(in srgb, var(--bc-orange) 14%, transparent);
|
|
}
|
|
|
|
/* ── Orbitron display wordmark ────────────────────────────────────────────
|
|
Applied to the "BooCode" text heading via the additive `boocode-display`
|
|
class (the only text wordmark — the sidebar wordmark is an <img>, which a
|
|
font can't restyle). Orbitron 800 is JS-imported in main.tsx. */
|
|
.theme-boocode-classic .boocode-display {
|
|
font-family: 'Orbitron', var(--font-sans);
|
|
font-weight: 800;
|
|
letter-spacing: 0.06em;
|
|
color: var(--bc-orange);
|
|
text-shadow: 0 0 14px color-mix(in srgb, var(--bc-orange) 45%, transparent);
|
|
}
|
|
|
|
/* Terminal cursor after the wordmark. Always rendered (a solid block when
|
|
motion is off — boolab's reduced-motion behaviour); blinks only when gated. */
|
|
.theme-boocode-classic .boocode-display::after {
|
|
content: '▮';
|
|
margin-left: 0.12em;
|
|
color: var(--bc-amber);
|
|
opacity: 1;
|
|
}
|
|
html.bc-anim-on.theme-boocode-classic .boocode-display::after {
|
|
animation: bc-caret-blink 1s steps(1, end) infinite;
|
|
}
|
|
|
|
/* ── App-wide scanline sweep ──────────────────────────────────────────────
|
|
A soft warm band that drifts down the viewport on a calm 8s cycle.
|
|
`position: fixed` keeps it viewport-locked (never creates document overflow);
|
|
mix-blend-mode: screen only lightens, so text underneath stays legible.
|
|
Defined ONLY under the gate → vanishes entirely when motion is off (the warm
|
|
palette + static rain-off state is the reduced-motion fallback).
|
|
`.h-dvh.bg-background` is unique to the AppShell root (App.tsx). */
|
|
html.bc-anim-on.theme-boocode-classic .h-dvh.bg-background::after {
|
|
content: '';
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 2;
|
|
pointer-events: none;
|
|
background: linear-gradient(
|
|
180deg,
|
|
transparent 46%,
|
|
color-mix(in srgb, var(--bc-orange) 9%, transparent) 50%,
|
|
transparent 54%
|
|
);
|
|
mix-blend-mode: screen;
|
|
animation: bc-scanline 8s linear infinite;
|
|
will-change: transform;
|
|
}
|
|
|
|
/* ── Card hover glow + lift ───────────────────────────────────────────────
|
|
The shadcn Card primitive (`data-slot="card"`). The orange rim + drop glow
|
|
apply on hover under any condition (hover feedback is acceptable under
|
|
reduced motion); the transition timing and the 1px lift are gated so motion
|
|
off = instant, no travel. */
|
|
.theme-boocode-classic [data-slot="card"]:hover {
|
|
border-color: color-mix(in srgb, var(--bc-orange) 55%, transparent);
|
|
box-shadow:
|
|
0 0 0 1px color-mix(in srgb, var(--bc-orange) 22%, transparent),
|
|
0 6px 18px -8px color-mix(in srgb, var(--bc-orange) 40%, transparent);
|
|
}
|
|
html.bc-anim-on.theme-boocode-classic [data-slot="card"] {
|
|
transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
|
|
}
|
|
html.bc-anim-on.theme-boocode-classic [data-slot="card"]:hover {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* ── Terminal-frame chrome on the rails ───────────────────────────────────
|
|
Monospace + a hairline orange edge-light on the sidebar (vertical list that
|
|
already truncates — low overflow risk). Pane top-bars are intentionally left
|
|
alone: they are crowded control rows where a wider mono font risks wrapping. */
|
|
.theme-boocode-classic .bg-sidebar {
|
|
font-family: var(--font-mono);
|
|
box-shadow: inset -1px 0 0 0 color-mix(in srgb, var(--bc-orange) 8%, transparent);
|
|
}
|
|
/* Floating surfaces (menus / dialogs / popovers) are intentionally NOT given an
|
|
inset box-shadow here — that would clobber shadcn's `shadow-md` elevation
|
|
(higher specificity) and flatten menus over the rain. The warm `--popover` /
|
|
`--border` tokens already theme them. */
|
|
|
|
/* ──────────────────────────────────────────────────────────────────────────
|
|
Ported boolab design-system classes. These are faithful copies of the
|
|
/opt/boolab `.bc-*` / `.boocode-*` chrome. No component in this repo renders
|
|
these class names yet (boolab's RepoStatusBar / HUD components don't exist
|
|
here), so they are DORMANT — shipped so the Classic design system is complete
|
|
and any future HUD element (prompt line, status pill, kbd hint, breadcrumb)
|
|
lights up automatically. All scoped to the theme; animations gated.
|
|
────────────────────────────────────────────────────────────────────────── */
|
|
|
|
/* Terminal prompt line: `$ boocode @host: path (branch)` */
|
|
.theme-boocode-classic .bc-prompt-line {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
row-gap: 0.25rem;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.8125rem;
|
|
color: var(--muted-foreground);
|
|
padding: 0.5rem 0.75rem;
|
|
background: var(--popover);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.theme-boocode-classic .bc-prompt-host { color: var(--bc-orange); }
|
|
.theme-boocode-classic .bc-prompt-branch { color: var(--bc-orange); opacity: 0.8; }
|
|
.theme-boocode-classic .bc-prompt-dollar {
|
|
color: var(--bc-orange);
|
|
text-shadow: 0 0 6px var(--bc-orange);
|
|
margin-right: 0.25rem;
|
|
}
|
|
|
|
/* Standalone blinking block caret. */
|
|
.theme-boocode-classic .bc-caret {
|
|
display: inline-block;
|
|
width: 0.6em;
|
|
height: 1em;
|
|
background: currentColor;
|
|
vertical-align: text-bottom;
|
|
margin-left: 0.1em;
|
|
}
|
|
html.bc-anim-on.theme-boocode-classic .bc-caret {
|
|
animation: bc-caret-blink 1s steps(1, end) infinite;
|
|
}
|
|
|
|
/* IDLE / SYNCING / ERROR status pills. */
|
|
.theme-boocode-classic .bc-status-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.1rem 0.5rem;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border);
|
|
background: var(--card);
|
|
font-size: 0.6875rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.12em;
|
|
font-family: var(--font-mono);
|
|
}
|
|
.theme-boocode-classic .bc-status-idle { color: #7ae07a; border-color: rgba(122, 224, 122, 0.4); }
|
|
.theme-boocode-classic .bc-status-syncing {
|
|
color: var(--bc-orange);
|
|
border-color: color-mix(in srgb, var(--bc-orange) 60%, transparent);
|
|
}
|
|
.theme-boocode-classic .bc-status-error { color: #ff6b6b; border-color: rgba(255, 107, 107, 0.5); }
|
|
.theme-boocode-classic .bc-status-syncing::before {
|
|
content: '▮';
|
|
color: var(--bc-orange);
|
|
}
|
|
html.bc-anim-on.theme-boocode-classic .bc-status-syncing::before {
|
|
animation: bc-caret-blink 1s steps(1, end) infinite;
|
|
}
|
|
|
|
/* Keyboard hint chip. */
|
|
.theme-boocode-classic .bc-key-hint {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 1.25rem;
|
|
padding: 0 0.3rem;
|
|
height: 1.1rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: 0.25rem;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.625rem;
|
|
color: var(--muted-foreground);
|
|
background: var(--popover);
|
|
}
|
|
|
|
/* Uppercase orange breadcrumb / overline. */
|
|
.theme-boocode-classic .boocode-breadcrumb {
|
|
font-family: 'Orbitron', var(--font-sans);
|
|
font-size: 0.6875rem;
|
|
letter-spacing: 0.22em;
|
|
text-transform: uppercase;
|
|
color: var(--bc-orange);
|
|
text-shadow: 0 0 8px color-mix(in srgb, var(--bc-orange) 40%, transparent);
|
|
}
|
|
|
|
/* Inset terminal-frame border. */
|
|
.theme-boocode-classic .boocode-terminal-frame {
|
|
border: 1px solid var(--border);
|
|
box-shadow:
|
|
inset 0 0 0 1px color-mix(in srgb, var(--bc-orange) 5%, transparent),
|
|
0 0 0 1px #0a0604;
|
|
}
|
|
|
|
/* Card hover glow + lift utility (boolab `.bc-card`), for non-shadcn surfaces. */
|
|
.theme-boocode-classic .bc-card {
|
|
position: relative;
|
|
border: 1px solid color-mix(in srgb, var(--bc-orange) 22%, transparent);
|
|
background: var(--card);
|
|
padding: 1rem;
|
|
border-radius: 0.375rem;
|
|
overflow: hidden;
|
|
}
|
|
.theme-boocode-classic .bc-card:hover {
|
|
border-color: color-mix(in srgb, var(--bc-orange) 55%, transparent);
|
|
box-shadow:
|
|
0 0 0 1px color-mix(in srgb, var(--bc-orange) 22%, transparent),
|
|
0 6px 18px -8px color-mix(in srgb, var(--bc-orange) 40%, transparent);
|
|
}
|
|
html.bc-anim-on.theme-boocode-classic .bc-card {
|
|
transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
|
|
}
|
|
html.bc-anim-on.theme-boocode-classic .bc-card:hover {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* ── Keyframes (global names, referenced only under the gate above) ───────── */
|
|
@keyframes bc-caret-blink {
|
|
0%, 49% { opacity: 1; }
|
|
50%, 100% { opacity: 0; }
|
|
}
|
|
@keyframes bc-scanline {
|
|
0% { transform: translateY(-100%); }
|
|
100% { transform: translateY(100%); }
|
|
}
|
|
|
|
/* ── 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 transform. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.theme-boocode-classic .boocode-display::after,
|
|
.theme-boocode-classic .bc-caret,
|
|
.theme-boocode-classic .bc-status-syncing::before {
|
|
animation: none;
|
|
opacity: 1;
|
|
}
|
|
.theme-boocode-classic .h-dvh.bg-background::after {
|
|
animation: none;
|
|
display: none;
|
|
}
|
|
.theme-boocode-classic [data-slot="card"],
|
|
.theme-boocode-classic .bc-card {
|
|
transition: none;
|
|
}
|
|
.theme-boocode-classic [data-slot="card"]:hover,
|
|
.theme-boocode-classic .bc-card:hover {
|
|
transform: none;
|
|
}
|
|
}
|