feat(frontend-v2): eliminate hardcoded rgba(255, ...) outside tokens.css

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) <noreply@anthropic.com>
This commit is contained in:
zurdi
2026-04-29 08:31:14 +00:00
parent bd12780c42
commit f97d7d28c9
11 changed files with 87 additions and 73 deletions

View File

@@ -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"),

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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 (510% 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);

View File

@@ -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;
}

View File

@@ -31,24 +31,17 @@ const resolvedWidth =
<style scoped>
.r-menu-panel {
background: rgba(16, 12, 28, 0.97);
background: var(--r-color-panel);
backdrop-filter: blur(28px);
-webkit-backdrop-filter: blur(28px);
border: 1px solid rgba(255, 255, 255, 0.1);
border: 1px solid var(--r-color-panel-border);
border-radius: var(--r-radius-card);
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.7),
0 4px 20px rgba(0, 0, 0, 0.4);
color: #fff;
color: var(--r-color-fg);
font-family: var(--r-font-family-sans);
display: flex;
flex-direction: column;
}
/* Light-mode variant uses an off-white glass surface. */
.r-v2.r-v2-light .r-menu-panel {
background: rgba(255, 255, 255, 0.97);
border-color: rgba(17, 17, 23, 0.1);
color: var(--r-color-fg);
}
</style>

View File

@@ -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. */
</style>
<style>

View File

