Commit Graph
145 Commits
Author SHA1 Message Date
openhands 759ae91745 perf: cache search and news archive, drop motion/react from public pages
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 26s
CI / tests-unit (push) Failing after 1m37s
CI / tests-integration (push) Successful in 1m38s
CI / tests-ui (push) Failing after 2m24s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Closes the four remaining LOW items.

Search and news archive caching
- A leading-wildcard LIKE cannot use an index, so every /search section cost a
  COUNT(*) scan plus an ordered page fetch, and /news did the same for its
  archive. Both now cache: search per section for 30s, the archive for 60s
  under the existing news revision so publishing an article drops it at once.
- Sections are cached independently, so one slow query cannot hold up the rest
  and a failure is not cached as a result.
- The cached value is passed through cacheSafe() so the Redis path and the
  in-process path return the same types; without it a cache hit would hand the
  events grid a string where a miss hands it a Date, and it calls toISOString()
  on that field. Dates are revived on the way out so the public signatures of
  loadNewsArchive and loadPublicSearch are unchanged.
- Archive entries are keyed on the REQUESTED page rather than the clamped one,
  so two requests that clamp onto the same page cannot alias each other.

motion/react out of the public bundle
- Converted the six public-facing users: the radio player, the typewriter text
  (a motion.span with no animation props at all), the photo lightbox, the
  animated counter, the footer CMS-info popup and the scroll reveal. That was
  the actual entry points — the counter and the popup reach the public home page
  and footer through static imports, so removing only the three originally named
  would have left the library in the bundle anyway.
- Each animation moved to a CSS class, and the two that animate on exit now hold
  the element for the length of the fade, which is what AnimatePresence used to
  do.
- motion/react now only ships with /admin and the two already-lazy nav panels.
- Two safety fixes came out of this: the scroll reveal starts at opacity 0, so
  it is forced visible under prefers-reduced-motion and via a <noscript> rule in
  the root layout; and it now emits the .motion-reveal class, which the theme
  panel's "Scroll Reveal" toggle selects and which previously matched nothing.
- The CMS-info backdrop became a real button in a pointer-transparent layer
  instead of a handler on a static element, so click-outside-to-dismiss is
  reachable by keyboard.

Fewer duplicate router refreshes
- Next.js re-renders the current route as part of a server action's own response
  when that action revalidates, and applies it with a seeded navigation; the
  router only skips its own update when the action did NOT revalidate. So the
  refresh after such an action fetched the same tree twice.
- useServerAction takes an opt-in `revalidated` flag that skips it. It is opt-in
  per call rather than derived from an action name, since a rename would
  silently change behaviour. Applied to the two user-facing call sites whose
  actions were verified to revalidate their own route.

Touch targets
- .btn was the one shared control at 40px; it and the lightbox and CMS-info
  close buttons are now 44px, as is the password toggle (the auth input already
  reserved 44px for it). The remaining 32px icon buttons pass WCAG 2.2 AA, which
  only asks for 24px; enlarging those inside inputs and overlays was left alone
  because it risks visual breakage that cannot be checked from here.
2026-10-09 17:35:38 +02:00
openhands 179484642f feat: per-account login lockout, mail index, resend captcha, i18n scoping
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 28s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Failing after 1m45s
CI / tests-ui (push) Successful in 2m29s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Closes the four HIGH/MEDIUM items left open after the previous pass.

Login lockout
- The only login limits were keyed on the client IP, so a distributed attempt
  could grind on one account indefinitely. Added a per-account lockout with a
  budget of 8 failures per 15 minutes.
- The bucket is keyed on the RESOLVED account id, not on the submitted string:
  users may sign in with either username or e-mail and neither the lookup nor
  the input normaliser folds case, so an input-keyed bucket would hand out a
  fresh budget per spelling of the same account.
- precheckLogin and NextAuth's authorize share the bucket, so the pre-check
  cannot be used to buy extra attempts and a client that skips it entirely is
  still bounded. Both check the lockout BEFORE verifying the password: the
  success path clears the counter, which would otherwise walk a locked account
  straight back in on the right password.
