This commit is contained in:
2026-06-04 17:08:09 -04:00
parent 3b165de7a9
commit 9bd7620a3b
4 changed files with 283 additions and 6 deletions
+111
View File
@@ -0,0 +1,111 @@
# Cooldown Button Component
A square Blazor button with a 12-second cooldown animation: when clicked, the button greys out and a circular transparency wedge dials open clockwise, progressively revealing the normal button state underneath.
---
## Visual Design
The button has two visual states:
**Idle state** A solid square button with the project's standard `--paper` background and `--primary` border. Hover inverts or brightens the paper. Click triggers a brief scale-down.
**Cooldown state** The native button fades to `opacity: 0` while an absolutely-positioned overlay covers it. The overlay uses a `conic-gradient` CSS mask to create a "dialling open" effect. A number in the centre shows the remaining seconds.
---
## Core Technique: Conic-Gradient Mask
The cooldown reveal is accomplished with a **conic-gradient mask** applied to the overlay `<div>`:
```
mask-image: conic-gradient(
transparent 0deg,
transparent {angle}deg,
#000 {angle}deg,
#000 360deg
);
```
- **`transparent`** lets the button underneath show through (revealed area)
- **`#000` (black)** fully masks the overlay, making it visible (greyed-out area)
At `angle = 0deg`, transparent covers nothing and the mask is entirely black → the overlay is fully opaque (button completely greyed out).
At `angle = 360deg`, transparent covers the full circle and black covers nothing → the overlay is fully transparent (button completely visible).
The angle animates linearly from 0 to 360 over the cooldown duration. Because `conic-gradient` starts at the 12 o'clock position and sweeps clockwise, the reveal begins at the top of the button and rotates around, like a clock hand or a dial opening.
---
## Implementation Architecture
### Component Parameters
| Parameter | Type | Default | Description |
|------------------|-------------------|---------|------------------------------------|
| `ChildContent` | `RenderFragment` | `null` | Text or content inside the button |
| `OnClick` | `EventCallback` | — | Fired when the button is clicked |
| `CooldownSeconds`| `int` | `12` | Duration of the cooldown in seconds|
| `Size` | `int` | `120` | Width and height in pixels (square)|
### Timer Loop
A `System.Timers.Timer` fires every ~33ms (≈30fps) during the cooldown:
```
OnTick:
elapsed = UtcNow - startTime
if elapsed >= CooldownSeconds → end cooldown, dispose timer
_elapsedAngle = (elapsed / CooldownSeconds) * 360
_remainingSeconds = CooldownSeconds - (int)elapsed
InvokeAsync(StateHasChanged)
```
On each tick, `_elapsedAngle` is written into the overlay's inline `style` attribute, causing Blazor to re-render the `mask-image`. The timer is disposed in `Dispose()` to prevent leaks.
### Disposal
The component implements `IDisposable` to clean up the timer when the component is removed from the render tree. This follows the same pattern used by `SearchDialogComponent` and `BuildChartComponent` elsewhere in the codebase.
---
## CSS Masking Details
Two vendor-prefixed properties are set to ensure cross-browser support:
```
mask-image: conic-gradient(...);
-webkit-mask-image: conic-gradient(...);
```
The overlay uses `pointer-events: none` and `user-select: none` so that interaction passes through to the button underneath (which is disabled and transparent).
An `rgba(22, 22, 24, 0.82)` semi-transparent background on the overlay produces the greyed-out appearance. The mask controls *where* this background is visible.
---
## Usage on the Home Page
The component is added to `Pages/Pages/Home/HomePage.razor` inside the first `PaperComponent`:
```razor
<CooldownButtonComponent CooldownSeconds="12"
Size="120"
OnClick="OnCooldownClick">
Click Me
</CooldownButtonComponent>
```
The `OnCooldownClick` handler in the page's `@code` block currently returns `Task.CompletedTask` (a no-op). This is the extension point where real work (e.g. triggering a game action, calling an API, showing a toast) would go.
---
## Adapting the Component
- **Change cooldown duration** set `CooldownSeconds` to any positive integer.
- **Change button size** set `Size` to any pixel dimension (button remains square).
- **Custom content** pass any Blazor markup as `ChildContent` (text, icons, spinners).
- **Handle the click** attach a handler to `OnClick` that returns `Task` or `void`.
The `--cooldown-size` CSS custom property is set inline on the wrapper so that the label, overlay, and button all scale together.