feat(home): rebuild the landing page on a shared panel system
CI / check (push) Successful in 32s
CI / tests-unit (push) Successful in 2m12s
CI / tests-integration (push) Successful in 2m43s
CI / tests-ui (push) Successful in 4m22s
CI / tests-imaging-browsers (push) Successful in 2m10s
CI / tests-quality (push) Successful in 1m32s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m1s
CI / check (push) Successful in 32s
CI / tests-unit (push) Successful in 2m12s
CI / tests-integration (push) Successful in 2m43s
CI / tests-ui (push) Successful in 4m22s
CI / tests-imaging-browsers (push) Successful in 2m10s
CI / tests-quality (push) Successful in 1m32s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m1s
The homepage mixed four different translucent washes (surface 44-60% plus a backdrop-filter each), rendered the same four counters twice, and showed a dangling "Welcome to " eyebrow because the localized string expected the hotel name. A missing theme wallpaper replaced the whole sky gradient with flat white, and stale CMS image references produced broken images and 404s. Replace the ad-hoc panels with one opaque, theme-derived `.panel` token set (surface, border, shadow, header, radius) and a `.panel-float` variant for the counter strip that straddles the hero edge. The strip is now the single home of the four headline counters; a failed read renders "unavailable" instead of silently dropping a cell. Add the fourth feature card (Games) that already had translated copy, link recent users to their profiles, and fall back to themed placeholders for news covers and camera photos without an upload. Add `pages.home.heroEyebrow` to all 25 locales, layer the CMS wallpaper over `--site-bg-gradient` instead of replacing it, and keep the sky in dark mode. Add `publicAssetExists` so local uploads that no longer exist are skipped (wallpaper, help topics) rather than rendered broken. Fix the `/help` topic icons to use the `/assets/images/help-center/` base the detail page already used. Throttle the spotlight glow to one update per animation frame, drop the dead `.glass-chip` rules, define the missing `.panel-float`, and fix the stat divider and reduced-motion selectors. Stat labels use the readable text token so they clear WCAG AA, and the language button's accessible name now contains its visible label. Verified: tsc, biome, 3590 unit tests, i18n audit unchanged, axe 0 violations at 390/768/1024/1280/1440 in light and dark, quality e2e 16/16.
This commit is contained in:
1 parent
ecd8e5852c
commit
97454f361b
35 files changed
+933
-659
No files matched your search
+177
-81
@@ -500,9 +500,14 @@ summary,
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Site background: Habbo sky ── */
|
||||
/* ── Site background: Habbo sky ──
|
||||
The sky lives in a custom property so the Theme Manager can layer a CMS
|
||||
wallpaper *on top of* it (`background-image: url(...), var(--site-bg-gradient)`)
|
||||
instead of replacing it. Painting the wallpaper as the only layer meant a
|
||||
missing upload silently erased the whole gradient and left the site on flat
|
||||
white. */
|
||||
.site-bg {
|
||||
background: linear-gradient(
|
||||
--site-bg-gradient: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--color-primary) 4%, #b3d9f7) 0%,
|
||||
color-mix(in srgb, var(--color-primary) 6%, #d4e8fa) 25%,
|
||||
@@ -510,7 +515,10 @@ summary,
|
||||
color-mix(in srgb, var(--color-primary) 10%, #f0e8d8) 75%,
|
||||
color-mix(in srgb, var(--color-primary) 14%, var(--color-background)) 100%
|
||||
);
|
||||
background-image: var(--site-bg-gradient);
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
}
|
||||
/* Habbo-style clouds */
|
||||
@@ -711,13 +719,16 @@ html.dark .cloud {
|
||||
);
|
||||
}
|
||||
html.dark .site-bg {
|
||||
background: linear-gradient(
|
||||
--site-bg-gradient: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--color-primary) 6%, #0a0e1a) 0%,
|
||||
color-mix(in srgb, var(--color-primary) 8%, #10152a) 30%,
|
||||
color-mix(in srgb, var(--color-primary) 10%, #161c30) 60%,
|
||||
color-mix(in srgb, var(--color-primary) 12%, #0f141f) 100%
|
||||
);
|
||||
/* Re-declaring `background-image` here would out-specify the wallpaper
|
||||
layer injected by ThemeVars (body.site-bg) and silently drop it in dark
|
||||
mode. The gradient is picked up through `--site-bg-gradient` instead. */
|
||||
background-attachment: fixed;
|
||||
}
|
||||
html.dark .site-bg::before {
|
||||
@@ -2559,17 +2570,6 @@ details[open] > summary .details-open\:rotate-180 {
|
||||
/* ── Classic-landing polish (theme-aware) ────────────────────────────
|
||||
Keeps the Habbo sky and theme colors; adds subtle depth + glow to the
|
||||
restored landing pages. Everything is driven by theme CSS variables. */
|
||||
.brand-halo {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-radius: 999px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
color-mix(in srgb, var(--color-primary) 30%, transparent) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
filter: blur(20px);
|
||||
}
|
||||
.card-glow {
|
||||
transition:
|
||||
box-shadow 0.3s ease,
|
||||
@@ -2647,6 +2647,123 @@ details[open] > summary .details-open\:rotate-180 {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* ── Landing panel system ────────────────────────────────────────────
|
||||
One recipe for every card on the public pages. The old panels each mixed
|
||||
their own translucent washes (surface 44–60% + backdrop blur), which read as
|
||||
a milky smear over the sky gradient, gave the same four numbers two
|
||||
different-looking boxes, and cost a backdrop snapshot per panel on every
|
||||
scroll. These tokens are opaque, theme-derived and shared. */
|
||||
:root {
|
||||
--panel-surface: color-mix(
|
||||
in srgb,
|
||||
var(--color-surface) 93%,
|
||||
var(--color-primary)
|
||||
);
|
||||
--panel-surface-alt: color-mix(
|
||||
in srgb,
|
||||
var(--color-surface) 88%,
|
||||
var(--color-accent)
|
||||
);
|
||||
--panel-surface-sunken: color-mix(
|
||||
in srgb,
|
||||
var(--color-surface) 96%,
|
||||
var(--color-background)
|
||||
);
|
||||
--panel-border: color-mix(in srgb, var(--color-text-muted) 16%, transparent);
|
||||
--panel-border-strong: color-mix(
|
||||
in srgb,
|
||||
var(--color-primary) 32%,
|
||||
transparent
|
||||
);
|
||||
--panel-shadow:
|
||||
0 1px 2px color-mix(in srgb, #0b1220 7%, transparent),
|
||||
0 18px 40px -26px color-mix(in srgb, #0b1220 55%, transparent);
|
||||
--panel-header-bg: color-mix(
|
||||
in srgb,
|
||||
var(--color-primary) 8%,
|
||||
var(--color-surface)
|
||||
);
|
||||
--panel-header-border: color-mix(
|
||||
in srgb,
|
||||
var(--color-text-muted) 12%,
|
||||
transparent
|
||||
);
|
||||
--panel-radius: 18px;
|
||||
}
|
||||
html.dark {
|
||||
--panel-surface: color-mix(
|
||||
in srgb,
|
||||
var(--color-surface) 88%,
|
||||
var(--color-background)
|
||||
);
|
||||
--panel-surface-alt: color-mix(
|
||||
in srgb,
|
||||
var(--color-surface) 82%,
|
||||
var(--color-background)
|
||||
);
|
||||
--panel-surface-sunken: color-mix(
|
||||
in srgb,
|
||||
var(--color-surface) 92%,
|
||||
var(--color-background)
|
||||
);
|
||||
--panel-border: color-mix(in srgb, var(--color-text-muted) 24%, transparent);
|
||||
--panel-border-strong: color-mix(
|
||||
in srgb,
|
||||
var(--color-primary) 46%,
|
||||
transparent
|
||||
);
|
||||
--panel-shadow:
|
||||
0 1px 2px rgb(0 0 0 / 0.4), 0 24px 48px -28px rgb(0 0 0 / 0.85);
|
||||
--panel-header-bg: color-mix(
|
||||
in srgb,
|
||||
var(--color-primary) 13%,
|
||||
var(--color-surface)
|
||||
);
|
||||
--panel-header-border: color-mix(
|
||||
in srgb,
|
||||
var(--color-text-muted) 20%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
/* The standard card. `rounded-[--panel-radius]` is spelled out in the
|
||||
component, this class only owns colour, border and depth. */
|
||||
.panel {
|
||||
background: var(--panel-surface);
|
||||
border: 1px solid var(--panel-border);
|
||||
box-shadow: var(--panel-shadow);
|
||||
}
|
||||
/* A panel that floats over the hero, so it needs a deeper shadow. */
|
||||
.panel-float {
|
||||
box-shadow:
|
||||
0 2px 6px color-mix(in srgb, #0b1220 10%, transparent),
|
||||
0 34px 64px -30px color-mix(in srgb, #0b1220 62%, transparent);
|
||||
}
|
||||
html.dark .panel-float {
|
||||
box-shadow:
|
||||
0 2px 6px rgb(0 0 0 / 0.5),
|
||||
0 36px 70px -30px rgb(0 0 0 / 0.9);
|
||||
}
|
||||
/* A tile that sits *inside* a panel — icon chips, stat wells, list rows. */
|
||||
.panel-sunken {
|
||||
background: var(--panel-surface-sunken);
|
||||
border: 1px solid color-mix(in srgb, var(--color-text-muted) 10%, transparent);
|
||||
}
|
||||
.panel-header {
|
||||
background: var(--panel-header-bg);
|
||||
border-bottom: 1px solid var(--panel-header-border);
|
||||
}
|
||||
/* Icon chip inside a panel. `--panel-tint` is set per instance. */
|
||||
.panel-tile {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--panel-tint, var(--color-primary)) 15%,
|
||||
transparent
|
||||
);
|
||||
box-shadow: inset 0 0 0 1px
|
||||
color-mix(in srgb, var(--panel-tint, var(--color-primary)) 26%, transparent);
|
||||
}
|
||||
|
||||
/* ── Smooth motion (landing) ───────────────────────────────────────── */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
@@ -2668,6 +2785,15 @@ html {
|
||||
|
||||
/* Gentle breathing pulse for the brand glows behind Frank / the hero. */
|
||||
.brand-halo {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-radius: 999px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
color-mix(in srgb, var(--color-primary) 30%, transparent) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
filter: blur(20px);
|
||||
animation: hero-halo-pulse 6s ease-in-out infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
@@ -2691,8 +2817,7 @@ html {
|
||||
.brand-halo {
|
||||
animation: none;
|
||||
}
|
||||
.hero-ring,
|
||||
.glass-chip {
|
||||
.hero-ring {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -2734,48 +2859,6 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
/* Frosted floating stat chips that bob gently on the hero.
|
||||
No backdrop-filter while animating: sampling the backdrop every frame is
|
||||
expensive, so the chips use a higher-opacity fill instead. */
|
||||
.glass-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.45rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--color-surface) 72%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--color-text-muted) 14%, transparent);
|
||||
box-shadow:
|
||||
0 10px 26px -10px color-mix(in srgb, #000 30%, transparent),
|
||||
inset 0 1px 0 0 color-mix(in srgb, #fff 12%, transparent);
|
||||
animation: float 5s ease-in-out infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
.glass-chip:nth-child(2) {
|
||||
animation-delay: -1.7s;
|
||||
}
|
||||
.glass-chip:nth-child(3) {
|
||||
animation-delay: -3.4s;
|
||||
}
|
||||
.glass-chip-dot {
|
||||
width: 0.45rem;
|
||||
height: 0.45rem;
|
||||
border-radius: 999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.glass-chip-value {
|
||||
font-weight: 900;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
.glass-chip-label {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* ── Public page polish: buttons ──────────────────────────────────── */
|
||||
.btn-brand {
|
||||
position: relative;
|
||||
@@ -2957,10 +3040,9 @@ html {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-readable);
|
||||
background: color-mix(in srgb, var(--color-surface) 55%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--color-text-muted) 16%, transparent);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
background: var(--panel-surface);
|
||||
border: 1px solid var(--panel-border);
|
||||
box-shadow: 0 1px 2px color-mix(in srgb, #0b1220 6%, transparent);
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
border-color 0.25s ease,
|
||||
@@ -2970,13 +3052,13 @@ html {
|
||||
.explore-pill:hover {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--color-primary) 16%,
|
||||
var(--color-surface)
|
||||
var(--color-primary) 14%,
|
||||
var(--panel-surface)
|
||||
);
|
||||
border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
|
||||
border-color: var(--panel-border-strong);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 26px -14px
|
||||
color-mix(in srgb, var(--color-primary) 80%, transparent);
|
||||
box-shadow: 0 14px 28px -16px
|
||||
color-mix(in srgb, var(--color-primary) 85%, transparent);
|
||||
}
|
||||
.explore-pill .pill-arrow {
|
||||
opacity: 0;
|
||||
@@ -3079,30 +3161,44 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
/* Dividers between the four counters in the stats panel. */
|
||||
/* Hairlines between the headline counters.
|
||||
The strip is two-up on narrow screens and four-up from `lg`. The old rules
|
||||
only knew the four-up layout: on mobile they drew a single vertical line
|
||||
between the two rows instead of between the two columns, and none between
|
||||
the columns at all. Both rules are drawn from the same token so the grid
|
||||
always reads as four separate figures. */
|
||||
.stat-cell {
|
||||
position: relative;
|
||||
}
|
||||
.stat-cell::before,
|
||||
.stat-cell::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
/* Four across: a vertical rule in front of every cell but the first. */
|
||||
.stat-cell:not(:first-child)::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -0.5rem;
|
||||
top: 18%;
|
||||
height: 64%;
|
||||
left: -0.75rem;
|
||||
top: 8%;
|
||||
height: 84%;
|
||||
width: 1px;
|
||||
background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
|
||||
}
|
||||
}
|
||||
@media (max-width: 1023px) {
|
||||
.stat-cell:nth-child(3)::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -0.5rem;
|
||||
top: 10%;
|
||||
height: 80%;
|
||||
/* Two across: a vertical rule between the two columns of each row… */
|
||||
.stat-cell:nth-child(even)::before {
|
||||
left: -0.75rem;
|
||||
top: 8%;
|
||||
height: 84%;
|
||||
width: 1px;
|
||||
background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
|
||||
}
|
||||
/* …and a horizontal rule between the two rows. */
|
||||
.stat-cell:nth-child(n + 3)::after {
|
||||
left: 6%;
|
||||
right: 6%;
|
||||
top: -0.75rem;
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user