- A successful login clears the failures, which needs two new primitives in
  rate-limit.ts: peekRateLimit (read-only, does not consume a unit) and
  clearRateLimit.
- Fixed a latent inconsistency while doing so: the in-process bucket capped its
  counter at the limit while Redis' INCR kept climbing, so the two backends
  disagreed about how far over the limit a key was. Both now track the true
  count.

Mail lookup index
- Added an index on users.mail (0035). Password reset, e-mail verification and
  the resend cooldown all resolve a single account from a submitted address and
  were full table scans of `users`. Deliberately non-unique: legacy rows can
  hold the same address more than once, so a unique index would fail to apply.

Resend captcha
- /verify's resend form triggers real outbound mail and was reachable with only
  a cooldown. It now runs the configured captcha before the account lookup and
  before any send.

Client message payload
- The root layout serialised the whole catalogue into every page. pages.admin
  and admin are ~177 KB of the ~235 KB and are unreachable from the public route
  group, so that layout now installs its own provider with the staff namespaces
  removed. Nested providers replace rather than merge, which is why this has to
  live in the segment layout. /admin, /mod, /client and /admin-next keep the
  full set; a guard test fails if a public page ever references a staff
  namespace.
2026-10-09 17:12:50 +02:00
openhands 6cc45d7413 feat: harden atoms-nexst against review findings (37 items)
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Failing after 1m49s
CI / tests-ui (push) Successful in 2m31s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Second review pass covering security, performance, admin tooling and the
public/room flows. All HIGH and MEDIUM findings from the audit are resolved;
nothing in this commit changes the visible feature set.

Authentication & session security
- CSP is now set on the request headers in the proxy, which is what Next.js
  uses to derive the render nonce, so the nonce is effective.
- 2FA: an already-enabled user cannot re-enroll, the setup endpoint is
  rate-limited per account, and confirmed codes are persisted so the second
  secret no longer silently never applies.
- Password reset revokes the ticket, authTicket and all personal access
  tokens, and bumps the token version so existing sessions die. The same
  revocation is now wired into the staff-side password reset.
- /reset and /verify return a stable error code instead of raw text; the
  mail lookups are ordered by id so duplicates cannot vary between runs.
- Resending the verification mail gets a per-address cooldown on top of the
  per-user limit.
- Issue API tokens with the narrower radio/ticket ability set instead of "*".

Authorization & input handling
- Mid-rank staff can no longer keep dynamically granted non-view admin.*
  permissions: existing grants are revoked by migration and the grant lookup
  is restricted to "%.view". Rank guards use the dynamic super-admin check.
- Alerting a user is permission-checked and audited like the other tools.
- Material mutations (giveCredits/giveDuckets/giveDiamonds, the admin user
  actions route, bulk user actions) are capped and rank-guarded, and bulk
  ids are bounded.
- updateRoom / updateRoomItem write through a field allowlist, and items
  may only be edited through their own room.
- Classnames reaching the filesystem are validated before use so a crafted
  value cannot escape the asset directories.
- The word filter now also covers offline mails, guild forum threads and
  replies, and user mottos.
- Media uploads are validated by magic bytes, /api/media requires the page
  edit permission, APP_URL must be configured once mail is enabled, and the
  diagnostics error route checks the fetch site header.

Admin tooling
- Secret settings render masked and cannot be overwritten with a blank or
  an arbitrary raw key; radio credentials are new password inputs.
- Commandocentrum balance changes are audited.
- Admin list pagination reads the caller's per-page instead of the max, and
  the log exporter caps offset and search length.

Performance
- Catalog translations are cached per module, with a cheap revision hash;
  the public online count uses a stale window instead of hammering the DB.
- The cache warmup now primes the payload the home route actually reads.
- TopHeader batches its queries into one round trip, and LCP avatars load
  eagerly.