@@ -47,21 +47,12 @@ const style = {
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.08),
var(--r-color-shimmer-sweep),
transparent
);
animation: r-skeleton-shimmer 1.4s infinite;
}
.r-v2-light .r-skeleton::after {
background: linear-gradient(
90deg,
transparent,
rgba(0, 0, 0, 0.06),
transparent
);
}
@keyframes r-skeleton-shimmer {
100% {
transform: translateX(100%);

View File

@@ -191,21 +191,19 @@ html:not([data-input])
font-weight: var(--r-font-weight-bold);
letter-spacing: 0.08em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.35);
}
.r-v2.r-v2-light .r-eyebrow {
color: var(--r-color-fg-muted);
}
/* ── RTooltip skin ─────────────────────────────────────────────
Vuetify teleports v-tooltip content to <body>, so scoped styles
can't reach it. RTooltip passes `content-class="r-tooltip"` and
these globals paint it in the v2 glass language. */
these globals paint it in the v2 glass language. The tooltip
surface tokens flip with the theme automatically. */
.v-overlay__content.r-tooltip {
background: rgba(7, 7, 15, 0.94);
border: 1px solid rgba(255, 255, 255, 0.09);
background: var(--r-color-tooltip-bg);
border: 1px solid var(--r-color-tooltip-border);
border-radius: 6px;
color: rgba(255, 255, 255, 0.92);
color: var(--r-color-fg);
font-size: 11.5px;
font-weight: 500;
line-height: 1.4;
@@ -219,16 +217,6 @@ html:not([data-input])
-webkit-backdrop-filter: blur(18px);
pointer-events: none;
}
/* Light theme (matches .r-v2-light on the app root). */
.r-v2.r-v2-light .v-overlay__content.r-tooltip,
.v-overlay__content.r-tooltip.r-v2-light {
background: rgba(245, 245, 250, 0.96);
border-color: rgba(17, 17, 23, 0.08);
color: rgba(17, 17, 23, 0.92);
box-shadow:
0 4px 14px rgba(0, 0, 0, 0.1),
0 1px 2px rgba(0, 0, 0, 0.08);
}
/* Shimmer loading — matches the mockup's card skeleton. */
.r-v2-shimmer {

View File

@@ -29,6 +29,13 @@
--r-color-warning: #fbbf24;
--r-color-danger: #ff5050;
--r-color-info: #93c5fd;
--r-color-overlay-fg: rgba(255, 255, 255, 0.95);
--r-color-overlay-fg-secondary: rgba(255, 255, 255, 0.85);
--r-color-overlay-fg-muted: rgba(255, 255, 255, 0.45);
--r-color-overlay-border: rgba(255, 255, 255, 0.12);
--r-color-overlay-border-strong: rgba(255, 255, 255, 0.25);
--r-color-overlay-scrim-soft: rgba(0, 0, 0, 0.55);
--r-color-overlay-scrim-strong: rgba(0, 0, 0, 0.78);
--r-font-family-sans:
"Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, "Inter", Roboto,
sans-serif;
@@ -119,6 +126,11 @@ html[data-input="pad"] .r-v2 {
--r-color-border: rgba(255, 255, 255, 0.07);
--r-color-border-strong: rgba(255, 255, 255, 0.15);
--r-color-focus: rgba(255, 255, 255, 0.45);
--r-color-panel: rgba(16, 12, 28, 0.97);
--r-color-panel-border: rgba(255, 255, 255, 0.1);
--r-color-tooltip-bg: rgba(7, 7, 15, 0.94);
--r-color-tooltip-border: rgba(255, 255, 255, 0.09);
--r-color-shimmer-sweep: rgba(255, 255, 255, 0.08);
}
/* Light surface palette — translucent black over the off-white base. */
@@ -134,4 +146,9 @@ html[data-input="pad"] .r-v2 {
--r-color-border: rgba(0, 0, 0, 0.07);
--r-color-border-strong: rgba(0, 0, 0, 0.15);
--r-color-focus: rgba(0, 0, 0, 0.45);
--r-color-panel: rgba(255, 255, 255, 0.97);
--r-color-panel-border: rgba(17, 17, 23, 0.1);
--r-color-tooltip-bg: rgba(245, 245, 250, 0.96);
--r-color-tooltip-border: rgba(17, 17, 23, 0.08);
--r-color-shimmer-sweep: rgba(0, 0, 0, 0.06);
}

View File

@@ -56,6 +56,14 @@ export const colorDark = {
borderStrong: "rgba(255, 255, 255, 0.15)",
// Used by global.css to draw the focus ring; translucent over dark surfaces.
focus: "rgba(255, 255, 255, 0.45)",
// Distinctive deep glass for menu/dialog panels — paired with --r-color-panel-border.
panel: "rgba(16, 12, 28, 0.97)",
panelBorder: "rgba(255, 255, 255, 0.1)",
// Tooltip surface — slightly more opaque than panel so floating chips read clearly.
tooltipBg: "rgba(7, 7, 15, 0.94)",
tooltipBorder: "rgba(255, 255, 255, 0.09)",
// Skeleton shimmer sweep — the translucent band that animates across .r-skeleton.
shimmerSweep: "rgba(255, 255, 255, 0.08)",
} as const;
export const colorLight = {
@@ -70,6 +78,26 @@ export const colorLight = {
border: "rgba(0, 0, 0, 0.07)",
borderStrong: "rgba(0, 0, 0, 0.15)",
focus: "rgba(0, 0, 0, 0.45)",
panel: "rgba(255, 255, 255, 0.97)",
panelBorder: "rgba(17, 17, 23, 0.1)",
tooltipBg: "rgba(245, 245, 250, 0.96)",
tooltipBorder: "rgba(17, 17, 23, 0.08)",
shimmerSweep: "rgba(0, 0, 0, 0.06)",
} as const;
// Cover-overlay surfaces — fixed dark glass values that never theme-flip.
// These are used by surfaces sitting on top of cover artwork (StatusBadge,
// GameCard chrome, GameActionBtn) where contrast against cover art matters
// more than page theme. Inverting them in light mode would lose contrast
// against bright covers.
export const colorOverlay = {
fg: "rgba(255, 255, 255, 0.95)",
fgSecondary: "rgba(255, 255, 255, 0.85)",
fgMuted: "rgba(255, 255, 255, 0.45)",
border: "rgba(255, 255, 255, 0.12)",
borderStrong: "rgba(255, 255, 255, 0.25)",
scrimSoft: "rgba(0, 0, 0, 0.55)",
scrimStrong: "rgba(0, 0, 0, 0.78)",
} as const;
export const fontFamily = {
@@ -181,6 +209,7 @@ export const tokens = {
colorStatus,
colorDark,
colorLight,
colorOverlay,
fontFamily,
fontSize,
lineHeight,