From f97d7d28c920a4ba5fc81b703a17ca8de5765327 Mon Sep 17 00:00:00 2001 From: zurdi Date: Wed, 29 Apr 2026 08:31:14 +0000 Subject: [PATCH] feat(frontend-v2): eliminate hardcoded rgba(255, ...) outside tokens.css MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three new token groups so the 21 sites previously documented as "intentional hardcoded rgba" can use proper tokens instead: - colorOverlay (fixed dark glass, never theme-flips) — drives the surfaces sitting on cover artwork: StatusBadge, GameCard platform-icon / badge, GameActionBtn. Tokens: --r-color-overlay-fg (.95) / -fg-secondary (.85) / -fg-muted (.45) / -border (.12) / -border-strong (.25) / -scrim-soft (.55) / -scrim-strong (.78). - panel + panelBorder pair (theme-flipped, in colorDark/colorLight) — the deep-glass / off-white surfaces that RDialog and RMenuPanel share. The inline `:global(.r-v2-light) X` overrides collapse into single rules. - tooltipBg + tooltipBorder pair (theme-flipped) — moves the v-overlay__content.r-tooltip skin in global.css off literals. - shimmerSweep (theme-flipped) — moves the linear-gradient sweep inside RSkeletonBlock::after off the hand-paired rgba values. Also: RMenuItem danger hover background now uses `color-mix(in srgb, var(--r-color-danger) 12%, transparent)` instead of the raw rgba(255, 80, 80, 0.12) — derived from the canonical danger token. Outside tokens.css (which holds the source values), v2 now has zero `rgba(255, 255, 255, X)` literals. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/scripts/build-tokens.ts | 2 ++ .../components/Gallery/GameCard/GameCard.vue | 14 ++++----- .../Gallery/GameCard/StatusBadge.vue | 16 +++++----- .../components/GameActions/GameActionBtn.vue | 16 +++++----- .../src/v2/lib/menus/RMenuItem/RMenuItem.vue | 4 +-- .../v2/lib/menus/RMenuPanel/RMenuPanel.vue | 13 ++------- .../src/v2/lib/overlays/RDialog/RDialog.vue | 16 ++++------ .../RSkeletonBlock/RSkeletonBlock.vue | 11 +------ frontend/src/v2/styles/global.css | 22 ++++---------- frontend/src/v2/styles/tokens.css | 17 +++++++++++ frontend/src/v2/tokens/index.ts | 29 +++++++++++++++++++ 11 files changed, 87 insertions(+), 73 deletions(-) diff --git a/frontend/scripts/build-tokens.ts b/frontend/scripts/build-tokens.ts index d971ded83..b2fa3bf2c 100644 --- a/frontend/scripts/build-tokens.ts +++ b/frontend/scripts/build-tokens.ts @@ -16,6 +16,7 @@ import { colorBrand, colorDark, colorLight, + colorOverlay, colorStatus, elevation, focus, @@ -102,6 +103,7 @@ function block(selector: string, lines: Entry[], comment?: string): string { const SHARED: Entry[] = [ ...entriesFor(colorBrand, "colorBrand", "--r-color-brand"), ...entriesFor(colorStatus, "colorStatus", "--r-color"), + ...entriesFor(colorOverlay, "colorOverlay", "--r-color-overlay"), ...entriesFor(fontFamily, "fontFamily", "--r-font-family"), ...entriesFor(fontSize, "fontSize", "--r-font-size"), ...entriesFor(lineHeight, "lineHeight", "--r-line-height"), diff --git a/frontend/src/v2/components/Gallery/GameCard/GameCard.vue b/frontend/src/v2/components/Gallery/GameCard/GameCard.vue index 7765409f3..c1c77859b 100644 --- a/frontend/src/v2/components/Gallery/GameCard/GameCard.vue +++ b/frontend/src/v2/components/Gallery/GameCard/GameCard.vue @@ -295,9 +295,9 @@ const morphStyle = computed(() => { min-width: 28px !important; min-height: 28px !important; border-radius: 50% !important; - background: rgba(0, 0, 0, 0.78) !important; - border: 1px solid rgba(255, 255, 255, 0.12) !important; - color: #fff !important; + background: var(--r-color-overlay-scrim-strong) !important; + border: 1px solid var(--r-color-overlay-border) !important; + color: var(--r-color-overlay-fg) !important; transition: background 0.12s ease, border-color 0.12s ease, @@ -305,7 +305,7 @@ const morphStyle = computed(() => { } .r-gc__platform-icon:hover { background: rgba(0, 0, 0, 0.9) !important; - border-color: rgba(255, 255, 255, 0.25) !important; + border-color: var(--r-color-overlay-border-strong) !important; transform: scale(1.08); } @@ -314,14 +314,14 @@ const morphStyle = computed(() => { position: absolute; bottom: 7px; left: 7px; - background: rgba(0, 0, 0, 0.78); - border: 1px solid rgba(255, 255, 255, 0.12); + background: var(--r-color-overlay-scrim-strong); + border: 1px solid var(--r-color-overlay-border); border-radius: var(--r-radius-sm); padding: 2px 6px; font-size: 9.5px; font-weight: var(--r-font-weight-semibold); letter-spacing: 0.03em; - color: rgba(255, 255, 255, 0.85); + color: var(--r-color-overlay-fg-secondary); opacity: 0; transition: opacity 0.12s ease; max-width: calc(100% - 14px); diff --git a/frontend/src/v2/components/Gallery/GameCard/StatusBadge.vue b/frontend/src/v2/components/Gallery/GameCard/StatusBadge.vue index 9722389a1..125855b84 100644 --- a/frontend/src/v2/components/Gallery/GameCard/StatusBadge.vue +++ b/frontend/src/v2/components/Gallery/GameCard/StatusBadge.vue @@ -171,9 +171,9 @@ function onActivatorClick(e: MouseEvent) { cursor: pointer; padding: 0; font-family: inherit; - background: rgba(0, 0, 0, 0.55); - border: 1px dashed rgba(255, 255, 255, 0.45); - color: rgba(255, 255, 255, 0.85); + background: var(--r-color-overlay-scrim-soft); + border: 1px dashed var(--r-color-overlay-fg-muted); + color: var(--r-color-overlay-fg-secondary); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); opacity: 0; @@ -189,9 +189,9 @@ function onActivatorClick(e: MouseEvent) { .r-gc-status:hover, .r-gc-status--open { opacity: 1; - background: rgba(0, 0, 0, 0.75); - border-color: rgba(255, 255, 255, 0.85); - color: #fff; + background: var(--r-color-overlay-scrim-strong); + border-color: var(--r-color-overlay-fg-secondary); + color: var(--r-color-overlay-fg); transform: scale(1.05); } @@ -200,8 +200,8 @@ function onActivatorClick(e: MouseEvent) { badge / rating pattern. */ .r-gc-status--set { border-style: solid; - border-color: rgba(255, 255, 255, 0.18); - background: rgba(0, 0, 0, 0.78); + border-color: var(--r-color-overlay-border-strong); + background: var(--r-color-overlay-scrim-strong); opacity: 1; } diff --git a/frontend/src/v2/components/GameActions/GameActionBtn.vue b/frontend/src/v2/components/GameActions/GameActionBtn.vue index d3d91b4b6..79634e1ae 100644 --- a/frontend/src/v2/components/GameActions/GameActionBtn.vue +++ b/frontend/src/v2/components/GameActions/GameActionBtn.vue @@ -186,11 +186,11 @@ function onClick(e: MouseEvent) { /* Dark glass so the button still reads when sitting on top of a bright or busy cover image in the GameCard overlay. In GameDetails the backdrop is already a dark blurred cover so this tone lands neutral - there too. Light translucent glass (5–10% white) was invisible on - any cover with white or pale art. */ - border: 1px solid rgba(255, 255, 255, 0.14); - background: rgba(0, 0, 0, 0.55); - color: rgba(255, 255, 255, 0.95); + there too. Overlay tokens never theme-flip — they stay dark over + any cover artwork. */ + border: 1px solid var(--r-color-overlay-border); + background: var(--r-color-overlay-scrim-soft); + color: var(--r-color-overlay-fg); display: inline-flex; align-items: center; justify-content: center; @@ -209,9 +209,9 @@ function onClick(e: MouseEvent) { border-color var(--r-motion-fast) var(--r-motion-ease-out); } .r-v2-game-btn:hover { - background: rgba(0, 0, 0, 0.72); - border-color: rgba(255, 255, 255, 0.28); - color: #fff; + background: var(--r-color-overlay-scrim-strong); + border-color: var(--r-color-overlay-border-strong); + color: var(--r-color-overlay-fg); } .r-v2-game-btn:active { transform: scale(0.94); diff --git a/frontend/src/v2/lib/menus/RMenuItem/RMenuItem.vue b/frontend/src/v2/lib/menus/RMenuItem/RMenuItem.vue index 6eb7f3a9b..c137f8cfc 100644 --- a/frontend/src/v2/lib/menus/RMenuItem/RMenuItem.vue +++ b/frontend/src/v2/lib/menus/RMenuItem/RMenuItem.vue @@ -6,7 +6,7 @@ // cases: 15px leading-icon slot (left), label, 9px rounded hover bg. // // Variants: -// * default — white-ish text, hover background rgba(255,255,255,0.09) +// * default — fg-secondary text, hover background var(--r-color-surface) // * active — filled accent (favourited style, brand "--r-color-fav") // * danger — red text, red-tinted hover (destructive actions) import { computed } from "vue"; @@ -165,7 +165,7 @@ const tag = computed(() => { opacity: 0.85; } .r-menu-item--danger:hover:not(.r-menu-item--disabled) { - background: rgba(255, 80, 80, 0.12); + background: color-mix(in srgb, var(--r-color-danger) 12%, transparent); color: var(--r-color-danger); opacity: 1; } diff --git a/frontend/src/v2/lib/menus/RMenuPanel/RMenuPanel.vue b/frontend/src/v2/lib/menus/RMenuPanel/RMenuPanel.vue index 99b1e277d..19a6f7ea2 100644 --- a/frontend/src/v2/lib/menus/RMenuPanel/RMenuPanel.vue +++ b/frontend/src/v2/lib/menus/RMenuPanel/RMenuPanel.vue @@ -31,24 +31,17 @@ const resolvedWidth = diff --git a/frontend/src/v2/lib/overlays/RDialog/RDialog.vue b/frontend/src/v2/lib/overlays/RDialog/RDialog.vue index 83a59ac46..34648f338 100644 --- a/frontend/src/v2/lib/overlays/RDialog/RDialog.vue +++ b/frontend/src/v2/lib/overlays/RDialog/RDialog.vue @@ -173,8 +173,8 @@ void props; .r-dialog__panel { display: flex; flex-direction: column; - background: rgba(16, 12, 28, 0.97); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--r-color-panel); + border: 1px solid var(--r-color-panel-border); border-radius: var(--r-radius-card); backdrop-filter: blur(28px); -webkit-backdrop-filter: blur(28px); @@ -282,15 +282,9 @@ void props; gap: 8px; } -/* Light theme — dialog root carries .r-v2-light only when the app shell - does (teleported content inherits the body data-theme, not our wrapper). - Matches RMenuPanel's light variant so the two surfaces read as one - family across the app. */ -:global(.r-v2.r-v2-light) .r-dialog__panel { - background: rgba(255, 255, 255, 0.97); - border-color: rgba(17, 17, 23, 0.1); - color: var(--r-color-fg); -} +/* The --r-color-panel pair flips automatically with the theme; the only + light-mode-specific tweak left is the foreground colour, which the + panel surface inherits from var(--r-color-fg) — also already paired. */