- motion/react and sonner are no longer part of the root layout; the nav
  dropdown and mobile nav panels are lazy client chunks. Anonymous visitors
  again get the navigation chrome, and public pages get an edge cacheable
  response.

Accessibility
- Nested <main> elements in phase pages became <section>; the page entrance
  and route progress animations are pure CSS that respect reduced motion.
2026-10-09 16:19:48 +02:00
openhands 3933214953 feat(auth): implement all 16 homepage/login/register review items
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Successful in 1m47s
CI / tests-ui (push) Successful in 2m30s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 2m56s
- add countArticles() (published-only, mirrors news-list) and warm total_articles
- localize homepage metadata; bind articleCount to both stats; unique photo alts
- drop duplicate news date and the mascot preload priorities
- extract shared AuthPageFrame/AuthUsersCards used by /login and /register
- login: localized noindex metadata, session redirect via safeRedirectPath,
  ?from passthrough from proxy, unified auth roster cache keys, registered notice
- register: localized metadata, session redirect to /me, unified cache keys
- add resend-verification flow on /verify with rate-limited non-enumerable action
- add safeRedirectPath() with unit tests
- register form: live requirements checklist + password mismatch guard
- login form: unverified state with resend-link CTA
- honour prefers-reduced-motion in TypewriterText
- add 6 translations across all 25 locales
2026-10-08 18:49:27 +02:00
openhands 3265c149da style(landing): add micro-interactions and polish public pages
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-unit (push) Successful in 1m48s
CI / tests-integration (push) Successful in 1m52s
CI / tests-ui (push) Successful in 2m35s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m7s
2026-10-06 22:59:03 +02:00
openhands 57710a7fe3 style(landing): polish the public index, login and register screens
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Failing after 26s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
- add theme-aware helpers: btn-brand, btn-glass-dark, auth-input, auth-label,
  auth-alert, explore-pill, avatar-tile, aurora blobs and a hero scroll cue
- reorder backdrop-filter declarations so the glass blur survives the
  production CSS optimizer in modern Chromium
- hero: aurora glow, frosted recent-users chip, premium CTA buttons and cue
- explore nav: icon pills with hover arrows; features: gradient icon tiles
- stats: single glass panel with column dividers; join CTA: aurora + ring
- auth forms: visible labels, icon inputs, eye/password toggle, gradient
  submit and pill footer links
- auth pages: gradient card frame, aurora accents on the intro panel and
  hover-lift avatar tiles; unified pill-shaped top bar
- drop the hard-coded register banner image in favor of the framed card
2026-10-06 22:00:02 +02:00
openhands 6bffc53779 refactor(auth): merge the duplicate login form and localize the auth screens
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 1m8s
CI / tests-integration (push) Successful in 1m53s
CI / tests-unit (push) Successful in 1m59s
CI / tests-ui (push) Successful in 2m42s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 4m33s
`home-login-form.tsx` and `login-form.tsx` were two ~240-line near-identical
components. Delete the former and give `LoginForm` a `variant` prop:

- `variant="page"`   sr-only labels plus the register/forgot footer (/login)
- `variant="compact"` visible labels, no footer (homepage sidebar)

Field ids now come from `useId()`, so the two usages can never collide, and the
hardcoded "Show"/"Hide"/"Loading" strings are translated.

Localization of the login and register screens:

- `home-login-form.tsx` was entirely hardcoded English.
- `passwordStrength()` returned hardcoded "Weak"/"Fair"/"Good"/"Strong".
- `register.ts` returned only English strings. It now returns a
  locale-independent `code` next to the message, and the form renders
  `t(code)` with the English string as a fallback.
- Backfilled the new keys across all 25 locales, plus the login/register
  strings that were still English in most of them. `ar`, `fi` and `ja` had
  their entire login/register namespace in English and are now filled in.
  Locale parity stays at 0 missing keys, as `i18n:check` requires.

