Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 2m2s
CI / tests-unit (push) Successful in 2m3s
CI / tests-ui (push) Successful in 2m39s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m40s
The Studio furniture pane was present in the DOM the whole time and still
looked empty. Every row existed, had real dimensions, and its image loaded
with a 200 — and none of it was visible.
The pane was wrapped in a `motion/react-m` element declared with
`initial={{ opacity: 0 }}` and `animate={{ opacity: 1 }}`. That minimal entry
renders the element but never runs the animation, so the inline style stayed at
opacity 0 and the content was painted transparently forever. Measured on the
live release: the table sat at opacity 1 directly inside a wrapper pinned at
`style="opacity: 0"`.
It went unnoticed because playwright.ui.config.ts sets reducedMotion to
"reduce", under which the animation is skipped and the element lands straight
on its final value. The existing Studio specs therefore passed while the real
browser showed nothing. That also means the perf win from the minimal entry
was never actually delivering a working fade — it only hid the breakage.
The decorative 150ms fade is now a CSS animation (.studio-list-fade-in). A CSS
animation cannot strand content this way: if it never runs, the element is
simply opaque. motion/react-m had exactly one usage in the app and is gone;
the four files that use the full motion/react are untouched and unaffected.
Adds e2e/ui/studio-visibility.spec.ts, which opts out of reduced motion and
asserts no ancestor of a furniture row is faded below 0.9. Verified it fails
on the old code with `Received: 0` and passes on the new, so this cannot
regress silently again.
3148 lines
76 KiB
CSS
3148 lines
76 KiB
CSS
@import "tailwindcss";
|
|
@plugin "@tailwindcss/forms";
|
|
@plugin "@tailwindcss/typography";
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
|
|
@theme {
|
|
--font-sans:
|
|
var(--font-nunito), "Nunito", ui-sans-serif, system-ui, -apple-system,
|
|
"Segoe UI", sans-serif;
|
|
--breakpoint-sm: 40rem;
|
|
--breakpoint-md: 48rem;
|
|
--breakpoint-lg: 64rem;
|
|
--breakpoint-xl: 80rem;
|
|
--breakpoint-2xl: 96rem;
|
|
--radius-sm: 8px;
|
|
--radius-md: 12px;
|
|
--radius-lg: 16px;
|
|
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
--shadow-card-hover:
|
|
0 8px 24px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.06);
|
|
}
|
|
|
|
/* The palette lives in plain CSS (`:root` / ThemeVars / the admin remap), so
|
|
Tailwind does not know these are theme colors and would otherwise skip the
|
|
`bg-primary`, `bg-destructive`, `text-foreground`, … utilities. Re-declare
|
|
them as inline theme tokens so shadcn components get their intended colors
|
|
while value overrides from the runtime theme still apply via the variable. */
|
|
@theme inline {
|
|
--color-primary: var(--color-primary);
|
|
--color-primary-hover: var(--color-primary-hover);
|
|
--color-primary-foreground: var(--color-primary-foreground);
|
|
--color-background: var(--color-background);
|
|
--color-foreground: var(--color-foreground);
|
|
--color-card: var(--color-card);
|
|
--color-card-foreground: var(--color-card-foreground);
|
|
--color-popover: var(--color-popover);
|
|
--color-popover-foreground: var(--color-popover-foreground);
|
|
--color-secondary: var(--color-secondary);
|
|
--color-secondary-foreground: var(--color-secondary-foreground);
|
|
--color-muted: var(--color-muted);
|
|
--color-muted-foreground: var(--color-muted-foreground);
|
|
--color-accent: var(--color-accent);
|
|
--color-accent-foreground: var(--color-accent-foreground);
|
|
--color-destructive: var(--color-destructive);
|
|
--color-destructive-foreground: var(--color-destructive-foreground);
|
|
--color-border: var(--color-border);
|
|
--color-input: var(--color-input);
|
|
--color-ring: var(--color-ring);
|
|
--color-surface: var(--color-surface);
|
|
--color-text: var(--color-text);
|
|
--color-text-muted: var(--color-text-muted);
|
|
--color-success: var(--color-success);
|
|
--color-warning: var(--color-warning);
|
|
--color-error: var(--color-error);
|
|
--color-info: var(--color-info);
|
|
}
|
|
|
|
/* Default theme variables. ThemeVars may override these at runtime. */
|
|
:root {
|
|
--color-primary: #eeb425;
|
|
--color-primary-hover: #d4a01f;
|
|
--color-primary-foreground: #1a1a2e;
|
|
--color-background: #f4f6f9;
|
|
--color-foreground: #0f172a;
|
|
--color-card: #ffffff;
|
|
--color-card-foreground: #0f172a;
|
|
--color-popover: #ffffff;
|
|
--color-popover-foreground: #0f172a;
|
|
--color-secondary: #f1f5f9;
|
|
--color-secondary-foreground: #0f172a;
|
|
--color-muted: #f1f5f9;
|
|
--color-muted-foreground: #6b7280;
|
|
--color-destructive: #ef4444;
|
|
--color-destructive-foreground: #ffffff;
|
|
--color-border: #e2e8f0;
|
|
--color-input: #e2e8f0;
|
|
--color-ring: #eeb425;
|
|
--color-surface: #ffffff;
|
|
--color-dropdown: #ffffff;
|
|
--color-navbar: #ffffff;
|
|
--color-navbar-text: #1e293b;
|
|
--color-text: #0f172a;
|
|
--color-text-muted: #6b7280;
|
|
--color-text-readable: #0f172a;
|
|
--color-text-muted-readable: #6b7280;
|
|
--color-primary-readable: #000000;
|
|
--color-accent-readable: #006b49;
|
|
--color-primary-foreground-readable: #1a1a2e;
|
|
--color-accent-foreground-readable: #000000;
|
|
--color-navbar-text-readable: #1e293b;
|
|
--color-accent: #10b981;
|
|
--color-accent-foreground: #000000;
|
|
--color-success: #16a34a;
|
|
--color-warning: #eab308;
|
|
--color-error: #ef4444;
|
|
--color-info: #0ea5e9;
|
|
--color-danger: #ef4444;
|
|
--gradient-from: #f59e0b;
|
|
--gradient-to: #10b981;
|
|
--button-color: #eeb425;
|
|
--button-text-color: #1a1a2e;
|
|
--button-text-color-readable: #1a1a2e;
|
|
--button-secondary-color: #22c55e;
|
|
--button-secondary-text-color: #ffffff;
|
|
--button-secondary-text-color-readable: #000000;
|
|
--button-secondary-hover-color: var(--color-success);
|
|
--button-danger-color: #ef4444;
|
|
--button-danger-text-color: #ffffff;
|
|
--button-danger-text-color-readable: #000000;
|
|
--button-danger-hover-color: #dc2626;
|
|
--button-outline-color: #eeb425;
|
|
--button-outline-text-color: #1a1a2e;
|
|
--button-outline-text-color-readable: #1a1a2e;
|
|
--button-outline-hover-color: #d4a01f;
|
|
--border-radius: 12px;
|
|
--border-color: #eeb425;
|
|
--font-family: var(--font-nunito), "Nunito", sans-serif;
|
|
--size-heading-h1: 32px;
|
|
--size-heading-h2: 24px;
|
|
--size-heading-h3: 20px;
|
|
--avatar-border-radius: 10px;
|
|
--avatar-border-color: #eeb425;
|
|
--badge-background: #eeb425;
|
|
--badge-text-color: #000000;
|
|
--badge-border-radius: 6px;
|
|
--card-border-radius: 14px;
|
|
--link-color: #eeb425;
|
|
--link-hover-color: #d4a01f;
|
|
--link-color-readable: #000000;
|
|
--link-hover-color-readable: #000000;
|
|
--color-surface-text-readable: #0f172a;
|
|
--color-dropdown-text-readable: #0f172a;
|
|
--color-pill-text-readable: #0f172a;
|
|
--color-badge-text-readable: #000000;
|
|
--color-overlay-text-readable: #ffffff;
|
|
--input-border-color: #d1d5db;
|
|
--input-focus-color: #eeb425;
|
|
--navbar-height: 56px;
|
|
--muted: var(--color-text-muted);
|
|
--border-subtle: color-mix(in srgb, var(--color-text-muted) 18%, transparent);
|
|
/* Transition variables for smoother, consistent UI */
|
|
--transition-fast: 0.1s;
|
|
--transition-normal: 0.2s;
|
|
--transition-slow: 0.3s;
|
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
/* Dark mode colors + readable contrast vars are generated by ThemeVars. */
|
|
html.dark input,
|
|
html.dark select,
|
|
html.dark textarea {
|
|
background: #11151c;
|
|
color: var(--color-text-readable, var(--color-text));
|
|
}
|
|
html.dark .admin-page table,
|
|
html.dark .admin-card {
|
|
border-color: var(--admin-border);
|
|
}
|
|
html.dark .currency,
|
|
html.dark .article-img {
|
|
background: #1f242d;
|
|
}
|
|
|
|
html {
|
|
scroll-behavior: smooth;
|
|
-webkit-text-size-adjust: 100%;
|
|
text-size-adjust: 100%;
|
|
}
|
|
|
|
/* ── Route transition (CSS only — no animation runtime on public pages) ── */
|
|
@keyframes page-enter-rise {
|
|
from {
|
|
opacity: 0;
|
|
transform: translate3d(0, 10px, 0);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
.page-enter {
|
|
animation: page-enter-rise 240ms ease-out both;
|
|
}
|
|
|
|
/* ── Radio player entrance (was a motion/react initial/animate pair) ────── */
|
|
@keyframes radio-player-enter {
|
|
from {
|
|
opacity: 0;
|
|
transform: translate3d(0, 20px, 0) scale(0.95);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
.radio-player-enter {
|
|
animation: radio-player-enter 400ms cubic-bezier(0.25, 0.1, 0.25, 1) both;
|
|
}
|
|
|
|
/* ── Animated counter entrance (was motion/react on a <span>) ───────────── */
|
|
@keyframes counter-rise {
|
|
from {
|
|
opacity: 0;
|
|
transform: translate3d(0, 12px, 0);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
.counter-rise {
|
|
animation: counter-rise 500ms ease-out both;
|
|
}
|
|
|
|
/* ── Scroll reveal (was motion/react whileInView) ───────────────────────── */
|
|
.motion-reveal {
|
|
opacity: 0;
|
|
}
|
|
@keyframes motion-reveal-rise {
|
|
from {
|
|
opacity: 0;
|
|
transform: translate3d(0, 24px, 0);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
.motion-reveal-in {
|
|
animation: motion-reveal-rise 550ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
}
|
|
/* The reveal hides its content until the animation runs, so a visitor who asks
|
|
for less motion must get it back immediately. The global reduced-motion block
|
|
below already flattens the animation duration; this restores the content. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.motion-reveal {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* ── Photo lightbox (was an AnimatePresence opacity pair) ───────────────── */
|
|
|
|
/* ── Studio furniture fade-in ─────────────────────────────────────────────
|
|
Replaces a `motion/react-m` wrapper that declared initial opacity 0 and
|
|
animate opacity 1. That minimal entry renders the element but never runs
|
|
the animation, so the style stayed at opacity: 0 and the entire furniture
|
|
list was present, loaded and measurable, yet invisible. A CSS animation
|
|
cannot strand content that way: if the animation never runs the element is
|
|
simply opaque. */
|
|
@keyframes studio-list-fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
.studio-list-fade-in {
|
|
animation: studio-list-fade-in 150ms ease-out both;
|
|
}
|
|
|
|
@keyframes lightbox-fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
@keyframes lightbox-fade-out {
|
|
from {
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
.lightbox-enter {
|
|
animation: lightbox-fade-in 200ms ease-out both;
|
|
}
|
|
/* Paired with the 180ms unmount delay in PhotoLightbox: 200ms in, 180ms out,
|
|
matching the 200ms the previous motion transition used. */
|
|
.lightbox-exit {
|
|
animation: lightbox-fade-out 180ms ease-in both;
|
|
}
|
|
|
|
/* ── Route progress indicator ─────────────────────────────────────────── */
|
|
.route-progress {
|
|
position: fixed;
|
|
inset: 0 0 auto 0;
|
|
z-index: 99999;
|
|
height: 3px;
|
|
overflow: hidden;
|
|
background: transparent;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 200ms linear;
|
|
}
|
|
.route-progress[data-loading="true"] {
|
|
opacity: 1;
|
|
}
|
|
@keyframes route-progress-run {
|
|
from {
|
|
width: 0%;
|
|
opacity: 1;
|
|
}
|
|
70% {
|
|
width: 85%;
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
width: 100%;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
.route-progress__bar {
|
|
display: block;
|
|
height: 100%;
|
|
width: 0;
|
|
background: var(--color-primary);
|
|
box-shadow: 0 0 10px var(--color-primary);
|
|
}
|
|
.route-progress[data-loading="true"] .route-progress__bar {
|
|
animation: route-progress-run 400ms ease-in-out both;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html:focus-within {
|
|
scroll-behavior: auto;
|
|
}
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
/* biome-ignore lint/complexity/noImportantStyles: must override any animation/transition for prefers-reduced-motion */
|
|
animation-duration: 0.01ms !important;
|
|
/* biome-ignore lint/complexity/noImportantStyles: must override any animation/transition for prefers-reduced-motion */
|
|
animation-iteration-count: 1 !important;
|
|
/* biome-ignore lint/complexity/noImportantStyles: must override any animation/transition for prefers-reduced-motion */
|
|
transition-duration: 0.01ms !important;
|
|
/* biome-ignore lint/complexity/noImportantStyles: must override any behavior for prefers-reduced-motion */
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|
|
body {
|
|
font-family: var(--font-family);
|
|
color: var(--color-text-readable, var(--color-text, #0f172a));
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
/* Mobile-friendly improvements for every screen */
|
|
button,
|
|
a,
|
|
input,
|
|
select,
|
|
textarea,
|
|
summary,
|
|
[role="button"],
|
|
[role="menuitem"] {
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
/* Momentum scrolling on iOS */
|
|
* {
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
/* Prevent iOS zoom on input focus */
|
|
@media (max-width: 48rem) {
|
|
input,
|
|
select,
|
|
textarea {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
|
|
/* Semantic colors: safe across every complete light/dark preset. */
|
|
.theme-text-muted {
|
|
color: var(--color-text-muted-readable);
|
|
}
|
|
.theme-text-subtle {
|
|
color: var(--color-text-subtle-readable);
|
|
}
|
|
.theme-text-disabled {
|
|
color: var(--color-text-disabled-readable);
|
|
}
|
|
.theme-status-success,
|
|
.theme-status-warning,
|
|
.theme-status-error,
|
|
.theme-status-info {
|
|
border: 1px solid var(--status-border);
|
|
background: var(--status-background);
|
|
color: var(--status-text);
|
|
}
|
|
.theme-status-success {
|
|
--status-border: var(--color-success-border);
|
|
--status-background: var(--color-success-subtle);
|
|
--status-text: var(--color-success-text-readable);
|
|
}
|
|
.theme-status-warning {
|
|
--status-border: var(--color-warning-border);
|
|
--status-background: var(--color-warning-subtle);
|
|
--status-text: var(--color-warning-text-readable);
|
|
}
|
|
.theme-status-error {
|
|
--status-border: var(--color-error-border);
|
|
--status-background: var(--color-error-subtle);
|
|
--status-text: var(--color-error-text-readable);
|
|
}
|
|
.theme-status-info {
|
|
--status-border: var(--color-info-border);
|
|
--status-background: var(--color-info-subtle);
|
|
--status-text: var(--color-info-text-readable);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
border-radius: 100px;
|
|
border: 4px solid transparent;
|
|
background-clip: content-box;
|
|
background-color: #2c3039;
|
|
}
|
|
::-webkit-scrollbar {
|
|
width: 12px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
/* Theme-aware scrollbars inside the admin panel */
|
|
[data-admin] ::-webkit-scrollbar {
|
|
width: 10px;
|
|
height: 10px;
|
|
}
|
|
[data-admin] ::-webkit-scrollbar-thumb {
|
|
background-color: color-mix(
|
|
in srgb,
|
|
var(--admin-text-muted) 32%,
|
|
transparent
|
|
);
|
|
}
|
|
[data-admin] ::-webkit-scrollbar-thumb:hover {
|
|
background-color: color-mix(
|
|
in srgb,
|
|
var(--admin-text-muted) 55%,
|
|
transparent
|
|
);
|
|
}
|
|
|
|
/* ── Atom theme components ────────────────────────────────── */
|
|
@layer components {
|
|
.nav-item {
|
|
@apply flex h-auto md:h-[54px] items-center text-[13px] font-bold px-3 md:px-4 relative;
|
|
color: var(--color-navbar-text-readable, var(--color-navbar-text, #64748b));
|
|
letter-spacing: 0.03em;
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
transition:
|
|
color 0.15s ease-in-out,
|
|
background-color 0.15s ease-in-out;
|
|
border-radius: 0.5rem;
|
|
min-height: 44px;
|
|
}
|
|
.nav-item:hover {
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
background-color: color-mix(in srgb, var(--color-primary) 8%, transparent);
|
|
}
|
|
.nav-item[aria-current="page"] {
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
|
}
|
|
.nav-item:focus-visible {
|
|
outline: 2px solid var(--color-primary-readable, var(--color-primary));
|
|
outline-offset: 2px;
|
|
}
|
|
.dropdown-item {
|
|
@apply block py-2.5 px-4 text-sm font-semibold rounded-lg mx-1;
|
|
color: var(--color-dropdown-text-readable, var(--color-text-readable));
|
|
transition:
|
|
background-color 0.1s ease-in-out,
|
|
color 0.1s ease-in-out;
|
|
min-height: 44px;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
.dropdown-item:hover {
|
|
background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
}
|
|
.dropdown-item:focus-visible {
|
|
outline: 2px solid var(--color-primary-readable, var(--color-primary));
|
|
outline-offset: -2px;
|
|
}
|
|
.dropdown-menu {
|
|
background-color: var(--color-dropdown);
|
|
border: 1px solid
|
|
color-mix(in srgb, var(--color-text-muted) 14%, transparent);
|
|
box-shadow:
|
|
0 8px 32px color-mix(in srgb, rgb(0, 0, 0) 20%, transparent),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 8%, transparent);
|
|
}
|
|
}
|
|
|
|
/* ── Site background: Habbo sky ── */
|
|
.site-bg {
|
|
background: linear-gradient(
|
|
180deg,
|
|
color-mix(in srgb, var(--color-primary) 4%, #b3d9f7) 0%,
|
|
color-mix(in srgb, var(--color-primary) 6%, #d4e8fa) 25%,
|
|
color-mix(in srgb, var(--color-primary) 8%, #e8f0f8) 50%,
|
|
color-mix(in srgb, var(--color-primary) 10%, #f0e8d8) 75%,
|
|
color-mix(in srgb, var(--color-primary) 14%, var(--color-background)) 100%
|
|
);
|
|
background-attachment: fixed;
|
|
position: relative;
|
|
}
|
|
/* Habbo-style clouds */
|
|
.site-bg::before {
|
|
content: "";
|
|
position: fixed;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
z-index: -1;
|
|
background:
|
|
radial-gradient(
|
|
ellipse 240px 60px at 15% 8%,
|
|
rgba(255, 255, 255, 0.5) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 320px 70px at 35% 12%,
|
|
rgba(255, 255, 255, 0.4) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 200px 50px at 55% 6%,
|
|
rgba(255, 255, 255, 0.45) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 280px 65px at 75% 10%,
|
|
rgba(255, 255, 255, 0.5) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 180px 45px at 90% 5%,
|
|
rgba(255, 255, 255, 0.35) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 260px 55px at 50% 18%,
|
|
rgba(255, 255, 255, 0.25) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 200px 40px at 20% 22%,
|
|
rgba(255, 255, 255, 0.2) 0%,
|
|
transparent 100%
|
|
);
|
|
}
|
|
/* Floating cloud puffs drifting across the public background. */
|
|
.clouds {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: -1;
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
}
|
|
.cloud {
|
|
position: absolute;
|
|
width: clamp(140px, 22vw, 260px);
|
|
height: clamp(40px, 7vw, 70px);
|
|
left: 0;
|
|
opacity: 0.7;
|
|
transform: scale(var(--cloud-scale, 1));
|
|
will-change: transform;
|
|
animation-name: cloud-drift;
|
|
animation-timing-function: linear;
|
|
animation-iteration-count: infinite;
|
|
background:
|
|
radial-gradient(
|
|
ellipse 62% 55% at 30% 50%,
|
|
rgba(255, 255, 255, 0.6) 0%,
|
|
transparent 70%
|
|
),
|
|
radial-gradient(
|
|
ellipse 70% 60% at 55% 45%,
|
|
rgba(255, 255, 255, 0.55) 0%,
|
|
transparent 72%
|
|
),
|
|
radial-gradient(
|
|
ellipse 60% 55% at 78% 58%,
|
|
rgba(255, 255, 255, 0.45) 0%,
|
|
transparent 72%
|
|
);
|
|
}
|
|
.cloud-1 {
|
|
--cloud-scale: 1.05;
|
|
top: 5%;
|
|
animation-duration: 34s;
|
|
animation-delay: -8s;
|
|
}
|
|
.cloud-2 {
|
|
--cloud-scale: 0.8;
|
|
top: 12%;
|
|
opacity: 0.85;
|
|
animation-duration: 46s;
|
|
animation-delay: -22s;
|
|
}
|
|
.cloud-3 {
|
|
--cloud-scale: 1.25;
|
|
top: 19%;
|
|
animation-duration: 28s;
|
|
animation-delay: -14s;
|
|
}
|
|
.cloud-4 {
|
|
--cloud-scale: 0.65;
|
|
top: 27%;
|
|
opacity: 0.9;
|
|
animation-duration: 52s;
|
|
animation-delay: -33s;
|
|
}
|
|
.cloud-5 {
|
|
--cloud-scale: 0.95;
|
|
top: 34%;
|
|
opacity: 0.8;
|
|
animation-duration: 38s;
|
|
animation-delay: -19s;
|
|
}
|
|
.cloud-6 {
|
|
--cloud-scale: 1.4;
|
|
top: 42%;
|
|
opacity: 0.55;
|
|
animation-duration: 60s;
|
|
animation-delay: -41s;
|
|
}
|
|
.cloud-7 {
|
|
--cloud-scale: 0.72;
|
|
top: 50%;
|
|
opacity: 0.75;
|
|
animation-duration: 43s;
|
|
animation-delay: -27s;
|
|
}
|
|
.cloud-8 {
|
|
--cloud-scale: 1.1;
|
|
top: 58%;
|
|
opacity: 0.5;
|
|
animation-duration: 66s;
|
|
animation-delay: -51s;
|
|
}
|
|
@keyframes cloud-drift {
|
|
0% {
|
|
transform: translateX(-260px) scale(var(--cloud-scale, 1));
|
|
}
|
|
100% {
|
|
transform: translateX(calc(100vw + 260px)) scale(var(--cloud-scale, 1));
|
|
}
|
|
}
|
|
html.dark .cloud {
|
|
opacity: 0.3;
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.cloud {
|
|
animation: none;
|
|
transform: translateX(calc(var(--cloud-frozen-x, 8vw) + 10vw))
|
|
scale(var(--cloud-scale, 1));
|
|
opacity: 0.5;
|
|
}
|
|
.cloud-1 {
|
|
--cloud-frozen-x: 6vw;
|
|
}
|
|
.cloud-2 {
|
|
--cloud-frozen-x: 32vw;
|
|
}
|
|
.cloud-3 {
|
|
--cloud-frozen-x: 58vw;
|
|
}
|
|
.cloud-4 {
|
|
--cloud-frozen-x: 12vw;
|
|
}
|
|
.cloud-5 {
|
|
--cloud-frozen-x: 44vw;
|
|
}
|
|
.cloud-6 {
|
|
--cloud-frozen-x: 73vw;
|
|
}
|
|
.cloud-7 {
|
|
--cloud-frozen-x: 26vw;
|
|
}
|
|
.cloud-8 {
|
|
--cloud-frozen-x: 66vw;
|
|
}
|
|
}
|
|
|
|
/* Subtle primary glow */
|
|
.site-bg::after {
|
|
content: "";
|
|
position: fixed;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
z-index: -1;
|
|
background:
|
|
radial-gradient(
|
|
ellipse 600px 400px at 20% 0%,
|
|
color-mix(in srgb, var(--color-primary) 10%, transparent) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 500px 300px at 80% 0%,
|
|
color-mix(in srgb, var(--color-primary) 6%, transparent) 0%,
|
|
transparent 100%
|
|
);
|
|
}
|
|
html.dark .site-bg {
|
|
background: 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%
|
|
);
|
|
background-attachment: fixed;
|
|
}
|
|
html.dark .site-bg::before {
|
|
opacity: 0.08;
|
|
background:
|
|
radial-gradient(
|
|
ellipse 240px 60px at 15% 8%,
|
|
rgba(200, 210, 255, 0.5) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 320px 70px at 35% 12%,
|
|
rgba(200, 210, 255, 0.4) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 200px 50px at 55% 6%,
|
|
rgba(200, 210, 255, 0.45) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 280px 65px at 75% 10%,
|
|
rgba(200, 210, 255, 0.5) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 180px 45px at 90% 5%,
|
|
rgba(200, 210, 255, 0.35) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 260px 55px at 50% 18%,
|
|
rgba(200, 210, 255, 0.25) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 200px 40px at 20% 22%,
|
|
rgba(200, 210, 255, 0.2) 0%,
|
|
transparent 100%
|
|
);
|
|
}
|
|
html.dark .site-bg::after {
|
|
background:
|
|
radial-gradient(
|
|
ellipse 600px 400px at 20% 0%,
|
|
color-mix(in srgb, var(--color-primary) 14%, transparent) 0%,
|
|
transparent 100%
|
|
),
|
|
radial-gradient(
|
|
ellipse 500px 300px at 80% 0%,
|
|
color-mix(in srgb, var(--color-primary) 8%, transparent) 0%,
|
|
transparent 100%
|
|
);
|
|
}
|
|
|
|
/* Admin must not inherit the public wallpaper — solid HK canvas only. */
|
|
body.site-bg:has([data-admin]),
|
|
html.dark body.site-bg:has([data-admin]) {
|
|
background: var(--admin-canvas);
|
|
background-image: none;
|
|
background-blend-mode: normal;
|
|
}
|
|
|
|
/* ── Animated site backdrop (Theme Manager effects) ────────────────
|
|
A fixed, decorative layer between the body wallpaper and the content.
|
|
Theme-driven: aurora + particles are pure CSS on transform/opacity, so
|
|
they stay cheap and pause for prefers-reduced-motion automatically. */
|
|
.site-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: -1;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
contain: layout paint style;
|
|
}
|
|
.site-backdrop > span {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
}
|
|
body:has([data-admin]) .site-backdrop {
|
|
display: none;
|
|
}
|
|
|
|
/* Aurora blobs (large, soft radial gradients — no filters). */
|
|
.site-backdrop > .site-backdrop-aurora {
|
|
inset: -12% -10%;
|
|
overflow: hidden;
|
|
}
|
|
.site-backdrop-blob {
|
|
position: absolute;
|
|
border-radius: 999px;
|
|
opacity: 0.42;
|
|
will-change: transform;
|
|
animation: aurora-drift 20s ease-in-out infinite alternate;
|
|
}
|
|
.site-backdrop-blob-1 {
|
|
width: clamp(320px, 44vw, 620px);
|
|
height: clamp(320px, 44vw, 620px);
|
|
top: -14%;
|
|
left: -10%;
|
|
background: radial-gradient(
|
|
circle at 32% 32%,
|
|
color-mix(in srgb, var(--color-primary) 60%, transparent) 0%,
|
|
transparent 68%
|
|
);
|
|
}
|
|
.site-backdrop-blob-2 {
|
|
width: clamp(280px, 38vw, 540px);
|
|
height: clamp(280px, 38vw, 540px);
|
|
top: -10%;
|
|
right: -8%;
|
|
background: radial-gradient(
|
|
circle at 62% 40%,
|
|
color-mix(in srgb, var(--color-accent) 48%, transparent) 0%,
|
|
transparent 68%
|
|
);
|
|
animation-delay: -7s;
|
|
animation-duration: 24s;
|
|
}
|
|
.site-backdrop-blob-3 {
|
|
width: clamp(360px, 50vw, 700px);
|
|
height: clamp(360px, 50vw, 700px);
|
|
bottom: -22%;
|
|
left: 12%;
|
|
background: radial-gradient(
|
|
circle at 50% 50%,
|
|
color-mix(in srgb, var(--color-primary) 34%, transparent) 0%,
|
|
color-mix(in srgb, var(--color-accent) 18%, transparent) 42%,
|
|
transparent 72%
|
|
);
|
|
animation-delay: -13s;
|
|
animation-duration: 28s;
|
|
}
|
|
@keyframes aurora-drift {
|
|
0% {
|
|
transform: translate3d(0, 0, 0) scale(1) rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: translate3d(6vw, 5vh, 0) scale(1.16) rotate(6deg);
|
|
}
|
|
}
|
|
html.dark .site-backdrop-blob {
|
|
opacity: 0.55;
|
|
}
|
|
|
|
/* Floating particles drifting upward. */
|
|
.site-backdrop-particles {
|
|
inset: 0;
|
|
}
|
|
.site-backdrop-dot {
|
|
position: absolute;
|
|
bottom: -4vh;
|
|
width: calc(var(--dot-size, 3) * 1px + 1px);
|
|
height: calc(var(--dot-size, 3) * 1px + 1px);
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--color-primary) 70%, transparent);
|
|
box-shadow: 0 0 8px color-mix(in srgb, var(--color-primary) 55%, transparent);
|
|
opacity: 0;
|
|
animation: particle-rise var(--dot-duration, 12s) linear infinite;
|
|
animation-delay: var(--dot-delay, 0s);
|
|
will-change: transform;
|
|
}
|
|
@keyframes particle-rise {
|
|
0% {
|
|
transform: translate3d(0, 0, 0);
|
|
opacity: 0;
|
|
}
|
|
10% {
|
|
opacity: var(--dot-alpha, 0.45);
|
|
}
|
|
88% {
|
|
opacity: var(--dot-alpha, 0.45);
|
|
}
|
|
100% {
|
|
transform: translate3d(var(--dot-drift, 2vw), -108vh, 0);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
html.dark .site-backdrop-dot {
|
|
background: color-mix(in srgb, var(--color-primary) 82%, transparent);
|
|
}
|
|
|
|
/* Overlay tint above image/effects keeps custom backgrounds readable. */
|
|
.site-backdrop-overlay {
|
|
inset: 0;
|
|
background: var(--background-overlay, transparent);
|
|
}
|
|
|
|
/* Show only the selected effect(s). */
|
|
.site-backdrop[data-effect="none"] .site-backdrop-particles,
|
|
.site-backdrop[data-effect="none"] .site-backdrop-aurora,
|
|
.site-backdrop[data-effect="aurora"] .site-backdrop-particles,
|
|
.site-backdrop[data-effect="particles"] .site-backdrop-aurora {
|
|
display: none;
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.site-backdrop-blob {
|
|
animation: none;
|
|
}
|
|
.site-backdrop-dot {
|
|
animation: none;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.article-body {
|
|
line-height: 1.7;
|
|
}
|
|
.article-body img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 8px;
|
|
}
|
|
.article-body p {
|
|
margin: 0 0 0.85rem;
|
|
}
|
|
.article-body a {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.text-muted {
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
}
|
|
.text-nav {
|
|
color: var(--color-navbar-text-readable, var(--color-navbar-text));
|
|
}
|
|
.text-shadow {
|
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
|
|
}
|
|
.text-shadow-sm {
|
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
|
|
}
|
|
.hover-lift {
|
|
transition:
|
|
transform 0.2s ease,
|
|
box-shadow 0.2s ease;
|
|
}
|
|
.hover-lift:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-card-hover);
|
|
}
|
|
/* ── App component classes (used across the converted pages) ── */
|
|
h1 {
|
|
font-size: var(--size-heading-h1, 30px);
|
|
font-weight: 800;
|
|
margin: 0.2em 0 0.5em;
|
|
}
|
|
h2 {
|
|
font-size: var(--size-heading-h2, 22px);
|
|
font-weight: 700;
|
|
}
|
|
h3 {
|
|
font-size: var(--size-heading-h3, 18px);
|
|
font-weight: 700;
|
|
}
|
|
a {
|
|
color: var(--link-color-readable, var(--link-color));
|
|
text-decoration: none;
|
|
}
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
.muted {
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
font-size: 0.875rem;
|
|
}
|
|
.hero {
|
|
background: linear-gradient(
|
|
135deg,
|
|
color-mix(in srgb, var(--gradient-from) 14%, var(--color-navbar)),
|
|
color-mix(in srgb, var(--gradient-to) 8%, var(--color-navbar))
|
|
);
|
|
border: 1px solid color-mix(in srgb, var(--color-primary) 20%, transparent);
|
|
border-radius: var(--radius-lg);
|
|
padding: 1.5rem 1.75rem;
|
|
box-shadow: var(--shadow-card);
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.card {
|
|
background: var(--color-surface);
|
|
color: var(--color-surface-text-readable, var(--color-text-readable));
|
|
border: 1px solid color-mix(in srgb, var(--color-text-muted) 10%, transparent);
|
|
border-radius: var(--card-border-radius);
|
|
box-shadow:
|
|
0 1px 2px rgba(0, 0, 0, 0.04),
|
|
0 2px 8px rgba(0, 0, 0, 0.03),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 6%, transparent);
|
|
padding: 1.1rem 1.25rem;
|
|
transition:
|
|
transform 0.2s ease,
|
|
box-shadow 0.2s ease;
|
|
}
|
|
.card:hover {
|
|
box-shadow:
|
|
0 4px 12px rgba(0, 0, 0, 0.06),
|
|
0 8px 24px rgba(0, 0, 0, 0.04),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 8%, transparent);
|
|
}
|
|
.card.hover:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow:
|
|
0 6px 16px rgba(0, 0, 0, 0.08),
|
|
0 12px 32px rgba(0, 0, 0, 0.06),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 10%, transparent);
|
|
}
|
|
.article-img {
|
|
width: 100%;
|
|
height: 140px;
|
|
object-fit: cover;
|
|
display: block;
|
|
background: linear-gradient(135deg, #fde68a, #fbbf24);
|
|
}
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.4rem;
|
|
padding: 0.5rem 1.1rem;
|
|
border-radius: var(--border-radius);
|
|
border: 2px solid transparent;
|
|
font-family: inherit;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
/* Desktop density: 40px. WCAG 2.2 AA only asks for 24px, so this is already
|
|
comfortable for a mouse. The 44px touch-target size (WCAG 2.1 AAA 2.5.5 /
|
|
Apple HIG) is applied below only for coarse pointers — growing every
|
|
button on desktop pushed each admin dialog and table 4px taller for no
|
|
benefit to mouse users. */
|
|
min-height: 40px;
|
|
transition: all 0.18s ease;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--color-text-muted) 14%,
|
|
var(--color-surface)
|
|
);
|
|
color: var(--color-text-readable, var(--color-text));
|
|
text-decoration: none;
|
|
}
|
|
|
|
/* Touch devices get the comfortable target size; desktop keeps the denser
|
|
layout. `pointer: coarse` matches finger input, so a hybrid laptop still uses
|
|
the 40px desktop metrics for its trackpad. */
|
|
@media (pointer: coarse) {
|
|
.btn {
|
|
min-height: 44px;
|
|
}
|
|
}
|
|
.btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
text-decoration: none;
|
|
}
|
|
.btn-primary {
|
|
background: var(--button-color);
|
|
color: var(--button-text-color-readable, var(--button-text-color));
|
|
}
|
|
.btn-secondary {
|
|
background: var(--button-secondary-color);
|
|
color: var(
|
|
--button-secondary-text-color-readable,
|
|
var(--button-secondary-text-color)
|
|
);
|
|
}
|
|
.btn-danger {
|
|
background: var(--button-danger-color);
|
|
color: var(
|
|
--button-danger-text-color-readable,
|
|
var(--button-danger-text-color)
|
|
);
|
|
}
|
|
.btn-outline {
|
|
background: transparent;
|
|
border-color: var(--button-outline-color);
|
|
color: var(
|
|
--button-outline-text-color-readable,
|
|
var(--button-outline-text-color)
|
|
);
|
|
}
|
|
input,
|
|
select,
|
|
textarea {
|
|
font-family: inherit;
|
|
font-size: 14px;
|
|
padding: 0.55rem 0.8rem;
|
|
border: 2px solid color-mix(in srgb, var(--color-text-muted) 20%, transparent);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--color-surface);
|
|
color: var(--color-text-readable, var(--color-text));
|
|
outline: none;
|
|
transition:
|
|
border-color 0.2s ease,
|
|
box-shadow 0.2s ease;
|
|
}
|
|
input:focus,
|
|
select:focus,
|
|
textarea:focus {
|
|
border-color: var(--color-primary);
|
|
box-shadow: 0 0 0 3px
|
|
color-mix(in srgb, var(--color-primary) 15%, transparent);
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
th {
|
|
text-align: left;
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
font-size: 0.78rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
padding: 0.5rem 0.6rem;
|
|
font-weight: 700;
|
|
}
|
|
td {
|
|
padding: 0.6rem 0.6rem;
|
|
border-top: 1px solid
|
|
color-mix(in srgb, var(--color-text-muted) 10%, transparent);
|
|
}
|
|
tbody tr {
|
|
transition: background 0.15s ease;
|
|
}
|
|
tbody tr:hover {
|
|
background: color-mix(in srgb, var(--color-primary) 5%, transparent);
|
|
}
|
|
.avatar {
|
|
border-radius: var(--avatar-border-radius);
|
|
background: var(--color-surface);
|
|
border: 2px solid var(--avatar-border-color);
|
|
}
|
|
.currency {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
font-weight: 800;
|
|
font-size: 0.85rem;
|
|
padding: 0.2rem 0.6rem 0.2rem 0.2rem;
|
|
border-radius: 999px;
|
|
background: var(--color-surface);
|
|
border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);
|
|
color: var(--color-pill-text-readable, var(--color-text-readable));
|
|
}
|
|
.nav-credit-icon {
|
|
background: #e9b124 url("/assets/images/icons/currency/credits.png") center
|
|
no-repeat;
|
|
background-size: contain;
|
|
outline: 1px solid #b26d18;
|
|
}
|
|
.nav-ducket-icon {
|
|
background: #c44aac url("/assets/images/icons/currency/duckets.png") center
|
|
no-repeat;
|
|
background-size: contain;
|
|
outline: 1px solid #812378;
|
|
}
|
|
.nav-diamond-icon {
|
|
background: #caf1f3 url("/assets/images/icons/currency/diamonds.png") center
|
|
no-repeat;
|
|
background-size: contain;
|
|
outline: 1px solid #6caff4;
|
|
}
|
|
/* ── Admin panel (Housekeeping) ─────────────────────────────────
|
|
Modern, consistent styling for all admin content pages. */
|
|
.admin-page {
|
|
scroll-margin-top: 1rem;
|
|
}
|
|
|
|
/* Hub chrome already shows the section title — soften page-level heroes. */
|
|
.admin-hub-panel > .space-y-6 > div:first-child > h1.text-3xl {
|
|
font-size: 1.15rem;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
/* Page heading */
|
|
.admin-page main > h1:first-child,
|
|
.admin-page > h1:first-child {
|
|
margin-top: 0;
|
|
}
|
|
|
|
/* Cards used inside admin pages */
|
|
.admin-card {
|
|
background: var(--admin-surface);
|
|
color: var(--admin-text);
|
|
border: 1px solid var(--admin-border);
|
|
border-radius: 16px;
|
|
box-shadow: var(--shadow-card);
|
|
padding: 1.25rem;
|
|
transition: box-shadow 0.2s ease;
|
|
}
|
|
|
|
/* Card with filter/search bar — compact horizontal layout */
|
|
.admin-filter-bar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.admin-filter-bar input,
|
|
.admin-filter-bar select {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
}
|
|
|
|
/* Compact stat row for list pages (counts, summaries) */
|
|
.admin-stat-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.admin-stat-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.4rem 0.75rem;
|
|
background: color-mix(in srgb, var(--admin-accent) 8%, var(--admin-surface));
|
|
border: 1px solid color-mix(in srgb, var(--admin-accent) 15%, transparent);
|
|
border-radius: 999px;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Tables */
|
|
.admin-page table {
|
|
width: 100%;
|
|
background: var(--admin-surface);
|
|
border: 1px solid var(--admin-border);
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
box-shadow: var(--shadow-card);
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
}
|
|
.admin-page thead th {
|
|
background: color-mix(in srgb, var(--admin-accent) 8%, var(--admin-surface));
|
|
color: var(--admin-text);
|
|
border-bottom: 1px solid var(--admin-border);
|
|
white-space: nowrap;
|
|
font-size: 0.68rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.05em;
|
|
text-transform: uppercase;
|
|
padding: 0.75rem 0.9rem;
|
|
}
|
|
.admin-page td {
|
|
padding: 0.7rem 0.9rem;
|
|
border-top: 1px solid
|
|
color-mix(in srgb, var(--admin-text-muted) 8%, transparent);
|
|
font-size: 0.88rem;
|
|
}
|
|
.admin-page tbody tr {
|
|
transition: background 0.12s ease;
|
|
}
|
|
.admin-page tbody tr:hover {
|
|
background: color-mix(in srgb, var(--admin-accent) 4%, transparent);
|
|
}
|
|
.admin-page thead tr:first-child th:first-child {
|
|
border-radius: 16px 0 0 0;
|
|
}
|
|
.admin-page thead tr:first-child th:last-child {
|
|
border-radius: 0 16px 0 0;
|
|
}
|
|
|
|
/* Section heading inside admin pages */
|
|
.admin-section-title {
|
|
font-size: 0.9rem;
|
|
font-weight: 700;
|
|
margin: 1.5rem 0 0.75rem;
|
|
color: var(--admin-text);
|
|
}
|
|
.admin-section-title:first-child {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.admin-info-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
}
|
|
|
|
/* Empty state */
|
|
.admin-empty {
|
|
text-align: center;
|
|
padding: 2.5rem 1rem;
|
|
color: var(--admin-text-muted);
|
|
font-size: 0.85rem;
|
|
}
|
|
/* ── Public design system (faithful to the atom theme) ──────────
|
|
content-card = surface card with a primary-tinted header (icon +
|
|
title + subtitle) and a padded body — the signature atom block. */
|
|
.content-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
background: var(--color-surface);
|
|
color: var(
|
|
--color-surface-text-readable,
|
|
var(--color-text-readable, var(--color-text))
|
|
);
|
|
border: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 10%,
|
|
transparent
|
|
);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow:
|
|
0 1px 2px rgba(0, 0, 0, 0.04),
|
|
0 2px 8px rgba(0, 0, 0, 0.03),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 6%, transparent);
|
|
overflow: hidden;
|
|
transition:
|
|
box-shadow 0.2s ease,
|
|
transform 0.2s ease;
|
|
}
|
|
.content-card:hover {
|
|
box-shadow:
|
|
0 4px 12px rgba(0, 0, 0, 0.06),
|
|
0 8px 24px rgba(0, 0, 0, 0.04),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 8%, transparent);
|
|
}
|
|
.content-card-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.85rem 1.1rem;
|
|
background: linear-gradient(
|
|
135deg,
|
|
color-mix(in srgb, var(--color-primary) 8%, var(--color-surface)),
|
|
color-mix(in srgb, var(--color-primary) 4%, var(--color-surface))
|
|
);
|
|
border-bottom: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 8%,
|
|
transparent
|
|
);
|
|
}
|
|
.content-card-icon {
|
|
flex: none;
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 10px;
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 1.1rem;
|
|
background: linear-gradient(
|
|
135deg,
|
|
color-mix(in srgb, var(--color-primary) 18%, var(--color-surface)),
|
|
color-mix(in srgb, var(--color-primary) 10%, var(--color-surface))
|
|
);
|
|
box-shadow: inset 0 0 0 1px
|
|
color-mix(in srgb, var(--color-primary) 16%, transparent);
|
|
}
|
|
.content-card-head-text {
|
|
min-width: 0;
|
|
flex: 1;
|
|
}
|
|
.content-card-title {
|
|
margin: 0;
|
|
font-weight: 700;
|
|
font-size: 0.92rem;
|
|
line-height: 1.3;
|
|
color: var(
|
|
--color-surface-text-readable,
|
|
var(--color-text-readable, var(--color-text))
|
|
);
|
|
}
|
|
.content-card-subtitle {
|
|
margin: 0.15rem 0 0;
|
|
font-size: 0.75rem;
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
}
|
|
.content-card-action {
|
|
flex: none;
|
|
}
|
|
.content-card-body {
|
|
padding: 1.1rem;
|
|
}
|
|
|
|
/* content-card used as a whole clickable element (e.g. community tiles) */
|
|
a.content-card {
|
|
text-decoration: none;
|
|
}
|
|
a.content-card:hover {
|
|
transform: translateY(-3px);
|
|
}
|
|
a.content-card:focus-visible {
|
|
outline: 2px solid var(--color-primary-readable, var(--color-primary));
|
|
outline-offset: 2px;
|
|
}
|
|
a.content-card {
|
|
transition:
|
|
box-shadow 0.2s ease,
|
|
transform 0.2s ease;
|
|
}
|
|
|
|
/* Responsive card grids (atom: 1 / sm2 / md3 / lg4). */
|
|
.card-grid {
|
|
display: grid;
|
|
gap: 0.75rem;
|
|
grid-template-columns: 1fr;
|
|
}
|
|
@media (min-width: 40rem) {
|
|
.card-grid.sm-2 {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
}
|
|
@media (min-width: 48rem) {
|
|
.card-grid.md-2 {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
.card-grid.md-3 {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
}
|
|
@media (min-width: 64rem) {
|
|
.card-grid.lg-3 {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 1.125rem;
|
|
}
|
|
.card-grid.lg-4 {
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 1.125rem;
|
|
}
|
|
}
|
|
|
|
/* Stat tiles (value in the primary colour + muted label). */
|
|
.stat-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
|
gap: 0.75rem;
|
|
}
|
|
.stat-block {
|
|
background: var(--color-surface);
|
|
border: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 10%,
|
|
transparent
|
|
);
|
|
border-radius: var(--radius-sm);
|
|
padding: 0.85rem 1rem;
|
|
text-align: center;
|
|
transition:
|
|
box-shadow 0.2s ease,
|
|
transform 0.2s ease;
|
|
box-shadow:
|
|
0 1px 2px rgba(0, 0, 0, 0.03),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 5%, transparent);
|
|
}
|
|
.stat-block:hover {
|
|
box-shadow:
|
|
0 2px 8px rgba(0, 0, 0, 0.06),
|
|
0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 8%, transparent);
|
|
transform: translateY(-1px);
|
|
}
|
|
.stat-block-value {
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
line-height: 1.1;
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
}
|
|
.stat-block-label {
|
|
margin-top: 0.2rem;
|
|
font-size: 0.75rem;
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
}
|
|
|
|
/* Empty states */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 2rem 1rem;
|
|
}
|
|
.empty-state-icon {
|
|
font-size: 2rem;
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
|
|
/* Online / offline pill */
|
|
.online-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
font-size: 0.74rem;
|
|
font-weight: 700;
|
|
padding: 0.1rem 0.55rem 0.1rem 0.45rem;
|
|
border-radius: 999px;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 14%,
|
|
transparent
|
|
);
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
}
|
|
.online-badge.online {
|
|
background: color-mix(in srgb, var(--color-success) 16%, transparent);
|
|
color: var(--color-success);
|
|
}
|
|
.online-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 999px;
|
|
background: currentColor;
|
|
}
|
|
.online-badge.online .online-dot {
|
|
background: var(--color-success);
|
|
box-shadow: 0 0 0 3px
|
|
color-mix(in srgb, var(--color-success) 22%, transparent);
|
|
}
|
|
|
|
/* Leaderboard rank chip */
|
|
.rank-badge {
|
|
display: inline-grid;
|
|
place-items: center;
|
|
min-width: 2.1rem;
|
|
height: 2.1rem;
|
|
padding: 0 0.35rem;
|
|
border-radius: 999px;
|
|
font-weight: 800;
|
|
font-size: 0.95rem;
|
|
background: var(--color-background);
|
|
border: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 18%,
|
|
transparent
|
|
);
|
|
}
|
|
.rank-badge.rank-1 {
|
|
background: color-mix(in srgb, var(--color-warning) 20%, transparent);
|
|
border-color: var(--color-warning);
|
|
}
|
|
.rank-badge.rank-2 {
|
|
background: color-mix(in srgb, #94a3b8 22%, transparent);
|
|
border-color: #94a3b8;
|
|
}
|
|
.rank-badge.rank-3 {
|
|
background: color-mix(in srgb, #f97316 20%, transparent);
|
|
border-color: #f97316;
|
|
}
|
|
|
|
/* ── Radio sidebar ──────────────────────────────────────────── */
|
|
.radio-layout {
|
|
display: flex;
|
|
gap: 1.5rem;
|
|
align-items: flex-start;
|
|
}
|
|
.radio-sidebar {
|
|
flex: none;
|
|
width: 200px;
|
|
position: sticky;
|
|
top: 5rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
.radio-sidebar-link:hover {
|
|
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
|
text-decoration: none;
|
|
}
|
|
@media (max-width: 48rem) {
|
|
.radio-layout {
|
|
flex-direction: column;
|
|
}
|
|
.radio-sidebar {
|
|
position: static;
|
|
width: 100%;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
gap: 0.25rem;
|
|
padding-bottom: 0.5rem;
|
|
border-bottom: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 14%,
|
|
transparent
|
|
);
|
|
overflow-x: auto;
|
|
}
|
|
.radio-sidebar-head {
|
|
display: none;
|
|
}
|
|
.radio-sidebar-link {
|
|
font-size: 0.82rem;
|
|
padding: 0.35rem 0.6rem;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
/* ── Skeleton shimmer animation ─────────────────────────────── */
|
|
@keyframes shimmer {
|
|
0% {
|
|
opacity: 0.6;
|
|
}
|
|
50% {
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
|
|
/* Footer CMS info popup */
|
|
.cms-info-trigger {
|
|
appearance: none;
|
|
border: 0;
|
|
background: transparent;
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
cursor: pointer;
|
|
font: inherit;
|
|
font-size: 0.78rem;
|
|
font-weight: 800;
|
|
line-height: 1;
|
|
padding: 0.35rem 0.5rem;
|
|
text-decoration: underline;
|
|
text-decoration-thickness: 2px;
|
|
text-underline-offset: 3px;
|
|
}
|
|
.cms-info-trigger:hover,
|
|
.cms-info-trigger:focus-visible {
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
}
|
|
.cms-info-trigger:focus-visible,
|
|
.cms-info-close:focus-visible {
|
|
outline: 3px solid color-mix(in srgb, var(--color-primary) 42%, transparent);
|
|
outline-offset: 3px;
|
|
}
|
|
/* Centering layer. Transparent and click-through itself, so only the backdrop
|
|
button and the dialog catch pointer events. */
|
|
.cms-info-layer {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 1000;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 1rem;
|
|
pointer-events: none;
|
|
}
|
|
.cms-info-backdrop {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 0;
|
|
border: 0;
|
|
cursor: default;
|
|
background: rgba(8, 10, 14, 0.68);
|
|
backdrop-filter: blur(6px);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
/* Enter/exit were motion/react opacity pairs; the dialog itself also used a
|
|
scale/translate pair. Paired with the 180ms unmount delay in CmsInfoPopup. */
|
|
@keyframes cms-info-backdrop-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
@keyframes cms-info-backdrop-out {
|
|
from {
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
@keyframes cms-info-dialog-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.95) translate3d(0, 10px, 0);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
@keyframes cms-info-dialog-out {
|
|
from {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
transform: scale(0.95) translate3d(0, 10px, 0);
|
|
}
|
|
}
|
|
.cms-info-backdrop-enter {
|
|
animation: cms-info-backdrop-in 200ms ease-out both;
|
|
}
|
|
.cms-info-backdrop-exit {
|
|
animation: cms-info-backdrop-out 180ms ease-in both;
|
|
}
|
|
.cms-info-dialog-enter {
|
|
animation: cms-info-dialog-in 200ms ease-out both;
|
|
}
|
|
.cms-info-dialog-exit {
|
|
animation: cms-info-dialog-out 180ms ease-in both;
|
|
}
|
|
/* Sits above the backdrop button inside the pointer-transparent layer. */
|
|
.cms-info-dialog-layer {
|
|
pointer-events: auto;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
.cms-info-dialog {
|
|
position: relative;
|
|
width: min(440px, 100%);
|
|
border: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-primary) 30%,
|
|
color-mix(in srgb, var(--color-text-muted) 22%, transparent)
|
|
);
|
|
border-radius: 8px;
|
|
background:
|
|
linear-gradient(
|
|
135deg,
|
|
color-mix(in srgb, var(--color-primary) 12%, transparent),
|
|
transparent 42%
|
|
),
|
|
var(--color-surface);
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.34);
|
|
color: var(
|
|
--color-surface-text-readable,
|
|
var(--color-text-readable, var(--color-text))
|
|
);
|
|
padding: 1.25rem;
|
|
}
|
|
.cms-info-close {
|
|
position: absolute;
|
|
top: 0.75rem;
|
|
right: 0.75rem;
|
|
display: grid;
|
|
/* 44px touch target (was 2rem/32px) while keeping the circular button visually
|
|
the same weight via its border and glyph. */
|
|
width: 2.75rem;
|
|
height: 2.75rem;
|
|
place-items: center;
|
|
border: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 18%,
|
|
transparent
|
|
);
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--color-surface) 84%, transparent);
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
cursor: pointer;
|
|
font-weight: 900;
|
|
line-height: 1;
|
|
}
|
|
.cms-info-close:hover {
|
|
color: var(
|
|
--color-surface-text-readable,
|
|
var(--color-text-readable, var(--color-text))
|
|
);
|
|
border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
|
|
}
|
|
.cms-info-kicker {
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
font-size: 0.74rem;
|
|
font-weight: 900;
|
|
letter-spacing: 0;
|
|
text-transform: uppercase;
|
|
}
|
|
.cms-info-dialog h2 {
|
|
margin: 0.2rem 2.2rem 0 0;
|
|
font-size: 1.75rem;
|
|
font-weight: 900;
|
|
line-height: 1.1;
|
|
}
|
|
.cms-info-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
margin-top: 0.65rem;
|
|
border: 1px solid color-mix(in srgb, var(--color-primary) 34%, transparent);
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--color-primary) 14%, transparent);
|
|
color: var(
|
|
--color-surface-text-readable,
|
|
var(--color-text-readable, var(--color-text))
|
|
);
|
|
font-size: 0.75rem;
|
|
font-weight: 900;
|
|
padding: 0.3rem 0.6rem;
|
|
}
|
|
.cms-info-copy {
|
|
margin: 0.9rem 0 0;
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
font-size: 0.92rem;
|
|
line-height: 1.55;
|
|
}
|
|
.cms-info-grid {
|
|
display: grid;
|
|
gap: 0.5rem;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
margin: 1rem 0 0;
|
|
}
|
|
.cms-info-grid div {
|
|
border: 1px solid
|
|
color-mix(
|
|
in srgb,
|
|
var(--color-text-muted-readable, var(--color-text-muted)) 14%,
|
|
transparent
|
|
);
|
|
border-radius: 8px;
|
|
background: color-mix(in srgb, var(--color-background) 68%, transparent);
|
|
padding: 0.7rem;
|
|
}
|
|
.cms-info-grid dt {
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
font-size: 0.68rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
}
|
|
.cms-info-grid dd {
|
|
margin: 0.18rem 0 0;
|
|
color: var(
|
|
--color-surface-text-readable,
|
|
var(--color-text-readable, var(--color-text))
|
|
);
|
|
font-size: 0.8rem;
|
|
font-weight: 900;
|
|
line-height: 1.25;
|
|
}
|
|
.cms-info-credit-list {
|
|
display: grid;
|
|
gap: 0.5rem;
|
|
margin-top: 1rem;
|
|
}
|
|
.cms-info-credit {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
|
|
border-radius: 8px;
|
|
background: color-mix(in srgb, var(--color-primary) 8%, transparent);
|
|
padding: 0.65rem 0.75rem;
|
|
}
|
|
.cms-info-credit span {
|
|
color: var(
|
|
--color-surface-text-readable,
|
|
var(--color-text-readable, var(--color-text))
|
|
);
|
|
font-weight: 900;
|
|
}
|
|
.cms-info-credit small {
|
|
color: var(--color-text-muted-readable, var(--color-text-muted));
|
|
font-size: 0.74rem;
|
|
font-weight: 700;
|
|
text-align: right;
|
|
}
|
|
|
|
@media (max-width: 520px) {
|
|
.cms-info-dialog {
|
|
padding: 1rem;
|
|
}
|
|
.cms-info-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.cms-info-credit {
|
|
align-items: flex-start;
|
|
flex-direction: column;
|
|
gap: 0.2rem;
|
|
}
|
|
.cms-info-credit small {
|
|
text-align: left;
|
|
}
|
|
}
|
|
|
|
/* ── Mobile responsive overrides ──────────────────────────── */
|
|
@media (max-width: 48rem) {
|
|
h1 {
|
|
font-size: clamp(1.25rem, 5vw, var(--size-heading-h1, 30px));
|
|
}
|
|
h2 {
|
|
font-size: clamp(1.1rem, 4vw, var(--size-heading-h2, 22px));
|
|
}
|
|
h3 {
|
|
font-size: clamp(0.95rem, 3.5vw, var(--size-heading-h3, 18px));
|
|
}
|
|
.hero {
|
|
padding: 1.25rem 1rem;
|
|
border-radius: var(--radius-md);
|
|
}
|
|
.card {
|
|
padding: 0.85rem 1rem;
|
|
border-radius: var(--radius-md);
|
|
}
|
|
.admin-card {
|
|
padding: 0.85rem;
|
|
}
|
|
.admin-filter-bar input,
|
|
.admin-filter-bar select {
|
|
min-width: 140px;
|
|
}
|
|
.admin-page td,
|
|
.admin-page th {
|
|
white-space: nowrap;
|
|
padding: 0.5rem 0.6rem;
|
|
font-size: 0.78rem;
|
|
}
|
|
.stat-grid {
|
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
gap: 0.6rem;
|
|
}
|
|
.stat-block {
|
|
padding: 0.65rem 0.5rem;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.stat-block-value {
|
|
font-size: 1.25rem;
|
|
}
|
|
.btn {
|
|
font-size: 13px;
|
|
padding: 0.4rem 0.85rem;
|
|
min-height: 44px;
|
|
}
|
|
.content-card-head {
|
|
padding: 0.65rem 0.85rem;
|
|
}
|
|
.content-card-body {
|
|
padding: 0.85rem;
|
|
}
|
|
.content-card-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 8px;
|
|
font-size: 0.95rem;
|
|
}
|
|
.nav-item {
|
|
white-space: normal;
|
|
text-wrap: balance;
|
|
font-size: 12px;
|
|
padding: 0.5rem 0.75rem;
|
|
height: auto;
|
|
min-height: 44px;
|
|
}
|
|
.dropdown-item {
|
|
font-size: 13px;
|
|
padding: 0.6rem 0.85rem;
|
|
min-height: 44px;
|
|
}
|
|
.content-card-title {
|
|
font-size: 0.85rem;
|
|
}
|
|
.content-card-subtitle {
|
|
font-size: 0.7rem;
|
|
}
|
|
}
|
|
|
|
/* Rich content-page grid (replaces repeated inline `display:grid;gap:1.5rem`). */
|
|
.page-grid {
|
|
display: grid;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
/* make tables scroll on small screens */
|
|
@media (max-width: 640px) {
|
|
table {
|
|
display: table;
|
|
}
|
|
.table-scroll {
|
|
display: block;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
border-radius: var(--radius-sm);
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: thin;
|
|
}
|
|
}
|
|
|
|
/* Cell text truncation helper for dense tables so long values (motto, URLs)
|
|
collapse gracefully instead of blowing out row width on small screens. */
|
|
.table-cell-truncate {
|
|
max-width: 14rem;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Safe-area insets for notched phones (header/nav/footer sit at screen edges) */
|
|
@supports (padding: max(0px)) {
|
|
.site-header,
|
|
nav,
|
|
footer {
|
|
padding-left: max(env(safe-area-inset-left), 0px);
|
|
padding-right: max(env(safe-area-inset-right), 0px);
|
|
}
|
|
}
|
|
|
|
/* ── Admin (HK) semantic token remap ──────────────────────────────────────
|
|
Shared shadcn UI primitives (Button, Badge, Input, Select, Card, Table,
|
|
Tabs, Dialog, Switch, Checkbox, Tooltip, …) are built on the site's
|
|
semantic tokens (--color-background, --color-primary, --color-popover,
|
|
--color-border, --color-ring, --color-muted-foreground, …). On admin
|
|
pages we remap those tokens onto the admin palette so every embedded
|
|
component renders with the admin theme and guaranteed contrast, without
|
|
touching any component file.
|
|
|
|
Gated by `body:has([data-admin])` so the public site is completely
|
|
unaffected. This also covers Radix portals (Dialog/Select/Dropdown/
|
|
Popover/Tooltip) which mount under <body>. */
|
|
body:has([data-admin]) {
|
|
/* Full public→admin token remap so shadcn + body + leftover
|
|
public utilities all inherit the admin palette with contrast. */
|
|
--color-background: var(--admin-canvas);
|
|
--color-foreground: var(--admin-text);
|
|
--color-card: var(--admin-surface);
|
|
--color-card-foreground: var(--admin-text);
|
|
--color-popover: var(--admin-surface-elevated);
|
|
--color-popover-foreground: var(--admin-text);
|
|
--color-primary: var(--admin-accent);
|
|
--color-primary-hover: color-mix(in srgb, var(--admin-accent) 84%, #000000);
|
|
--color-primary-foreground: var(--admin-accent-foreground);
|
|
--color-secondary: color-mix(
|
|
in srgb,
|
|
var(--admin-accent) 14%,
|
|
var(--admin-surface)
|
|
);
|
|
--color-secondary-foreground: var(--admin-text);
|
|
--color-muted: color-mix(in srgb, var(--admin-text) 9%, var(--admin-surface));
|
|
--color-muted-foreground: var(--admin-text-muted);
|
|
--color-accent: color-mix(
|
|
in srgb,
|
|
var(--admin-accent) 16%,
|
|
var(--admin-surface)
|
|
);
|
|
--color-accent-foreground: var(--admin-text);
|
|
--color-destructive: var(--admin-error);
|
|
--color-destructive-foreground: var(--admin-error-foreground);
|
|
--color-border: var(--admin-border);
|
|
--color-input: var(--admin-border);
|
|
--color-ring: var(--admin-accent);
|
|
/* Public Atom tokens still referenced by body / .muted / inputs */
|
|
--color-text: var(--admin-text);
|
|
--color-text-readable: var(--admin-text);
|
|
--color-text-muted: var(--admin-text-muted);
|
|
--color-text-muted-readable: var(--admin-text-muted);
|
|
--color-surface: var(--admin-surface);
|
|
--color-surface-text-readable: var(--admin-text);
|
|
--color-dropdown: var(--admin-surface);
|
|
--color-dropdown-text-readable: var(--admin-text);
|
|
--input-border-color: var(--admin-border);
|
|
--input-focus-color: var(--admin-accent);
|
|
--muted: var(--admin-text-muted);
|
|
--border-subtle: color-mix(in srgb, var(--admin-text-muted) 22%, transparent);
|
|
color: var(--admin-text);
|
|
background-color: var(--admin-canvas);
|
|
font-family: var(--font-family);
|
|
}
|
|
|
|
/* Admin forms / boxes follow admin tokens in both light and dark. */
|
|
body:has([data-admin])
|
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
[type="file"]
|
|
):not([type="color"]),
|
|
body:has([data-admin]) select,
|
|
body:has([data-admin]) textarea {
|
|
background-color: var(--admin-surface-elevated);
|
|
color: var(--admin-text);
|
|
border-color: var(--admin-border);
|
|
caret-color: var(--admin-text);
|
|
}
|
|
body:has([data-admin]) input::placeholder,
|
|
body:has([data-admin]) textarea::placeholder {
|
|
color: var(--admin-text-muted);
|
|
opacity: 0.85;
|
|
}
|
|
body:has([data-admin]) .muted,
|
|
body:has([data-admin]) .admin-empty,
|
|
body:has([data-admin]) .text-muted-foreground {
|
|
color: var(--admin-text-muted);
|
|
}
|
|
body:has([data-admin]) .admin-card,
|
|
body:has([data-admin]) .admin-page table {
|
|
background: var(--admin-surface);
|
|
color: var(--admin-text);
|
|
border-color: var(--admin-border);
|
|
}
|
|
body:has([data-admin]) .btn-outline {
|
|
color: var(--admin-text);
|
|
border-color: var(--admin-border);
|
|
background: transparent;
|
|
}
|
|
body:has([data-admin]) .btn-outline:hover {
|
|
background: color-mix(in srgb, var(--admin-accent) 12%, transparent);
|
|
color: var(--admin-text);
|
|
}
|
|
/* Override public html.dark input hardcodes inside admin. */
|
|
html.dark
|
|
body:has([data-admin])
|
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
[type="file"]
|
|
):not([type="color"]),
|
|
html.dark body:has([data-admin]) select,
|
|
html.dark body:has([data-admin]) textarea {
|
|
background: var(--admin-surface-elevated);
|
|
color: var(--admin-text);
|
|
}
|
|
|
|
/* Opaque floating surfaces for Radix portals (Select / Dropdown / Popover / Dialog).
|
|
Without this, bg-popover can resolve transparent and lists paint over forms. */
|
|
body:has([data-admin]) [data-slot="select-content"],
|
|
body:has([data-admin]) [data-slot="dropdown-menu-content"],
|
|
body:has([data-admin]) [data-slot="dropdown-menu-sub-content"],
|
|
body:has([data-admin]) [data-slot="popover-content"],
|
|
body:has([data-admin]) [data-slot="context-menu-content"],
|
|
body:has([data-admin]) [data-slot="dialog-content"],
|
|
body:has([data-admin]) [data-slot="sheet-content"] {
|
|
background-color: var(--admin-surface-elevated);
|
|
color: var(--admin-text);
|
|
border-color: var(--admin-border);
|
|
box-shadow:
|
|
0 18px 48px rgba(0, 0, 0, 0.45),
|
|
0 4px 12px rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
body:has([data-admin]) [data-slot="select-trigger"],
|
|
body:has([data-admin]) [data-slot="dropdown-menu-trigger"] {
|
|
background-color: var(--admin-surface-elevated);
|
|
color: var(--admin-text);
|
|
border-color: var(--admin-border);
|
|
}
|
|
|
|
body:has([data-admin]) [data-slot="select-item"][data-highlighted],
|
|
body:has([data-admin]) [data-slot="select-item"]:focus,
|
|
body:has([data-admin]) [data-slot="dropdown-menu-item"][data-highlighted],
|
|
body:has([data-admin]) [data-slot="dropdown-menu-item"]:focus,
|
|
body:has([data-admin]) [data-slot="context-menu-item"][data-highlighted],
|
|
body:has([data-admin]) [data-slot="context-menu-item"]:focus {
|
|
background-color: color-mix(
|
|
in srgb,
|
|
var(--admin-accent) 18%,
|
|
var(--admin-surface-elevated)
|
|
);
|
|
color: var(--admin-text);
|
|
}
|
|
|
|
body:has([data-admin]) [data-slot="card"] {
|
|
background-color: var(--admin-surface);
|
|
color: var(--admin-text);
|
|
border-color: var(--admin-border);
|
|
}
|
|
|
|
/* ── Details/summary open states for nav ──────────────────── */
|
|
details[open] > summary .details-open\:hidden {
|
|
display: none;
|
|
}
|
|
details[open] > summary .details-open\:block {
|
|
display: block;
|
|
}
|
|
details[open] > summary .details-open\:rotate-180 {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
/* ── Public error / 404 screen ───────────────────────────── */
|
|
@keyframes error-screen-rise {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(14px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.error-screen {
|
|
position: relative;
|
|
isolation: isolate;
|
|
display: grid;
|
|
place-items: center;
|
|
min-height: min(70vh, 36rem);
|
|
padding: 2rem 1rem 3rem;
|
|
}
|
|
|
|
.error-screen-glow {
|
|
pointer-events: none;
|
|
position: absolute;
|
|
inset: 10% 15% auto;
|
|
height: 12rem;
|
|
border-radius: 50%;
|
|
background: radial-gradient(
|
|
ellipse at center,
|
|
color-mix(in srgb, var(--color-primary) 35%, transparent),
|
|
transparent 70%
|
|
);
|
|
filter: blur(8px);
|
|
z-index: 0;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.error-screen-panel {
|
|
position: relative;
|
|
z-index: 1;
|
|
width: min(100%, 28rem);
|
|
text-align: center;
|
|
padding: 2.25rem 1.75rem 2rem;
|
|
border-radius: 1.25rem;
|
|
border: 1px solid
|
|
color-mix(in srgb, var(--color-primary) 28%, var(--border-subtle));
|
|
background: linear-gradient(
|
|
165deg,
|
|
color-mix(in srgb, var(--color-primary) 10%, var(--color-surface)),
|
|
var(--color-surface)
|
|
);
|
|
box-shadow:
|
|
0 20px 48px color-mix(in srgb, rgb(0, 0, 0) 18%, transparent),
|
|
inset 0 1px 0 color-mix(in srgb, var(--color-primary) 18%, transparent);
|
|
animation: error-screen-rise 0.45s ease-out both;
|
|
}
|
|
|
|
.error-screen-code {
|
|
margin: 0 0 0.65rem;
|
|
font-family: var(--font-pixel), "Courier New", monospace;
|
|
font-size: clamp(2.75rem, 9vw, 3.75rem);
|
|
font-weight: 700;
|
|
letter-spacing: 0.06em;
|
|
line-height: 1;
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
text-shadow: 0 0 36px
|
|
color-mix(in srgb, var(--color-primary) 45%, transparent);
|
|
}
|
|
|
|
.error-screen-title {
|
|
margin: 0 0 0.4rem;
|
|
font-size: 1.4rem;
|
|
font-weight: 800;
|
|
letter-spacing: -0.02em;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.error-screen-subtitle {
|
|
margin: 0 0 0.85rem;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
opacity: 0.88;
|
|
}
|
|
|
|
.error-screen-body {
|
|
margin: 0 0 1.5rem;
|
|
font-size: 0.95rem;
|
|
line-height: 1.55;
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
.error-screen-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.65rem;
|
|
justify-content: center;
|
|
}
|
|
|
|
.error-screen-actions .btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.45rem;
|
|
}
|
|
|
|
.error-screen-btn-icon {
|
|
width: 1rem;
|
|
height: 1rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.error-screen-footer {
|
|
margin-top: 1.25rem;
|
|
}
|
|
|
|
.error-screen-digest {
|
|
margin: 0;
|
|
font-size: 0.72rem;
|
|
color: var(--color-text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
word-break: break-all;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.error-screen-panel {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* ── Premium effects ── */
|
|
@keyframes float {
|
|
0%,
|
|
100% {
|
|
transform: translateY(0);
|
|
}
|
|
50% {
|
|
transform: translateY(-8px);
|
|
}
|
|
}
|
|
@keyframes shimmer-glow {
|
|
0% {
|
|
opacity: 0.4;
|
|
}
|
|
50% {
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
opacity: 0.4;
|
|
}
|
|
}
|
|
@keyframes fade-in-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(16px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
@keyframes scale-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.95);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
@keyframes pulse-glow {
|
|
0%,
|
|
100% {
|
|
opacity: 0.5;
|
|
}
|
|
50% {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.animate-float {
|
|
animation: float 4s ease-in-out infinite;
|
|
will-change: transform;
|
|
}
|
|
.animate-shimmer {
|
|
animation: shimmer-glow 2.5s ease-in-out infinite;
|
|
}
|
|
.animate-fade-in-up {
|
|
animation: fade-in-up 0.6s ease-out both;
|
|
}
|
|
.animate-scale-in {
|
|
animation: scale-in 0.4s ease-out both;
|
|
}
|
|
.animate-pulse-glow {
|
|
animation: pulse-glow 2s ease-in-out infinite;
|
|
}
|
|
.animate-shake {
|
|
animation: shake 0.45s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
|
|
}
|
|
@keyframes shake {
|
|
10%,
|
|
90% {
|
|
transform: translateX(-2px);
|
|
}
|
|
20%,
|
|
80% {
|
|
transform: translateX(4px);
|
|
}
|
|
30%,
|
|
50%,
|
|
70% {
|
|
transform: translateX(-6px);
|
|
}
|
|
40%,
|
|
60% {
|
|
transform: translateX(6px);
|
|
}
|
|
}
|
|
|
|
/* Glass morphism */
|
|
.glass {
|
|
background: color-mix(in srgb, var(--color-surface) 70%, transparent);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
backdrop-filter: blur(20px);
|
|
border: 1px solid color-mix(in srgb, var(--color-text-muted) 10%, transparent);
|
|
}
|
|
.glass-strong {
|
|
background: color-mix(in srgb, var(--color-surface) 85%, transparent);
|
|
-webkit-backdrop-filter: blur(32px);
|
|
backdrop-filter: blur(32px);
|
|
border: 1px solid color-mix(in srgb, var(--color-text-muted) 12%, transparent);
|
|
}
|
|
|
|
/* Premium card with depth */
|
|
.card-premium {
|
|
background: var(--color-surface);
|
|
border: 1px solid color-mix(in srgb, var(--color-text-muted) 10%, transparent);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow:
|
|
0 1px 3px rgba(0, 0, 0, 0.04),
|
|
0 4px 12px rgba(0, 0, 0, 0.04),
|
|
0 12px 32px rgba(0, 0, 0, 0.04);
|
|
transition:
|
|
transform 0.25s ease,
|
|
box-shadow 0.25s ease;
|
|
}
|
|
.card-premium:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow:
|
|
0 1px 3px rgba(0, 0, 0, 0.04),
|
|
0 6px 16px rgba(0, 0, 0, 0.06),
|
|
0 16px 40px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
/* Gradient border wrapper */
|
|
.gradient-border {
|
|
position: relative;
|
|
border-radius: var(--radius-lg);
|
|
background: linear-gradient(
|
|
135deg,
|
|
var(--color-primary),
|
|
color-mix(in srgb, var(--color-primary) 50%, var(--color-accent))
|
|
);
|
|
padding: 1px;
|
|
}
|
|
.gradient-border > * {
|
|
border-radius: calc(var(--radius-lg) - 1px);
|
|
}
|
|
|
|
/* Input focus glow */
|
|
.input-glow:focus {
|
|
outline: none;
|
|
border-color: var(--color-primary);
|
|
box-shadow:
|
|
0 0 0 3px color-mix(in srgb, var(--color-primary) 15%, transparent),
|
|
0 0 20px color-mix(in srgb, var(--color-primary) 10%, transparent);
|
|
}
|
|
.input-glow {
|
|
transition:
|
|
border-color 0.25s ease,
|
|
box-shadow 0.25s ease;
|
|
}
|
|
|
|
/* Typing cursor blink */
|
|
@keyframes blink {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
.typing-cursor::after {
|
|
content: "|";
|
|
animation: blink 0.8s step-end infinite;
|
|
margin-left: 2px;
|
|
font-weight: 300;
|
|
color: var(--color-primary);
|
|
}
|
|
|
|
/* Number count-up */
|
|
@keyframes count-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
.animate-count {
|
|
animation: count-up 0.6s ease-out both;
|
|
}
|
|
|
|
/* Premium button shine */
|
|
.btn-shine {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.btn-shine::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: -50%;
|
|
left: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: linear-gradient(
|
|
transparent 0%,
|
|
transparent 35%,
|
|
color-mix(in srgb, white 8%, transparent) 50%,
|
|
transparent 65%,
|
|
transparent 100%
|
|
);
|
|
transform: translateX(-60%) rotate(25deg);
|
|
transition: transform 0.6s cubic-bezier(0.33, 1, 0.68, 1);
|
|
pointer-events: none;
|
|
will-change: transform;
|
|
}
|
|
.btn-shine:hover::after {
|
|
transform: translateX(30%) rotate(25deg);
|
|
}
|
|
|
|
/* Staggered children animation */
|
|
.stagger-children > * {
|
|
animation: fade-in-up 0.5s ease-out both;
|
|
}
|
|
.stagger-children > *:nth-child(1) {
|
|
animation-delay: 0.05s;
|
|
}
|
|
.stagger-children > *:nth-child(2) {
|
|
animation-delay: 0.1s;
|
|
}
|
|
.stagger-children > *:nth-child(3) {
|
|
animation-delay: 0.15s;
|
|
}
|
|
.stagger-children > *:nth-child(4) {
|
|
animation-delay: 0.2s;
|
|
}
|
|
.stagger-children > *:nth-child(5) {
|
|
animation-delay: 0.25s;
|
|
}
|
|
.stagger-children > *:nth-child(6) {
|
|
animation-delay: 0.3s;
|
|
}
|
|
.stagger-children > *:nth-child(7) {
|
|
animation-delay: 0.35s;
|
|
}
|
|
.stagger-children > *:nth-child(8) {
|
|
animation-delay: 0.4s;
|
|
}
|
|
|
|
/* ── 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,
|
|
transform 0.3s ease,
|
|
border-color 0.3s ease;
|
|
}
|
|
.card-glow:hover {
|
|
box-shadow:
|
|
0 12px 32px -10px color-mix(in srgb, var(--color-primary) 32%, transparent),
|
|
0 0 0 1px color-mix(in srgb, var(--color-primary) 22%, transparent);
|
|
}
|
|
|
|
/* Gradient accent hairline drawn on the top edge of a relative card. */
|
|
.card-hairline {
|
|
position: relative;
|
|
}
|
|
.card-hairline::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset-inline: 2%;
|
|
top: 0;
|
|
height: 2px;
|
|
border-radius: 999px;
|
|
background: linear-gradient(
|
|
90deg,
|
|
transparent,
|
|
color-mix(in srgb, var(--color-primary) 75%, transparent) 30%,
|
|
var(--color-primary) 50%,
|
|
color-mix(in srgb, var(--color-accent) 70%, transparent) 70%,
|
|
transparent
|
|
);
|
|
opacity: 0.55;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Brand gradient text (primary → accent). */
|
|
.gradient-text {
|
|
background: linear-gradient(
|
|
115deg,
|
|
var(--color-primary) 10%,
|
|
color-mix(in srgb, var(--color-primary) 55%, var(--color-accent)) 48%,
|
|
color-mix(in srgb, var(--color-accent) 92%, var(--color-primary)) 92%
|
|
);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
}
|
|
|
|
/* Section eyebrow label with a small accent tick. */
|
|
.eyebrow {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.18em;
|
|
text-transform: uppercase;
|
|
color: var(--color-primary-readable, var(--color-primary));
|
|
}
|
|
.eyebrow::before {
|
|
content: "";
|
|
width: 1.25rem;
|
|
height: 2px;
|
|
border-radius: 999px;
|
|
background: currentColor;
|
|
opacity: 0.75;
|
|
}
|
|
|
|
/* Section heading on the public landing pages. */
|
|
.section-title {
|
|
color: var(--color-text-readable);
|
|
font-family: var(--font-nunito);
|
|
font-weight: 900;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
/* ── Smooth motion (landing) ───────────────────────────────────────── */
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
/* Soft, slow drift for hero artwork (Ken Burns). */
|
|
.ken-burns {
|
|
animation: ken-burns 20s ease-in-out infinite alternate;
|
|
will-change: transform;
|
|
}
|
|
@keyframes ken-burns {
|
|
from {
|
|
transform: scale(1.02) translateY(0);
|
|
}
|
|
to {
|
|
transform: scale(1.08) translateY(-1.5%);
|
|
}
|
|
}
|
|
|
|
/* Gentle breathing pulse for the brand glows behind Frank / the hero. */
|
|
.brand-halo {
|
|
animation: hero-halo-pulse 6s ease-in-out infinite;
|
|
will-change: transform;
|
|
}
|
|
@keyframes hero-halo-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 0.55;
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
opacity: 0.9;
|
|
transform: scale(1.07);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html {
|
|
scroll-behavior: auto;
|
|
}
|
|
.ken-burns,
|
|
.brand-halo {
|
|
animation: none;
|
|
}
|
|
.hero-ring,
|
|
.glass-chip {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* ── Hero pro touches ──────────────────────────────────────────────── */
|
|
/* Continuously shifting gradient hairline drawn on the hero border.
|
|
Animated with opacity only (fully composited — no per-frame repaint). */
|
|
.hero-ring {
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
padding: 1px;
|
|
background: linear-gradient(
|
|
120deg,
|
|
color-mix(in srgb, var(--color-primary) 70%, transparent) 0%,
|
|
color-mix(in srgb, var(--color-accent) 60%, transparent) 35%,
|
|
var(--color-surface) 50%,
|
|
color-mix(in srgb, var(--color-accent) 60%, transparent) 65%,
|
|
color-mix(in srgb, var(--color-primary) 70%, transparent) 100%
|
|
);
|
|
-webkit-mask:
|
|
linear-gradient(#fff 0 0) content-box,
|
|
linear-gradient(#fff 0 0);
|
|
-webkit-mask-composite: xor;
|
|
mask:
|
|
linear-gradient(#fff 0 0) content-box,
|
|
linear-gradient(#fff 0 0);
|
|
mask-composite: exclude;
|
|
pointer-events: none;
|
|
animation: hero-ring-pulse 14s ease-in-out infinite;
|
|
}
|
|
@keyframes hero-ring-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 0.75;
|
|
}
|
|
50% {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
border-radius: 999px;
|
|
background: linear-gradient(
|
|
120deg,
|
|
var(--color-primary) 0%,
|
|
color-mix(in srgb, var(--color-primary) 72%, var(--color-accent)) 55%,
|
|
color-mix(in srgb, var(--color-accent) 85%, var(--color-primary)) 100%
|
|
);
|
|
color: var(--color-primary-foreground);
|
|
box-shadow:
|
|
0 10px 26px -12px color-mix(in srgb, var(--color-primary) 75%, transparent),
|
|
inset 0 1px 0 0 color-mix(in srgb, white 35%, transparent);
|
|
transition:
|
|
transform 0.22s ease,
|
|
box-shadow 0.22s ease,
|
|
filter 0.22s ease;
|
|
}
|
|
.btn-brand:hover {
|
|
transform: translateY(-2px);
|
|
filter: brightness(1.05);
|
|
box-shadow:
|
|
0 18px 36px -14px color-mix(in srgb, var(--color-primary) 85%, transparent),
|
|
inset 0 1px 0 0 color-mix(in srgb, white 45%, transparent);
|
|
}
|
|
.btn-brand:active {
|
|
transform: translateY(0) scale(0.985);
|
|
}
|
|
.btn-brand:focus-visible {
|
|
outline: none;
|
|
box-shadow:
|
|
0 0 0 3px color-mix(in srgb, var(--color-primary) 35%, transparent),
|
|
0 14px 32px -14px color-mix(in srgb, var(--color-primary) 80%, transparent);
|
|
}
|
|
|
|
/* Frosted CTA meant to sit on top of the dark hero image. */
|
|
.btn-glass-dark {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
border-radius: 999px;
|
|
color: #fff;
|
|
background: color-mix(in srgb, #ffffff 10%, transparent);
|
|
border: 1px solid color-mix(in srgb, #ffffff 34%, transparent);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
backdrop-filter: blur(12px);
|
|
box-shadow: inset 0 1px 0 0 color-mix(in srgb, #ffffff 25%, transparent);
|
|
transition:
|
|
background 0.22s ease,
|
|
border-color 0.22s ease,
|
|
transform 0.22s ease;
|
|
}
|
|
.btn-glass-dark:hover {
|
|
background: color-mix(in srgb, #ffffff 20%, transparent);
|
|
border-color: color-mix(in srgb, #ffffff 60%, transparent);
|
|
transform: translateY(-2px);
|
|
}
|
|
.btn-glass-dark:active {
|
|
transform: translateY(0);
|
|
}
|
|
.btn-glass-dark:focus-visible {
|
|
outline: none;
|
|
border-color: #fff;
|
|
box-shadow: 0 0 0 3px
|
|
color-mix(in srgb, var(--color-primary) 55%, transparent);
|
|
}
|
|
|
|
/* ── Public page polish: auth fields ──────────────────────────────── */
|
|
.auth-label {
|
|
display: block;
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
color: var(--color-text-muted);
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
.auth-input {
|
|
width: 100%;
|
|
border-radius: 14px;
|
|
border: 1.5px solid
|
|
color-mix(in srgb, var(--color-text-muted) 22%, transparent);
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--color-background) 88%,
|
|
var(--color-surface)
|
|
);
|
|
padding: 0.78rem 1rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--color-text-readable);
|
|
transition:
|
|
border-color 0.2s ease,
|
|
box-shadow 0.2s ease,
|
|
background-color 0.2s ease;
|
|
}
|
|
.auth-input::placeholder {
|
|
color: color-mix(in srgb, var(--color-text-muted) 75%, transparent);
|
|
font-weight: 500;
|
|
}
|
|
.auth-input:hover:not(:disabled):not(:focus) {
|
|
border-color: color-mix(in srgb, var(--color-primary) 45%, transparent);
|
|
}
|
|
.auth-input:focus {
|
|
outline: none;
|
|
border-color: var(--color-primary);
|
|
background: var(--color-surface);
|
|
box-shadow:
|
|
0 0 0 3px color-mix(in srgb, var(--color-primary) 16%, transparent),
|
|
0 0 24px -8px color-mix(in srgb, var(--color-primary) 60%, transparent);
|
|
}
|
|
.auth-input:disabled {
|
|
opacity: 0.55;
|
|
cursor: not-allowed;
|
|
}
|
|
.auth-input.has-icon {
|
|
padding-left: 2.75rem;
|
|
}
|
|
.auth-input.has-action {
|
|
padding-right: 2.75rem;
|
|
}
|
|
|
|
/* Alert box used for form errors. */
|
|
.auth-alert {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 0.5rem;
|
|
border-radius: 14px;
|
|
padding: 0.7rem 0.85rem;
|
|
font-size: 0.85rem;
|
|
font-weight: 700;
|
|
color: color-mix(
|
|
in srgb,
|
|
var(--color-danger) 85%,
|
|
var(--color-text-readable)
|
|
);
|
|
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
|
border: 1px solid color-mix(in srgb, var(--color-danger) 32%, transparent);
|
|
}
|
|
|
|
/* Same box with the success tint — post-registration confirmation, etc. */
|
|
.auth-alert.auth-alert--success {
|
|
color: color-mix(
|
|
in srgb,
|
|
var(--color-success) 85%,
|
|
var(--color-text-readable)
|
|
);
|
|
background: color-mix(in srgb, var(--color-success) 10%, transparent);
|
|
border-color: color-mix(in srgb, var(--color-success) 32%, transparent);
|
|
}
|
|
|
|
/* ── Public page polish: explore pills ────────────────────────────── */
|
|
.explore-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.6rem 1.15rem;
|
|
border-radius: 999px;
|
|
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);
|
|
transition:
|
|
background 0.25s ease,
|
|
border-color 0.25s ease,
|
|
transform 0.25s ease,
|
|
box-shadow 0.25s ease;
|
|
}
|
|
.explore-pill:hover {
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--color-primary) 16%,
|
|
var(--color-surface)
|
|
);
|
|
border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 12px 26px -14px
|
|
color-mix(in srgb, var(--color-primary) 80%, transparent);
|
|
}
|
|
.explore-pill .pill-arrow {
|
|
opacity: 0;
|
|
transform: translateX(-4px);
|
|
transition:
|
|
opacity 0.25s ease,
|
|
transform 0.25s ease;
|
|
}
|
|
.explore-pill:hover .pill-arrow {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
.explore-pill:focus-visible {
|
|
outline: none;
|
|
border-color: var(--color-primary);
|
|
box-shadow: 0 0 0 3px
|
|
color-mix(in srgb, var(--color-primary) 20%, transparent);
|
|
}
|
|
|
|
/* ── Public page polish: avatar tiles ─────────────────────────────── */
|
|
.avatar-tile {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.55rem 0.3rem;
|
|
border-radius: 14px;
|
|
background: color-mix(in srgb, var(--color-primary) 6%, transparent);
|
|
border: 1px solid color-mix(in srgb, var(--color-primary) 14%, transparent);
|
|
transition:
|
|
transform 0.25s ease,
|
|
border-color 0.25s ease,
|
|
box-shadow 0.25s ease,
|
|
background 0.25s ease;
|
|
}
|
|
.avatar-tile:hover {
|
|
transform: translateY(-3px);
|
|
background: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--color-primary) 45%, transparent);
|
|
box-shadow: 0 12px 24px -14px
|
|
color-mix(in srgb, var(--color-primary) 85%, transparent);
|
|
}
|
|
.avatar-name {
|
|
font-size: 9.5px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.04em;
|
|
color: var(--color-text-muted);
|
|
max-width: 100%;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.avatar-tile:hover .avatar-name {
|
|
color: var(--color-text-readable);
|
|
}
|
|
.avatar-online {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 999px;
|
|
background: var(--color-accent);
|
|
box-shadow: 0 0 0 2px var(--color-surface);
|
|
}
|
|
|
|
/* ── Public page polish: drifting aurora blobs ────────────────────── */
|
|
.aurora-blob {
|
|
position: absolute;
|
|
border-radius: 999px;
|
|
filter: blur(70px);
|
|
pointer-events: none;
|
|
will-change: transform;
|
|
animation: aurora-drift 16s ease-in-out infinite alternate;
|
|
}
|
|
@keyframes aurora-drift {
|
|
from {
|
|
transform: translate3d(0, 0, 0) scale(1);
|
|
opacity: 0.55;
|
|
}
|
|
to {
|
|
transform: translate3d(30px, -24px, 0) scale(1.15);
|
|
opacity: 0.85;
|
|
}
|
|
}
|
|
|
|
/* Small bobbing scroll cue at the bottom of the hero. */
|
|
.hero-cue {
|
|
animation: float 2.4s ease-in-out infinite;
|
|
will-change: transform;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.aurora-blob,
|
|
.hero-cue {
|
|
animation: none;
|
|
}
|
|
.btn-brand:hover,
|
|
.btn-glass-dark:hover,
|
|
.explore-pill:hover,
|
|
.avatar-tile:hover {
|
|
transform: none;
|
|
}
|
|
}
|
|
|
|
/* Dividers between the four counters in the stats panel. */
|
|
.stat-cell {
|
|
position: relative;
|
|
}
|
|
@media (min-width: 1024px) {
|
|
.stat-cell:not(:first-child)::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: -0.5rem;
|
|
top: 18%;
|
|
height: 64%;
|
|
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%;
|
|
width: 1px;
|
|
background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
|
|
}
|
|
}
|
|
|
|
/* Mouse-tracking spotlight glow for premium cards. The dot position comes
|
|
from CSS custom properties updated on pointermove (see SpotlightCard). */
|
|
.spotlight {
|
|
position: relative;
|
|
isolation: isolate;
|
|
}
|
|
.spotlight::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.4s ease;
|
|
background: radial-gradient(
|
|
340px circle at var(--spot-x, 50%) var(--spot-y, 50%),
|
|
color-mix(in srgb, var(--color-primary) 20%, transparent),
|
|
transparent 70%
|
|
);
|
|
z-index: -1;
|
|
}
|
|
.spotlight:hover::before,
|
|
.spotlight:focus-within::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Section number mark (01 / 02 / …) used as a faint watermark in headers. */
|
|
.number-mark {
|
|
font-family: var(--font-nunito);
|
|
font-weight: 900;
|
|
letter-spacing: -0.02em;
|
|
line-height: 1;
|
|
background: linear-gradient(
|
|
120deg,
|
|
color-mix(in srgb, var(--color-primary) 65%, transparent),
|
|
color-mix(in srgb, var(--color-accent) 50%, transparent)
|
|
);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.spotlight::before {
|
|
transition: none;
|
|
}
|
|
.animate-fade-in-up,
|
|
.animate-scale-in,
|
|
.animate-count {
|
|
animation: none;
|
|
}
|
|
}
|