fix(decnet_web/css): light-mode contrast across wizards, code blocks, hovers

Sweeps four invariant violations that were leaking dark surfaces
into light mode and producing the unreadable / inverted areas:

  1. Hardcoded `color: #000` in 14 :hover rules across 11 CSS
     files swapped to `color: var(--bg)` — collapses to #000 in
     dark mode (no-op), becomes cream in light. Fixes DEPLOY
     DECKIES (button hover was rendering charcoal-purple text on
     charcoal-purple background).
  2. Hardcoded `background: #000` (3 sites) and `#0d1117`
     (3 sites) replaced with `var(--bg)` / `var(--panel)`. Fixes
     code blocks and modal panels staying dark on cream — the
     deploy-wizard preview, topology-creation NAME input, and the
     MazeNET canvas backdrop now follow the active theme.
  3. `rgba(0,0,0,0.35)` and `rgba(0,0,0,0.5)` input/card
     backgrounds (ServiceConfigForm, DeckyFleet .input)
     swapped to `var(--panel)`. Fixes per-service config rows
     in the deploy wizard rendering as dark slabs.
  4. SVG arrow markers in MazeNET Canvas.tsx hardcoded
     `fill="#00ff41"` / "#ee82ee" — replaced with currentColor +
     style hook so they re-resolve on theme change.

New behaviour: light-mode hovers tint instead of inverting. The
dark-mode rules fully fill bg with --matrix/--violet/--alert and
flip text to --bg; that lands cream-on-near-ink in light mode
and reads as a jarring colour inversion every cursor move. Light
mode now layers a *-tint-10 background and keeps text in its
base colour. Single override block in index.css targets every
scoped `.X-btn`/`.btn`/`button:hover` via :is() + [class*="-btn"]
so we don't have to chase every component file.
This commit is contained in:
2026-05-09 03:43:47 -04:00
parent 34c778277a
commit 11b2da7d54
13 changed files with 81 additions and 43 deletions

View File