Copy that did not match the enforced rules: the UI advertised "min 8 chars"
(EN) / "min 6 tekens" (NL) while registration requires 12 characters plus an
uppercase, a lowercase, a digit and a special character. Corrected in every
locale. `password-reset.ts` enforced only 6 characters and is raised to 12 to
match registration.

Accessibility: `login-form.tsx` had no `<label>`, no `id` and no `required` on
any field. All three are now present, and error banners are announced with
`role="alert"`.

Adds `src/i18n/auth-messages.test.ts`, which asserts every `RegisterErrorCode`
resolves to a non-empty message in all 25 locales; verified it fails when a key
is removed. The existing register tests now also assert the error `code`.
2026-10-04 18:50:23 +02:00
openhands f81b114b69 perf(cache): single-flight avatar renders, cacheable public reads, cheap row counts
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m38s
CI / tests-unit (push) Successful in 1m43s
CI / tests-ui (push) Successful in 2m30s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m7s
Three separate things that were each costing more than they needed to on the
hot path.

- Single-flight avatar renders. The disk cache was checked first and a miss
  went straight to the upstream, with nothing shared between callers, so a page
  requesting dozens of avatars at once turned N concurrent requests for one
  figure into N renders. A render is the most expensive operation this app
  does, and the duplication happened exactly when the cache had nothing to
  offer. Eight concurrent requests now cause one render instead of eight. The
  map lives on globalThis because Next can evaluate the module more than once
  per process, and two copies would each start their own render.

