Commit Graph
489 Commits
Author SHA1 Message Date
tiennm99 f9accfd70a feat(remotion): rework winner celebration and spin motion
The animation's weakest moment was its final frame, which is also the frame
most chat clients show as the GIF's poster image.

Celebration:
- Confetti launches from two cannons framing the wheel instead of bursting
  inside the hub, using closed-form drag ballistics so chips decelerate and
  flutter rather than flying a clean parabola. The field previously drained
  from 70 particles to 3 before the GIF ended; it now stays populated, and a
  2500ms hold ends with 68 on screen instead of 0.
- The opening frame rendered at opacity 0, throwing away the one frame where
  particles are still clustered. Removed the fade-in and made the tail
  frame-relative and quantized to bound its palette cost.
- Chips streak along their direction of travel in proportion to speed, faking
  the motion blur a GIF cannot carry. A second volley, sized to the
  celebration window, keeps long holds from thinning out.
- Each theme carries its own confetti palette. Slice colors are tuned to hold
  dark text over a large area, which left five of six mono colors under 2.4:1
  against the background and invisible at particle size.
- The winner's name appears over the hub during the hold. At a 200px chat
  display the pointer is ~17px and a slice label ~9px, so the poster frame did
  not say who won.
- Winner emphasis is an opaque stroke, not a drop-shadow. A filter on a Pie
  paints outside the shape, so later siblings overpainted it and only one
  neighbour ever showed the halo.

Motion:
- Turn count scales with the frame budget and slice width. A fixed 5 turns put
  peak speed at ~40deg per frame regardless of option count, so 65 of 98
  frames advanced more than a full slice at 32 options and the wheel appeared
  to run backwards. Turn counts floor rather than round, since rounding up
  reintroduces the aliasing, and stay integral so the winner still lands on
  the detent.
- The spin launch now blends into the deceleration at a matched slope. The
  previous curve started at maximum speed, and a naive ease-in jumped from 18
  to 41deg per frame in a single frame.
- A damped recoil rocks the wheel into its detent, resolving to exactly zero
  so the landing and the flapper's rest position are unaffected.
- The pointer is drawn as SVG with a real outline and a tick as slices pass.
  clip-path had been discarding its border and shadow entirely.

Legibility:
- Labels flip by their rest-frame angle so every name reads upright in the
  final frame. This reverses an earlier decision to leave them slice-aligned;
  three of eight names rendered mirrored on a default wheel.
- Label text renders inside a promoted compositing layer, which switches
  Chromium from LCD sub-pixel to grayscale antialiasing. Sub-pixel fringes put
  2178 colored pixels into an all-grayscale theme and consumed 255 of 256
  palette entries. Promoting the one rotating container rather than each label
  keeps render time flat on dense wheels.
- Line boxes leave room for stacked Vietnamese diacritics and shrink to fit
  rather than clipping.
- Slice colors are ordered for colorblind separation, and the last slice
  avoids reusing the first slice's color when the option count leaves a
  remainder of one.