@@ -19,9 +19,9 @@
cursor: pointer;
transition: all 0.3s ease;
}
.bounty-root .btn:hover { background: var(--matrix); color: #000; box-shadow: var(--matrix-glow); }
.bounty-root .btn:hover { background: var(--matrix); color: var(--bg); box-shadow: var(--matrix-glow); }
.bounty-root .btn.violet { border-color: var(--violet); color: var(--violet); }
.bounty-root .btn.violet:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.bounty-root .btn.violet:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.bounty-root .btn.ghost { border-color: var(--border); color: var(--matrix); opacity: 0.7; }
.bounty-root .btn.ghost:hover { opacity: 1; border-color: var(--matrix); background: transparent; box-shadow: var(--matrix-glow); }
.bounty-root .btn:disabled { opacity: 0.3; cursor: not-allowed; }

View File

@@ -198,7 +198,7 @@
}
.add-user-form select {
background: #0d1117;
background: var(--panel);
border: 1px solid var(--border-color);
color: var(--text-color);
padding: 8px 12px;
@@ -213,7 +213,7 @@
}
.role-select {
background: #0d1117;
background: var(--panel);
border: 1px solid var(--border-color);
color: var(--text-color);
padding: 4px 8px;

View File

@@ -19,9 +19,9 @@
cursor: pointer;
transition: all 0.3s ease;
}
.credentials-root .btn:hover { background: var(--matrix); color: #000; box-shadow: var(--matrix-glow); }
.credentials-root .btn:hover { background: var(--matrix); color: var(--bg); box-shadow: var(--matrix-glow); }
.credentials-root .btn.violet { border-color: var(--violet); color: var(--violet); }
.credentials-root .btn.violet:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.credentials-root .btn.violet:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.credentials-root .btn.ghost { border-color: var(--border); color: var(--matrix); opacity: 0.7; }
.credentials-root .btn.ghost:hover { opacity: 1; border-color: var(--matrix); background: transparent; box-shadow: var(--matrix-glow); }
.credentials-root .btn:disabled { opacity: 0.3; cursor: not-allowed; }

View File

@@ -52,11 +52,11 @@
align-items: center;
gap: 8px;
}
.fleet-root .btn:hover { background: var(--matrix); color: #000; box-shadow: var(--matrix-glow); }
.fleet-root .btn:hover { background: var(--matrix); color: var(--bg); box-shadow: var(--matrix-glow); }
.fleet-root .btn.violet { border-color: var(--violet); color: var(--violet); }
.fleet-root .btn.violet:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.fleet-root .btn.violet:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.fleet-root .btn.alert { border-color: var(--alert); color: var(--alert); }
.fleet-root .btn.alert:hover { background: var(--alert); color: #000; box-shadow: 0 0 10px rgba(255, 65, 65, 0.5); }
.fleet-root .btn.alert:hover { background: var(--alert); color: var(--bg); box-shadow: 0 0 10px rgba(255, 65, 65, 0.5); }
.fleet-root .btn.ghost { border-color: var(--border); color: var(--matrix); opacity: 0.7; }
.fleet-root .btn.ghost:hover { color: var(--matrix); opacity: 1; border-color: var(--matrix); box-shadow: var(--matrix-glow); background: transparent; }
.fleet-root .btn.small { padding: 4px 10px; font-size: 0.68rem; }
@@ -409,7 +409,7 @@
.tweak-group label { font-size: 0.62rem; opacity: 0.55; letter-spacing: 1.5px; }
.input {
width: 100%;
background: rgba(0, 0, 0, 0.5);
background: var(--panel);
border: 1px solid var(--border);
color: var(--matrix);
padding: 8px 12px;
@@ -422,7 +422,7 @@
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.code-block {
background: #000;
background: var(--bg);
border: 1px solid var(--border);
padding: 14px 16px;
font-size: 0.75rem;

View File

@@ -21,11 +21,11 @@
}
.logs-root .btn:hover {
background: var(--matrix);
color: #000;
color: var(--bg);
box-shadow: var(--matrix-glow);
}
.logs-root .btn.violet { border-color: var(--violet); color: var(--violet); }
.logs-root .btn.violet:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.logs-root .btn.violet:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.logs-root .btn.ghost { border-color: var(--border); color: var(--matrix); opacity: 0.7; }
.logs-root .btn.ghost:hover { opacity: 1; border-color: var(--matrix); background: transparent; box-shadow: var(--matrix-glow); }
.logs-root .btn:disabled { opacity: 0.3; cursor: not-allowed; }

View File

@@ -142,10 +142,10 @@ const Canvas = forwardRef<HTMLDivElement, Props>(function Canvas(
<svg className="maze-svg" overflow="visible">
<defs>
<marker id="arrow-matrix" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#00ff41" />
<path d="M0,0 L10,5 L0,10 z" fill="currentColor" style={{ color: 'var(--matrix)' }} />
</marker>
<marker id="arrow-violet" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#ee82ee" />
<path d="M0,0 L10,5 L0,10 z" fill="currentColor" style={{ color: 'var(--violet)' }} />
</marker>
<marker id="arrow-alert" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#ff4141" />

View File

@@ -46,7 +46,7 @@ body.maze-fullscreen .maze-shell {
gap: 8px;
transition: all 0.3s;
}
.maze-btn:hover { background: var(--matrix); color: #000; box-shadow: var(--matrix-glow); }
.maze-btn:hover { background: var(--matrix); color: var(--bg); box-shadow: var(--matrix-glow); }
.maze-btn.ghost { border-color: var(--border); color: var(--matrix); opacity: 0.7; }
.maze-btn.ghost:hover {
background: transparent; color: var(--matrix); opacity: 1;
@@ -127,14 +127,14 @@ body.maze-fullscreen .maze-shell {
/* ── Canvas ─────────────────────────────────── */
.maze-canvas-wrap {
position: relative; background: #000;
position: relative; background: var(--bg);
overflow: hidden; user-select: none;
height: 100%; min-height: 0;
}
.maze-pan-layer { position: absolute; inset: 0; will-change: transform; }
.maze-grid-bg {
position: absolute; inset: 0;
pointer-events: none; opacity: 0.6; overflow: hidden; background: #000;
pointer-events: none; opacity: 0.6; overflow: hidden; background: var(--bg);
}
.maze-grid-bg svg { display: block; width: 100%; height: 100%; }
.maze-svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
@@ -372,7 +372,7 @@ body.maze-fullscreen .maze-shell {
padding: 30px 10px; font-size: 0.7rem; letter-spacing: 1px;
}
.maze-diff {
background: #000; border: 1px solid var(--border);
background: var(--bg); border: 1px solid var(--border);
padding: 10px 12px; font-size: 0.68rem; line-height: 1.6;
white-space: pre; overflow-x: auto;
}
@@ -562,7 +562,7 @@ body.maze-fullscreen .maze-shell {
}
.maze-btn.alert:hover {
background: var(--alert);
color: #000;
color: var(--bg);
box-shadow: 0 0 10px rgba(255, 65, 65, 0.5);
opacity: 1;
}

View File

@@ -51,7 +51,7 @@ select.svc-cfg-input {
font-family: var(--font-mono);
font-size: 0.72rem;
padding: 4px 6px;
background: rgba(0, 0, 0, 0.35);
background: var(--panel);
border: 1px solid var(--border);
border-radius: 0;
color: var(--matrix);
@@ -146,10 +146,10 @@ select.svc-cfg-input {
}
.svc-cfg-btn:hover:not(:disabled) {
background: var(--matrix);
color: #000;
color: var(--bg);
}
.svc-cfg-btn.violet { border-color: var(--violet); color: var(--violet); }
.svc-cfg-btn.violet:hover:not(:disabled) { background: var(--violet); color: #000; }
.svc-cfg-btn.violet:hover:not(:disabled) { background: var(--violet); color: var(--bg); }
.svc-cfg-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.svc-cfg-pw-toggle {

View File

@@ -89,14 +89,14 @@
gap: 8px;
}
.control-btn:hover { background: var(--matrix); color: #000; box-shadow: var(--matrix-glow); }
.control-btn:hover { background: var(--matrix); color: var(--bg); box-shadow: var(--matrix-glow); }
.control-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.control-btn.primary {
border-color: var(--violet);
color: var(--violet);
}
.control-btn.primary:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.control-btn.primary:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.control-btn.danger {
border-color: var(--alert, var(--alert));
@@ -104,7 +104,7 @@
}
.control-btn.danger:hover {
background: var(--alert, var(--alert));
color: #000;
color: var(--bg);
box-shadow: 0 0 10px rgba(255, 77, 77, 0.5);
}

View File

@@ -162,7 +162,7 @@
}
.ctw-field input[type='text'] {
padding: 8px 10px;
background: #000;
background: var(--bg);
border: 1px solid var(--border, var(--panel-border));
color: var(--text-color);
font-family: var(--font-mono);
@@ -191,7 +191,7 @@
margin-right: 6px;
}
.ctw-note code {
background: #000;
background: var(--bg);
padding: 1px 5px;
font-family: var(--font-mono);
color: var(--matrix, #33ff66);
@@ -232,7 +232,7 @@
align-items: center;
gap: 8px;
}
.ctw-btn:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.ctw-btn:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.ctw-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.ctw-btn.ghost { border-color: var(--border); color: var(--matrix); opacity: 0.7; }
.ctw-btn.ghost:hover {

View File

@@ -48,16 +48,16 @@
align-items: center;
gap: 8px;
}
.tlist-btn:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.tlist-btn:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.tlist-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.tlist-btn.ghost { border-color: var(--matrix); color: var(--matrix); }
.tlist-btn.ghost:hover { background: var(--matrix); color: #000; box-shadow: var(--matrix-glow); }
.tlist-btn.ghost:hover { background: var(--matrix); color: var(--bg); box-shadow: var(--matrix-glow); }
.tlist-btn.small { padding: 4px 10px; font-size: 0.68rem; }
.tlist-btn.danger { border-color: var(--alert, #e74c3c); color: var(--alert, #e74c3c); }
.tlist-btn.danger:hover { background: var(--alert, #e74c3c); color: #000; box-shadow: 0 0 10px rgba(231, 76, 60, 0.5); }
.tlist-btn.danger:hover { background: var(--alert, #e74c3c); color: var(--bg); box-shadow: 0 0 10px rgba(231, 76, 60, 0.5); }
.tlist-btn.danger.armed { background: var(--alert, #e74c3c); color: #000; }
.tlist-btn.warn { border-color: var(--warn, #e0a040); color: var(--warn, #e0a040); }
.tlist-btn.warn:hover { background: var(--warn, #e0a040); color: #000; box-shadow: 0 0 10px rgba(224, 160, 64, 0.5); }
.tlist-btn.warn:hover { background: var(--warn, #e0a040); color: var(--bg); box-shadow: 0 0 10px rgba(224, 160, 64, 0.5); }
.tlist-btn.warn.armed { background: var(--warn, #e0a040); color: #000; }
.tlist-create-row {

View File

@@ -49,15 +49,15 @@
}
.webhooks-root .btn:hover {
background: var(--matrix);
color: #000;
color: var(--bg);
box-shadow: var(--matrix-glow);
}
.webhooks-root .btn.violet { border-color: var(--violet); color: var(--violet); }
.webhooks-root .btn.violet:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.webhooks-root .btn.violet:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.webhooks-root .btn.alert { border-color: var(--alert, #ff4d4d); color: var(--alert, #ff4d4d); }
.webhooks-root .btn.alert:hover { background: var(--alert, #ff4d4d); color: #000; box-shadow: 0 0 10px rgba(255, 77, 77, 0.5); }
.webhooks-root .btn.alert:hover { background: var(--alert, #ff4d4d); color: var(--bg); box-shadow: 0 0 10px rgba(255, 77, 77, 0.5); }
.webhooks-root .btn.warn { border-color: var(--warn, #e0a040); color: var(--warn, #e0a040); }
.webhooks-root .btn.warn:hover { background: var(--warn, #e0a040); color: #000; box-shadow: 0 0 10px rgba(224, 160, 64, 0.5); }
.webhooks-root .btn.warn:hover { background: var(--warn, #e0a040); color: var(--bg); box-shadow: 0 0 10px rgba(224, 160, 64, 0.5); }
.webhooks-root .btn.ghost { border-color: var(--border-color); color: var(--matrix); opacity: 0.7; }
.webhooks-root .btn.ghost:hover { opacity: 1; border-color: var(--matrix); background: transparent; box-shadow: var(--matrix-glow); }
.webhooks-root .btn:disabled { opacity: 0.3; cursor: not-allowed; }
@@ -126,7 +126,7 @@
}
.webhooks-root .action-btn.fire:hover {
background: var(--violet);
color: #000;
color: var(--bg);
box-shadow: var(--violet-glow);
}
@@ -218,7 +218,7 @@
.webhooks-root .wh-form-grid input[type="url"],
.webhooks-root .wh-form-grid input[type="password"],
.webhooks-root .wh-form-grid textarea {
background: #0d1117;
background: var(--panel);
border: 1px solid var(--border-color);
color: var(--text-color);
padding: 8px 12px;
@@ -307,7 +307,7 @@
}
.wh-secret-modal .wh-secret-value {
background: #0d1117;
background: var(--panel);
border: 1px solid var(--border-color);
padding: 10px 12px;
font-size: 0.85rem;
@@ -336,6 +336,6 @@
transition: all 0.3s ease;
}
.wh-secret-modal .btn.violet { border-color: var(--violet); color: var(--violet); }
.wh-secret-modal .btn.violet:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.wh-secret-modal .btn.violet:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.wh-secret-modal .btn.ghost { border-color: var(--border-color); color: var(--matrix); opacity: 0.7; }
.wh-secret-modal .btn.ghost:hover { opacity: 1; border-color: var(--matrix); background: transparent; box-shadow: var(--matrix-glow); }

View File

@@ -172,6 +172,44 @@ html[data-theme="light"] {
--accent-glow: none;
}
/* Hover behaviour in light mode.
*
* Dark mode hovers fully invert: bg fills with --matrix/--violet/
* --alert and text becomes --bg. That works on black-cream because
* neon-on-ink has obvious contrast. In light mode that same flip
* lands cream text on near-ink and reads as a jarring colour
* inversion every time the cursor moves.
*
* Light mode therefore tints instead of inverting — hover backgrounds
* use the matching --*-tint-10, text stays in its base colour.
* Targets every common scoped button class via [class*="-btn"] plus
* the unprefixed .btn/button selectors. */
html[data-theme="light"]
:is(button, .btn, [class*="-btn"]):hover:not(:disabled) {
background: var(--matrix-tint-10);
color: var(--matrix);
box-shadow: none;
}
html[data-theme="light"]
:is(button, .btn, [class*="-btn"]).violet:hover:not(:disabled),
html[data-theme="light"]
:is(button, .btn, [class*="-btn"]).primary:hover:not(:disabled) {
background: var(--violet-tint-10);
color: var(--violet);
}
html[data-theme="light"]
:is(button, .btn, [class*="-btn"]).alert:hover:not(:disabled),
html[data-theme="light"]
:is(button, .btn, [class*="-btn"]).danger:hover:not(:disabled) {
background: var(--alert-tint-10);
color: var(--alert);
}
html[data-theme="light"]
:is(button, .btn, [class*="-btn"]).warn:hover:not(:disabled) {
background: var(--warn-tint-10);
color: var(--warn);
}
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
@@ -220,9 +258,9 @@ button:disabled {
/* Shared .btn variants (unscoped) */
.btn.violet { border-color: var(--violet); color: var(--violet); }
.btn.violet:hover { background: var(--violet); color: #000; box-shadow: var(--violet-glow); }
.btn.violet:hover { background: var(--violet); color: var(--bg); box-shadow: var(--violet-glow); }
.btn.alert { border-color: var(--alert); color: var(--alert); }
.btn.alert:hover { background: var(--alert); color: #000; box-shadow: 0 0 10px rgba(255, 65, 65, 0.5); }
.btn.alert:hover { background: var(--alert); color: var(--bg); box-shadow: 0 0 10px rgba(255, 65, 65, 0.5); }
.btn.ghost { border-color: var(--border); color: var(--matrix); opacity: 0.7; }
.btn.ghost:hover {
background: transparent; color: var(--matrix); opacity: 1;