mirror of
https://github.com/rommapp/romm.git
synced 2026-06-28 06:46:00 +00:00
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:
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user