- Let public read-only routes be cached by a shared cache. Every JSON response
  was `cache-control: no-store`, so a CDN in front of the app could not answer
  any of it and every request reached the origin. publicCacheControl() opts a
  route in with s-maxage and stale-while-revalidate, using the same TTL as the
  server-side cache so the two layers cannot disagree. The default stays
  no-store: most routes here are personalised, admin-only or auth-dependent.
  /api/badges/leaderboard is deliberately left alone because it returns
  per-viewer rank entries to signed-in callers.

  Note this only takes effect once a cache rule exists for /api/* at the CDN, or
  the explicit `cache: "no-store"` is dropped from the client fetches (24 files
  do that today, including the /api/online poll). The headers alone are inert
  until one of those happens.

- Take the homepage row counts from the storage engine estimate instead of
  COUNT(*), which walks an index and gets slower as the tables grow. A missing
  or zero estimate falls back to the exact count rather than ever showing a
  wrong zero. The online count stays exact: it is an indexed read over a small
  subset and a few seconds of drift reads as broken rather than approximate.

The counters move into one module because the homepage and the boot warm-up
populate the same cache keys, so two implementations would race to write
different values into the same entry.

3223 tests pass.
2026-09-25 18:42:23 +02:00
openhands 203399aab7 fix(cache): true LRU, stale-while-revalidate and cross-process invalidation
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 32s
CI / tests-integration (push) Successful in 1m38s
CI / tests-unit (push) Successful in 1m42s
CI / tests-ui (push) Successful in 2m33s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m43s
The in-process cache was a FIFO of 500 entries that was never touched on a
read, so a key polled on every request could be evicted by an unrelated burst
of dynamic keys. That looked exactly like the cache being cleared at random,
and it is what made the site fall back to the database unpredictably.

- Evict least-recently-used instead, and raise the default budget to 2000
  (CACHE_MEMORY_MAX_ENTRIES). Reading a key now marks it as used, so a hot key
  only leaves when a hotter one takes its place.
- Add opt-in stale-while-revalidate (CachedOptions.staleMs). The grace window
  lives on the entry, so one call site opting in protects every reader of that
  key. A failed background refresh keeps serving the last good value instead of
  falling through to the origin, and is reported once rather than per read.
- Invalidate across processes. invalidateKey() now clears memory, deletes the
  Redis key and publishes a signal, so a value written by one process is no
  longer served stale by the others for the rest of its TTL. A failed Redis
  delete no longer skips the broadcast.
- Guard against a refresh that started before an invalidation writing its
  outdated result back into the cache.
- Read the news revision at most once a second per process instead of on every
  call, with a pub/sub signal to drop the local copy when it rotates. A Redis
  outage now degrades to the in-process cache rather than to no cache at all.
- Warm the hot public keys on boot, so the first visitors after a deploy do not
  each pay for a miss.
- Count hits, misses, stale serves, errors and evictions per key, exposed at
  GET /api/admin/devops/cache. Without it a wrong REDIS_URL, a full budget and
  a dead origin all look identical from the outside.
- Enforce the imaging cache budget for real: records are .img/.json pairs, so
  the old cap counted files and never removed anything while entries were
  fresh. Sweeps are throttled per directory and prune to a low-water mark.
- Cap the JWT version map, and stop per-test scratch roots from littering the
  runtime imaging cache.

Public read-only endpoints get grace windows; admin, account and auth data
deliberately stays fresh. Redis TTLs get a little jitter so keys written
together no longer expire together.

3209 tests pass. next build could not be verified on this host: the optimized
build is OOM-killed before prerender, so this has not run in a real Next
runtime yet.
2026-09-25 18:26:45 +02:00
openhands b2777634f7 ci: run all workflows on the self-hosted host runner
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 4m39s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m42s
- Switch test-runner and renovate workflows from ubuntu-latest to
  self-hosted now that a native host runner is running as a systemd service
- Replace remaining hardcoded color utilities in the homepage with theme
  tokens and inline rgba styles to satisfy the no-hardcoded-colors contract
- Restore dual UserAvatarThumbnail usage on the homepage (hero avatar stack
  plus community grid) to satisfy the public avatar presentation contract
2026-09-22 20:59:02 +02:00
openhands 628d24c0c7 feat(home): redesign landing page with cinematic hero and glass panels
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Failing after 1m38s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
2026-09-22 20:28:23 +02:00
openhands 8a66db4ed7 fix(imaging): make avatar and badge images resilient to upstream outages
CI / check (push) Successful in 4m11s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m51s
- Add persistent disk cache for rendered avatars/badges (storage/imaging)
  so repeats never touch the flaky local renderer and cached renders
  survive upstream downtime
- Serve cache-first with stale-on-error; cut primary/fallback timeouts
  from 10s/6s to 4s/4s so failing images cannot stall pages
- Avatar proxy now returns a graceful 200 silhouette instead of 502 when
  no renderer can produce a figure, so no broken-image glyphs appear
- Badge endpoint becomes a caching proxy trying configured CDN, public
  Habbo CDN and local /swf copy in order, and drops the fragile IP rate
  limit that could blank badge streams
- Route all site badge images (profile, me, badges, apply pages) through
  the cached proxy instead of hot-linking images.habbo.com
2026-09-20 12:58:55 +02:00
openhands c3ff497050 feat(referrals): add referral attribution and daily login rewards
CI / check (push) Successful in 4m25s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m54s
- Track referral attribution at registration via ?ref code with
  same-IP and duplicate-pair guards
- Add daily login rewards with streak tracking, claim flow and
  sendCurrency payout backed by RCON with DB fallback
- Add admin pages for referral settings and the daily reward schedule
- Add migration 0033 with tables, seed schedule, settings and ACL grants
- Add admin.referrals.* and admin.dailyrewards.* permission slugs
- Localize new copy in en, nl and it
2026-09-20 12:29:01 +02:00
openhands d6111387e4 feat(auth): align login and register with themed intro, translated social cards
CI / check (push) Successful in 4m15s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m52s
2026-09-18 13:35:22 +02:00
openhands 469f69ddd7 feat(home): two-column hero with live status panel, explore nav, featured news
CI / check (push) Successful in 4m8s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m0s
2026-09-18 13:27:08 +02:00
openhands d0db352f36 feat(home): make index clearer with decluttered hero and live stats panel
CI / check (push) Successful in 4m13s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m0s
2026-09-18 13:06:26 +02:00
openhands d131124515 feat(theme): animate public background with aurora and particles, polish landing pages
Add background_effect (aurora/particles), background_overlay tint and
opacity to the theme manager, rendered site-wide by ThemeVars on every
public page. Polish the home and register pages (hero mascot, live stat
pulse, date pills, photo strip, CTA band, theme-aware register intro,
i18n for home/register section).
2026-09-18 12:44:39 +02:00
openhands 8638e81444 refactor(db): typed query helpers, shared test FormData helper
CI / check (push) Successful in 4m10s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m4s
Replace raw db.execute tuple casts with queryRows/rowsFrom/execResult/
affectedRows helpers from lib/db, drop redundant mysql2 casts on typed
query builders, and centralize per-test fakeForm into test/fake-form.
Update db mocks in tests so helpers resolve against mocked execute.
2026-09-17 21:02:57 +02:00
Simo f7b9b55700 fix(news): preserve recoverable reads and verify publication against real services
CI / check (push) Failing after 29s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-13 19:31:32 +02:00
Simo 8abfe352ef fix(security): authorize site uploads and harden tokens, media and request identity
CI / check (push) Successful in 3m15s
CI / deploy (push) Successful in 1m19s
CI / publish-container (push) Successful in 48s
2026-09-13 19:24:43 +02:00
Simo 1eee6661f9 feat(notifications): add account-scoped inbox and persistent preferences
CI / check (push) Failing after 1m18s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-13 17:59:22 +02:00
openhands d2572757cd perf(site): feature card tilt & news card zoom micro-interactions
CI / check (push) Successful in 2m26s
CI / deploy (push) Successful in 1m2s
CI / publish-container (push) Successful in 50s
- Feature cards: group hover zoom & rotate on icon badge, smoother
  card lift on hover (-translate-y-1.5).
- Stats cards: primary accent on numbers, pill badge for labels.
- News cards: smoother 500ms transition with scale-108 image zoom.
2026-09-12 18:16:29 +02:00
openhands fd7b29a5f8 feat(site): pro hero with animated ring and floating stat chips
- hero-ring: continuously shifting gradient hairline around the hero
  frame (mask-drawn, reduced-motion safe).
- glass-chip: frosted floating pills under the CTAs that bob on a
  staggered loop, live online counter keeps ticking for the Online chip.
- Taller hero for more presence on desktop.
2026-09-12 18:08:09 +02:00
openhands e5e17d75ae perf(site): inline small classic icons and favicon as base64
CI / check (push) Successful in 2m27s
CI / deploy (push) Successful in 1m34s
CI / publish-container (push) Successful in 50s
- New src/lib/site-icons.ts: base64 data URIs for the 13 tiny classic
  icons actually referenced in markup (100–2000 bytes), replacing extra
  requests with inline payloads. home.png, dynamic flags and currency
  sets stay on the filesystem.
- Default favicon is now served server-side as a base64 SVG data URI
  (memoized), while a DB-configured custom favicon still takes priority.
- Icons render through <Image unoptimized>, so data URIs pass through
  untouched on all affected pages (home, login, register, settings,
  navigation, auth top bar, client loading).
2026-09-12 17:28:01 +02:00
openhands 97012a78f6 feat(site): drifting cloud puffs across the public background
CI / check (push) Successful in 2m40s
CI / deploy (push) Successful in 2m13s
CI / publish-container (push) Successful in 2m10s
Add a fixed, decorative layer of soft clouds that slowly float across
the Habbo sky behind the content:
- Five clouds at staggered sizes, heights, opacities and loop timings
  (60s–190s) so the drift feels organic.
- Dimmed further in dark mode; frozen by prefers-reduced-motion.
- Pure decoration: aria-hidden, pointer-events: none, no color
  utilities in markup (cloud shapes live in globals.css).
2026-09-12 17:12:55 +02:00
openhands efd3b00075 feat(site): smoother motion across the landing pages
CI / check (push) Successful in 2m27s
CI / deploy (push) Successful in 1m19s
CI / publish-container (push) Successful in 1m4s
- Slow Ken Burns drift on the hero artwork for cinematic depth.
- Gentle breathing pulse on the brand glows behind Frank and the hero.
- Silkier reveal easing (cubic-bezier .22/1/.36/1, 0.55s) site-wide.
- Eased, longer hover transitions on the hero CTAs.
- Smooth page scrolling, all guarded by prefers-reduced-motion.
2026-09-12 16:51:48 +02:00
openhands bf5b9918d9 feat(site): structured landing sections with eyebrow headings
CI / check (push) Successful in 2m42s
CI / deploy (push) Successful in 1m25s
CI / publish-container (push) Successful in 54s
Give the home page a clear, professional information hierarchy:
- Add eyebrow labels + headings for Features, Live stats and Community
  sections using reusable, theme-aware .eyebrow / .section-title styles.
- Loosen the vertical rhythm (gap-8/10) so each block breathes.
- New messages resolve via the existing English fallback for all locales.
2026-09-12 16:44:21 +02:00
openhands 3265cf1fad feat(site): consistent card radius and remove duplicate login CTAs
CI / check (push) Successful in 2m32s
CI / deploy (push) Successful in 1m17s
CI / publish-container (push) Successful in 53s
Professional tidy-up of the landing experience:
- SurfaceCard: unified rounded-xl radius for a crisper, consistent look.
- Hero: matches the new card radius and gains a dual-direction title
  shadow so the headline stays readable over the header artwork.
- Login/register: drop the duplicated "no account / have an account"
  paragraphs — the forms already ship an inline footer, so one clear CTA
  cluster remains and the side column is cleaner.
2026-09-12 16:35:28 +02:00
openhands 29b9a4b0dc feat(site): professional card system and tidy landing layout
CI / check (push) Successful in 2m26s
CI / deploy (push) Successful in 1m26s
CI / publish-container (push) Successful in 1m1s
Refine the public UI for a cleaner, more professional and scannable
landing experience without leaving the classic Habbo style:

- SurfaceCard: softer layered shadow, gradient accent hairline on the top
  edge and a bolder header title across all public cards.
- Home: gradient hotel-name in the hero headline, shine effect on the
  primary CTA, hairline on the top bar.
- Login/register: consistent avatar tiles with rounded corners, subtle
  borders and a gentle hover lift; uniform username sizing.
- Add reusable theme-aware .card-hairline and .gradient-text utilities.
2026-09-12 16:29:55 +02:00
openhands d755ab7ce1 revert(site): restore classic Habbo landing with soft theme polish
CI / check (push) Successful in 2m27s
CI / deploy (push) Successful in 1m45s
CI / publish-container (push) Successful in 1m55s
Replace the premium dark-gaming redesign of home, login and register with
the original classic landing (Habbo sky background, AuthTopBar, SurfaceCard
layout). Keeps the theme background visible again and adds a subtle
theme-aware brand halo behind the hero/Frank plus a soft primary glow on
card hover.

Also upgrades dependencies: next 16.3.5, vite 8.3.0 (typescript 7.0.2 was
already latest). Temporarily lowers pnpm minimumReleaseAge to 60 min so the
fresh 16.3.5 release can be installed; restore to 1440 once it is 24h old.
2026-09-12 16:14:21 +02:00
openhands cc58f16230 fix(site): satisfy theme-color and avatar contract tests for landing redesign
CI / check (push) Successful in 2m36s
CI / deploy (push) Successful in 1m28s
CI / publish-container (push) Successful in 46s
2026-09-12 15:51:22 +02:00
openhands 741f01e897 feat(site): redesign home, login and register with premium dark gaming theme
CI / check (push) Failing after 1m5s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
Replaced the classic Habbo landing style on the home, login and register pages with a modern premium dark-gaming look: always-dark hero canvas with brand glows, grid overlay and ambient orbs, glass panels, gradient text and floating art. Adds shared LandingTopBar, AuthShell and BrandFrank components plus reusable premium CSS utilities. Build, typecheck and lint pass.
2026-09-12 15:40:54 +02:00
Simo baeb54aeb5 feat(devops): separate public page server timing diagnostics
CI / check (push) Successful in 2m23s
CI / deploy (push) Successful in 1m13s
CI / publish-container (push) Successful in 44s
2026-09-11 10:49:08 +02:00
Simo 445ef13846 fix(public): distinguish unavailable data from empty results and missing pages
CI / check (push) Successful in 56s
CI / deploy (push) Successful in 1m30s
CI / publish-container (push) Successful in 44s
2026-09-11 00:39:53 +02:00
Simo 440ce4e556 test(me): use committed translations in dashboard tests
CI / check (push) Successful in 55s
CI / deploy (push) Successful in 19s
CI / publish-container (push) Successful in 1m23s
2026-09-11 00:34:21 +02:00
Simo 74223984dc feat(profile): add privacy controls and progressive photo gallery
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:31:29 +02:00
Simo ba9c61d808 feat(search): search public news events users and open rooms
CI / check (push) Failing after 54s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:29:54 +02:00
Simo 506e14783c test(i18n): validate nested housekeeping translation groups
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:28:26 +02:00
Simo 96d3e3f602 feat(events): add weekly browsing and personal registrations
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:27:41 +02:00
Simo 5d18af932d feat(news): add searchable paginated publication archive
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:19:12 +02:00
Simo a537c55793 feat(me): show actionable personal tasks with unavailable states
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:15:46 +02:00
Simo c389c3893d feat(cms): improve catalog, editorial recovery and operations
CI / check (push) Successful in 52s
CI / deploy (push) Successful in 2m10s
CI / publish-container (push) Failing after 1m18s
2026-09-09 19:36:15 +02:00
Simo 9b0ea2fb16 fix(news): restore publication flow and deduplicate dashboard friends
CI / check (push) Successful in 1m6s
CI / deploy (push) Successful in 59s
2026-09-06 18:32:43 +02:00
Simo b5dcadb67b feat(staff): present members in responsive profile cards 2026-09-06 11:53:05 +02:00
Simo 85a6df162b feat(me): organize personal dashboard and localize user actions
CI / check (push) Successful in 1m41s
CI / deploy (push) Successful in 1m29s
CI / e2e (push) Successful in 23s
2026-09-06 11:30:24 +02:00
Simo 31691c1c7d feat(profile): organize profile sections and use native currency icons
CI / check (push) Successful in 1m36s
CI / deploy (push) Successful in 1m40s
CI / e2e (push) Successful in 21s
2026-09-06 10:56:20 +02:00
Simo 6146995758 fix(profile): remove public role statistic
CI / check (push) Successful in 1m30s
CI / deploy (push) Successful in 1m29s
CI / e2e (push) Successful in 21s
2026-09-06 10:44:58 +02:00
openhands 8c12fc6c60 refactor(cms): deduplicate shared admin components and fix studio batch completion
CI / check (push) Failing after 1m35s
CI / deploy (push) Skipped
CI / e2e (push) Skipped
- fix(studio): stop markBatchDone infinite recursion so batches complete
- refactor(api): merge api-response into api and drop the duplicate module
- refactor(media): extract shared media loader and URL validator
- refactor(ui): extract shared LoadingSpinner for site and admin groups
- refactor(dates): consolidate raw date formatting into formatDate util
- refactor(logs): share a single generic log-list loader across tables
- refactor(theme): merge both ColorField components and reuse contrast helpers
- refactor(import): extract shared ImportErrorBanner and SearchInput
- chore(): remove dead theme-editor-tabs after inlining tab components
2026-09-05 20:58:42 +02:00
Simo 816e3875c2 feat(admin): add production error center and refresh CMS dependencies 2026-09-05 19:53:09 +02:00
Simo e17346ab9f refactor(cms): separate dashboard data and editor components with focused UX fixes
CI / check (push) Successful in 1m32s
CI / deploy (push) Successful in 1m20s
CI / e2e (push) Successful in 21s
2026-09-05 18:46:03 +02:00