Commit Graph
100 Commits
Author SHA1 Message Date
hz c63227fcf5 Merge pull request 'Main' (#16) from main into production
Reviewed-on: admin/step3#16
2026-07-30 15:04:21 -05:00
hzandClaude Sonnet 5 9f150baecc Simplify exam simulations and strip the slot machine down to just the machine
Simulations no longer color-code correct/incorrect choices, retry a
failed per-question autosave, or show the RZ coin/ELO badge - users
move on immediately after submitting a timed block, same as a real
exam. The slot machine panel now shows only the cabinet/reels/lever
and a status line (title, banner, controls row, rewards list, and
footer removed), the reels fill the cabinet window instead of
floating in a black border, the coin-burst animation no longer
starts oversized, and the toolbar button is UAT-only via
Brand.is_uat_environment().

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-30 14:50:12 -05:00
hz 94de696af1 Fix shield icon rendering, move slot machine to a movable panel
The shield icon's dark/mushy rendering was caused by disabled mipmaps
on all 6 slot-machine icon textures (inherited from the flat lucide
icon convention, wrong for these gradient-heavy assets) - at small
display sizes this caused aliasing that hit the shield's fine detail
(RZ lettering, metallic bevels) hardest since the other icons are
bolder/simpler shapes. Enabling mipmaps/generate on all 6 fixes it;
confirmed by rendering the actual in-game presentation size before
and after, not just eyeballing the source file.

Also converts the slot machine from a full standalone screen (sidebar
nav entry, main.gd-managed) into a movable/draggable floating panel
opened from the question-player toolbar, mirroring the existing
calculator panel exactly: same top_level+custom title-bar-drag+
viewport-clamp mechanics, same open/close/toggle API, instantiated
once alongside the calculator and closed on session hide. Shrunk the
cabinet and restacked the rewards list below it (was beside it) to
fit a toolbox-sized floating panel instead of a full page, wrapped in
a ScrollContainer for any overflow.

Removed the sidebar entry and screens[] registration from main.gd and
the now-dead routing case from navigation_routes.gd.
2026-07-30 05:18:51 -05:00
hz 98518e8d39 Keep answered follow-ups expanded 2026-07-30 05:11:54 -05:00
hz fa5df8795f Reduce answer review scroll redraws 2026-07-30 05:02:03 -05:00
hz 9402a5d6de Merge pull request 'Fix simulated exam question flow' (#15) from fix/simulation-exam-flow into production
Reviewed-on: admin/step3#15
2026-07-30 04:27:48 -05:00
hz a58aa401c8 Fix slot machine: real spin animation, working lever, honest reels
Three real bugs from user testing:
- The lever was a plain TextureRect with mouse_filter set to IGNORE -
  it visually looked interactive but could never receive a click.
  Swapped it for a TextureButton wired to the same spin action, plus
  a quick pull-and-spring-back rotation for tactile feedback.
- The "reel animation" was just rapidly swapping each cell's texture
  in place - no actual motion. Reels are now real scrolling strips
  (a clipped viewport + a taller VBoxContainer strip that scrolls and
  recycles its front icon to the back), which is what an actual
  spinning reel look/feel requires.
- Every reel unconditionally settled on the coin icon regardless of
  what was actually won, so a jackpot and a QBank Reset Token looked
  identical - the two facts users need to trust the mechanic. Reels
  now settle with the real reward's icon landing in the middle
  (payline) row on all three columns, with random decorative icons
  above/below, so the visual result always matches the actual payout.

Verified the settle math directly (a reel-only probe confirming the
middle icon matches the requested target after recycling) and the
lever's click wiring (confirms _spinning toggles and the disabled
state round-trips), plus a real rendered screenshot of a settled
spin, before cleaning up the probes.
2026-07-30 04:26:42 -05:00
hz bf56f4192f Fix simulated exam question flow 2026-07-30 04:26:18 -05:00
hz 18802f4e8d Merge pull request 'Require production approval for question media' (#14) from fix/production-media-review into production
Reviewed-on: admin/step3#14
2026-07-30 04:20:22 -05:00
hz 0676c7f95e Require production approval for question media 2026-07-30 04:18:31 -05:00
hz 09502944ec Require production approval for question media 2026-07-30 04:16:18 -05:00
hz 3fd670d111 Replace hand-coded cabinet/lever/icons with real generated art
The user generated actual 3D-rendered assets (cabinet frame, lever,
reel-symbol sprite sheet) via an image model - swaps out the
StyleBoxFlat-approximated cabinet, Tween-animated marquee lights/glow,
and gradient-SVG icons from the last pass for the real thing.

The generated PNGs had no real alpha channel (a baked checkerboard
pattern standing in for transparency, a known limitation of some
image-gen output) - added a flood-fill background remover (BFS from
the image border, matching near-white/grey checkerboard tones) to
produce genuinely transparent PNGs, then sliced the sprite sheet into
6 individual icons and measured the cabinet's empty reel-window
rect as a fraction of the frame image so the real 3x3 reel grid and
lever can be positioned precisely over the static art via anchors.

Verified with actual rendered screenshots at each step (window
alignment, then lever sizing/position) rather than just structural
checks, since this is exactly the kind of thing that looks fine in
code and wrong on screen.
2026-07-30 04:06:35 -05:00
hz 0bd74fcabf Temporarily make slot machine spins free for testing
SpinCost set to 0 so spins don't require RZ Coins - revert to 50
once testing is done (marked with a TODO). The frontend already
reads spin cost from GET /api/credits/spin-config rather than
hardcoding it, so the "Cost per spin" display and the Spin button's
enabled state update automatically.
2026-07-30 03:39:44 -05:00
hz 1bc7fbfb3c Generate real glossy reel-symbol art and close the visual gap further
Hand-authored 6 gradient-shaded SVG icons (gold coin, purple gem,
green potion, blue RZ shield, coin stack, gift box) to replace the
flat monochrome placeholder icons in the reels and reward list - no
image-generation tool is available here, so this is genuine vector
art with radial/linear gradients and highlight layers rather than a
literal asset match to the mockup's renders.

Combined with real Godot techniques: a layered-label glow effect on
the "RZ" marquee, chase-lit marquee bulbs down both sides of the
reel screen (looping Tweens), a much more pronounced arch silhouette
and neon border on the cabinet, and an overall scale-up (bigger
reels, marquee, lever) so the machine reads as the dominant element
instead of a cramped placeholder. Verified with an actual rendered
screenshot (not just structural checks) at each iteration.
2026-07-30 01:54:45 -05:00
hz bcb411bed8 Make the slot machine its own sidebar entry; rework its visuals
Moves Slot Machine into the sidebar's account section (alongside RZ
Shop/Settings/Log out) instead of being reachable only from the shop -
removes the shop's promo banner and the screen's "Back to Shop" link
accordingly.

Also closes a chunk of the visual gap against the mockup: a proper
cabinet-style panel (arched top corners, glowing border), a light
"screen" panel holding a real 3x3 reel grid instead of 3 single icons,
a decorative lever, an "RZ" marquee, and the cost/spin/auto-spin
controls each in their own bordered box. Added two new vendored icons
(gem, flask-conical) for reel variety and reward-list icons.
2026-07-30 01:20:24 -05:00
hz 1860e6ede6 Add an RZ Coin slot machine
Adds a spend-coins-for-a-random-reward mini-game reachable from the
shop. Backend mirrors the store's existing atomic/idempotent purchase
pattern (SpinAsync in CreditStore.cs) with a server-authoritative,
weighted reward roll and a rolling per-user spin-rate guard; odds are
served from GET /api/credits/spin-config so the client never hardcodes
numbers that could drift from the real payout table. The mockup's
"Streak Protector" reward was swapped for a real Exam Simulation Token
since streaks aren't currently a persisted, protectable concept.

Also fixes two latent bugs surfaced while building this:
- credits_service.gd's operation-id builder referenced a nonexistent
  AuthState.user_id (should be .username), throwing on every credit
  action.
- _post() silently no-op'd with no signal when signed out, which
  combined with a GDScript closure gotcha (lambdas capture outer
  locals by value, so reassigning inside one never reached the
  caller) meant a spin attempted while unauthenticated could hang
  forever instead of surfacing an error.
2026-07-30 00:29:49 -05:00
hz 4909cf0b71 Move RZ Shop nav entry to the lower account section, rename it
Groups it with Profile/Settings/Log out instead of the primary study
nav, and shortens the label from "RZ Coin Shop" to "RZ Shop".
2026-07-29 21:39:04 -05:00
hz a162ce31c6 Add tab row, feature bullets, and a Popular badge to the RZ Coin Shop
Matches the latest reference more closely: a Featured/Tokens/Boosts/
Content/Customization/Bundles tab row (only Featured is populated -
the rest are inert labels since there's no real data behind them
yet), per-card checkmark feature bullets, and a Popular badge on the
Exam Simulation Token card. Client-side presentation only, no backend
changes.
2026-07-29 20:52:59 -05:00
hz 19371b7a93 Restyle the RZ Coin Shop screen as a card grid
Replaces the plain title/list layout with a header, a coin-balance
summary card, and a product card grid (icon, description, price,
solid Buy button) matching the app's existing card/badge visual
language. Only the two real products render - no fabricated
categories or bundles, since the backend doesn't have that data yet.
2026-07-29 19:01:23 -05:00
hz da641a8d1d Add Rz Coin utility store 2026-07-29 18:34:54 -05:00
hz f39bfb42be Merge pull request 'Main' (#13) from main into production
Reviewed-on: admin/step3#13
2026-07-29 01:35:56 -05:00
hz c14ac1d65b Require reference ranges for published question labs 2026-07-29 01:28:37 -05:00
hz f082be38e0 Restore Rz Coins caption and fix rank/ELO badge wrapping below toolbar
Reverts the coin caption back to "Rz Coins" and shrinks the badge's
icons/fonts/spacing so both segments fit in the same footprint budget
the toolbar reserves. That budget was a hardcoded 900px sized for the
old single-segment badge; bump it to 960px to match the wider badge
instead of letting it overflow onto its own row.
2026-07-29 01:16:47 -05:00
hz 845907e825 Show rank tier and ELO alongside RZ points in the coins badge
Adds a server-computed tier/division (Bronze..Diamond, III-I) to the
ratings profile endpoint and surfaces it next to the numeric ELO in the
shared header badge, matching the coin balance display already there.
2026-07-29 00:25:26 -05:00
hz 89dbe3e31a Initialize credit earning ledger arrays 2026-07-28 20:52:20 -05:00
hz dd203b24f9 Allow SUPER accounts to earn Rz Coins 2026-07-28 20:36:15 -05:00
hzandClaude Sonnet 5 6f41906101 Gate Rz Coins on correct-first-try-within-time-limit; match coin-flight animation to it
Credits were previously awarded on any first attempt regardless of
correctness or timing. The award now requires the first-ever attempt
to be correct (record.Attempt.IsCorrect) and submitted before the
adaptive per-question timer expired (new MetTimeLimit request field,
computed client-side from the same elapsed/target comparison that
already drives the visible countdown bar). The coin-flight animation
in question_player.gd now only plays when both conditions are met
(BlockSessionService.submit_core_attempt's existing "mastered" flag
plus met_time_limit), so the effect never promises a coin that isn't
actually earned.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-28 20:17:07 -05:00
hzandClaude Sonnet 5 efae7e0b2b Add live RZ Points balance widget to question and shell headers
Wires the recently-merged question-completion-credits backend
(GET /api/credits) into the client for the first time: a new
CreditsService autoload fetches/caches the balance, and the embedded
credits field on first-answer responses updates it instantly with no
extra round trip. A shared CreditsBadge widget (star icon, balance,
decorative chevron per mockup) surfaces it in both the question
screen toolbar and the main shell header.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-28 19:33:33 -05:00
hzandClaude Sonnet 5 05a5298ed1 Add home button to question screen toolbar to exit the run
Previously the only way out of an active question run was Escape to
open the pause overlay, then click Exit Run. Exposes main.gd's exit
logic publicly (_exit_run -> exit_current_run) so a house-icon button
in the question toolbar can trigger it directly.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-28 19:06:12 -05:00
hz b511d27a20 Merge question completion credits 2026-07-28 18:58:21 -05:00
hz 22b0df7aed Add isolated question completion credits 2026-07-28 18:40:49 -05:00
hz 63fa073530 Merge pull request 'Enable HTML5 virtual keyboard for text field input on mobile Safari' (#12) from main into production
Reviewed-on: admin/step3#12
2026-07-27 21:58:41 -05:00
hzandClaude Sonnet 5 41731f5804 Enable HTML5 virtual keyboard for text field input on mobile Safari
iPhone users hit the Web build via Safari, but experimental_virtual_keyboard
was disabled, so focusing a LineEdit/TextEdit never summoned the on-screen
keyboard.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-27 21:42:12 -05:00
hz e3e18f6415 Merge pull request 'Fix login/calculator theme bugs, gate flashcard button, hide system label pre-answer' (#11) from main into production
Reviewed-on: admin/step3#11
2026-07-27 01:03:50 -05:00
hz 9f836c8765 Fix login/calculator theme bugs, gate flashcard button, hide system label pre-answer
- Steam-linked login no longer writes literal "<null>" into the email field
  (and no longer locks it read-only) when the verified identity has no email
- Login screen releases focus on app focus-out so the username caret stops
  blinking while the window is inactive
- Calculator display text stays legible in light mode; the LCD-style panel
  background is always dark, so its text should not follow the app theme
- Question player: gate the "save as flashcard" toolbar button behind
  Brand.is_uat_environment(), matching the nav item, so it's hidden in prod
- Hide the discipline/system label while a question is unanswered so it
  can't hint at the answer; reveal it once the explanation is shown
- Top-align question content instead of vertically centering it, with a
  20px top margin, so it sits closer to the progress indicator
2026-07-27 00:43:18 -05:00
hz 86ae650272 Merge pull request 'Main' (#10) from main into production
Reviewed-on: admin/step3#10
2026-07-26 18:37:53 -05:00
hz 23d573e221 Fix GDScript ternary and shadowed-identifier warnings in dashboard scripts
Godot's static checker can't unify a float branch with a null branch in
a ternary, so point_value() now uses explicit if/return; renaming the
local sign variable to sign_prefix avoids shadowing the builtin sign().
2026-07-26 18:28:55 -05:00
hzandClaude Sonnet 5 548b519d44 Vertically center the login screen wordmark against the logo
The RanaZe/STEP 3 TRAINER text block was top-aligned inside the
brand row's HBoxContainer, which is taller than the text due to the
190x190 icon beside it, so it hugged the top instead of centering
against the shield mark.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 18:08:36 -05:00
hz 1ff2d612a4 Merge pull request 'Main' (#9) from main into production
Reviewed-on: admin/step3#9
2026-07-26 16:53:15 -05:00
hz 1d5d03a4f6 Add difficulty 4 question gap batch 2026-07-26 16:32:41 -05:00
hzandClaude Sonnet 5 efe60f36c5 Fix dashboard info-icon rendering and profile <null> display
Info badge: the icon was a PanelContainer using StyleBoxFlat corner
radius to fake a circle, with the radius exceeding half the box size,
causing visible artifacts. Replaced with a custom-drawn Control
(draw_arc) for a clean ring at any size, and added a hover tooltip
explaining what each stat card measures.

Profile: Dictionary.get(key, default) only falls back when the key is
missing, not when it's present with a null value, so a null
displayName rendered as the literal string "<null>" (str(null) in
GDScript) - including pre-filling the edit-profile dialog with it.
Added a shared string_field() helper that treats null the same as
missing, applied everywhere an account/preference string field is
displayed or edited.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 16:27:46 -05:00
hzandClaude Sonnet 5 f680024dc3 Rewrite the donation screen's mission statement
Repositions the copy around being an independent, at-cost alternative
to the Step 3 prep industry's subscription funnels, and broadens the
audience from residents specifically to any Step 3 test taker.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 16:19:21 -05:00
hz 94e0e5dd70 Confirm passwords for external account creation 2026-07-26 14:52:41 -05:00
hzandClaude Sonnet 5 74398133f7 Exclude sample exam simulations from the dashboard's readiness score
The SUPER-role "preview results" QA tooling seeds a completed
ExamSimulation with scripted (not real) answers, flagged IsSample.
The dashboard's readiness lookup picked whichever completed
simulation was most recent regardless of that flag, so running the
QA preview on an account could override its real Exam Readiness card
with a synthetic pass probability. Recent-activity/weekly-activity
queries got the same exclusion since they read from the same
collection for the same reason.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 14:47:07 -05:00
hz 447f080552 Add ready question production promotion 2026-07-26 01:17:55 -05:00
hzandClaude Sonnet 5 ef44347bf6 Fix loader tagline centering and dashboard card icons
Centers the loader's tagline under the logo, replaces the broken
info-glyph (a Unicode character missing from the Inter font, which
rendered as a tofu box) with a drawn circle-and-i badge, and swaps
the stat-card icons to match the mockup: book for Question Bank
Rating, bar chart for Exam Readiness, star for Average Score.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 01:06:05 -05:00
hz 4251885a73 Redesign dashboard top-4 stats as separate cards
Frontend half of the dashboard stat-card redesign, matching the
provided mockup. The 4 metrics used to be plain rows sharing one
bordered box (_add_metric -> a single metric_grid inside one _card).
Replaced with 4 independently-_card-wrapped panels in a responsive
GridContainer (4 cols / 2 compact, same threshold _update_responsive
already used).

Each card now has: a colored icon badge, title + info glyph, a big
value, a colored trend/status line, a small visual, and a footer
caption:
- Question Bank Rating / CCS Rating: a new minimal DashboardSparkline
  (dashboard_sparkline.gd) fed from the current rating and the
  week-ago snapshot (reconstructed client-side from the delta the API
  already returns - no separate history endpoint needed). Trend line
  and caption come from the new delta/percentile fields; both degrade
  to a plain single-point sparkline and a rated-count caption when no
  snapshot exists yet.
- Exam Readiness: a new DashboardSegmentBar (dashboard_segment_bar.gd -
  nothing like a segmented pill progress indicator existed anywhere in
  the codebase) instead of a sparkline, colored by the High/Medium/Low
  label; shows a neutral "complete a simulation" state when the user
  has none yet rather than a fabricated readiness.
- Average Score: reuses the existing ProgressBar/_style_progress,
  relabeled from "First-Attempt Accuracy".

Dropped the old "Study Time" card (not part of the mockup - the
underlying data still drives the weekly activity chart below, it just
no longer gets a dedicated top-line number) and the now-fully-unused
_format_duration.

Verified headlessly: instantiated the real screen against fake
DashboardService data, confirmed every card's value/trend/caption text
and the segment-bar/progress-bar values match the mockup exactly, and
rendered actual screenshots of both the populated and "no data yet"
fallback states.
2026-07-26 00:43:39 -05:00
hz 34d283e213 Add rating snapshots, percentile, and exam readiness to the dashboard API
Backend half of the dashboard stat-card redesign. Three new data
points the old DashboardResponse had no way to compute:

- Week-over-week rating deltas: UserRating/CcsUserRating only ever
  stored the current Elo (mutated in place, UpdatedUtc just means
  "last touched"), so there was no way to know what a rating was 7
  days ago. Added UserRatingSnapshot (new collection, unique on
  UserId+DateUtc) and UserRatingSnapshotService, a BackgroundService
  following AnswerStatisticsMaintenanceService's exact shape (a
  PeriodicTimer plus a Mongo lease so replicas behind the HPA don't
  race), writing one snapshot per user per day. No historical
  backfill - deltas are simply null for a user's first ~7 days after
  this ships, which the DTO represents honestly via nullable ints
  rather than a fake "+0".
- Percentile rank: DashboardStore now counts how many users have a
  higher OverallElo/Elo than the current user out of the total.
- Exam Readiness + projected pass rate: reuses the existing, tested
  IRT-based ExamSimulationScore.PassProbability (a real normal-CDF
  estimate of scoring >=200, computed in ExamSimulationScoring.Calculate)
  rather than inventing a new prediction model. DashboardStore pulls
  the latest completed simulation's score directly and maps
  PassProbability to a High/Medium/Low label via tunable thresholds.
- Average Score's "+6% vs last 100" needed no new data source at all -
  just one more slice (Skip(100).Take(100)) over the QuestionAttempts
  already loaded for FirstAttemptAccuracy.

Added DashboardStore.Percentile/ReadinessLabel as pure static helpers
(matching the existing Percentage/CalculateCurrentStreak pattern) and
unit tests for both. Full backend suite: 165 passed, 0 failed.
2026-07-26 00:39:11 -05:00
hz 500477b612 Add dashboard performance trends 2026-07-25 22:43:34 -05:00
hz 46953ee71a Reach Settings/Profile from the pause menu, with a way back
The pause menu (Escape during a question/CCS/flashcards run) only had
Resume/UI Sounds/Exit Run/Log Out. Settings already has an equivalent
"Interface sounds" toggle, so drop the duplicate from the pause menu.
More importantly, "player" and "ccs_player" hide the entire app shell
(header + sidebar), so there was no way to reach Settings or Profile
at all mid-session - add Profile/Settings buttons to the pause menu to
close that gap.

Navigating there follows the existing return_screen payload
convention (already used by simulation_history/results/review) rather
than inventing new navigation infrastructure: _navigate_from_pause()
remembers current_screen_name and passes it through show_screen's
payload. Both screens show a "Back to session" button when that's
set, and hide it otherwise (including on ordinary non-paused visits).

show_screen() unconditionally resumes the question timer via
_close_app_menu() as a side effect of any navigation, so
_navigate_from_pause() re-pauses it immediately when leaving "player" -
the timer then resumes for real, for free, when the user actually
returns via show_screen(return_screen).

progress_profile.gd rebuilds its whole child tree on every theme
toggle (_rebuild -> _build), which would otherwise wipe the button's
visibility - added _refresh_back_to_session_visibility(), called both
after _build() and from on_show(), so it survives a theme change while
paused.

Flipped tests/web_responsive_test.gd's assertion, which previously
required the pause menu NOT to have a Profile button.

Verified end-to-end headlessly: opening the pause menu from a live
"player" session, navigating to Settings and Profile, confirming
current_question_pause_started_at is correctly paused while away and
resumed on return, confirming the back-to-session button appears only
when reached via pause (not on ordinary nav), and confirming it
survives a dark/light theme toggle on the Profile screen.
2026-07-25 22:30:03 -05:00
hz c0252a916d Merge pull request 'Main' (#7) from main into production
Reviewed-on: admin/step3#7
2026-07-25 22:12:40 -05:00
hz aa5497c516 Center the legal consent row instead of left-aligning it
Wrapped the row in a CenterContainer so the checkbox+text sit centered
under the field column instead of hugging the left edge. Had to drop
the RichTextLabel's EXPAND_FILL (meaningless inside a CenterContainer,
which never stretches its child) in favor of an explicit 480px
minimum width - without one, the label collapsed toward zero width
and wrapped character-by-character, which is what the first attempt
at this actually rendered as before I caught it.

Verified with a direct coordinate check: the row's horizontal center
now matches the confirm-password field's center exactly (delta 0.0).
2026-07-25 11:00:48 -05:00
hz 6b1e41c187 Fix loading-screen sizing shift from the Inter font swap
font-display: swap paints with the fallback font stack first, then
swaps to Inter once the 876KB variable font finishes downloading.
.loader-wordmark was already width-fixed from an earlier centering
fix, but .loader-tagline, .loader-message, and .progress-percent had
no explicit width and (more importantly) used line-height: normal,
which is derived from each font's own metrics - Inter and the fallback
stack disagree by 3-4px per line. Those three deltas summed exactly to
the .loader column's total 10px height change, which is what visibly
shifted the whole centered loading screen once the swap happened.

Gave the width-dependent elements width: 100% (matching the wordmark)
and gave all three text elements an explicit unitless line-height
(computed from font-size, not font metrics) so their box size no
longer depends on which font is currently active.

Verified empirically: forced the fallback font stack on the live page
and diffed getBoundingClientRect() against Inter for every loader
element - all width/height deltas are 0 after the fix (previously up
to 17.5px width / 10px cascaded height).
2026-07-25 10:53:53 -05:00
hz eb9e018339 Fix non-square consent checkbox with a dedicated Panel square
Root cause: CheckBox/Button reserves vertical space for a text
baseline even with an empty string, so no amount of custom_minimum_size
or stylebox content-margin tweaking produced a true square - it just
came out close-but-not-quite in some layouts and visibly elongated in
others.

Decoupled the visual square from the CheckBox entirely: a plain Panel
(exactly 20x20, toggled by direct click) now draws the border/fill,
while a real (invisible) CheckBox still holds .button_pressed for the
rest of the code to read. Also added left padding on the row and
vertically centered both the square and the consent text within it.

Verified with a direct size dump (20x20 in both checked/unchecked
states, where it previously measured non-square) and rendered
screenshots of both states.
2026-07-25 10:45:45 -05:00
hz bff16cea98 Fix oversized, borderless consent checkbox
The generic "style every Button" pass in _apply_theme also matched
CheckBox (it's a Button subtype) and ran after the checkbox-specific
styling, overwriting the intended blue border with the plain default
button box - and that box's own content margins were inflating the
checkbox well past its 26x26 minimum (actual rendered size was 60x52).
Skip CheckBox in that generic loop, zero out the checkbox stylebox's
content margins, shrink it to 22x22, and stop it stretching to the
row's full height. Also sized the consent text down (15->13) and gave
the row some vertical breathing room.

Verified with a rendered screenshot and a direct stylebox/size dump:
checkbox is now 22x24 with a real 2px blue border, matching the
provided reference.
2026-07-25 10:26:35 -05:00
hz 5a2d825181 Rework legal consent to match the provided design
Drop the bordered card - it wasn't the right direction. Instead:
- Plain small checkbox with just a blue border (no fill/icon when
  unchecked), matching the reference exactly.
- "Terms of Service"/"Privacy Policy" render as inline blue links
  within the sentence (RichTextLabel + BBCode [url], routed through
  the same _open_legal_document as the footer), instead of a duplicate
  link row underneath. meta_underlined = false to match the
  reference's plain (non-underlined) link style.

Verified with real rendered screenshots at each step, including a
pixel-level crop confirming the underline was actually gone.
2026-07-25 10:13:18 -05:00
hz 7e977e0d30 Clean up the legal consent checkbox layout
A bare CheckBox's word-wrap left-aligns every wrapped line flush with
the checkbox glyph, so "Privacy Policy." on its own line read as
unindented and sloppy next to an otherwise fully themed form. Split
the toggle from its label (a separate Label wraps within its own
column instead, so wrapped lines stay aligned under the first line)
and wrapped the row in a bordered card matching the app's field
styling. Clicking the label also toggles the checkbox now, for a
bigger hit target.

Verified with an actual rendered screenshot: wrapped text now indents
consistently, and both checked/unchecked states are legible.
2026-07-25 09:52:41 -05:00
hz ba8e0ec256 Remove duplicate Terms/Privacy links from account creation
Register and Steam-linking forms rendered their own Terms of
Service/Privacy Policy links right under the consent checkbox, on top
of the ones already pinned to the bottom of the panel. The consent
checkbox text alone is enough; the persistent footer covers the links.
2026-07-25 09:36:06 -05:00
hz 7be83661f9 Standardize password reveal to the in-field eye icon everywhere
Register, reset, and Steam-linking forms used a separate "Show
password" CheckButton row; the main login form already had the eye
icon embedded in the field itself. Generalized _add_password_reveal
(previously hardwired to a single tracked button) to support multiple
fields via password_reveal_entries, and used it for every password
field instead. Removed the now-unused _add_show_password.

Verified all 6 password fields (login, register x2, reset x2, steam)
get a working reveal button and there's no leftover "Show password"
checkbox anywhere.
2026-07-25 09:34:44 -05:00
hz bb9f59b3cc Center STEP 3 TRAINER under the RanaZe wordmark
.loader-wordmark had no explicit width or text-align, so as a flex
item (align-items: center) it shrink-wrapped to whatever width the
"RanaZe" inline spans needed. wordmark-trainer's display:block then
filled that same ambiguous width rather than the wordmark block's
true rendered box, leaving it off-center relative to "RanaZe" above
it. Giving .loader-wordmark an explicit width:100% and text-align:
center makes both lines center within the same known-width box.

Verified in a real browser: both elements now report identical
getBoundingClientRect() centers (x=960 in a 1920px viewport).
2026-07-25 09:14:27 -05:00
hz 854c887a1a Center login panel content, pin legal links to the bottom
Making the auth panel a fixed height (to kill the scrollbar) left the
shorter views (login, reset) stranded at the top with dead space below.
Split panel_content into a main_column (header, message, mode views -
expand-fill, center-aligned) and the legal footer as a sibling that
stays pinned to the panel's bottom edge regardless of which view is
showing or how tall its content is.

Verified headlessly: top/bottom whitespace above and below the active
view's content is now equal (e.g. login 174/174, steam 2/2), and the
footer's position doesn't move between modes.
2026-07-25 09:10:19 -05:00
hz ce4db7cec8 Remove the login panel scrollbar
The auth panel was a fixed 710x820 (640x790 on narrower desktop
windows) regardless of which view was showing, but Register and the
Steam linking form need up to ~990px to lay out without scrolling -
so the inner ScrollContainer was always kicking in for those views.

- Tightened form spacing (_new_form separation 16->10, field height
  68->56, panel_content separation 24->16, page margins 56->28) to
  shrink how much height each view actually needs.
- Size the panel to the tallest view's real requirement (1000/1010),
  clamped to whatever height the window actually has so a short window
  shrinks the panel - and lets the ScrollContainer take over as a
  fallback - instead of the panel overflowing off-screen.

Verified headlessly across 1920x1080, 1366x768, and a narrow
compact-desktop width: no scrollbar in any mode at the two common
resolutions, and the panel now stays within the window (falling back
to scrolling only for the two tallest forms on the 768px-tall case,
rather than clipping off both edges of the screen as a fixed height
would have).
2026-07-25 00:52:12 -05:00
hz 191aa685fa Fix shadowed category_label warning in the report-question form
A local category_label in _build_report_modal() shadowed the
class-level category_label field (the question's topic tag near the
stem). Renamed the local to report_category_label.
2026-07-25 00:52:02 -05:00
hz 81e543fdf5 Merge pull request 'Main' (#6) from main into production
Reviewed-on: admin/step3#6
2026-07-25 00:39:14 -05:00
hz 65ae99e2ce Update exe boot splash to the current shield logo
boot_splash/image was still on ranaze-loader-icon-v1.png, the mark
from before the app rebranded to the shield "RZ" logo (c91e70d). Point
it at assets/branding/ranaze-mark.png, the same mark already used for
the in-app header brand throughout the current UI.
2026-07-25 00:31:57 -05:00
hz b7b290c63b Fix deployment_environment default so the debugger reads UAT
project.godot's API URLs already pointed at UAT (us3q.ranaze.com), but
deployment_environment itself was still "development" - a leftover
label that never matched the actual origin. Since the UAT-only feature
gates (Dynamic Questions, CCS Cases, Flashcards) check for the literal
"uat" value, running from the Godot editor/debugger hid all three even
though it's UAT in every other respect.

Set it to "uat" to match reality, and update the two places that
sed-patch this value at build time (web/Dockerfile for the UAT/PRD web
builds, tools/export_steam_release.sh for the Steam build) so they
still match the source string they're replacing.
2026-07-25 00:30:14 -05:00
hz c1a71b1192 Feature-gate Flashcards to UAT only
Same treatment as Dynamic Questions and CCS Cases: hide the nav
sidebar entry and the dashboard's "flashcards due" review card unless
step3/deployment_environment is "uat".
2026-07-25 00:30:06 -05:00
hz ca218a6ae5 Merge pull request 'Main' (#5) from main into production
Reviewed-on: admin/step3#5
2026-07-24 23:46:10 -05:00
hz f3a2e65627 Skip UAT/PRD deploys for changes that don't touch the deployable
Docs, local release tooling (tools/), and the GDScript test suite
(never executed by this pipeline, and irrelevant to the API image,
the exported web build, or k8s manifests) don't need a full deploy.
workflow_dispatch still allows a manual run for any of these if ever
needed.
2026-07-24 23:36:09 -05:00
hz bca31bd142 Add a Steam release export script pointed at production
project.godot stays checked in against UAT (us3q.ranaze.com) so the
Godot editor/debugger keeps testing against UAT. This script patches a
local copy to prd/s3q.ranaze.com just for the Windows Desktop export -
the same swap web/Dockerfile already does for UAT vs PRD web builds -
runs the export, then always restores the original file.

Verified: after a run, `git diff project.godot` is clean and the
exported .pck contains s3q.ranaze.com with no trace of us3q.ranaze.com.
2026-07-24 23:32:48 -05:00
hz 0e473343d0 Fix stale web_responsive_test icon assertion
It checked for a "DynamicQuestionsIcon" mode card that no longer
exists in study_selector.gd (that mode moved to its own nav item and
screen a while back), so it always failed. CCS Cases is now also
UAT-gated and won't be present in the default test environment either,
so point the check at Question Bank, the one mode card that's always
rendered.
2026-07-24 23:17:59 -05:00
hz 6df0a88fd4 Feature-gate Dynamic Questions and CCS Cases to UAT only
Both are still in progress and shouldn't be reachable in production
(or local dev) yet - hide every entry point (nav sidebar, the
dashboard's "dynamic concepts due" review card, and the CCS Cases
mode card on the study selector) unless step3/deployment_environment
is "uat", the same setting that already drives the UAT badge. Added
Brand.is_uat_environment() as the shared check.
2026-07-24 23:12:47 -05:00
hz 118c215608 Add a setting to turn off follow-up questions
The optional "FOLLOW-UP QUESTIONS" reinforcement section shown during
answer review is now gated behind a Settings toggle (on by default),
persisted locally the same way as dark mode and interface sounds.
2026-07-24 23:01:06 -05:00
hz cd1648fe38 Shorten "Skip question" button to "Skip" 2026-07-24 22:14:24 -05:00
hz f1b290aa73 Dock the question toolbar in line with the brand row
The toolbar had followed the progress group down into the row below
the branding after it moved there - readers expect actions docked to
the top brand row, not hanging off the question count.
2026-07-24 22:10:49 -05:00
hz 50f54b1bec Polish question player header/toolbar and add configurable block size
- Fix the toolbar wrapping onto two rows: its minimum width was tuned
  for an older, narrower button set and never widened back out after
  reverting to bare icon buttons.
- Drop the "Time elapsed"/"Ctrl+Enter" readout next to Submit - it was
  clutter; the underlying timer logic keeps running unseen.
- Move the RanaZe brand mark above the question-count row instead of
  sharing a line with it.
- Progress text drops the word "Question" everywhere ("1 of 10"), and
  core/dynamic sessions now cycle against a configurable block size
  (Settings > Question block size, default 10) instead of counting up
  forever.
2026-07-24 21:57:03 -05:00
hz 62e65fd22c Add Windows Desktop export with RanaZe branding
Replace the default Godot boot splash and exe icon with the RanaZe
mark so a Windows build reads as RanaZe from first launch, not the
engine it's built on.
2026-07-24 21:56:53 -05:00
hz 5197234210 Drop toolbar text labels in favor of hover tooltips
Varying label widths (especially when a button hides itself, e.g.
"Labs" on a question with no lab results) broke even spacing between
icons. Every toolbar button already has a tooltip_text carrying the
same label as hover text, so drop the labels/wrapper containers
entirely and go back to plain, uniformly-spaced 44x44 icon buttons.
2026-07-24 21:05:34 -05:00
hz 576b48d581 Fix toolbar gaps and theme toggle shape
- A toolbar icon that hides itself at runtime (labs, notes,
  flashcard, bookmark) left its text label behind, showing as an
  orphaned label floating in a gap. The label's wrapper now tracks
  the button's own visibility.
- The theme toggle used a distinct circular pill shape; changed it
  to match the same rounded-square box every other toolbar icon uses.
2026-07-24 19:55:49 -05:00
hz d073f82a76 Fit the toolbar on a single row
The 11 labeled groups are wider than the old bare-icon buttons, so
the toolbar's allotted minimum width (tuned for the old layout) was
too narrow and forced a wrap onto two rows. Widened it and raised
the compact-mode breakpoint to match.
2026-07-24 03:23:14 -05:00
hz a94cc5188f Fix bottom-bar time caption for core sessions
Core sessions show a per-question stopwatch that counts up (no fixed
deadline, just a pacing signal), not a countdown - it was incorrectly
labeled "Time remaining" like the simulation/Timed countdown; now
reads "Time elapsed" to match what the number actually does.
2026-07-24 03:16:30 -05:00
hz 0902cc238a Un-collapse the question toolbar - show every action directly
The 6-item + "More" overflow popup made the toolbar feel sparse and
hid frequently-used actions (flag, report, flashcard, theme) behind
an extra click. Reverting to a flat toolbar where every action is
directly visible and labeled, including text size (grouped A-/A+
under one "Text size" caption instead of behind a popup).
2026-07-24 03:03:40 -05:00
hz 974de67032 Redesign question bank screen to match new mockup
Adapts the quiz screen's header, toolbar, choice cards, and bottom bar
to the designer's new layout:

- Header: adds the RanaZe logo/wordmark and a question-progress bar +
  percentage alongside the existing "Question N of M" text.
- Toolbar: consolidates 12 icon-only/chip buttons into 6 labeled items
  (Text size, Calculator, Clear highlight, Notes, Bookmark, More), with
  a new "More" popup holding the rest (labs, flag, report, flashcard,
  theme toggle, feedback). The underlying buttons are reparented, not
  duplicated, so every existing theming/visibility/state call site
  keeps working unmodified.
- Category tag: shows the question's discipline (falling back to
  system) above the stem.
- Choice cards: every choice now renders as a subtly-bordered rounded
  card even before answering, not just a flat divider row; the
  currently-selected pre-submit choice shows a checkmark.
- Bottom bar: merges the Skip/Submit row and the timer row into one -
  Skip as a plain link, a centered "X of Y" progress bar, and a
  time slot that shows the existing countdown when a limit applies or
  a new count-up stopwatch ("Time elapsed") otherwise, plus a
  Ctrl+Enter/Cmd+Enter submit shortcut with a visible hint.
2026-07-24 02:28:22 -05:00
hz edcc0def5d Fix CCS category order search 2026-07-23 23:14:38 -05:00
hz b9d4687b1b Fix dynamic Elo clamp updates 2026-07-23 23:01:52 -05:00
hz 0ec486f132 Stabilize UAT API pod selection 2026-07-23 17:03:02 -05:00
hz 32c455bba0 Improve dynamic question learning loop 2026-07-23 16:50:53 -05:00
hz c91e70deb5 Update app branding to the new shield "RZ" logo
Replaces the OS/window icon with the new dark-background logo design,
and derives a transparent shield-mark cutout (via chroma-key against
the flat background) for the in-app login/sidebar mark and the web
loading-screen logo. Brand.ranaze_mark_texture() no longer needs the
AtlasTexture crop since the new source asset is already tightly
cropped to the mark.
2026-07-23 16:24:49 -05:00
hz fae38d2ded Adapt dynamic question difficulty to learner Elo 2026-07-23 16:18:44 -05:00
hz 5b5741de37 Show selection percentages on unselected distractors during review
Previously only the correct choice and any choice the student actually
selected got a percentage badge once reviewing; never-selected
distractors were left blank. Now every choice shows its selection
percentage as soon as the question is answered, matching the earlier
change that made unselected distractors render red like the rest of
the review UI.
2026-07-23 15:54:39 -05:00
hz b1b83dae05 Fix dynamic mastery first-attempt upsert 2026-07-23 15:25:06 -05:00
hz 332da7c181 Fix stale-node crash in UI sound auto-wiring
node_added already fires once per node as it enters the tree (including
every descendant of a newly-added subtree), so the recursive call_deferred
in _on_node_added was redundant and held a raw Node reference across a
frame boundary. If that node got freed before the deferred call fired
(e.g. a container torn down and rebuilt the same frame), the engine's
message queue couldn't marshal the argument and threw "Cannot convert
argument 1 from Object to Object". Wiring synchronously removes the
window entirely.

Also silences the intentional integer division in _duration_text,
matching the same annotation already applied to _elapsed_simulated_text.
2026-07-23 15:13:56 -05:00
hz 48f404518f Fix GDScript editor warnings (shadowed vars, unused param, integer division)
Renames locals/params that shadowed base-class properties (Node.name,
Control.size, BoxContainer.alignment), prefixes an intentionally-unused
parameter, and annotates a deliberate integer division so the console
stays clean during development.
2026-07-23 14:30:48 -05:00
hz 4a4bed2ac5 Add missing lucide/link.svg icon
The connected-accounts "Connect" button in progress_profile.gd
referenced res://assets/icons/lucide/link.svg, which didn't exist,
causing a resource-load error whenever the Steam/Google linking row
rendered.
2026-07-23 14:26:26 -05:00
hz 88688d3eb0 Add Steam and Google brand icons to login OAuth buttons
Steam uses a theme-aware monochrome glyph (light/dark variants) since
the button background flips between navy and white; Google keeps its
official 4-color mark, which reads fine on either background.
2026-07-23 14:19:17 -05:00
hz 92c5688b55 Upgrade dynamic questions to authored parity 2026-07-23 14:11:18 -05:00
hz e174b5db15 Return dynamic question runs to dashboard 2026-07-23 13:52:24 -05:00
hz 403944b249 Assign IDs when promoting dynamic blueprints 2026-07-23 13:40:53 -05:00
hz 8390d795e5 Publish dynamic questions during UAT deploy 2026-07-23 13:30:22 -05:00
hz 6a6e703aa4 Fix dynamic questions sidebar icon 2026-07-23 13:19:32 -05:00