@digilogiclabs/saas-factory-ui 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +41 -8
- package/dist/index.d.ts +41 -8
- package/dist/index.js +141 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +141 -25
- package/dist/index.mjs.map +1 -1
- package/dist/web/index.d.mts +41 -8
- package/dist/web/index.d.ts +41 -8
- package/dist/web/index.js +141 -25
- package/dist/web/index.js.map +1 -1
- package/dist/web/index.mjs +141 -25
- package/dist/web/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/web/index.d.mts
CHANGED
|
@@ -5586,14 +5586,37 @@ interface RandomPlayerPickerProps {
|
|
|
5586
5586
|
renderWinner?: (winner: RandomPlayerParticipant) => ReactNode;
|
|
5587
5587
|
/** Imperative handle. `spinRef.current?.()` triggers a spin. */
|
|
5588
5588
|
spinRef?: MutableRefObject<(() => void) | null>;
|
|
5589
|
+
/**
|
|
5590
|
+
* Imperative reset handle. `resetRef.current?.()` restores the pool to
|
|
5591
|
+
* the full `participants` list and clears history/winner — the escape
|
|
5592
|
+
* hatch for consumers that hide the built-in controls
|
|
5593
|
+
* (`showControls={false}`) but still need a "reset draft" affordance
|
|
5594
|
+
* once draft-mode pops drain the pool.
|
|
5595
|
+
*/
|
|
5596
|
+
resetRef?: MutableRefObject<(() => void) | null>;
|
|
5597
|
+
/**
|
|
5598
|
+
* Fired whenever the internal pool changes (mount, structural sync,
|
|
5599
|
+
* draft-mode pop, spin-again flush, reset). Draft-mode consumers use
|
|
5600
|
+
* this to mirror the live pool — e.g. to disable an external spin
|
|
5601
|
+
* trigger when the pool drains below 2 instead of presenting a dead
|
|
5602
|
+
* button, and to offer a reset once the draft completes.
|
|
5603
|
+
*/
|
|
5604
|
+
onPoolChange?: (pool: RandomPlayerParticipant[]) => void;
|
|
5605
|
+
/**
|
|
5606
|
+
* Caption shown in place of `placeholderCaption` when draft-mode pops
|
|
5607
|
+
* drain the pool below 2 while the roster itself still has 2+ members.
|
|
5608
|
+
* Default: "Draft complete — reset to spin again".
|
|
5609
|
+
*/
|
|
5610
|
+
draftCompleteCaption?: string;
|
|
5589
5611
|
/**
|
|
5590
5612
|
* Visual intensity preset. Default "normal".
|
|
5591
|
-
* - "subtle" / "normal":
|
|
5592
|
-
*
|
|
5593
|
-
*
|
|
5594
|
-
*
|
|
5595
|
-
*
|
|
5596
|
-
* particles/glow/shake on reveal.
|
|
5613
|
+
* - "subtle" / "normal": shorter spins, standard CSS ease-out curve.
|
|
5614
|
+
* - "dramatic": physics-driven spin — 2.5× duration, rAF-driven
|
|
5615
|
+
* exponential velocity decay (v ∝ e^(-4t)) that keeps the wheel
|
|
5616
|
+
* visibly winding down through the FULL duration and lands exactly
|
|
5617
|
+
* at the end, +12 extra revolutions, ticks fired on actual
|
|
5618
|
+
* wedge/row crossings, plus more particles/glow/shake on reveal.
|
|
5619
|
+
* Falls back to a 250ms CSS settle under prefers-reduced-motion.
|
|
5597
5620
|
*/
|
|
5598
5621
|
intensity?: RandomPlayerIntensity;
|
|
5599
5622
|
/**
|
|
@@ -5636,7 +5659,7 @@ interface RandomPlayerPickerProps {
|
|
|
5636
5659
|
/** Accessible label for the wheel root. */
|
|
5637
5660
|
ariaLabel?: string;
|
|
5638
5661
|
}
|
|
5639
|
-
declare function RandomPlayerPicker({ participants, mode, reelVisibleRows, reelRowHeight, theme, darkMode, size, onPick, onSpinStart, onTick, trigger, label, removeWinners, spinDuration, minRevolutions, maxRevolutions, showHistory, showControls, showStats, renderWinner, spinRef, intensity, enableHaptics, tickSoundUrl, winSoundUrl, soundVolume, placeholderMode, placeholderCaption, className, style, ariaLabel, }: RandomPlayerPickerProps): react_jsx_runtime.JSX.Element;
|
|
5662
|
+
declare function RandomPlayerPicker({ participants, mode, reelVisibleRows, reelRowHeight, theme, darkMode, size, onPick, onSpinStart, onTick, trigger, label, removeWinners, spinDuration, minRevolutions, maxRevolutions, showHistory, showControls, showStats, renderWinner, spinRef, resetRef, onPoolChange, draftCompleteCaption, intensity, enableHaptics, tickSoundUrl, winSoundUrl, soundVolume, placeholderMode, placeholderCaption, className, style, ariaLabel, }: RandomPlayerPickerProps): react_jsx_runtime.JSX.Element;
|
|
5640
5663
|
|
|
5641
5664
|
type HeroBannerTransition = "fade" | "slide" | "zoom" | "blur" | "reveal" | "push";
|
|
5642
5665
|
type HeroBannerImageFilter = "none" | "cinematic" | "dramatic" | "cool" | "warm" | "desaturated" | "noir" | "vibrant";
|
|
@@ -6202,10 +6225,20 @@ interface TeamGeneratorProps {
|
|
|
6202
6225
|
maxPlayers?: number;
|
|
6203
6226
|
/** Initial roster. Uncontrolled — the component manages names internally. */
|
|
6204
6227
|
defaultNames?: string[];
|
|
6228
|
+
/** Initial generated teams (e.g. restored from a saved session).
|
|
6229
|
+
* Uncontrolled — the component takes over after mount. An empty array
|
|
6230
|
+
* is treated as "no teams". */
|
|
6231
|
+
defaultTeams?: string[][];
|
|
6205
6232
|
/** Called whenever the name pool changes. */
|
|
6206
6233
|
onNamesChange?: (names: string[]) => void;
|
|
6207
6234
|
/** Called whenever teams are (re)generated. */
|
|
6208
6235
|
onGenerate?: (teams: string[][]) => void;
|
|
6236
|
+
/** Called whenever the generated teams change for ANY reason — a
|
|
6237
|
+
* (re)generation, or invalidation to `null` when a player is removed,
|
|
6238
|
+
* the pool is cleared, or the team count changes. Unlike `onGenerate`,
|
|
6239
|
+
* this mirrors the full lifecycle of the on-screen split so hosts can
|
|
6240
|
+
* persist it and restore via `defaultTeams`. */
|
|
6241
|
+
onTeamsChange?: (teams: string[][] | null) => void;
|
|
6209
6242
|
/** Called whenever the team count selection changes. */
|
|
6210
6243
|
onTeamCountChange?: (count: number) => void;
|
|
6211
6244
|
/** Localized / customized labels. */
|
|
@@ -6218,7 +6251,7 @@ interface TeamGeneratorProps {
|
|
|
6218
6251
|
* CSV / TSV roster from a spreadsheet. Default `true`. */
|
|
6219
6252
|
enableImport?: boolean;
|
|
6220
6253
|
}
|
|
6221
|
-
declare function TeamGenerator({ theme, darkMode, teamCountOptions, defaultTeamCount, minPlayersPerTeam, maxPlayers, defaultNames, onNamesChange, onGenerate, onTeamCountChange, labels, ariaLabel, className, enableImport, }: TeamGeneratorProps): react_jsx_runtime.JSX.Element;
|
|
6254
|
+
declare function TeamGenerator({ theme, darkMode, teamCountOptions, defaultTeamCount, minPlayersPerTeam, maxPlayers, defaultNames, defaultTeams, onNamesChange, onGenerate, onTeamsChange, onTeamCountChange, labels, ariaLabel, className, enableImport, }: TeamGeneratorProps): react_jsx_runtime.JSX.Element;
|
|
6222
6255
|
declare namespace TeamGenerator {
|
|
6223
6256
|
var displayName: string;
|
|
6224
6257
|
}
|
package/dist/web/index.d.ts
CHANGED
|
@@ -5586,14 +5586,37 @@ interface RandomPlayerPickerProps {
|
|
|
5586
5586
|
renderWinner?: (winner: RandomPlayerParticipant) => ReactNode;
|
|
5587
5587
|
/** Imperative handle. `spinRef.current?.()` triggers a spin. */
|
|
5588
5588
|
spinRef?: MutableRefObject<(() => void) | null>;
|
|
5589
|
+
/**
|
|
5590
|
+
* Imperative reset handle. `resetRef.current?.()` restores the pool to
|
|
5591
|
+
* the full `participants` list and clears history/winner — the escape
|
|
5592
|
+
* hatch for consumers that hide the built-in controls
|
|
5593
|
+
* (`showControls={false}`) but still need a "reset draft" affordance
|
|
5594
|
+
* once draft-mode pops drain the pool.
|
|
5595
|
+
*/
|
|
5596
|
+
resetRef?: MutableRefObject<(() => void) | null>;
|
|
5597
|
+
/**
|
|
5598
|
+
* Fired whenever the internal pool changes (mount, structural sync,
|
|
5599
|
+
* draft-mode pop, spin-again flush, reset). Draft-mode consumers use
|
|
5600
|
+
* this to mirror the live pool — e.g. to disable an external spin
|
|
5601
|
+
* trigger when the pool drains below 2 instead of presenting a dead
|
|
5602
|
+
* button, and to offer a reset once the draft completes.
|
|
5603
|
+
*/
|
|
5604
|
+
onPoolChange?: (pool: RandomPlayerParticipant[]) => void;
|
|
5605
|
+
/**
|
|
5606
|
+
* Caption shown in place of `placeholderCaption` when draft-mode pops
|
|
5607
|
+
* drain the pool below 2 while the roster itself still has 2+ members.
|
|
5608
|
+
* Default: "Draft complete — reset to spin again".
|
|
5609
|
+
*/
|
|
5610
|
+
draftCompleteCaption?: string;
|
|
5589
5611
|
/**
|
|
5590
5612
|
* Visual intensity preset. Default "normal".
|
|
5591
|
-
* - "subtle" / "normal":
|
|
5592
|
-
*
|
|
5593
|
-
*
|
|
5594
|
-
*
|
|
5595
|
-
*
|
|
5596
|
-
* particles/glow/shake on reveal.
|
|
5613
|
+
* - "subtle" / "normal": shorter spins, standard CSS ease-out curve.
|
|
5614
|
+
* - "dramatic": physics-driven spin — 2.5× duration, rAF-driven
|
|
5615
|
+
* exponential velocity decay (v ∝ e^(-4t)) that keeps the wheel
|
|
5616
|
+
* visibly winding down through the FULL duration and lands exactly
|
|
5617
|
+
* at the end, +12 extra revolutions, ticks fired on actual
|
|
5618
|
+
* wedge/row crossings, plus more particles/glow/shake on reveal.
|
|
5619
|
+
* Falls back to a 250ms CSS settle under prefers-reduced-motion.
|
|
5597
5620
|
*/
|
|
5598
5621
|
intensity?: RandomPlayerIntensity;
|
|
5599
5622
|
/**
|
|
@@ -5636,7 +5659,7 @@ interface RandomPlayerPickerProps {
|
|
|
5636
5659
|
/** Accessible label for the wheel root. */
|
|
5637
5660
|
ariaLabel?: string;
|
|
5638
5661
|
}
|
|
5639
|
-
declare function RandomPlayerPicker({ participants, mode, reelVisibleRows, reelRowHeight, theme, darkMode, size, onPick, onSpinStart, onTick, trigger, label, removeWinners, spinDuration, minRevolutions, maxRevolutions, showHistory, showControls, showStats, renderWinner, spinRef, intensity, enableHaptics, tickSoundUrl, winSoundUrl, soundVolume, placeholderMode, placeholderCaption, className, style, ariaLabel, }: RandomPlayerPickerProps): react_jsx_runtime.JSX.Element;
|
|
5662
|
+
declare function RandomPlayerPicker({ participants, mode, reelVisibleRows, reelRowHeight, theme, darkMode, size, onPick, onSpinStart, onTick, trigger, label, removeWinners, spinDuration, minRevolutions, maxRevolutions, showHistory, showControls, showStats, renderWinner, spinRef, resetRef, onPoolChange, draftCompleteCaption, intensity, enableHaptics, tickSoundUrl, winSoundUrl, soundVolume, placeholderMode, placeholderCaption, className, style, ariaLabel, }: RandomPlayerPickerProps): react_jsx_runtime.JSX.Element;
|
|
5640
5663
|
|
|
5641
5664
|
type HeroBannerTransition = "fade" | "slide" | "zoom" | "blur" | "reveal" | "push";
|
|
5642
5665
|
type HeroBannerImageFilter = "none" | "cinematic" | "dramatic" | "cool" | "warm" | "desaturated" | "noir" | "vibrant";
|
|
@@ -6202,10 +6225,20 @@ interface TeamGeneratorProps {
|
|
|
6202
6225
|
maxPlayers?: number;
|
|
6203
6226
|
/** Initial roster. Uncontrolled — the component manages names internally. */
|
|
6204
6227
|
defaultNames?: string[];
|
|
6228
|
+
/** Initial generated teams (e.g. restored from a saved session).
|
|
6229
|
+
* Uncontrolled — the component takes over after mount. An empty array
|
|
6230
|
+
* is treated as "no teams". */
|
|
6231
|
+
defaultTeams?: string[][];
|
|
6205
6232
|
/** Called whenever the name pool changes. */
|
|
6206
6233
|
onNamesChange?: (names: string[]) => void;
|
|
6207
6234
|
/** Called whenever teams are (re)generated. */
|
|
6208
6235
|
onGenerate?: (teams: string[][]) => void;
|
|
6236
|
+
/** Called whenever the generated teams change for ANY reason — a
|
|
6237
|
+
* (re)generation, or invalidation to `null` when a player is removed,
|
|
6238
|
+
* the pool is cleared, or the team count changes. Unlike `onGenerate`,
|
|
6239
|
+
* this mirrors the full lifecycle of the on-screen split so hosts can
|
|
6240
|
+
* persist it and restore via `defaultTeams`. */
|
|
6241
|
+
onTeamsChange?: (teams: string[][] | null) => void;
|
|
6209
6242
|
/** Called whenever the team count selection changes. */
|
|
6210
6243
|
onTeamCountChange?: (count: number) => void;
|
|
6211
6244
|
/** Localized / customized labels. */
|
|
@@ -6218,7 +6251,7 @@ interface TeamGeneratorProps {
|
|
|
6218
6251
|
* CSV / TSV roster from a spreadsheet. Default `true`. */
|
|
6219
6252
|
enableImport?: boolean;
|
|
6220
6253
|
}
|
|
6221
|
-
declare function TeamGenerator({ theme, darkMode, teamCountOptions, defaultTeamCount, minPlayersPerTeam, maxPlayers, defaultNames, onNamesChange, onGenerate, onTeamCountChange, labels, ariaLabel, className, enableImport, }: TeamGeneratorProps): react_jsx_runtime.JSX.Element;
|
|
6254
|
+
declare function TeamGenerator({ theme, darkMode, teamCountOptions, defaultTeamCount, minPlayersPerTeam, maxPlayers, defaultNames, defaultTeams, onNamesChange, onGenerate, onTeamsChange, onTeamCountChange, labels, ariaLabel, className, enableImport, }: TeamGeneratorProps): react_jsx_runtime.JSX.Element;
|
|
6222
6255
|
declare namespace TeamGenerator {
|
|
6223
6256
|
var displayName: string;
|
|
6224
6257
|
}
|
package/dist/web/index.js
CHANGED
|
@@ -33381,6 +33381,11 @@ var WEDGE_PALETTE = [
|
|
|
33381
33381
|
];
|
|
33382
33382
|
var DEFAULT_EASING = "cubic-bezier(0.25, 0.46, 0.45, 0.94)";
|
|
33383
33383
|
var DRAMATIC_EASING = "cubic-bezier(0.39, 0.575, 0.565, 1)";
|
|
33384
|
+
var PHYSICS_DECAY_K = 4;
|
|
33385
|
+
function physicsRampDown(t) {
|
|
33386
|
+
return (1 - Math.exp(-PHYSICS_DECAY_K * t)) / (1 - Math.exp(-PHYSICS_DECAY_K));
|
|
33387
|
+
}
|
|
33388
|
+
var REEL_LEAD_IN = 32;
|
|
33384
33389
|
var INTENSITY_CONFIG = {
|
|
33385
33390
|
subtle: {
|
|
33386
33391
|
particles: 14,
|
|
@@ -33452,6 +33457,9 @@ function ReelStage({
|
|
|
33452
33457
|
winner,
|
|
33453
33458
|
spinning,
|
|
33454
33459
|
reelY,
|
|
33460
|
+
displayY,
|
|
33461
|
+
cssTransition,
|
|
33462
|
+
tapeRef,
|
|
33455
33463
|
reelVisibleRows,
|
|
33456
33464
|
reelRowHeight,
|
|
33457
33465
|
size,
|
|
@@ -33475,17 +33483,16 @@ function ReelStage({
|
|
|
33475
33483
|
const stripHeight = reelVisibleRows * reelRowHeight;
|
|
33476
33484
|
const centerOffsetPx = (reelVisibleRows - 1) / 2 * reelRowHeight;
|
|
33477
33485
|
const P = pool.length;
|
|
33478
|
-
const leadIn = 32;
|
|
33479
33486
|
const tapeRows = Math.max(
|
|
33480
|
-
reelVisibleRows +
|
|
33481
|
-
Math.ceil((reelY + stripHeight) / reelRowHeight) +
|
|
33487
|
+
reelVisibleRows + REEL_LEAD_IN + 1,
|
|
33488
|
+
Math.ceil((reelY + stripHeight) / reelRowHeight) + REEL_LEAD_IN + 1
|
|
33482
33489
|
);
|
|
33483
33490
|
const tape = Array.from({ length: tapeRows }, (_, i) => {
|
|
33484
|
-
const poolIdx = P === 0 ? 0 : ((i -
|
|
33491
|
+
const poolIdx = P === 0 ? 0 : ((i - REEL_LEAD_IN) % P + P) % P;
|
|
33485
33492
|
return pool[poolIdx];
|
|
33486
33493
|
});
|
|
33487
|
-
const effectiveY =
|
|
33488
|
-
const transition = spinning ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none";
|
|
33494
|
+
const effectiveY = displayY + REEL_LEAD_IN * reelRowHeight;
|
|
33495
|
+
const transition = spinning && cssTransition ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none";
|
|
33489
33496
|
return /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
|
|
33490
33497
|
"div",
|
|
33491
33498
|
{
|
|
@@ -33530,6 +33537,7 @@ function ReelStage({
|
|
|
33530
33537
|
) : /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
|
|
33531
33538
|
"div",
|
|
33532
33539
|
{
|
|
33540
|
+
ref: tapeRef,
|
|
33533
33541
|
"aria-hidden": "true",
|
|
33534
33542
|
style: {
|
|
33535
33543
|
position: "absolute",
|
|
@@ -33745,6 +33753,9 @@ function RandomPlayerPicker({
|
|
|
33745
33753
|
showStats = true,
|
|
33746
33754
|
renderWinner,
|
|
33747
33755
|
spinRef,
|
|
33756
|
+
resetRef,
|
|
33757
|
+
onPoolChange,
|
|
33758
|
+
draftCompleteCaption = "Draft complete \u2014 reset to spin again",
|
|
33748
33759
|
intensity = "normal",
|
|
33749
33760
|
enableHaptics = false,
|
|
33750
33761
|
tickSoundUrl,
|
|
@@ -33759,6 +33770,7 @@ function RandomPlayerPicker({
|
|
|
33759
33770
|
const dark = useDarkMode(darkMode);
|
|
33760
33771
|
const reducedMotion = usePrefersReducedMotion();
|
|
33761
33772
|
const intensityCfg = INTENSITY_CONFIG[intensity];
|
|
33773
|
+
const physicsDriven = intensity === "dramatic" && !reducedMotion;
|
|
33762
33774
|
const [pool, setPool] = (0, import_react73.useState)(participants);
|
|
33763
33775
|
const [rotation, setRotation] = (0, import_react73.useState)(0);
|
|
33764
33776
|
const [reelY, setReelY] = (0, import_react73.useState)(0);
|
|
@@ -33777,6 +33789,11 @@ function RandomPlayerPicker({
|
|
|
33777
33789
|
const winAudioRef = (0, import_react73.useRef)(null);
|
|
33778
33790
|
const containerRef = (0, import_react73.useRef)(null);
|
|
33779
33791
|
const prevIdsRef = (0, import_react73.useRef)("");
|
|
33792
|
+
const wheelSvgRef = (0, import_react73.useRef)(null);
|
|
33793
|
+
const reelTapeRef = (0, import_react73.useRef)(null);
|
|
33794
|
+
const rafRef = (0, import_react73.useRef)(null);
|
|
33795
|
+
const visualRotationRef = (0, import_react73.useRef)(null);
|
|
33796
|
+
const visualReelYRef = (0, import_react73.useRef)(null);
|
|
33780
33797
|
(0, import_react73.useEffect)(() => {
|
|
33781
33798
|
if (typeof window === "undefined" || !tickSoundUrl) {
|
|
33782
33799
|
tickAudioRef.current = null;
|
|
@@ -33830,6 +33847,12 @@ function RandomPlayerPicker({
|
|
|
33830
33847
|
clearTimeout(popTimer.current);
|
|
33831
33848
|
popTimer.current = null;
|
|
33832
33849
|
}
|
|
33850
|
+
if (rafRef.current) {
|
|
33851
|
+
cancelAnimationFrame(rafRef.current);
|
|
33852
|
+
rafRef.current = null;
|
|
33853
|
+
}
|
|
33854
|
+
visualRotationRef.current = null;
|
|
33855
|
+
visualReelYRef.current = null;
|
|
33833
33856
|
pendingPopIdxRef.current = null;
|
|
33834
33857
|
setSpinning(false);
|
|
33835
33858
|
setPool(participants);
|
|
@@ -33941,7 +33964,56 @@ function RandomPlayerPicker({
|
|
|
33941
33964
|
} else {
|
|
33942
33965
|
setRotation(finalRotation);
|
|
33943
33966
|
}
|
|
33944
|
-
|
|
33967
|
+
const fireTick = () => {
|
|
33968
|
+
onTick?.();
|
|
33969
|
+
const ta = tickAudioRef.current;
|
|
33970
|
+
if (ta) {
|
|
33971
|
+
try {
|
|
33972
|
+
ta.currentTime = 0;
|
|
33973
|
+
void ta.play().catch(() => {
|
|
33974
|
+
});
|
|
33975
|
+
} catch {
|
|
33976
|
+
}
|
|
33977
|
+
}
|
|
33978
|
+
};
|
|
33979
|
+
if (physicsDriven) {
|
|
33980
|
+
const from = mode === "reel" ? activeReelY : activeRotation;
|
|
33981
|
+
const to = mode === "reel" ? finalReelY : finalRotation;
|
|
33982
|
+
const crossUnit = mode === "reel" ? reelRowHeight : activeAnglePerWedge * 180 / Math.PI;
|
|
33983
|
+
if (mode === "reel") visualReelYRef.current = from;
|
|
33984
|
+
else visualRotationRef.current = from;
|
|
33985
|
+
let crossings = 0;
|
|
33986
|
+
const t0 = performance.now();
|
|
33987
|
+
const step = (now) => {
|
|
33988
|
+
const t = Math.min(1, (now - t0) / effectiveDuration);
|
|
33989
|
+
const value = from + (to - from) * physicsRampDown(t);
|
|
33990
|
+
if (mode === "reel") {
|
|
33991
|
+
visualReelYRef.current = value;
|
|
33992
|
+
const el = reelTapeRef.current;
|
|
33993
|
+
if (el) {
|
|
33994
|
+
el.style.transform = `translateY(${-(value + REEL_LEAD_IN * reelRowHeight)}px)`;
|
|
33995
|
+
}
|
|
33996
|
+
} else {
|
|
33997
|
+
visualRotationRef.current = value;
|
|
33998
|
+
const el = wheelSvgRef.current;
|
|
33999
|
+
if (el) el.style.transform = `rotate(${value}deg)`;
|
|
34000
|
+
}
|
|
34001
|
+
const crossed = Math.floor(Math.abs(value - from) / crossUnit);
|
|
34002
|
+
if (crossed > crossings) {
|
|
34003
|
+
crossings = crossed;
|
|
34004
|
+
fireTick();
|
|
34005
|
+
}
|
|
34006
|
+
if (t < 1) {
|
|
34007
|
+
rafRef.current = requestAnimationFrame(step);
|
|
34008
|
+
} else {
|
|
34009
|
+
rafRef.current = null;
|
|
34010
|
+
visualRotationRef.current = null;
|
|
34011
|
+
visualReelYRef.current = null;
|
|
34012
|
+
}
|
|
34013
|
+
};
|
|
34014
|
+
rafRef.current = requestAnimationFrame(step);
|
|
34015
|
+
}
|
|
34016
|
+
if (!physicsDriven && (onTick || tickAudioRef.current) && !reducedMotion) {
|
|
33945
34017
|
const wedges = mode === "reel" ? Math.max(1, reelRevRows + targetRowOffset) : (() => {
|
|
33946
34018
|
const totalDelta = deltaMod + revolutions * 360;
|
|
33947
34019
|
const wedgeDeg = activeAnglePerWedge * 180 / Math.PI;
|
|
@@ -33959,16 +34031,7 @@ function RandomPlayerPicker({
|
|
|
33959
34031
|
const eased = 1 - Math.pow(1 - progress, 3);
|
|
33960
34032
|
const interval = startInterval + (endInterval - startInterval) * eased;
|
|
33961
34033
|
tickInterval.current = setTimeout(() => {
|
|
33962
|
-
|
|
33963
|
-
const ta = tickAudioRef.current;
|
|
33964
|
-
if (ta) {
|
|
33965
|
-
try {
|
|
33966
|
-
ta.currentTime = 0;
|
|
33967
|
-
void ta.play().catch(() => {
|
|
33968
|
-
});
|
|
33969
|
-
} catch {
|
|
33970
|
-
}
|
|
33971
|
-
}
|
|
34034
|
+
fireTick();
|
|
33972
34035
|
elapsed += interval;
|
|
33973
34036
|
schedule();
|
|
33974
34037
|
}, interval);
|
|
@@ -34028,7 +34091,7 @@ function RandomPlayerPicker({
|
|
|
34028
34091
|
pendingPopIdxRef.current = null;
|
|
34029
34092
|
if (!removeWinnersRef.current) return;
|
|
34030
34093
|
setPool((p) => p.filter((_, i) => i !== finalWinnerIdx));
|
|
34031
|
-
setWinner(null);
|
|
34094
|
+
if (activePool.length - 1 >= 2) setWinner(null);
|
|
34032
34095
|
setRotation(0);
|
|
34033
34096
|
setReelY(0);
|
|
34034
34097
|
}, 2800);
|
|
@@ -34051,6 +34114,7 @@ function RandomPlayerPicker({
|
|
|
34051
34114
|
maxRevolutions,
|
|
34052
34115
|
history,
|
|
34053
34116
|
reducedMotion,
|
|
34117
|
+
physicsDriven,
|
|
34054
34118
|
enableHaptics,
|
|
34055
34119
|
intensityCfg.particles,
|
|
34056
34120
|
intensityCfg.durationMult,
|
|
@@ -34062,12 +34126,20 @@ function RandomPlayerPicker({
|
|
|
34062
34126
|
if (spinRef && spinRef.current === spin) spinRef.current = null;
|
|
34063
34127
|
};
|
|
34064
34128
|
}, [spin, spinRef]);
|
|
34129
|
+
const onPoolChangeRef = (0, import_react73.useRef)(onPoolChange);
|
|
34130
|
+
(0, import_react73.useEffect)(() => {
|
|
34131
|
+
onPoolChangeRef.current = onPoolChange;
|
|
34132
|
+
});
|
|
34133
|
+
(0, import_react73.useEffect)(() => {
|
|
34134
|
+
onPoolChangeRef.current?.(pool);
|
|
34135
|
+
}, [pool]);
|
|
34065
34136
|
(0, import_react73.useEffect)(
|
|
34066
34137
|
() => () => {
|
|
34067
34138
|
if (winnerTimer.current) clearTimeout(winnerTimer.current);
|
|
34068
34139
|
if (particleTimer.current) clearTimeout(particleTimer.current);
|
|
34069
34140
|
if (popTimer.current) clearTimeout(popTimer.current);
|
|
34070
34141
|
if (tickInterval.current) clearTimeout(tickInterval.current);
|
|
34142
|
+
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
34071
34143
|
},
|
|
34072
34144
|
[]
|
|
34073
34145
|
);
|
|
@@ -34097,6 +34169,12 @@ function RandomPlayerPicker({
|
|
|
34097
34169
|
clearTimeout(popTimer.current);
|
|
34098
34170
|
popTimer.current = null;
|
|
34099
34171
|
}
|
|
34172
|
+
if (rafRef.current) {
|
|
34173
|
+
cancelAnimationFrame(rafRef.current);
|
|
34174
|
+
rafRef.current = null;
|
|
34175
|
+
}
|
|
34176
|
+
visualRotationRef.current = null;
|
|
34177
|
+
visualReelYRef.current = null;
|
|
34100
34178
|
pendingPopIdxRef.current = null;
|
|
34101
34179
|
setSpinning(false);
|
|
34102
34180
|
setPool(participants);
|
|
@@ -34106,9 +34184,17 @@ function RandomPlayerPicker({
|
|
|
34106
34184
|
setReelY(0);
|
|
34107
34185
|
setParticles([]);
|
|
34108
34186
|
}, [participants]);
|
|
34187
|
+
(0, import_react73.useEffect)(() => {
|
|
34188
|
+
if (resetRef) resetRef.current = reset;
|
|
34189
|
+
return () => {
|
|
34190
|
+
if (resetRef && resetRef.current === reset) resetRef.current = null;
|
|
34191
|
+
};
|
|
34192
|
+
}, [reset, resetRef]);
|
|
34109
34193
|
const isIdle = n < 2;
|
|
34194
|
+
const drainedByDraft = isIdle && participants.length >= 2;
|
|
34195
|
+
const effectiveIdleCaption = drainedByDraft ? draftCompleteCaption : placeholderCaption;
|
|
34110
34196
|
const placeholderDiscColor = dark ? "#2a3344" : "#e2e8f0";
|
|
34111
|
-
if (isIdle && placeholderMode === "message") {
|
|
34197
|
+
if (isIdle && !drainedByDraft && placeholderMode === "message") {
|
|
34112
34198
|
return /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
|
|
34113
34199
|
"div",
|
|
34114
34200
|
{
|
|
@@ -34130,6 +34216,8 @@ function RandomPlayerPicker({
|
|
|
34130
34216
|
const shakeKeyframe = `rppShake_${intensity}`;
|
|
34131
34217
|
const glowKeyframe = `rppGlow_${intensity}`;
|
|
34132
34218
|
const pulseKeyframe = `rppPulse_${intensity}`;
|
|
34219
|
+
const displayRotation = visualRotationRef.current ?? rotation;
|
|
34220
|
+
const displayReelY = visualReelYRef.current ?? reelY;
|
|
34133
34221
|
return /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
|
|
34134
34222
|
"div",
|
|
34135
34223
|
{
|
|
@@ -34176,13 +34264,33 @@ function RandomPlayerPicker({
|
|
|
34176
34264
|
[data-rpp-anim] { animation: none !important; }
|
|
34177
34265
|
}
|
|
34178
34266
|
` }),
|
|
34179
|
-
|
|
34267
|
+
isIdle && placeholderMode === "message" ? (
|
|
34268
|
+
/* Drained draft in "message" mode — compact box in the wheel's
|
|
34269
|
+
place; the winner card / stats / controls / history below stay
|
|
34270
|
+
mounted so the endgame isn't a dead end. */
|
|
34271
|
+
/* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
|
|
34272
|
+
"div",
|
|
34273
|
+
{
|
|
34274
|
+
style: {
|
|
34275
|
+
padding: "2rem 1rem",
|
|
34276
|
+
textAlign: "center",
|
|
34277
|
+
color: c.dim,
|
|
34278
|
+
borderRadius: 12,
|
|
34279
|
+
border: `1px dashed ${c.border}`
|
|
34280
|
+
},
|
|
34281
|
+
children: effectiveIdleCaption
|
|
34282
|
+
}
|
|
34283
|
+
)
|
|
34284
|
+
) : mode === "reel" ? /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
|
|
34180
34285
|
ReelStage,
|
|
34181
34286
|
{
|
|
34182
34287
|
pool,
|
|
34183
34288
|
winner,
|
|
34184
34289
|
spinning,
|
|
34185
34290
|
reelY,
|
|
34291
|
+
displayY: displayReelY,
|
|
34292
|
+
cssTransition: !physicsDriven,
|
|
34293
|
+
tapeRef: reelTapeRef,
|
|
34186
34294
|
reelVisibleRows,
|
|
34187
34295
|
reelRowHeight,
|
|
34188
34296
|
size,
|
|
@@ -34191,7 +34299,7 @@ function RandomPlayerPicker({
|
|
|
34191
34299
|
dark,
|
|
34192
34300
|
isIdle,
|
|
34193
34301
|
placeholderMode,
|
|
34194
|
-
placeholderCaption,
|
|
34302
|
+
placeholderCaption: effectiveIdleCaption,
|
|
34195
34303
|
placeholderDiscColor,
|
|
34196
34304
|
spinDuration,
|
|
34197
34305
|
intensityCfg,
|
|
@@ -34248,6 +34356,7 @@ function RandomPlayerPicker({
|
|
|
34248
34356
|
/* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
|
|
34249
34357
|
"svg",
|
|
34250
34358
|
{
|
|
34359
|
+
ref: wheelSvgRef,
|
|
34251
34360
|
width: size,
|
|
34252
34361
|
height: size,
|
|
34253
34362
|
"aria-hidden": "true",
|
|
@@ -34256,8 +34365,8 @@ function RandomPlayerPicker({
|
|
|
34256
34365
|
top: 36,
|
|
34257
34366
|
left: 0,
|
|
34258
34367
|
transformOrigin: `${cx}px ${cy}px`,
|
|
34259
|
-
transform: `rotate(${
|
|
34260
|
-
transition: spinning ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none",
|
|
34368
|
+
transform: `rotate(${displayRotation}deg)`,
|
|
34369
|
+
transition: spinning && !physicsDriven ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none",
|
|
34261
34370
|
filter: `drop-shadow(0 8px 28px ${dark ? "rgba(0,0,0,0.55)" : "rgba(0,0,0,0.15)"})`
|
|
34262
34371
|
},
|
|
34263
34372
|
children: [
|
|
@@ -34410,7 +34519,7 @@ function RandomPlayerPicker({
|
|
|
34410
34519
|
pointerEvents: "none",
|
|
34411
34520
|
zIndex: 3
|
|
34412
34521
|
},
|
|
34413
|
-
children:
|
|
34522
|
+
children: effectiveIdleCaption
|
|
34414
34523
|
}
|
|
34415
34524
|
),
|
|
34416
34525
|
particles.map((p) => /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
|
|
@@ -39890,8 +39999,10 @@ function TeamGenerator({
|
|
|
39890
39999
|
minPlayersPerTeam = 2,
|
|
39891
40000
|
maxPlayers = Number.POSITIVE_INFINITY,
|
|
39892
40001
|
defaultNames = [],
|
|
40002
|
+
defaultTeams,
|
|
39893
40003
|
onNamesChange,
|
|
39894
40004
|
onGenerate,
|
|
40005
|
+
onTeamsChange,
|
|
39895
40006
|
onTeamCountChange,
|
|
39896
40007
|
labels,
|
|
39897
40008
|
ariaLabel = "Team generator",
|
|
@@ -39906,12 +40017,17 @@ function TeamGenerator({
|
|
|
39906
40017
|
const [names, setNames] = (0, import_react78.useState)(defaultNames);
|
|
39907
40018
|
const [input, setInput] = (0, import_react78.useState)("");
|
|
39908
40019
|
const [teamCount, setTeamCount] = (0, import_react78.useState)(initialCount);
|
|
39909
|
-
const [teams, setTeams] = (0, import_react78.useState)(
|
|
40020
|
+
const [teams, setTeams] = (0, import_react78.useState)(
|
|
40021
|
+
defaultTeams && defaultTeams.length > 0 ? defaultTeams : null
|
|
40022
|
+
);
|
|
39910
40023
|
const inputRef = (0, import_react78.useRef)(null);
|
|
39911
40024
|
const minNames = teamCount * minPlayersPerTeam;
|
|
39912
40025
|
(0, import_react78.useEffect)(() => {
|
|
39913
40026
|
onNamesChange?.(names);
|
|
39914
40027
|
}, [names, onNamesChange]);
|
|
40028
|
+
(0, import_react78.useEffect)(() => {
|
|
40029
|
+
onTeamsChange?.(teams);
|
|
40030
|
+
}, [teams, onTeamsChange]);
|
|
39915
40031
|
const addName = (0, import_react78.useCallback)(() => {
|
|
39916
40032
|
const trimmed = input.trim();
|
|
39917
40033
|
if (!trimmed) return;
|