# Prize Wheel Giveaway

## Objective

Run a spin-the-wheel giveaway where each entrant gets one spin and a recorded prize.

A giveaway with a wheel interface, one server-decided spin per entrant, and a durable record of every result.

## Before You Begin

This feature is being added to an application that already exists and already
works. Do not scaffold a new project, and do not assume a blank slate.

Inspect the codebase first and establish:

- The existing application structure and where code of this kind already lives.
- The framework and version in use.
- The existing design system — colours, spacing, typography, and component conventions.
- Existing UI components you can reuse instead of writing new ones.
- The existing database structure, if this feature needs to persist anything.
- The existing authentication and authorization system, if this feature is user-scoped.
- Dependencies already installed, so you don't add a library that duplicates one.
- The existing test setup and conventions.

Only start writing code once you understand the above. If the application
already implements part of this feature, extend it rather than replacing it.

## Implementation Instructions

1. Decide the outcome on the server before the wheel starts turning. The request returns a prize, the animation lands on it, and the client is only ever playing back a result it was given.
2. Model prizes with explicit weights and optional stock counts, and let an operator edit them without a deploy. A giveaway whose odds live in code cannot be corrected once it is live.
3. Tie an entry to an account where the app has one, and where it does not, combine the strongest identifiers available and treat a single spin as the hard limit across all of them.
4. Record every spin through the app's existing Audit Log: who, when, which prize, the weights in force at that moment, and the request that produced it. Giveaways generate disputes and an unlogged spin cannot be defended.
5. Do not treat the animation as the source of truth. Reusing a wheel library that picks a random segment locally and reports it means a determined entrant can pick their own prize from the console.

## UI and UX Requirements

Match the application's existing design system exactly. Reuse its components,
spacing, and typography. This feature should look like it was always there.

## Responsive Requirements

Works on mobile, tablet, and desktop. Touch targets are large enough to hit on a
phone, and nothing overflows horizontally at 320px.

## Accessibility Requirements

- Fully keyboard navigable.
- Correct semantic elements and ARIA roles.
- Visible focus states.
- Meets WCAG AA contrast.
- Dynamic changes are announced to screen readers.
- Respects prefers-reduced-motion.

## Edge Cases

- Any outcome computed in the browser can be inspected and replayed until it produces the desired prize. The client must never hold the weights, the prize list with odds, or the random draw, only the label it is told to display.
- One spin per entrant has to hold across devices, browsers, and signed-out sessions. Enforce it with a uniqueness constraint on the entry record rather than a check-then-write, so two simultaneous requests cannot both pass the check.
- Limited prizes are over-awarded when stock is decremented after the prize is announced. Reserve the prize and decrement the count in the same transaction that decides the outcome, and fall back to the next eligible prize if the stock has just run out.
- Every spin needs a durable record before the result is shown, including spins where the response never reached the entrant. An entrant who claims they won something must be answerable from the log rather than from memory.
- A spinning wheel is exactly the animation that causes problems for motion-sensitive viewers. Honour the app's existing Reduced Motion Support by showing the result directly, with no rotation and no flashing, and make that path equally celebratory rather than a degraded consolation.
- The connection can drop between the outcome being decided and the wheel finishing. Persist the result first so reloading the page shows the prize already won rather than offering a second spin or losing the first one.
- Prizes and giveaways attract automated entry. Apply the app's existing Rate Limiting, and require whatever identity verification the app already has before an entry counts.
- A wheel with a losing segment must say so plainly when it lands there. Ambiguity about whether a spin was a loss or a failure is the single most common source of giveaway complaints.

## Testing

Exercise the feature end to end in the running application. Cover every edge case
above, then run the existing test suite and confirm nothing regressed.

## Acceptance Criteria

- [ ] Prize outcomes are decided server-side and the client receives only the result to display.
- [ ] Each entrant can spin exactly once, enforced by a database constraint across devices and sessions.
- [ ] Limited prizes cannot be awarded beyond their stock, even under concurrent spins.
- [ ] Every spin is written to the audit log with the prize, the entrant, and the weights in force.
- [ ] Prize weights and stock are editable by an operator without a deploy.
- [ ] Reduced-motion viewers get the result without rotation or flashing.
- [ ] A dropped connection after the outcome shows the prize already won rather than granting a second spin.
- [ ] The feature matches the existing design system.
- [ ] No existing functionality is broken.

## Adaptation Rules

- Match the existing design system. Do not introduce a new colour palette,
  spacing scale, or component library.
- Reuse existing components and utilities wherever they fit.
- Follow the naming, file layout, and code style already present.
- Do not upgrade, replace, or remove existing dependencies to make this
  feature fit. Adapt the feature to the app, not the app to the feature.
- Do not break existing functionality. If a change is genuinely required in
  existing code, make the smallest one that works and say so.
- If something in these instructions conflicts with how the application is
  built, follow the application and explain the deviation.

## Final Verification

Before you report the work as done:

1. Re-read the acceptance criteria above and check each one against what you
   actually built.
2. Run the application and exercise the feature end to end.
3. Run the existing test suite and confirm you have broken nothing.
4. Check the feature on mobile, tablet, and desktop widths.
5. Check keyboard navigation and focus handling.
6. Summarize what changed: files added, files modified, and anything you
   deliberately did differently because of how this application is built.

If any acceptance criterion is unmet, fix it before reporting completion.
