@digilogiclabs/saas-factory-ui 2.3.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 +61 -11
- package/dist/index.d.ts +61 -11
- package/dist/index.js +278 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +278 -90
- package/dist/index.mjs.map +1 -1
- package/dist/web/index.d.mts +61 -11
- package/dist/web/index.d.ts +61 -11
- package/dist/web/index.js +384 -194
- package/dist/web/index.js.map +1 -1
- package/dist/web/index.mjs +327 -137
- package/dist/web/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/web/index.mjs
CHANGED
|
@@ -30226,10 +30226,10 @@ function TiltCard({
|
|
|
30226
30226
|
// src/components/ui/dice-roller/index.tsx
|
|
30227
30227
|
import {
|
|
30228
30228
|
useCallback as useCallback41,
|
|
30229
|
-
useEffect as
|
|
30229
|
+
useEffect as useEffect51,
|
|
30230
30230
|
useMemo as useMemo26,
|
|
30231
30231
|
useRef as useRef40,
|
|
30232
|
-
useState as
|
|
30232
|
+
useState as useState66
|
|
30233
30233
|
} from "react";
|
|
30234
30234
|
|
|
30235
30235
|
// src/hooks/web/use-dark-mode.ts
|
|
@@ -30279,21 +30279,6 @@ function useDarkMode(forced) {
|
|
|
30279
30279
|
return dark;
|
|
30280
30280
|
}
|
|
30281
30281
|
|
|
30282
|
-
// src/hooks/web/use-prefers-reduced-motion.ts
|
|
30283
|
-
import { useEffect as useEffect51, useState as useState66 } from "react";
|
|
30284
|
-
function usePrefersReducedMotion() {
|
|
30285
|
-
const [reduced, setReduced] = useState66(false);
|
|
30286
|
-
useEffect51(() => {
|
|
30287
|
-
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
30288
|
-
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
30289
|
-
setReduced(mq.matches);
|
|
30290
|
-
const read = () => setReduced(mq.matches);
|
|
30291
|
-
mq.addEventListener("change", read);
|
|
30292
|
-
return () => mq.removeEventListener("change", read);
|
|
30293
|
-
}, []);
|
|
30294
|
-
return reduced;
|
|
30295
|
-
}
|
|
30296
|
-
|
|
30297
30282
|
// src/components/ui/dice-roller/index.tsx
|
|
30298
30283
|
import { Fragment as Fragment30, jsx as jsx113, jsxs as jsxs86 } from "react/jsx-runtime";
|
|
30299
30284
|
var PRESETS = {
|
|
@@ -30512,8 +30497,8 @@ function simulateDice(arenaW, arenaH, diceSize, starts, numDice) {
|
|
|
30512
30497
|
return frames;
|
|
30513
30498
|
}
|
|
30514
30499
|
function useArenaBounds(ref) {
|
|
30515
|
-
const [bounds, setBounds] =
|
|
30516
|
-
|
|
30500
|
+
const [bounds, setBounds] = useState66({ w: 400, h: 260 });
|
|
30501
|
+
useEffect51(() => {
|
|
30517
30502
|
if (!ref.current) return;
|
|
30518
30503
|
const el = ref.current;
|
|
30519
30504
|
const update = () => setBounds({ w: el.offsetWidth, h: el.offsetHeight });
|
|
@@ -30568,10 +30553,9 @@ function DiceRoller({
|
|
|
30568
30553
|
}) {
|
|
30569
30554
|
const clampedCount = Math.max(1, Math.min(MAX_COUNT, count2));
|
|
30570
30555
|
const dark = useDarkMode(darkMode);
|
|
30571
|
-
const
|
|
30572
|
-
const [
|
|
30573
|
-
const [
|
|
30574
|
-
const [history, setHistory] = useState67([]);
|
|
30556
|
+
const [rolling, setRolling] = useState66(false);
|
|
30557
|
+
const [results, setResults] = useState66(null);
|
|
30558
|
+
const [history, setHistory] = useState66([]);
|
|
30575
30559
|
const dieWrapperRefs = useRef40([]);
|
|
30576
30560
|
const cubeRefs = useRef40([]);
|
|
30577
30561
|
const shadowRefs = useRef40([]);
|
|
@@ -30704,7 +30688,15 @@ function DiceRoller({
|
|
|
30704
30688
|
},
|
|
30705
30689
|
[]
|
|
30706
30690
|
);
|
|
30707
|
-
|
|
30691
|
+
useEffect51(() => {
|
|
30692
|
+
if (timersRef.current.length > 0) {
|
|
30693
|
+
timersRef.current.forEach((t) => clearTimeout(t));
|
|
30694
|
+
timersRef.current = [];
|
|
30695
|
+
}
|
|
30696
|
+
if (rollingRef.current) {
|
|
30697
|
+
rollingRef.current = false;
|
|
30698
|
+
setRolling(false);
|
|
30699
|
+
}
|
|
30708
30700
|
const rawW = arenaRef.current?.offsetWidth ?? 0;
|
|
30709
30701
|
const rawH = arenaRef.current?.offsetHeight ?? 0;
|
|
30710
30702
|
const w = rawW > 20 ? rawW : arena.w > 20 ? arena.w : 400;
|
|
@@ -30737,7 +30729,7 @@ function DiceRoller({
|
|
|
30737
30729
|
defaultPositions,
|
|
30738
30730
|
half
|
|
30739
30731
|
]);
|
|
30740
|
-
|
|
30732
|
+
useEffect51(() => {
|
|
30741
30733
|
setHistory([]);
|
|
30742
30734
|
}, [sides]);
|
|
30743
30735
|
const roll = useCallback41(() => {
|
|
@@ -30861,25 +30853,24 @@ function DiceRoller({
|
|
|
30861
30853
|
defaultPositions,
|
|
30862
30854
|
effectiveSize,
|
|
30863
30855
|
half,
|
|
30864
|
-
reducedMotion,
|
|
30865
30856
|
onRoll,
|
|
30866
30857
|
writeCubeRotation,
|
|
30867
30858
|
writeDiePosition,
|
|
30868
30859
|
showHistory,
|
|
30869
30860
|
historyMax
|
|
30870
30861
|
]);
|
|
30871
|
-
|
|
30862
|
+
useEffect51(() => {
|
|
30872
30863
|
if (rollRef) rollRef.current = roll;
|
|
30873
30864
|
return () => {
|
|
30874
30865
|
if (rollRef && rollRef.current === roll) rollRef.current = null;
|
|
30875
30866
|
};
|
|
30876
30867
|
}, [roll, rollRef]);
|
|
30877
|
-
|
|
30868
|
+
useEffect51(() => {
|
|
30878
30869
|
if (!autoRoll) return;
|
|
30879
30870
|
const t = setTimeout(roll, 300);
|
|
30880
30871
|
return () => clearTimeout(t);
|
|
30881
30872
|
}, []);
|
|
30882
|
-
|
|
30873
|
+
useEffect51(
|
|
30883
30874
|
() => () => {
|
|
30884
30875
|
timersRef.current.forEach((t) => clearTimeout(t));
|
|
30885
30876
|
timersRef.current = [];
|
|
@@ -31180,10 +31171,10 @@ DiceRoller.displayName = "DiceRoller";
|
|
|
31180
31171
|
|
|
31181
31172
|
// src/components/ui/bracket-generator/index.tsx
|
|
31182
31173
|
import {
|
|
31183
|
-
useState as
|
|
31174
|
+
useState as useState67,
|
|
31184
31175
|
useCallback as useCallback42,
|
|
31185
31176
|
useRef as useRef41,
|
|
31186
|
-
useEffect as
|
|
31177
|
+
useEffect as useEffect52,
|
|
31187
31178
|
useMemo as useMemo27,
|
|
31188
31179
|
useLayoutEffect as useLayoutEffect2
|
|
31189
31180
|
} from "react";
|
|
@@ -31406,7 +31397,7 @@ function computeLayout(rounds, firstRoundCount) {
|
|
|
31406
31397
|
};
|
|
31407
31398
|
}
|
|
31408
31399
|
function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
|
|
31409
|
-
const [transform, setTransform] =
|
|
31400
|
+
const [transform, setTransform] = useState67({
|
|
31410
31401
|
x: 0,
|
|
31411
31402
|
y: 0,
|
|
31412
31403
|
scale: 1
|
|
@@ -31426,7 +31417,7 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
|
|
|
31426
31417
|
},
|
|
31427
31418
|
[contentSize]
|
|
31428
31419
|
);
|
|
31429
|
-
|
|
31420
|
+
useEffect52(() => {
|
|
31430
31421
|
const el = viewportRef.current;
|
|
31431
31422
|
if (!el) return;
|
|
31432
31423
|
const onWheel = (e) => {
|
|
@@ -31657,12 +31648,12 @@ function BracketGenerator({
|
|
|
31657
31648
|
ariaLabel = "Tournament bracket"
|
|
31658
31649
|
}) {
|
|
31659
31650
|
const dark = useDarkMode(darkMode);
|
|
31660
|
-
const [bracketState, setBracketState] =
|
|
31651
|
+
const [bracketState, setBracketState] = useState67({
|
|
31661
31652
|
matches: [],
|
|
31662
31653
|
rounds: 0,
|
|
31663
31654
|
champion: null
|
|
31664
31655
|
});
|
|
31665
|
-
const [simulating, setSimulating] =
|
|
31656
|
+
const [simulating, setSimulating] = useState67(false);
|
|
31666
31657
|
const autoRef = useRef41(null);
|
|
31667
31658
|
const viewportRef = useRef41(null);
|
|
31668
31659
|
const effectiveParticipants = useMemo27(() => {
|
|
@@ -31717,7 +31708,7 @@ function BracketGenerator({
|
|
|
31717
31708
|
minZoom,
|
|
31718
31709
|
maxZoom
|
|
31719
31710
|
);
|
|
31720
|
-
const [miniMapHidden, setMiniMapHidden] =
|
|
31711
|
+
const [miniMapHidden, setMiniMapHidden] = useState67(false);
|
|
31721
31712
|
const miniMapDragRef = useRef41(false);
|
|
31722
31713
|
const resumeRef = useRef41(resumeMatches);
|
|
31723
31714
|
const hydrateFromResume = useCallback42(() => {
|
|
@@ -31851,7 +31842,7 @@ function BracketGenerator({
|
|
|
31851
31842
|
[]
|
|
31852
31843
|
);
|
|
31853
31844
|
const prevBracketStateRef = useRef41(bracketState);
|
|
31854
|
-
|
|
31845
|
+
useEffect52(() => {
|
|
31855
31846
|
const prev = prevBracketStateRef.current;
|
|
31856
31847
|
const next = bracketState;
|
|
31857
31848
|
if (prev === next) return;
|
|
@@ -31875,7 +31866,7 @@ function BracketGenerator({
|
|
|
31875
31866
|
[effectiveParticipants, seedingStrategy]
|
|
31876
31867
|
);
|
|
31877
31868
|
const seededKeyRef = useRef41(null);
|
|
31878
|
-
|
|
31869
|
+
useEffect52(() => {
|
|
31879
31870
|
if (seededKeyRef.current === structuralKey) {
|
|
31880
31871
|
setBracketState((prev) => {
|
|
31881
31872
|
if (!prev.matches.length) return prev;
|
|
@@ -31906,7 +31897,7 @@ function BracketGenerator({
|
|
|
31906
31897
|
seedBracket();
|
|
31907
31898
|
}
|
|
31908
31899
|
}, [structuralKey, effectiveParticipants, hydrateFromResume, seedBracket]);
|
|
31909
|
-
|
|
31900
|
+
useEffect52(() => {
|
|
31910
31901
|
return () => {
|
|
31911
31902
|
if (autoRef.current) clearTimeout(autoRef.current);
|
|
31912
31903
|
};
|
|
@@ -31917,7 +31908,7 @@ function BracketGenerator({
|
|
|
31917
31908
|
return () => clearTimeout(timer);
|
|
31918
31909
|
}
|
|
31919
31910
|
}, [contentSize.w]);
|
|
31920
|
-
|
|
31911
|
+
useEffect52(() => {
|
|
31921
31912
|
if (autoSimulate && bracketState.matches.length && !bracketState.champion) {
|
|
31922
31913
|
const timer = setTimeout(autoPlay, 500);
|
|
31923
31914
|
return () => clearTimeout(timer);
|
|
@@ -32638,10 +32629,10 @@ function BracketGenerator({
|
|
|
32638
32629
|
// src/components/ui/coin-flipper/index.tsx
|
|
32639
32630
|
import {
|
|
32640
32631
|
useCallback as useCallback43,
|
|
32641
|
-
useEffect as
|
|
32632
|
+
useEffect as useEffect53,
|
|
32642
32633
|
useMemo as useMemo28,
|
|
32643
32634
|
useRef as useRef42,
|
|
32644
|
-
useState as
|
|
32635
|
+
useState as useState68
|
|
32645
32636
|
} from "react";
|
|
32646
32637
|
import { jsx as jsx115, jsxs as jsxs88 } from "react/jsx-runtime";
|
|
32647
32638
|
var PRESETS3 = {
|
|
@@ -32826,10 +32817,10 @@ function CoinFlipper({
|
|
|
32826
32817
|
}) {
|
|
32827
32818
|
const dark = useDarkMode(darkMode);
|
|
32828
32819
|
const tc = typeof theme5 === "string" ? PRESETS3[theme5] || PRESETS3.gold : theme5;
|
|
32829
|
-
const [result, setResult] =
|
|
32830
|
-
const [flipping, setFlipping] =
|
|
32831
|
-
const [history, setHistory] =
|
|
32832
|
-
const [stats, setStats] =
|
|
32820
|
+
const [result, setResult] = useState68(null);
|
|
32821
|
+
const [flipping, setFlipping] = useState68(false);
|
|
32822
|
+
const [history, setHistory] = useState68([]);
|
|
32823
|
+
const [stats, setStats] = useState68({ heads: 0, tails: 0 });
|
|
32833
32824
|
const flipIdRef = useRef42(0);
|
|
32834
32825
|
const coinRef = useRef42(null);
|
|
32835
32826
|
const shadowRef = useRef42(null);
|
|
@@ -32885,16 +32876,19 @@ function CoinFlipper({
|
|
|
32885
32876
|
}, frames.length * stepMs);
|
|
32886
32877
|
timersRef.current.push(settleTimer);
|
|
32887
32878
|
}, [flipping, onFlip, historyMax]);
|
|
32888
|
-
|
|
32879
|
+
useEffect53(() => {
|
|
32889
32880
|
if (flipRef) flipRef.current = flip;
|
|
32881
|
+
return () => {
|
|
32882
|
+
if (flipRef && flipRef.current === flip) flipRef.current = null;
|
|
32883
|
+
};
|
|
32890
32884
|
}, [flip, flipRef]);
|
|
32891
|
-
|
|
32885
|
+
useEffect53(() => {
|
|
32892
32886
|
if (autoFlip) {
|
|
32893
32887
|
const timer = setTimeout(flip, 300);
|
|
32894
32888
|
return () => clearTimeout(timer);
|
|
32895
32889
|
}
|
|
32896
32890
|
}, []);
|
|
32897
|
-
|
|
32891
|
+
useEffect53(() => {
|
|
32898
32892
|
return () => {
|
|
32899
32893
|
timersRef.current.forEach(clearTimeout);
|
|
32900
32894
|
};
|
|
@@ -33337,6 +33331,23 @@ import {
|
|
|
33337
33331
|
useRef as useRef43,
|
|
33338
33332
|
useState as useState70
|
|
33339
33333
|
} from "react";
|
|
33334
|
+
|
|
33335
|
+
// src/hooks/web/use-prefers-reduced-motion.ts
|
|
33336
|
+
import { useEffect as useEffect54, useState as useState69 } from "react";
|
|
33337
|
+
function usePrefersReducedMotion() {
|
|
33338
|
+
const [reduced, setReduced] = useState69(false);
|
|
33339
|
+
useEffect54(() => {
|
|
33340
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
33341
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
33342
|
+
setReduced(mq.matches);
|
|
33343
|
+
const read = () => setReduced(mq.matches);
|
|
33344
|
+
mq.addEventListener("change", read);
|
|
33345
|
+
return () => mq.removeEventListener("change", read);
|
|
33346
|
+
}, []);
|
|
33347
|
+
return reduced;
|
|
33348
|
+
}
|
|
33349
|
+
|
|
33350
|
+
// src/components/ui/random-player-picker/index.tsx
|
|
33340
33351
|
import { Fragment as Fragment31, jsx as jsx116, jsxs as jsxs89 } from "react/jsx-runtime";
|
|
33341
33352
|
var PRESETS4 = {
|
|
33342
33353
|
blue: {
|
|
@@ -33394,6 +33405,11 @@ var WEDGE_PALETTE = [
|
|
|
33394
33405
|
];
|
|
33395
33406
|
var DEFAULT_EASING = "cubic-bezier(0.25, 0.46, 0.45, 0.94)";
|
|
33396
33407
|
var DRAMATIC_EASING = "cubic-bezier(0.39, 0.575, 0.565, 1)";
|
|
33408
|
+
var PHYSICS_DECAY_K = 4;
|
|
33409
|
+
function physicsRampDown(t) {
|
|
33410
|
+
return (1 - Math.exp(-PHYSICS_DECAY_K * t)) / (1 - Math.exp(-PHYSICS_DECAY_K));
|
|
33411
|
+
}
|
|
33412
|
+
var REEL_LEAD_IN = 32;
|
|
33397
33413
|
var INTENSITY_CONFIG = {
|
|
33398
33414
|
subtle: {
|
|
33399
33415
|
particles: 14,
|
|
@@ -33465,6 +33481,9 @@ function ReelStage({
|
|
|
33465
33481
|
winner,
|
|
33466
33482
|
spinning,
|
|
33467
33483
|
reelY,
|
|
33484
|
+
displayY,
|
|
33485
|
+
cssTransition,
|
|
33486
|
+
tapeRef,
|
|
33468
33487
|
reelVisibleRows,
|
|
33469
33488
|
reelRowHeight,
|
|
33470
33489
|
size,
|
|
@@ -33488,17 +33507,16 @@ function ReelStage({
|
|
|
33488
33507
|
const stripHeight = reelVisibleRows * reelRowHeight;
|
|
33489
33508
|
const centerOffsetPx = (reelVisibleRows - 1) / 2 * reelRowHeight;
|
|
33490
33509
|
const P = pool.length;
|
|
33491
|
-
const leadIn = 32;
|
|
33492
33510
|
const tapeRows = Math.max(
|
|
33493
|
-
reelVisibleRows +
|
|
33494
|
-
Math.ceil((reelY + stripHeight) / reelRowHeight) +
|
|
33511
|
+
reelVisibleRows + REEL_LEAD_IN + 1,
|
|
33512
|
+
Math.ceil((reelY + stripHeight) / reelRowHeight) + REEL_LEAD_IN + 1
|
|
33495
33513
|
);
|
|
33496
33514
|
const tape = Array.from({ length: tapeRows }, (_, i) => {
|
|
33497
|
-
const poolIdx = P === 0 ? 0 : ((i -
|
|
33515
|
+
const poolIdx = P === 0 ? 0 : ((i - REEL_LEAD_IN) % P + P) % P;
|
|
33498
33516
|
return pool[poolIdx];
|
|
33499
33517
|
});
|
|
33500
|
-
const effectiveY =
|
|
33501
|
-
const transition = spinning ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none";
|
|
33518
|
+
const effectiveY = displayY + REEL_LEAD_IN * reelRowHeight;
|
|
33519
|
+
const transition = spinning && cssTransition ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none";
|
|
33502
33520
|
return /* @__PURE__ */ jsxs89(
|
|
33503
33521
|
"div",
|
|
33504
33522
|
{
|
|
@@ -33543,6 +33561,7 @@ function ReelStage({
|
|
|
33543
33561
|
) : /* @__PURE__ */ jsx116(
|
|
33544
33562
|
"div",
|
|
33545
33563
|
{
|
|
33564
|
+
ref: tapeRef,
|
|
33546
33565
|
"aria-hidden": "true",
|
|
33547
33566
|
style: {
|
|
33548
33567
|
position: "absolute",
|
|
@@ -33758,6 +33777,9 @@ function RandomPlayerPicker({
|
|
|
33758
33777
|
showStats = true,
|
|
33759
33778
|
renderWinner,
|
|
33760
33779
|
spinRef,
|
|
33780
|
+
resetRef,
|
|
33781
|
+
onPoolChange,
|
|
33782
|
+
draftCompleteCaption = "Draft complete \u2014 reset to spin again",
|
|
33761
33783
|
intensity = "normal",
|
|
33762
33784
|
enableHaptics = false,
|
|
33763
33785
|
tickSoundUrl,
|
|
@@ -33772,6 +33794,7 @@ function RandomPlayerPicker({
|
|
|
33772
33794
|
const dark = useDarkMode(darkMode);
|
|
33773
33795
|
const reducedMotion = usePrefersReducedMotion();
|
|
33774
33796
|
const intensityCfg = INTENSITY_CONFIG[intensity];
|
|
33797
|
+
const physicsDriven = intensity === "dramatic" && !reducedMotion;
|
|
33775
33798
|
const [pool, setPool] = useState70(participants);
|
|
33776
33799
|
const [rotation, setRotation] = useState70(0);
|
|
33777
33800
|
const [reelY, setReelY] = useState70(0);
|
|
@@ -33784,10 +33807,17 @@ function RandomPlayerPicker({
|
|
|
33784
33807
|
const particleTimer = useRef43(null);
|
|
33785
33808
|
const popTimer = useRef43(null);
|
|
33786
33809
|
const tickInterval = useRef43(null);
|
|
33810
|
+
const pendingPopIdxRef = useRef43(null);
|
|
33811
|
+
const removeWinnersRef = useRef43(removeWinners);
|
|
33787
33812
|
const tickAudioRef = useRef43(null);
|
|
33788
33813
|
const winAudioRef = useRef43(null);
|
|
33789
33814
|
const containerRef = useRef43(null);
|
|
33790
33815
|
const prevIdsRef = useRef43("");
|
|
33816
|
+
const wheelSvgRef = useRef43(null);
|
|
33817
|
+
const reelTapeRef = useRef43(null);
|
|
33818
|
+
const rafRef = useRef43(null);
|
|
33819
|
+
const visualRotationRef = useRef43(null);
|
|
33820
|
+
const visualReelYRef = useRef43(null);
|
|
33791
33821
|
useEffect55(() => {
|
|
33792
33822
|
if (typeof window === "undefined" || !tickSoundUrl) {
|
|
33793
33823
|
tickAudioRef.current = null;
|
|
@@ -33822,10 +33852,33 @@ function RandomPlayerPicker({
|
|
|
33822
33852
|
winAudioRef.current = null;
|
|
33823
33853
|
};
|
|
33824
33854
|
}, [winSoundUrl, soundVolume]);
|
|
33855
|
+
useEffect55(() => {
|
|
33856
|
+
removeWinnersRef.current = removeWinners;
|
|
33857
|
+
}, [removeWinners]);
|
|
33825
33858
|
useEffect55(() => {
|
|
33826
33859
|
const ids = participants.map((p) => p.id).join("|");
|
|
33827
33860
|
const structuralChange = ids !== prevIdsRef.current;
|
|
33828
33861
|
if (structuralChange) {
|
|
33862
|
+
if (winnerTimer.current) {
|
|
33863
|
+
clearTimeout(winnerTimer.current);
|
|
33864
|
+
winnerTimer.current = null;
|
|
33865
|
+
}
|
|
33866
|
+
if (tickInterval.current) {
|
|
33867
|
+
clearTimeout(tickInterval.current);
|
|
33868
|
+
tickInterval.current = null;
|
|
33869
|
+
}
|
|
33870
|
+
if (popTimer.current) {
|
|
33871
|
+
clearTimeout(popTimer.current);
|
|
33872
|
+
popTimer.current = null;
|
|
33873
|
+
}
|
|
33874
|
+
if (rafRef.current) {
|
|
33875
|
+
cancelAnimationFrame(rafRef.current);
|
|
33876
|
+
rafRef.current = null;
|
|
33877
|
+
}
|
|
33878
|
+
visualRotationRef.current = null;
|
|
33879
|
+
visualReelYRef.current = null;
|
|
33880
|
+
pendingPopIdxRef.current = null;
|
|
33881
|
+
setSpinning(false);
|
|
33829
33882
|
setPool(participants);
|
|
33830
33883
|
setHistory([]);
|
|
33831
33884
|
setWinner(null);
|
|
@@ -33882,33 +33935,50 @@ function RandomPlayerPicker({
|
|
|
33882
33935
|
[dark]
|
|
33883
33936
|
);
|
|
33884
33937
|
const spin = useCallback44(() => {
|
|
33885
|
-
if (spinning
|
|
33938
|
+
if (spinning) return;
|
|
33939
|
+
let activePool = pool;
|
|
33940
|
+
if (popTimer.current) {
|
|
33941
|
+
clearTimeout(popTimer.current);
|
|
33942
|
+
popTimer.current = null;
|
|
33943
|
+
const pendingIdx = pendingPopIdxRef.current;
|
|
33944
|
+
pendingPopIdxRef.current = null;
|
|
33945
|
+
if (pendingIdx != null && pendingIdx >= 0 && pendingIdx < pool.length && removeWinnersRef.current) {
|
|
33946
|
+
activePool = pool.filter((_, i) => i !== pendingIdx);
|
|
33947
|
+
setPool(activePool);
|
|
33948
|
+
setWinner(null);
|
|
33949
|
+
}
|
|
33950
|
+
}
|
|
33951
|
+
const activeN = activePool.length;
|
|
33952
|
+
if (activeN < 2) return;
|
|
33953
|
+
const activeAnglePerWedge = 2 * Math.PI / activeN;
|
|
33954
|
+
const activeRotation = rotation;
|
|
33955
|
+
const activeReelY = reelY;
|
|
33886
33956
|
onSpinStart?.();
|
|
33887
|
-
const winnerIdx = weightedIndex(
|
|
33957
|
+
const winnerIdx = weightedIndex(activePool);
|
|
33888
33958
|
const minRev = minRevolutions + intensityCfg.revsBoost;
|
|
33889
33959
|
const maxRev = maxRevolutions + intensityCfg.revsBoost;
|
|
33890
33960
|
const minFullRevs = Math.ceil(minRev);
|
|
33891
33961
|
const maxFullRevs = Math.max(minFullRevs, Math.floor(maxRev));
|
|
33892
33962
|
const revolutions = minFullRevs + Math.floor(Math.random() * (maxFullRevs - minFullRevs + 1));
|
|
33893
|
-
const wedgeMidRad = winnerIdx *
|
|
33963
|
+
const wedgeMidRad = winnerIdx * activeAnglePerWedge + activeAnglePerWedge / 2;
|
|
33894
33964
|
const wedgeMidDeg = wedgeMidRad * 180 / Math.PI;
|
|
33895
|
-
const wheelWobble = (Math.random() - 0.5) * (
|
|
33896
|
-
const currentMod = (
|
|
33965
|
+
const wheelWobble = (Math.random() - 0.5) * (activeAnglePerWedge * 180 / Math.PI) * 0.6;
|
|
33966
|
+
const currentMod = (activeRotation % 360 + 360) % 360;
|
|
33897
33967
|
const targetMod = ((-wedgeMidDeg + wheelWobble) % 360 + 360) % 360;
|
|
33898
33968
|
let deltaMod = targetMod - currentMod;
|
|
33899
33969
|
if (deltaMod < 0) deltaMod += 360;
|
|
33900
|
-
const finalRotation =
|
|
33970
|
+
const finalRotation = activeRotation + deltaMod + revolutions * 360;
|
|
33901
33971
|
const centerOffsetRows = (reelVisibleRows - 1) / 2;
|
|
33902
33972
|
const centerOffsetPx = centerOffsetRows * reelRowHeight;
|
|
33903
33973
|
const currentCenterRow = Math.round(
|
|
33904
|
-
(
|
|
33974
|
+
(activeReelY + centerOffsetPx) / reelRowHeight
|
|
33905
33975
|
);
|
|
33906
|
-
const currentCenterMod = mod(currentCenterRow,
|
|
33907
|
-
const targetRowOffset = (winnerIdx - currentCenterMod +
|
|
33908
|
-
const reelRevRows = Math.round(revolutions *
|
|
33909
|
-
const finalReelY =
|
|
33910
|
-
const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight,
|
|
33911
|
-
const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx <
|
|
33976
|
+
const currentCenterMod = mod(currentCenterRow, activeN);
|
|
33977
|
+
const targetRowOffset = (winnerIdx - currentCenterMod + activeN) % activeN;
|
|
33978
|
+
const reelRevRows = Math.round(revolutions * activeN);
|
|
33979
|
+
const finalReelY = activeReelY + (reelRevRows + targetRowOffset) * reelRowHeight;
|
|
33980
|
+
const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, activeN) : wheelIndexAtPointer(finalRotation, activeN);
|
|
33981
|
+
const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < activePool.length ? settledWinnerIdx : winnerIdx;
|
|
33912
33982
|
const effectiveDuration = reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult);
|
|
33913
33983
|
setSpinning(true);
|
|
33914
33984
|
setWinner(null);
|
|
@@ -33918,10 +33988,59 @@ function RandomPlayerPicker({
|
|
|
33918
33988
|
} else {
|
|
33919
33989
|
setRotation(finalRotation);
|
|
33920
33990
|
}
|
|
33921
|
-
|
|
33991
|
+
const fireTick = () => {
|
|
33992
|
+
onTick?.();
|
|
33993
|
+
const ta = tickAudioRef.current;
|
|
33994
|
+
if (ta) {
|
|
33995
|
+
try {
|
|
33996
|
+
ta.currentTime = 0;
|
|
33997
|
+
void ta.play().catch(() => {
|
|
33998
|
+
});
|
|
33999
|
+
} catch {
|
|
34000
|
+
}
|
|
34001
|
+
}
|
|
34002
|
+
};
|
|
34003
|
+
if (physicsDriven) {
|
|
34004
|
+
const from = mode === "reel" ? activeReelY : activeRotation;
|
|
34005
|
+
const to = mode === "reel" ? finalReelY : finalRotation;
|
|
34006
|
+
const crossUnit = mode === "reel" ? reelRowHeight : activeAnglePerWedge * 180 / Math.PI;
|
|
34007
|
+
if (mode === "reel") visualReelYRef.current = from;
|
|
34008
|
+
else visualRotationRef.current = from;
|
|
34009
|
+
let crossings = 0;
|
|
34010
|
+
const t0 = performance.now();
|
|
34011
|
+
const step = (now) => {
|
|
34012
|
+
const t = Math.min(1, (now - t0) / effectiveDuration);
|
|
34013
|
+
const value = from + (to - from) * physicsRampDown(t);
|
|
34014
|
+
if (mode === "reel") {
|
|
34015
|
+
visualReelYRef.current = value;
|
|
34016
|
+
const el = reelTapeRef.current;
|
|
34017
|
+
if (el) {
|
|
34018
|
+
el.style.transform = `translateY(${-(value + REEL_LEAD_IN * reelRowHeight)}px)`;
|
|
34019
|
+
}
|
|
34020
|
+
} else {
|
|
34021
|
+
visualRotationRef.current = value;
|
|
34022
|
+
const el = wheelSvgRef.current;
|
|
34023
|
+
if (el) el.style.transform = `rotate(${value}deg)`;
|
|
34024
|
+
}
|
|
34025
|
+
const crossed = Math.floor(Math.abs(value - from) / crossUnit);
|
|
34026
|
+
if (crossed > crossings) {
|
|
34027
|
+
crossings = crossed;
|
|
34028
|
+
fireTick();
|
|
34029
|
+
}
|
|
34030
|
+
if (t < 1) {
|
|
34031
|
+
rafRef.current = requestAnimationFrame(step);
|
|
34032
|
+
} else {
|
|
34033
|
+
rafRef.current = null;
|
|
34034
|
+
visualRotationRef.current = null;
|
|
34035
|
+
visualReelYRef.current = null;
|
|
34036
|
+
}
|
|
34037
|
+
};
|
|
34038
|
+
rafRef.current = requestAnimationFrame(step);
|
|
34039
|
+
}
|
|
34040
|
+
if (!physicsDriven && (onTick || tickAudioRef.current) && !reducedMotion) {
|
|
33922
34041
|
const wedges = mode === "reel" ? Math.max(1, reelRevRows + targetRowOffset) : (() => {
|
|
33923
34042
|
const totalDelta = deltaMod + revolutions * 360;
|
|
33924
|
-
const wedgeDeg =
|
|
34043
|
+
const wedgeDeg = activeAnglePerWedge * 180 / Math.PI;
|
|
33925
34044
|
return Math.max(
|
|
33926
34045
|
1,
|
|
33927
34046
|
Math.round(totalDelta / Math.max(wedgeDeg, 1))
|
|
@@ -33936,16 +34055,7 @@ function RandomPlayerPicker({
|
|
|
33936
34055
|
const eased = 1 - Math.pow(1 - progress, 3);
|
|
33937
34056
|
const interval = startInterval + (endInterval - startInterval) * eased;
|
|
33938
34057
|
tickInterval.current = setTimeout(() => {
|
|
33939
|
-
|
|
33940
|
-
const ta = tickAudioRef.current;
|
|
33941
|
-
if (ta) {
|
|
33942
|
-
try {
|
|
33943
|
-
ta.currentTime = 0;
|
|
33944
|
-
void ta.play().catch(() => {
|
|
33945
|
-
});
|
|
33946
|
-
} catch {
|
|
33947
|
-
}
|
|
33948
|
-
}
|
|
34058
|
+
fireTick();
|
|
33949
34059
|
elapsed += interval;
|
|
33950
34060
|
schedule();
|
|
33951
34061
|
}, interval);
|
|
@@ -33953,7 +34063,7 @@ function RandomPlayerPicker({
|
|
|
33953
34063
|
schedule();
|
|
33954
34064
|
}
|
|
33955
34065
|
winnerTimer.current = setTimeout(() => {
|
|
33956
|
-
const picked =
|
|
34066
|
+
const picked = activePool[finalWinnerIdx];
|
|
33957
34067
|
if (!picked) {
|
|
33958
34068
|
setSpinning(false);
|
|
33959
34069
|
return;
|
|
@@ -33999,9 +34109,13 @@ function RandomPlayerPicker({
|
|
|
33999
34109
|
particleTimer.current = setTimeout(() => setParticles([]), 2400);
|
|
34000
34110
|
}
|
|
34001
34111
|
if (removeWinners) {
|
|
34112
|
+
pendingPopIdxRef.current = finalWinnerIdx;
|
|
34002
34113
|
popTimer.current = setTimeout(() => {
|
|
34114
|
+
popTimer.current = null;
|
|
34115
|
+
pendingPopIdxRef.current = null;
|
|
34116
|
+
if (!removeWinnersRef.current) return;
|
|
34003
34117
|
setPool((p) => p.filter((_, i) => i !== finalWinnerIdx));
|
|
34004
|
-
setWinner(null);
|
|
34118
|
+
if (activePool.length - 1 >= 2) setWinner(null);
|
|
34005
34119
|
setRotation(0);
|
|
34006
34120
|
setReelY(0);
|
|
34007
34121
|
}, 2800);
|
|
@@ -34010,8 +34124,6 @@ function RandomPlayerPicker({
|
|
|
34010
34124
|
}, [
|
|
34011
34125
|
pool,
|
|
34012
34126
|
spinning,
|
|
34013
|
-
n,
|
|
34014
|
-
anglePerWedge,
|
|
34015
34127
|
rotation,
|
|
34016
34128
|
reelY,
|
|
34017
34129
|
mode,
|
|
@@ -34026,6 +34138,7 @@ function RandomPlayerPicker({
|
|
|
34026
34138
|
maxRevolutions,
|
|
34027
34139
|
history,
|
|
34028
34140
|
reducedMotion,
|
|
34141
|
+
physicsDriven,
|
|
34029
34142
|
enableHaptics,
|
|
34030
34143
|
intensityCfg.particles,
|
|
34031
34144
|
intensityCfg.durationMult,
|
|
@@ -34037,16 +34150,25 @@ function RandomPlayerPicker({
|
|
|
34037
34150
|
if (spinRef && spinRef.current === spin) spinRef.current = null;
|
|
34038
34151
|
};
|
|
34039
34152
|
}, [spin, spinRef]);
|
|
34153
|
+
const onPoolChangeRef = useRef43(onPoolChange);
|
|
34154
|
+
useEffect55(() => {
|
|
34155
|
+
onPoolChangeRef.current = onPoolChange;
|
|
34156
|
+
});
|
|
34157
|
+
useEffect55(() => {
|
|
34158
|
+
onPoolChangeRef.current?.(pool);
|
|
34159
|
+
}, [pool]);
|
|
34040
34160
|
useEffect55(
|
|
34041
34161
|
() => () => {
|
|
34042
34162
|
if (winnerTimer.current) clearTimeout(winnerTimer.current);
|
|
34043
34163
|
if (particleTimer.current) clearTimeout(particleTimer.current);
|
|
34044
34164
|
if (popTimer.current) clearTimeout(popTimer.current);
|
|
34045
34165
|
if (tickInterval.current) clearTimeout(tickInterval.current);
|
|
34166
|
+
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
34046
34167
|
},
|
|
34047
34168
|
[]
|
|
34048
34169
|
);
|
|
34049
34170
|
useEffect55(() => {
|
|
34171
|
+
if (trigger === "external") return;
|
|
34050
34172
|
const el = containerRef.current;
|
|
34051
34173
|
if (!el) return;
|
|
34052
34174
|
const h = (e) => {
|
|
@@ -34057,8 +34179,28 @@ function RandomPlayerPicker({
|
|
|
34057
34179
|
};
|
|
34058
34180
|
el.addEventListener("keydown", h);
|
|
34059
34181
|
return () => el.removeEventListener("keydown", h);
|
|
34060
|
-
}, [spin]);
|
|
34182
|
+
}, [spin, trigger]);
|
|
34061
34183
|
const reset = useCallback44(() => {
|
|
34184
|
+
if (winnerTimer.current) {
|
|
34185
|
+
clearTimeout(winnerTimer.current);
|
|
34186
|
+
winnerTimer.current = null;
|
|
34187
|
+
}
|
|
34188
|
+
if (tickInterval.current) {
|
|
34189
|
+
clearTimeout(tickInterval.current);
|
|
34190
|
+
tickInterval.current = null;
|
|
34191
|
+
}
|
|
34192
|
+
if (popTimer.current) {
|
|
34193
|
+
clearTimeout(popTimer.current);
|
|
34194
|
+
popTimer.current = null;
|
|
34195
|
+
}
|
|
34196
|
+
if (rafRef.current) {
|
|
34197
|
+
cancelAnimationFrame(rafRef.current);
|
|
34198
|
+
rafRef.current = null;
|
|
34199
|
+
}
|
|
34200
|
+
visualRotationRef.current = null;
|
|
34201
|
+
visualReelYRef.current = null;
|
|
34202
|
+
pendingPopIdxRef.current = null;
|
|
34203
|
+
setSpinning(false);
|
|
34062
34204
|
setPool(participants);
|
|
34063
34205
|
setHistory([]);
|
|
34064
34206
|
setWinner(null);
|
|
@@ -34066,9 +34208,17 @@ function RandomPlayerPicker({
|
|
|
34066
34208
|
setReelY(0);
|
|
34067
34209
|
setParticles([]);
|
|
34068
34210
|
}, [participants]);
|
|
34211
|
+
useEffect55(() => {
|
|
34212
|
+
if (resetRef) resetRef.current = reset;
|
|
34213
|
+
return () => {
|
|
34214
|
+
if (resetRef && resetRef.current === reset) resetRef.current = null;
|
|
34215
|
+
};
|
|
34216
|
+
}, [reset, resetRef]);
|
|
34069
34217
|
const isIdle = n < 2;
|
|
34218
|
+
const drainedByDraft = isIdle && participants.length >= 2;
|
|
34219
|
+
const effectiveIdleCaption = drainedByDraft ? draftCompleteCaption : placeholderCaption;
|
|
34070
34220
|
const placeholderDiscColor = dark ? "#2a3344" : "#e2e8f0";
|
|
34071
|
-
if (isIdle && placeholderMode === "message") {
|
|
34221
|
+
if (isIdle && !drainedByDraft && placeholderMode === "message") {
|
|
34072
34222
|
return /* @__PURE__ */ jsx116(
|
|
34073
34223
|
"div",
|
|
34074
34224
|
{
|
|
@@ -34090,6 +34240,8 @@ function RandomPlayerPicker({
|
|
|
34090
34240
|
const shakeKeyframe = `rppShake_${intensity}`;
|
|
34091
34241
|
const glowKeyframe = `rppGlow_${intensity}`;
|
|
34092
34242
|
const pulseKeyframe = `rppPulse_${intensity}`;
|
|
34243
|
+
const displayRotation = visualRotationRef.current ?? rotation;
|
|
34244
|
+
const displayReelY = visualReelYRef.current ?? reelY;
|
|
34093
34245
|
return /* @__PURE__ */ jsxs89(
|
|
34094
34246
|
"div",
|
|
34095
34247
|
{
|
|
@@ -34136,13 +34288,33 @@ function RandomPlayerPicker({
|
|
|
34136
34288
|
[data-rpp-anim] { animation: none !important; }
|
|
34137
34289
|
}
|
|
34138
34290
|
` }),
|
|
34139
|
-
|
|
34291
|
+
isIdle && placeholderMode === "message" ? (
|
|
34292
|
+
/* Drained draft in "message" mode — compact box in the wheel's
|
|
34293
|
+
place; the winner card / stats / controls / history below stay
|
|
34294
|
+
mounted so the endgame isn't a dead end. */
|
|
34295
|
+
/* @__PURE__ */ jsx116(
|
|
34296
|
+
"div",
|
|
34297
|
+
{
|
|
34298
|
+
style: {
|
|
34299
|
+
padding: "2rem 1rem",
|
|
34300
|
+
textAlign: "center",
|
|
34301
|
+
color: c.dim,
|
|
34302
|
+
borderRadius: 12,
|
|
34303
|
+
border: `1px dashed ${c.border}`
|
|
34304
|
+
},
|
|
34305
|
+
children: effectiveIdleCaption
|
|
34306
|
+
}
|
|
34307
|
+
)
|
|
34308
|
+
) : mode === "reel" ? /* @__PURE__ */ jsx116(
|
|
34140
34309
|
ReelStage,
|
|
34141
34310
|
{
|
|
34142
34311
|
pool,
|
|
34143
34312
|
winner,
|
|
34144
34313
|
spinning,
|
|
34145
34314
|
reelY,
|
|
34315
|
+
displayY: displayReelY,
|
|
34316
|
+
cssTransition: !physicsDriven,
|
|
34317
|
+
tapeRef: reelTapeRef,
|
|
34146
34318
|
reelVisibleRows,
|
|
34147
34319
|
reelRowHeight,
|
|
34148
34320
|
size,
|
|
@@ -34151,7 +34323,7 @@ function RandomPlayerPicker({
|
|
|
34151
34323
|
dark,
|
|
34152
34324
|
isIdle,
|
|
34153
34325
|
placeholderMode,
|
|
34154
|
-
placeholderCaption,
|
|
34326
|
+
placeholderCaption: effectiveIdleCaption,
|
|
34155
34327
|
placeholderDiscColor,
|
|
34156
34328
|
spinDuration,
|
|
34157
34329
|
intensityCfg,
|
|
@@ -34208,6 +34380,7 @@ function RandomPlayerPicker({
|
|
|
34208
34380
|
/* @__PURE__ */ jsxs89(
|
|
34209
34381
|
"svg",
|
|
34210
34382
|
{
|
|
34383
|
+
ref: wheelSvgRef,
|
|
34211
34384
|
width: size,
|
|
34212
34385
|
height: size,
|
|
34213
34386
|
"aria-hidden": "true",
|
|
@@ -34216,8 +34389,8 @@ function RandomPlayerPicker({
|
|
|
34216
34389
|
top: 36,
|
|
34217
34390
|
left: 0,
|
|
34218
34391
|
transformOrigin: `${cx}px ${cy}px`,
|
|
34219
|
-
transform: `rotate(${
|
|
34220
|
-
transition: spinning ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none",
|
|
34392
|
+
transform: `rotate(${displayRotation}deg)`,
|
|
34393
|
+
transition: spinning && !physicsDriven ? `transform ${reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult)}ms ${intensityCfg.easing}` : "none",
|
|
34221
34394
|
filter: `drop-shadow(0 8px 28px ${dark ? "rgba(0,0,0,0.55)" : "rgba(0,0,0,0.15)"})`
|
|
34222
34395
|
},
|
|
34223
34396
|
children: [
|
|
@@ -34370,7 +34543,7 @@ function RandomPlayerPicker({
|
|
|
34370
34543
|
pointerEvents: "none",
|
|
34371
34544
|
zIndex: 3
|
|
34372
34545
|
},
|
|
34373
|
-
children:
|
|
34546
|
+
children: effectiveIdleCaption
|
|
34374
34547
|
}
|
|
34375
34548
|
),
|
|
34376
34549
|
particles.map((p) => /* @__PURE__ */ jsx116(
|
|
@@ -38531,27 +38704,28 @@ function GameTimer({
|
|
|
38531
38704
|
const now = performance.now();
|
|
38532
38705
|
const delta = now - lastTickRef.current;
|
|
38533
38706
|
lastTickRef.current = now;
|
|
38534
|
-
|
|
38535
|
-
|
|
38536
|
-
|
|
38537
|
-
|
|
38538
|
-
|
|
38539
|
-
|
|
38540
|
-
|
|
38707
|
+
const next = elapsedRef.current + delta;
|
|
38708
|
+
if (mode === "countdown" && next >= durationMs) {
|
|
38709
|
+
elapsedRef.current = durationMs;
|
|
38710
|
+
setElapsedMs(durationMs);
|
|
38711
|
+
setRunning(false);
|
|
38712
|
+
setCompleted(true);
|
|
38713
|
+
onComplete?.();
|
|
38714
|
+
return;
|
|
38715
|
+
}
|
|
38716
|
+
if (mode === "countdown") {
|
|
38717
|
+
const secLeft = Math.ceil((durationMs - next) / 1e3);
|
|
38718
|
+
if (secLeft <= warningAt && !warningFiredRef.current) {
|
|
38719
|
+
warningFiredRef.current = true;
|
|
38720
|
+
onWarning?.();
|
|
38541
38721
|
}
|
|
38542
|
-
if (
|
|
38543
|
-
|
|
38544
|
-
|
|
38545
|
-
warningFiredRef.current = true;
|
|
38546
|
-
onWarning?.();
|
|
38547
|
-
}
|
|
38548
|
-
if (secLeft <= dangerAt && !dangerFiredRef.current) {
|
|
38549
|
-
dangerFiredRef.current = true;
|
|
38550
|
-
onDanger?.();
|
|
38551
|
-
}
|
|
38722
|
+
if (secLeft <= dangerAt && !dangerFiredRef.current) {
|
|
38723
|
+
dangerFiredRef.current = true;
|
|
38724
|
+
onDanger?.();
|
|
38552
38725
|
}
|
|
38553
|
-
|
|
38554
|
-
|
|
38726
|
+
}
|
|
38727
|
+
elapsedRef.current = next;
|
|
38728
|
+
setElapsedMs(next);
|
|
38555
38729
|
rafRef.current = requestAnimationFrame(tick);
|
|
38556
38730
|
}, [mode, durationMs, warningAt, dangerAt, onComplete, onWarning, onDanger]);
|
|
38557
38731
|
useEffect59(() => {
|
|
@@ -38572,6 +38746,7 @@ function GameTimer({
|
|
|
38572
38746
|
}, []);
|
|
38573
38747
|
const reset = useCallback48(() => {
|
|
38574
38748
|
setRunning(false);
|
|
38749
|
+
elapsedRef.current = 0;
|
|
38575
38750
|
setElapsedMs(0);
|
|
38576
38751
|
setCompleted(false);
|
|
38577
38752
|
setLaps([]);
|
|
@@ -39880,8 +40055,10 @@ function TeamGenerator({
|
|
|
39880
40055
|
minPlayersPerTeam = 2,
|
|
39881
40056
|
maxPlayers = Number.POSITIVE_INFINITY,
|
|
39882
40057
|
defaultNames = [],
|
|
40058
|
+
defaultTeams,
|
|
39883
40059
|
onNamesChange,
|
|
39884
40060
|
onGenerate,
|
|
40061
|
+
onTeamsChange,
|
|
39885
40062
|
onTeamCountChange,
|
|
39886
40063
|
labels,
|
|
39887
40064
|
ariaLabel = "Team generator",
|
|
@@ -39896,12 +40073,17 @@ function TeamGenerator({
|
|
|
39896
40073
|
const [names, setNames] = useState76(defaultNames);
|
|
39897
40074
|
const [input, setInput] = useState76("");
|
|
39898
40075
|
const [teamCount, setTeamCount] = useState76(initialCount);
|
|
39899
|
-
const [teams, setTeams] = useState76(
|
|
40076
|
+
const [teams, setTeams] = useState76(
|
|
40077
|
+
defaultTeams && defaultTeams.length > 0 ? defaultTeams : null
|
|
40078
|
+
);
|
|
39900
40079
|
const inputRef = useRef49(null);
|
|
39901
40080
|
const minNames = teamCount * minPlayersPerTeam;
|
|
39902
40081
|
useEffect61(() => {
|
|
39903
40082
|
onNamesChange?.(names);
|
|
39904
40083
|
}, [names, onNamesChange]);
|
|
40084
|
+
useEffect61(() => {
|
|
40085
|
+
onTeamsChange?.(teams);
|
|
40086
|
+
}, [teams, onTeamsChange]);
|
|
39905
40087
|
const addName = useCallback50(() => {
|
|
39906
40088
|
const trimmed = input.trim();
|
|
39907
40089
|
if (!trimmed) return;
|
|
@@ -40528,6 +40710,8 @@ function RoundRobinScheduler({
|
|
|
40528
40710
|
theme: theme5 = "slate",
|
|
40529
40711
|
darkMode,
|
|
40530
40712
|
minTeams = 3,
|
|
40713
|
+
maxTeams,
|
|
40714
|
+
nameMaxLength,
|
|
40531
40715
|
defaultTeams = [],
|
|
40532
40716
|
resumeRounds,
|
|
40533
40717
|
onTeamsChange,
|
|
@@ -40550,39 +40734,41 @@ function RoundRobinScheduler({
|
|
|
40550
40734
|
useEffect62(() => {
|
|
40551
40735
|
onTeamsChange?.(teams);
|
|
40552
40736
|
}, [teams, onTeamsChange]);
|
|
40553
|
-
const
|
|
40554
|
-
|
|
40555
|
-
if (names.length === 0) return;
|
|
40556
|
-
setTeams((prev) => {
|
|
40737
|
+
const mergeNames = useCallback51(
|
|
40738
|
+
(prev, incoming) => {
|
|
40557
40739
|
const existing = new Set(prev.map((t) => t.toLowerCase()));
|
|
40558
40740
|
const next = [...prev];
|
|
40559
|
-
for (const
|
|
40560
|
-
if (
|
|
40561
|
-
|
|
40562
|
-
next.push(name);
|
|
40563
|
-
}
|
|
40564
|
-
}
|
|
40565
|
-
return next;
|
|
40566
|
-
});
|
|
40567
|
-
setInput("");
|
|
40568
|
-
setRounds([]);
|
|
40569
|
-
}, []);
|
|
40570
|
-
const importTeams = useCallback51((incoming) => {
|
|
40571
|
-
if (incoming.length === 0) return;
|
|
40572
|
-
setTeams((prev) => {
|
|
40573
|
-
const existing = new Set(prev.map((t) => t.toLowerCase()));
|
|
40574
|
-
const next = [...prev];
|
|
40575
|
-
for (const name of incoming) {
|
|
40741
|
+
for (const rawName of incoming) {
|
|
40742
|
+
if (maxTeams != null && next.length >= maxTeams) break;
|
|
40743
|
+
const name = nameMaxLength != null ? rawName.slice(0, nameMaxLength) : rawName;
|
|
40576
40744
|
const key = name.toLowerCase();
|
|
40577
|
-
if (!existing.has(key)) {
|
|
40745
|
+
if (name.length > 0 && !existing.has(key)) {
|
|
40578
40746
|
existing.add(key);
|
|
40579
40747
|
next.push(name);
|
|
40580
40748
|
}
|
|
40581
40749
|
}
|
|
40582
40750
|
return next;
|
|
40583
|
-
}
|
|
40584
|
-
|
|
40585
|
-
|
|
40751
|
+
},
|
|
40752
|
+
[maxTeams, nameMaxLength]
|
|
40753
|
+
);
|
|
40754
|
+
const addTeams = useCallback51(
|
|
40755
|
+
(raw) => {
|
|
40756
|
+
const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
40757
|
+
if (names.length === 0) return;
|
|
40758
|
+
setTeams((prev) => mergeNames(prev, names));
|
|
40759
|
+
setInput("");
|
|
40760
|
+
setRounds([]);
|
|
40761
|
+
},
|
|
40762
|
+
[mergeNames]
|
|
40763
|
+
);
|
|
40764
|
+
const importTeams = useCallback51(
|
|
40765
|
+
(incoming) => {
|
|
40766
|
+
if (incoming.length === 0) return;
|
|
40767
|
+
setTeams((prev) => mergeNames(prev, incoming));
|
|
40768
|
+
setRounds([]);
|
|
40769
|
+
},
|
|
40770
|
+
[mergeNames]
|
|
40771
|
+
);
|
|
40586
40772
|
const handleKeyDown = useCallback51(
|
|
40587
40773
|
(e) => {
|
|
40588
40774
|
if (e.key === "Enter") {
|
|
@@ -41076,6 +41262,8 @@ function Scoreboard({
|
|
|
41076
41262
|
onTeamsChange,
|
|
41077
41263
|
onRoundAdvance,
|
|
41078
41264
|
onNewGame,
|
|
41265
|
+
onReset,
|
|
41266
|
+
nameMaxLength,
|
|
41079
41267
|
labels,
|
|
41080
41268
|
ariaLabel = "Scoreboard",
|
|
41081
41269
|
className
|
|
@@ -41150,7 +41338,8 @@ function Scoreboard({
|
|
|
41150
41338
|
setRound(1);
|
|
41151
41339
|
setHistory([]);
|
|
41152
41340
|
setHistoryOpen(false);
|
|
41153
|
-
|
|
41341
|
+
onReset?.();
|
|
41342
|
+
}, [onReset]);
|
|
41154
41343
|
const newGame = useCallback52(() => {
|
|
41155
41344
|
setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
|
|
41156
41345
|
setRound(1);
|
|
@@ -41372,6 +41561,7 @@ function Scoreboard({
|
|
|
41372
41561
|
{
|
|
41373
41562
|
type: "text",
|
|
41374
41563
|
value: team.name,
|
|
41564
|
+
maxLength: nameMaxLength,
|
|
41375
41565
|
onChange: (e) => updateName(index, e.target.value),
|
|
41376
41566
|
style: nameInputStyle,
|
|
41377
41567
|
"aria-label": `Team ${index + 1} name`
|