Light admin derives from the public light theme; dark keeps HK overrides. Remap inputs, cards, muted text, and kill the public wallpaper on admin so fonts and boxes stay readable in both modes.
Co-authored-by: Cursor <[email protected]>
Reuse ThemeSwitcher with an admin variant (Sun/Moon) next to the staff profile and in the housekeeping header.
Co-authored-by: Cursor <[email protected]>
Replace the long flat nav with hub entries, shared AdminHubChrome tabs, and AdminPageShell. Existing URLs stay stable; Settings/Radio and other sections now share one chrome.
Co-authored-by: Cursor <[email protected]>
Public chrome and width live only under (site). /admin and /client inherit the root layout alone, so housekeeping width is no longer tied to the public template.
Co-authored-by: Cursor <[email protected]>
Rooms queried the wrong Prisma model and a non-existent owner relation. Settings now use grouped managed fields plus a searchable advanced key/value panel.
Co-authored-by: Cursor <[email protected]>
Sync dark-mode state from the DOM after mount so admin text no longer needs a double toggle, and normalize text/button colors against their surfaces on theme save.
Co-authored-by: Cursor <[email protected]>
Public cards no longer paint with always-dark admin surfaces, and admin pages fully remap background/text/surface so shadcn UI stays readable on the admin canvas.
Co-authored-by: Cursor <[email protected]>
Derive admin/public text colors from WCAG contrast, unify ThemeVars CSS emission, and keep navbar overrides in sync with readable vars.
Co-authored-by: Cursor <[email protected]>
The clothing importer and imager helpers pointed at a missing route; proxy Habbo with disk/memory cache so avatars work again.
Co-authored-by: Cursor <[email protected]>
Gate permissions on ACL manage + resolve super-admin from live user ranks, restore prefixes API routes, and anglicize hardcoded admin copy with nav i18n.
Co-authored-by: Cursor <[email protected]>
Gate pages and mutations on module PERMS instead of dashboard-only staff checks, add radio view/edit slugs with migration 0015, and expand the operations contract tests.
Co-authored-by: Cursor <[email protected]>
Gate translations, RCON, and user mutations on SETTINGS_EDIT, RCON_EXECUTE, and USERS_EDIT instead of dashboard/rank checks; redirect the legacy user-edit URL to the guarded canonical page.
Co-authored-by: Cursor <[email protected]>
Restore valid browser JS in theme-init, close mobile sidebar on navigation, and split autoDjForm title/trackTitle across locales.
Co-authored-by: Cursor <[email protected]>
The /client launcher read a non-existent `nitro_client_url` setting, so it
always fell through to "No client configured" and the client never launched.
Faithful to AtomCMS's NitroController + nitro.blade.php, build the launch URL
as {nitro_path}/index.html?sso=<ticket> plus the toolbar colour params, via a
shared buildNitroClientUrl helper. The Flash launcher's Nitro fallback used the
same dead key and is fixed too.
Beyond parity — the web-feasible versions of the "host-only" items plus
extras AtomCMS doesn't have:
- App-level abuse/DDoS guard (src/lib/services/abuse-guard.ts): counts
requests per IP and auto-adds flooders to website_ip_blacklist (enforced
by the access guard) + fires ddosDetected(). OFF by default, tunable via
settings. The iptables layer stays host-only; this is the real app-tier
mitigation. Access guard now also enforces the IP blacklist (cached).
- PWA: a themeable web manifest (src/app/manifest.ts) + a service worker
(public/sw.js, cache-first assets / network-first pages) registered after
hydration — the hotel is now installable.
- /api/health: DB + emulator(RCON) + runtime status probe.
- /developers: a public API documentation page covering every REST endpoint
with its method, path and auth requirement.
- jobs-worker: daily emulator JAR backup (runs host-side in the worker, like
AtomCMS's backup command) — copies + prunes; no-ops unless EMULATOR_JAR_PATH
+ EMULATOR_BACKUP_DIR are set.
Verified live (prod, amx_test): /api/health ok, manifest + sw served, docs
page renders, normal pages unaffected by the guard. tsc 0, vitest 49/49,
next build 0.
- Rich profile (/u/[username]): wallet (credits/duckets/diamonds), friends
grid (messenger_friendships), and owned rooms sections.
- Login history: new website_login_logs table (model + migration 0007),
recorded on every successful sign-in (ip + user-agent), surfaced on a new
/settings/sessions page (with failed-attempt list from failed_logins).
- Photos lightbox + home article slider (client components, no Swiper dep).
- /client/flash launcher (SSO ticket like the Nitro page).
- Admin: private chatlogs section in /admin/logs, /admin/radio/moderation
(shout moderation), a "users by rank" inline bar chart on the dashboard,
and a TinyMCE rich-text editor on the article admin forms.
- Niche API: /api/values/[id], /api/guilds(+/[id]), /api/radio/auto-play.
Verified live (prod, amx_test): login recorded → /settings/sessions shows
it with device; profile renders wallet/friends/rooms; dashboard chart +
private-chat logs + /client/flash + /api/guilds all OK. Reverted test data.
tsc 0, vitest 49/49, next build 0.
Final parity push (web-tier only):
- REST API write + token auth: POST /api/tokens (issue a personal_access_token
for the session user), Bearer auth via src/lib/api-auth.ts, POST
/api/articles/[slug]/comment, GET/DELETE /api/me/tokens, full tickets API
(/api/tickets +[id] +[id]/reply), radio current-dj/points/points-leaderboard/
embed-config + POST shouts, and a real-time /api/radio/stream (SSE). 31 public
API routes total.
- Pages: /draw-badge (buy a custom profile badge → credits + RCON), /me
dashboard (stats + online friends + referral claim). Wired into the nav.
- HTML sanitisation (sanitize-html) — the HTMLPurifier equivalent — applied to
writeable boxes + article bodies before dangerouslySetInnerHTML.
- "Dusk" dark theme preset + a default-dark site option honoured by the
no-flash boot script.
Verified live (prod, amx_test): token issue → Bearer endpoint 200, no-token
401; /api/me/tokens lists it; current-dj/leaderboard JSON; /me + /draw-badge
200; reverted the test user + tokens. tsc 0, vitest 49/49, next build 0.
Phase D of the parity push:
- Radio player: fixed bottom-right widget (port of atom's radio-player.blade)
that streams the hotel radio via <audio>, with play/pause, volume, current
DJ / now-playing and live listener count, polling the public API every 15s.
A server gate (RadioPlayerGate) only mounts it when radio is enabled + a
stream URL is set, so no widget JS ships when off. Mounted in the layout.
- Logo generator (/logo): client tool — hotel name + font/colour/size pickers
with a live preview and "download PNG" via canvas.
- Public room page (/room/[id]): renders an emulator room (name, owner,
users/max, state, category) in a ContentCard.
Verified live (prod, amx_test): /logo renders the generator, /room/50 shows
the real room "Dark Elegant Bundle", and with radio enabled the player
mounts fixed bottom-right (audio + play/pause + Test FM); reverted the test
settings. tsc 0, vitest 49/49, next build 0.
Grew /admin/theme from colours-only to a full theme editor, all applied
live via website_settings + ThemeVars:
- Typography: body font (10 web-safe + Google options; Google fonts load
via an injected <link>) and H1/H2/H3 sizes (globals.css now reads
--size-heading-* vars).
- Buttons & links: secondary/danger button colours + link/link-hover.
- Custom CSS: a raw textarea injected after the theme variables (staff-
trusted), for anything the controls don't cover.
- Presets: 6 → 13 (added Galaxy, Royal, Cyberpunk, Neon, Coffee, Arctic,
Christmas). ThemeVars now injects all the new vars + the font link.
Verified live (prod, amx_test): saved font=mono / H1=44px / custom CSS →
the public home reflected --font-family "Courier New", --size-heading-h1
44px and the injected rule; reverted the test settings. tsc 0,
vitest 49/49, next build 0.