2026-07-25 15:18:04 +07:00
tiennm99 ad3e50b7f4 docs: add confetti and staleness review report 2026-07-25 02:27:50 +07:00
tiennm99 07e43960b8 chore(deps): patch fast-uri, find-my-way, and postcss advisories
Update three transitive dependencies to their patched releases (fast-uri 3.1.4, find-my-way 9.7.0, postcss 8.5.22), clearing 3 high-severity Dependabot alerts. All were within existing parent semver ranges. Verified with pnpm audit (clean), lint, typecheck, 58 tests, and the API render smoke test.
2026-07-25 01:55:09 +07:00
tiennm99 a6c65768f5 test: cover render concurrency limit and winner picker 2026-07-25 01:17:19 +07:00
tiennm99 8fece5fd7f refactor(cli): reuse pickWinnerIndex for local winner selection
Route the CLI through the shared winner picker instead of crypto.randomInt so the positive-integer guard and selection intent stay consistent with the API.
2026-07-25 01:17:18 +07:00
tiennm99 ccb91adef4 fix(security): compare API token in constant time
Length-guarded timingSafeEqual avoids leaking token information through response timing on the auth path.
2026-07-25 01:17:18 +07:00
tiennm99 5048fff952 chore(schema): drop unused exported constants
themes, allowedSizes, and allowedFps had no importers; the theme list already lives in the request schema.
2026-07-25 01:17:18 +07:00
tiennm99 e20169bd62 docs: document winner confetti celebration 2026-07-25 01:17:18 +07:00
tiennm99 e8a3f87e3b chore: remove unused prettier dep and dead code
Drop prettier (no config, script, or eslint integration), the unused render-semaphore state() method and CreateRenderSemaphore typedef, and ignore .claude/settings.local.json.
2026-07-24 23:34:33 +07:00
tiennm99 f053682f84 fix(server): bind entrypoint on Windows
The guard compared import.meta.url against 'file://'+argv[1], which never matched on Windows (mismatched slashes and drive prefix), so the process started without ever calling app.listen(). Compare import.meta.filename to argv[1] instead.
2026-07-24 23:34:25 +07:00
tiennm99 376a7ae688 feat(renderer): add winner-reveal confetti burst
Deterministic frame-based confetti fired from the wheel center during the winner hold, seeded by winner index and colored from the theme palette. Uses Remotion's random() so each GIF frame renders identically.
2026-07-24 23:34:13 +07:00
tiennm99 5ca7e343ac docs(stock): record stock info delivery 2026-07-23 12:27:12 +07:00
tiennm99 52a8b928f0 feat(stock): add detailed stock info command 2026-07-23 12:27:12 +07:00
tiennm99 d1d17af7c4 docs(stock): finalize stock-events delivery 2026-07-23 10:08:47 +07:00
tiennm99 71dcef70c0 docs(stock): add stock-events plan artifacts 2026-07-23 10:05:44 +07:00
tiennm99 803df7ce99 feat(stock): add stock events lookup 2026-07-23 10:03:41 +07:00
tiennm99 8fcaf07bc3 docs(stock): record dividend retirement workflow 2026-07-22 18:56:07 +07:00
tiennm99 f9d1e52f6d fix(stock): retire dividend command and migrate stats 2026-07-22 18:55:57 +07:00
tiennm99 e46d10cc36 docs(portfolio): document mobile column layout 2026-07-22 17:36:29 +07:00
tiennm99 7009d5f7b2 fix(portfolio): tighten mobile column formatting 2026-07-22 17:36:29 +07:00
tiennm99 df240a5db0 feat(stock): persist per-user dividend history 2026-07-22 16:17:29 +07:00
tiennm99 8de459b131 fix(renderer): use consistent wheel label colors 2026-07-22 14:34:01 +07:00
tiennm99 7a0163ec01 feat: format compact portfolio numbers 2026-07-22 12:27:52 +07:00
tiennm99 7487d21499 fix(stock): note currency in portfolio title 2026-07-22 10:59:40 +07:00
tiennm99 cc7dd4aa10 docs: remove completed implementation records 2026-07-21 19:13:34 +07:00
tiennm99 d14b34d8ce docs(deploy): refresh persisted storage layout 2026-07-21 19:13:28 +07:00
tiennm99 5980c384c7 refactor(coin): retire completed startup cleanup 2026-07-21 19:13:23 +07:00
tiennm99 bfa04974a8 refactor(metrics): remove unused AI counters 2026-07-21 19:13:15 +07:00
tiennm99 ec06095ee7 chore(stock): log dividend event checks 2026-07-21 18:50:48 +07:00
tiennm99 01efb83df7 fix(coin): remove stale dividend cursor fields 2026-07-21 18:50:30 +07:00
tiennm99 ee2c520944 docs(stock): document dividend event buttons 2026-07-21 18:20:14 +07:00
tiennm99 d0098dafdd feat(stock): add dividend event buttons 2026-07-21 18:19:50 +07:00
tiennm99 53718dc8b8 docs(stock): research dividend event APIs 2026-07-21 17:13:43 +07:00
tiennm99 96ba34384a refactor(portfolio): retire completed migrations 2026-07-21 17:13:19 +07:00
tiennm99 7083079b39 feat(portfolio): nest asset positions 2026-07-21 16:42:33 +07:00
tiennm99 7711029f4c docs(portfolio): document cost basis and P&L 2026-07-21 15:52:30 +07:00
tiennm99 0dd93e3bf9 feat(portfolio): track stock and coin cost basis 2026-07-21 15:52:05 +07:00
tiennm99 d2c3129d41 refactor(commands): standardize parameter conventions 2026-07-21 14:51:28 +07:00
tiennm99 a211b7f8b7 fix(commands): remove examples from command discovery 2026-07-21 13:52:53 +07:00
tiennm99 b5c7427520 fix(commands): render examples conditionally with inline help code 2026-07-21 13:21:01 +07:00
tiennm99 f5ba9d4032 feat(commands): improve discovery and normalize parameters 2026-07-21 11:45:20 +07:00
tiennm99 3316eec74c test: provision MongoDB 8 with Testcontainers 2026-07-21 09:39:06 +07:00
tiennm99 f0d361eab5 refactor: adopt cross-platform Go development workflow 2026-07-21 09:05:20 +07:00
tiennm99 6ceb7aff02 docs(stock): document dividend commands 2026-07-20 17:33:10 +07:00
tiennm99 8b2ac571d9 feat(stock): add ratio dividend commands 2026-07-20 17:32:37 +07:00
tiennm99 6c934f6807 feat(modules): remove world cup module
The 2026 tournament has ended, so the schedule and daily digest commands
have no upcoming matches to report. Drops the module, its catalog entry,
command menu entries, and the WC_FOOTBALL_DATA_TOKEN env var.

Stored subscriber and match-cache documents are left in place.
2026-07-20 11:03:59 +07:00
tiennm99 15c383d6f1 feat(misc): add protected /ff giving-up template
Replies with a fixed Vietnamese copypasta for when T1 starts losing, built
from the local community's running jokes. Protected, so it stays out of the
public command menu like the other gated commands.
2026-07-19 20:09:22 +07:00
tiennm99 7c82842c1a feat(modules): match commands case-insensitively
Mobile keyboards autocapitalize, so a typed /Ping silently did nothing and
read as the bot being broken. Registered names are lowercase-only per
validateCommand, so folding case cannot make two commands collide, and the
canonical Command.Name still drives stats, metrics, hooks, and logs.
2026-07-19 20:09:16 +07:00
tiennm99 566b06a461 feat(renderer): add local GIF render CLI 2026-07-15 15:50:34 +07:00
tiennm99 963e8087a8 docs: clean legacy plans and document local GIFs 2026-07-15 15:01:00 +07:00