@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/web/index.js CHANGED
@@ -30225,7 +30225,7 @@ function TiltCard({
30225
30225
  }
30226
30226
 
30227
30227
  // src/components/ui/dice-roller/index.tsx
30228
- var import_react70 = require("react");
30228
+ var import_react69 = require("react");
30229
30229
 
30230
30230
  // src/hooks/web/use-dark-mode.ts
30231
30231
  var import_react68 = require("react");
@@ -30274,21 +30274,6 @@ function useDarkMode(forced) {
30274
30274
  return dark;
30275
30275
  }
30276
30276
 
30277
- // src/hooks/web/use-prefers-reduced-motion.ts
30278
- var import_react69 = require("react");
30279
- function usePrefersReducedMotion() {
30280
- const [reduced, setReduced] = (0, import_react69.useState)(false);
30281
- (0, import_react69.useEffect)(() => {
30282
- if (typeof window === "undefined" || !window.matchMedia) return;
30283
- const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
30284
- setReduced(mq.matches);
30285
- const read = () => setReduced(mq.matches);
30286
- mq.addEventListener("change", read);
30287
- return () => mq.removeEventListener("change", read);
30288
- }, []);
30289
- return reduced;
30290
- }
30291
-
30292
30277
  // src/components/ui/dice-roller/index.tsx
30293
30278
  var import_jsx_runtime113 = require("react/jsx-runtime");
30294
30279
  var PRESETS = {
@@ -30507,8 +30492,8 @@ function simulateDice(arenaW, arenaH, diceSize, starts, numDice) {
30507
30492
  return frames;
30508
30493
  }
30509
30494
  function useArenaBounds(ref) {
30510
- const [bounds, setBounds] = (0, import_react70.useState)({ w: 400, h: 260 });
30511
- (0, import_react70.useEffect)(() => {
30495
+ const [bounds, setBounds] = (0, import_react69.useState)({ w: 400, h: 260 });
30496
+ (0, import_react69.useEffect)(() => {
30512
30497
  if (!ref.current) return;
30513
30498
  const el = ref.current;
30514
30499
  const update = () => setBounds({ w: el.offsetWidth, h: el.offsetHeight });
@@ -30563,19 +30548,18 @@ function DiceRoller({
30563
30548
  }) {
30564
30549
  const clampedCount = Math.max(1, Math.min(MAX_COUNT, count2));
30565
30550
  const dark = useDarkMode(darkMode);
30566
- const reducedMotion = usePrefersReducedMotion();
30567
- const [rolling, setRolling] = (0, import_react70.useState)(false);
30568
- const [results, setResults] = (0, import_react70.useState)(null);
30569
- const [history, setHistory] = (0, import_react70.useState)([]);
30570
- const dieWrapperRefs = (0, import_react70.useRef)([]);
30571
- const cubeRefs = (0, import_react70.useRef)([]);
30572
- const shadowRefs = (0, import_react70.useRef)([]);
30573
- const numeralRefs = (0, import_react70.useRef)([]);
30574
- const currentPositionsRef = (0, import_react70.useRef)(null);
30575
- const rollingRef = (0, import_react70.useRef)(false);
30576
- const dieSetters = (0, import_react70.useRef)([]);
30577
- const cubeSetters = (0, import_react70.useRef)([]);
30578
- const shadowSetters = (0, import_react70.useRef)([]);
30551
+ const [rolling, setRolling] = (0, import_react69.useState)(false);
30552
+ const [results, setResults] = (0, import_react69.useState)(null);
30553
+ const [history, setHistory] = (0, import_react69.useState)([]);
30554
+ const dieWrapperRefs = (0, import_react69.useRef)([]);
30555
+ const cubeRefs = (0, import_react69.useRef)([]);
30556
+ const shadowRefs = (0, import_react69.useRef)([]);
30557
+ const numeralRefs = (0, import_react69.useRef)([]);
30558
+ const currentPositionsRef = (0, import_react69.useRef)(null);
30559
+ const rollingRef = (0, import_react69.useRef)(false);
30560
+ const dieSetters = (0, import_react69.useRef)([]);
30561
+ const cubeSetters = (0, import_react69.useRef)([]);
30562
+ const shadowSetters = (0, import_react69.useRef)([]);
30579
30563
  const getDieSetter = (i) => {
30580
30564
  if (!dieSetters.current[i]) {
30581
30565
  dieSetters.current[i] = (el) => {
@@ -30600,7 +30584,7 @@ function DiceRoller({
30600
30584
  }
30601
30585
  return shadowSetters.current[i];
30602
30586
  };
30603
- const numeralSetters = (0, import_react70.useRef)([]);
30587
+ const numeralSetters = (0, import_react69.useRef)([]);
30604
30588
  const getNumeralSetter = (i) => {
30605
30589
  if (!numeralSetters.current[i]) {
30606
30590
  numeralSetters.current[i] = (el) => {
@@ -30609,11 +30593,11 @@ function DiceRoller({
30609
30593
  }
30610
30594
  return numeralSetters.current[i];
30611
30595
  };
30612
- const timersRef = (0, import_react70.useRef)([]);
30613
- const rafHandleRef = (0, import_react70.useRef)(null);
30614
- const arenaRef = (0, import_react70.useRef)(null);
30596
+ const timersRef = (0, import_react69.useRef)([]);
30597
+ const rafHandleRef = (0, import_react69.useRef)(null);
30598
+ const arenaRef = (0, import_react69.useRef)(null);
30615
30599
  const arena = useArenaBounds(arenaRef);
30616
- const colors = (0, import_react70.useMemo)(() => {
30600
+ const colors = (0, import_react69.useMemo)(() => {
30617
30601
  if (typeof theme5 === "object") {
30618
30602
  const pipL = theme5.pip;
30619
30603
  const pipD = theme5.pipDark ?? theme5.pip;
@@ -30650,7 +30634,7 @@ function DiceRoller({
30650
30634
  chipDim: dark ? "#3a4358" : "#94a3b8"
30651
30635
  };
30652
30636
  }, [theme5, dark]);
30653
- const effectiveSize = (0, import_react70.useMemo)(() => {
30637
+ const effectiveSize = (0, import_react69.useMemo)(() => {
30654
30638
  if (!responsive2) return size;
30655
30639
  const needed = clampedCount * size * 1.6;
30656
30640
  if (arena.w >= needed || arena.w === 0) return size;
@@ -30661,7 +30645,7 @@ function DiceRoller({
30661
30645
  const pipDiameter = Math.round(effectiveSize * 0.2);
30662
30646
  const faceRadius = Math.round(effectiveSize * 0.07);
30663
30647
  const arenaHeight = arenaHeightProp ?? Math.max(effectiveSize * 3, 200);
30664
- const faceTransformFor = (0, import_react70.useCallback)(
30648
+ const faceTransformFor = (0, import_react69.useCallback)(
30665
30649
  (value) => {
30666
30650
  const p = D6_FACE_PLACEMENTS[value];
30667
30651
  const parts = [];
@@ -30672,14 +30656,14 @@ function DiceRoller({
30672
30656
  },
30673
30657
  [half]
30674
30658
  );
30675
- const defaultPositions = (0, import_react70.useCallback)(
30659
+ const defaultPositions = (0, import_react69.useCallback)(
30676
30660
  (w, h, n) => Array.from({ length: n }, (_, i) => ({
30677
30661
  x: (i + 1) * w / (n + 1),
30678
30662
  y: h / 2
30679
30663
  })),
30680
30664
  []
30681
30665
  );
30682
- const writeDiePosition = (0, import_react70.useCallback)(
30666
+ const writeDiePosition = (0, import_react69.useCallback)(
30683
30667
  (i, x, y, sx = 1, sy = 1) => {
30684
30668
  const el = dieWrapperRefs.current[i];
30685
30669
  if (!el) return;
@@ -30690,7 +30674,7 @@ function DiceRoller({
30690
30674
  },
30691
30675
  [half]
30692
30676
  );
30693
- const writeCubeRotation = (0, import_react70.useCallback)(
30677
+ const writeCubeRotation = (0, import_react69.useCallback)(
30694
30678
  (i, rx, ry, rz, withTransition, tiltZ = 0) => {
30695
30679
  const el = cubeRefs.current[i];
30696
30680
  if (!el) return;
@@ -30699,7 +30683,15 @@ function DiceRoller({
30699
30683
  },
30700
30684
  []
30701
30685
  );
30702
- (0, import_react70.useEffect)(() => {
30686
+ (0, import_react69.useEffect)(() => {
30687
+ if (timersRef.current.length > 0) {
30688
+ timersRef.current.forEach((t) => clearTimeout(t));
30689
+ timersRef.current = [];
30690
+ }
30691
+ if (rollingRef.current) {
30692
+ rollingRef.current = false;
30693
+ setRolling(false);
30694
+ }
30703
30695
  const rawW = arenaRef.current?.offsetWidth ?? 0;
30704
30696
  const rawH = arenaRef.current?.offsetHeight ?? 0;
30705
30697
  const w = rawW > 20 ? rawW : arena.w > 20 ? arena.w : 400;
@@ -30732,10 +30724,10 @@ function DiceRoller({
30732
30724
  defaultPositions,
30733
30725
  half
30734
30726
  ]);
30735
- (0, import_react70.useEffect)(() => {
30727
+ (0, import_react69.useEffect)(() => {
30736
30728
  setHistory([]);
30737
30729
  }, [sides]);
30738
- const roll = (0, import_react70.useCallback)(() => {
30730
+ const roll = (0, import_react69.useCallback)(() => {
30739
30731
  if (rollingRef.current) return;
30740
30732
  rollingRef.current = true;
30741
30733
  setRolling(true);
@@ -30856,25 +30848,24 @@ function DiceRoller({
30856
30848
  defaultPositions,
30857
30849
  effectiveSize,
30858
30850
  half,
30859
- reducedMotion,
30860
30851
  onRoll,
30861
30852
  writeCubeRotation,
30862
30853
  writeDiePosition,
30863
30854
  showHistory,
30864
30855
  historyMax
30865
30856
  ]);
30866
- (0, import_react70.useEffect)(() => {
30857
+ (0, import_react69.useEffect)(() => {
30867
30858
  if (rollRef) rollRef.current = roll;
30868
30859
  return () => {
30869
30860
  if (rollRef && rollRef.current === roll) rollRef.current = null;
30870
30861
  };
30871
30862
  }, [roll, rollRef]);
30872
- (0, import_react70.useEffect)(() => {
30863
+ (0, import_react69.useEffect)(() => {
30873
30864
  if (!autoRoll) return;
30874
30865
  const t = setTimeout(roll, 300);
30875
30866
  return () => clearTimeout(t);
30876
30867
  }, []);
30877
- (0, import_react70.useEffect)(
30868
+ (0, import_react69.useEffect)(
30878
30869
  () => () => {
30879
30870
  timersRef.current.forEach((t) => clearTimeout(t));
30880
30871
  timersRef.current = [];
@@ -31174,7 +31165,7 @@ function DiceRoller({
31174
31165
  DiceRoller.displayName = "DiceRoller";
31175
31166
 
31176
31167
  // src/components/ui/bracket-generator/index.tsx
31177
- var import_react71 = require("react");
31168
+ var import_react70 = require("react");
31178
31169
 
31179
31170
  // src/utils/array.ts
31180
31171
  function shuffle(arr) {
@@ -31394,15 +31385,15 @@ function computeLayout(rounds, firstRoundCount) {
31394
31385
  };
31395
31386
  }
31396
31387
  function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31397
- const [transform, setTransform] = (0, import_react71.useState)({
31388
+ const [transform, setTransform] = (0, import_react70.useState)({
31398
31389
  x: 0,
31399
31390
  y: 0,
31400
31391
  scale: 1
31401
31392
  });
31402
- const isDragging = (0, import_react71.useRef)(false);
31403
- const dragStart = (0, import_react71.useRef)({ x: 0, y: 0 });
31404
- const pinchRef = (0, import_react71.useRef)(null);
31405
- const clampTransform = (0, import_react71.useCallback)(
31393
+ const isDragging = (0, import_react70.useRef)(false);
31394
+ const dragStart = (0, import_react70.useRef)({ x: 0, y: 0 });
31395
+ const pinchRef = (0, import_react70.useRef)(null);
31396
+ const clampTransform = (0, import_react70.useCallback)(
31406
31397
  (t, vw, vh) => {
31407
31398
  const sw = contentSize.w * t.scale;
31408
31399
  const sh = contentSize.h * t.scale;
@@ -31414,7 +31405,7 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31414
31405
  },
31415
31406
  [contentSize]
31416
31407
  );
31417
- (0, import_react71.useEffect)(() => {
31408
+ (0, import_react70.useEffect)(() => {
31418
31409
  const el = viewportRef.current;
31419
31410
  if (!el) return;
31420
31411
  const onWheel = (e) => {
@@ -31439,7 +31430,7 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31439
31430
  el.addEventListener("wheel", onWheel, { passive: false });
31440
31431
  return () => el.removeEventListener("wheel", onWheel);
31441
31432
  }, [viewportRef, minZoom, maxZoom, clampTransform]);
31442
- const onPointerDown = (0, import_react71.useCallback)(
31433
+ const onPointerDown = (0, import_react70.useCallback)(
31443
31434
  (e) => {
31444
31435
  if (e.target.closest("[data-no-pan]")) return;
31445
31436
  isDragging.current = true;
@@ -31452,7 +31443,7 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31452
31443
  },
31453
31444
  [transform]
31454
31445
  );
31455
- const onPointerMove = (0, import_react71.useCallback)(
31446
+ const onPointerMove = (0, import_react70.useCallback)(
31456
31447
  (e) => {
31457
31448
  if (!isDragging.current) return;
31458
31449
  const el = viewportRef.current;
@@ -31472,11 +31463,11 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31472
31463
  },
31473
31464
  [viewportRef, clampTransform]
31474
31465
  );
31475
- const onPointerUp = (0, import_react71.useCallback)((e) => {
31466
+ const onPointerUp = (0, import_react70.useCallback)((e) => {
31476
31467
  isDragging.current = false;
31477
31468
  e.currentTarget.style.cursor = "grab";
31478
31469
  }, []);
31479
- const onTouchStart = (0, import_react71.useCallback)(
31470
+ const onTouchStart = (0, import_react70.useCallback)(
31480
31471
  (e) => {
31481
31472
  if (e.touches.length === 2) {
31482
31473
  const [t1, t2] = [e.touches[0], e.touches[1]];
@@ -31496,7 +31487,7 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31496
31487
  },
31497
31488
  [transform]
31498
31489
  );
31499
- const onTouchMove = (0, import_react71.useCallback)(
31490
+ const onTouchMove = (0, import_react70.useCallback)(
31500
31491
  (e) => {
31501
31492
  if (e.touches.length === 2 && pinchRef.current) {
31502
31493
  e.preventDefault();
@@ -31533,10 +31524,10 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31533
31524
  },
31534
31525
  [viewportRef, minZoom, maxZoom, clampTransform]
31535
31526
  );
31536
- const onTouchEnd = (0, import_react71.useCallback)(() => {
31527
+ const onTouchEnd = (0, import_react70.useCallback)(() => {
31537
31528
  pinchRef.current = null;
31538
31529
  }, []);
31539
- const zoomBy = (0, import_react71.useCallback)(
31530
+ const zoomBy = (0, import_react70.useCallback)(
31540
31531
  (factor) => {
31541
31532
  const el = viewportRef.current;
31542
31533
  if (!el) return;
@@ -31555,7 +31546,7 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31555
31546
  },
31556
31547
  [viewportRef, minZoom, maxZoom, clampTransform]
31557
31548
  );
31558
- const fit = (0, import_react71.useCallback)(() => {
31549
+ const fit = (0, import_react70.useCallback)(() => {
31559
31550
  const el = viewportRef.current;
31560
31551
  if (!el || !contentSize.w) return;
31561
31552
  const rect = el.getBoundingClientRect();
@@ -31569,10 +31560,10 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31569
31560
  scale
31570
31561
  });
31571
31562
  }, [viewportRef, contentSize, minZoom, maxZoom]);
31572
- const reset = (0, import_react71.useCallback)(() => {
31563
+ const reset = (0, import_react70.useCallback)(() => {
31573
31564
  setTransform({ x: 40, y: 40, scale: 1 });
31574
31565
  }, []);
31575
- const panTo = (0, import_react71.useCallback)(
31566
+ const panTo = (0, import_react70.useCallback)(
31576
31567
  (contentX, contentY) => {
31577
31568
  const el = viewportRef.current;
31578
31569
  if (!el) return;
@@ -31645,15 +31636,15 @@ function BracketGenerator({
31645
31636
  ariaLabel = "Tournament bracket"
31646
31637
  }) {
31647
31638
  const dark = useDarkMode(darkMode);
31648
- const [bracketState, setBracketState] = (0, import_react71.useState)({
31639
+ const [bracketState, setBracketState] = (0, import_react70.useState)({
31649
31640
  matches: [],
31650
31641
  rounds: 0,
31651
31642
  champion: null
31652
31643
  });
31653
- const [simulating, setSimulating] = (0, import_react71.useState)(false);
31654
- const autoRef = (0, import_react71.useRef)(null);
31655
- const viewportRef = (0, import_react71.useRef)(null);
31656
- const effectiveParticipants = (0, import_react71.useMemo)(() => {
31644
+ const [simulating, setSimulating] = (0, import_react70.useState)(false);
31645
+ const autoRef = (0, import_react70.useRef)(null);
31646
+ const viewportRef = (0, import_react70.useRef)(null);
31647
+ const effectiveParticipants = (0, import_react70.useMemo)(() => {
31657
31648
  if (participantsProp?.length) {
31658
31649
  return padToSize(
31659
31650
  participantsProp.map((p, i) => ({
@@ -31672,7 +31663,7 @@ function BracketGenerator({
31672
31663
  }));
31673
31664
  }, [participantsProp, size]);
31674
31665
  const preset = typeof theme5 === "string" ? PRESETS2[theme5] || PRESETS2.blue : null;
31675
- const themeColors = (0, import_react71.useMemo)(() => {
31666
+ const themeColors = (0, import_react70.useMemo)(() => {
31676
31667
  if (typeof theme5 === "object") return theme5;
31677
31668
  const p = preset;
31678
31669
  return {
@@ -31681,9 +31672,9 @@ function BracketGenerator({
31681
31672
  color: p.color
31682
31673
  };
31683
31674
  }, [theme5, dark, preset]);
31684
- const colors = (0, import_react71.useMemo)(() => buildColorTokens(dark), [dark]);
31675
+ const colors = (0, import_react70.useMemo)(() => buildColorTokens(dark), [dark]);
31685
31676
  const roundLabels = ROUND_NAMES[effectiveParticipants.length] || [];
31686
- const layout = (0, import_react71.useMemo)(() => {
31677
+ const layout = (0, import_react70.useMemo)(() => {
31687
31678
  if (!bracketState.rounds)
31688
31679
  return {
31689
31680
  positions: {},
@@ -31692,7 +31683,7 @@ function BracketGenerator({
31692
31683
  };
31693
31684
  return computeLayout(bracketState.rounds, effectiveParticipants.length / 2);
31694
31685
  }, [bracketState.rounds, effectiveParticipants.length]);
31695
- const contentSize = (0, import_react71.useMemo)(
31686
+ const contentSize = (0, import_react70.useMemo)(
31696
31687
  () => ({
31697
31688
  w: layout.totalW + CANVAS_PAD * 2,
31698
31689
  h: layout.totalH + CANVAS_PAD * 2
@@ -31705,10 +31696,10 @@ function BracketGenerator({
31705
31696
  minZoom,
31706
31697
  maxZoom
31707
31698
  );
31708
- const [miniMapHidden, setMiniMapHidden] = (0, import_react71.useState)(false);
31709
- const miniMapDragRef = (0, import_react71.useRef)(false);
31710
- const resumeRef = (0, import_react71.useRef)(resumeMatches);
31711
- const hydrateFromResume = (0, import_react71.useCallback)(() => {
31699
+ const [miniMapHidden, setMiniMapHidden] = (0, import_react70.useState)(false);
31700
+ const miniMapDragRef = (0, import_react70.useRef)(false);
31701
+ const resumeRef = (0, import_react70.useRef)(resumeMatches);
31702
+ const hydrateFromResume = (0, import_react70.useCallback)(() => {
31712
31703
  const resume = resumeRef.current;
31713
31704
  if (!resume?.length) return null;
31714
31705
  const n = effectiveParticipants.length;
@@ -31748,7 +31739,7 @@ function BracketGenerator({
31748
31739
  const finalMatch = matches.find((m) => m.round === rounds - 1);
31749
31740
  return { matches, rounds, champion: finalMatch?.winner ?? null };
31750
31741
  }, [effectiveParticipants]);
31751
- const seedBracket = (0, import_react71.useCallback)(() => {
31742
+ const seedBracket = (0, import_react70.useCallback)(() => {
31752
31743
  const { matches, rounds } = buildBracket(
31753
31744
  effectiveParticipants,
31754
31745
  seedingStrategy
@@ -31771,7 +31762,7 @@ function BracketGenerator({
31771
31762
  };
31772
31763
  setBracketState(newState);
31773
31764
  }, [effectiveParticipants, seedingStrategy]);
31774
- const simulateNext = (0, import_react71.useCallback)(() => {
31765
+ const simulateNext = (0, import_react70.useCallback)(() => {
31775
31766
  setBracketState((prev) => {
31776
31767
  const next = prev.matches.find((m) => m.a && m.b && !m.winner);
31777
31768
  if (!next) return prev;
@@ -31786,7 +31777,7 @@ function BracketGenerator({
31786
31777
  return { ...prev, matches, champion };
31787
31778
  });
31788
31779
  }, []);
31789
- const autoPlay = (0, import_react71.useCallback)(() => {
31780
+ const autoPlay = (0, import_react70.useCallback)(() => {
31790
31781
  if (simulating) {
31791
31782
  if (autoRef.current) clearTimeout(autoRef.current);
31792
31783
  setSimulating(false);
@@ -31815,7 +31806,7 @@ function BracketGenerator({
31815
31806
  };
31816
31807
  tick();
31817
31808
  }, [simulating, simulateDelayMs]);
31818
- const advanceWinner = (0, import_react71.useCallback)(
31809
+ const advanceWinner = (0, import_react70.useCallback)(
31819
31810
  (matchId, winnerSlot) => {
31820
31811
  setBracketState((prev) => {
31821
31812
  const m = prev.matches.find((x) => x.id === matchId);
@@ -31838,8 +31829,8 @@ function BracketGenerator({
31838
31829
  },
31839
31830
  []
31840
31831
  );
31841
- const prevBracketStateRef = (0, import_react71.useRef)(bracketState);
31842
- (0, import_react71.useEffect)(() => {
31832
+ const prevBracketStateRef = (0, import_react70.useRef)(bracketState);
31833
+ (0, import_react70.useEffect)(() => {
31843
31834
  const prev = prevBracketStateRef.current;
31844
31835
  const next = bracketState;
31845
31836
  if (prev === next) return;
@@ -31858,12 +31849,12 @@ function BracketGenerator({
31858
31849
  onStateChange?.(next);
31859
31850
  prevBracketStateRef.current = next;
31860
31851
  }, [bracketState, onMatchComplete, onChampion, onStateChange]);
31861
- const structuralKey = (0, import_react71.useMemo)(
31852
+ const structuralKey = (0, import_react70.useMemo)(
31862
31853
  () => `${seedingStrategy}|${effectiveParticipants.map((p) => `${p.id}:${p.seed ?? ""}`).join(",")}`,
31863
31854
  [effectiveParticipants, seedingStrategy]
31864
31855
  );
31865
- const seededKeyRef = (0, import_react71.useRef)(null);
31866
- (0, import_react71.useEffect)(() => {
31856
+ const seededKeyRef = (0, import_react70.useRef)(null);
31857
+ (0, import_react70.useEffect)(() => {
31867
31858
  if (seededKeyRef.current === structuralKey) {
31868
31859
  setBracketState((prev) => {
31869
31860
  if (!prev.matches.length) return prev;
@@ -31894,24 +31885,24 @@ function BracketGenerator({
31894
31885
  seedBracket();
31895
31886
  }
31896
31887
  }, [structuralKey, effectiveParticipants, hydrateFromResume, seedBracket]);
31897
- (0, import_react71.useEffect)(() => {
31888
+ (0, import_react70.useEffect)(() => {
31898
31889
  return () => {
31899
31890
  if (autoRef.current) clearTimeout(autoRef.current);
31900
31891
  };
31901
31892
  }, []);
31902
- (0, import_react71.useLayoutEffect)(() => {
31893
+ (0, import_react70.useLayoutEffect)(() => {
31903
31894
  if (contentSize.w > 0) {
31904
31895
  const timer = setTimeout(fit, 50);
31905
31896
  return () => clearTimeout(timer);
31906
31897
  }
31907
31898
  }, [contentSize.w]);
31908
- (0, import_react71.useEffect)(() => {
31899
+ (0, import_react70.useEffect)(() => {
31909
31900
  if (autoSimulate && bracketState.matches.length && !bracketState.champion) {
31910
31901
  const timer = setTimeout(autoPlay, 500);
31911
31902
  return () => clearTimeout(timer);
31912
31903
  }
31913
31904
  }, [autoSimulate]);
31914
- const stats = (0, import_react71.useMemo)(() => {
31905
+ const stats = (0, import_react70.useMemo)(() => {
31915
31906
  const done = bracketState.matches.filter((m) => m.winner).length;
31916
31907
  const total = bracketState.matches.length;
31917
31908
  const remaining = bracketState.matches.filter(
@@ -31919,7 +31910,7 @@ function BracketGenerator({
31919
31910
  ).length;
31920
31911
  return { done, total, remaining };
31921
31912
  }, [bracketState]);
31922
- const connectorPaths = (0, import_react71.useMemo)(() => {
31913
+ const connectorPaths = (0, import_react70.useMemo)(() => {
31923
31914
  const paths = [];
31924
31915
  bracketState.matches.forEach((m) => {
31925
31916
  if (m.round >= bracketState.rounds - 1) return;
@@ -32624,7 +32615,7 @@ function BracketGenerator({
32624
32615
  }
32625
32616
 
32626
32617
  // src/components/ui/coin-flipper/index.tsx
32627
- var import_react72 = require("react");
32618
+ var import_react71 = require("react");
32628
32619
  var import_jsx_runtime115 = require("react/jsx-runtime");
32629
32620
  var PRESETS3 = {
32630
32621
  gold: {
@@ -32808,15 +32799,15 @@ function CoinFlipper({
32808
32799
  }) {
32809
32800
  const dark = useDarkMode(darkMode);
32810
32801
  const tc = typeof theme5 === "string" ? PRESETS3[theme5] || PRESETS3.gold : theme5;
32811
- const [result, setResult] = (0, import_react72.useState)(null);
32812
- const [flipping, setFlipping] = (0, import_react72.useState)(false);
32813
- const [history, setHistory] = (0, import_react72.useState)([]);
32814
- const [stats, setStats] = (0, import_react72.useState)({ heads: 0, tails: 0 });
32815
- const flipIdRef = (0, import_react72.useRef)(0);
32816
- const coinRef = (0, import_react72.useRef)(null);
32817
- const shadowRef = (0, import_react72.useRef)(null);
32818
- const timersRef = (0, import_react72.useRef)([]);
32819
- const colors = (0, import_react72.useMemo)(
32802
+ const [result, setResult] = (0, import_react71.useState)(null);
32803
+ const [flipping, setFlipping] = (0, import_react71.useState)(false);
32804
+ const [history, setHistory] = (0, import_react71.useState)([]);
32805
+ const [stats, setStats] = (0, import_react71.useState)({ heads: 0, tails: 0 });
32806
+ const flipIdRef = (0, import_react71.useRef)(0);
32807
+ const coinRef = (0, import_react71.useRef)(null);
32808
+ const shadowRef = (0, import_react71.useRef)(null);
32809
+ const timersRef = (0, import_react71.useRef)([]);
32810
+ const colors = (0, import_react71.useMemo)(
32820
32811
  () => ({
32821
32812
  bg: dark ? "#0f1117" : "#f8fafc",
32822
32813
  surface: dark ? "#1a1d27" : "#ffffff",
@@ -32827,7 +32818,7 @@ function CoinFlipper({
32827
32818
  }),
32828
32819
  [dark]
32829
32820
  );
32830
- const flip = (0, import_react72.useCallback)(() => {
32821
+ const flip = (0, import_react71.useCallback)(() => {
32831
32822
  if (flipping) return;
32832
32823
  setFlipping(true);
32833
32824
  const outcome = Math.random() < 0.5 ? "heads" : "tails";
@@ -32867,16 +32858,19 @@ function CoinFlipper({
32867
32858
  }, frames.length * stepMs);
32868
32859
  timersRef.current.push(settleTimer);
32869
32860
  }, [flipping, onFlip, historyMax]);
32870
- (0, import_react72.useEffect)(() => {
32861
+ (0, import_react71.useEffect)(() => {
32871
32862
  if (flipRef) flipRef.current = flip;
32863
+ return () => {
32864
+ if (flipRef && flipRef.current === flip) flipRef.current = null;
32865
+ };
32872
32866
  }, [flip, flipRef]);
32873
- (0, import_react72.useEffect)(() => {
32867
+ (0, import_react71.useEffect)(() => {
32874
32868
  if (autoFlip) {
32875
32869
  const timer = setTimeout(flip, 300);
32876
32870
  return () => clearTimeout(timer);
32877
32871
  }
32878
32872
  }, []);
32879
- (0, import_react72.useEffect)(() => {
32873
+ (0, import_react71.useEffect)(() => {
32880
32874
  return () => {
32881
32875
  timersRef.current.forEach(clearTimeout);
32882
32876
  };
@@ -33313,6 +33307,23 @@ function CoinFlipper({
33313
33307
 
33314
33308
  // src/components/ui/random-player-picker/index.tsx
33315
33309
  var import_react73 = require("react");
33310
+
33311
+ // src/hooks/web/use-prefers-reduced-motion.ts
33312
+ var import_react72 = require("react");
33313
+ function usePrefersReducedMotion() {
33314
+ const [reduced, setReduced] = (0, import_react72.useState)(false);
33315
+ (0, import_react72.useEffect)(() => {
33316
+ if (typeof window === "undefined" || !window.matchMedia) return;
33317
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
33318
+ setReduced(mq.matches);
33319
+ const read = () => setReduced(mq.matches);
33320
+ mq.addEventListener("change", read);
33321
+ return () => mq.removeEventListener("change", read);
33322
+ }, []);
33323
+ return reduced;
33324
+ }
33325
+
33326
+ // src/components/ui/random-player-picker/index.tsx
33316
33327
  var import_jsx_runtime116 = require("react/jsx-runtime");
33317
33328
  var PRESETS4 = {
33318
33329
  blue: {
@@ -33370,6 +33381,11 @@ var WEDGE_PALETTE = [
33370
33381
  ];
33371
33382
  var DEFAULT_EASING = "cubic-bezier(0.25, 0.46, 0.45, 0.94)";
33372
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;
33373
33389
  var INTENSITY_CONFIG = {
33374
33390
  subtle: {
33375
33391
  particles: 14,
@@ -33441,6 +33457,9 @@ function ReelStage({
33441
33457
  winner,
33442
33458
  spinning,
33443
33459
  reelY,
33460
+ displayY,
33461
+ cssTransition,
33462
+ tapeRef,
33444
33463
  reelVisibleRows,
33445
33464
  reelRowHeight,
33446
33465
  size,
@@ -33464,17 +33483,16 @@ function ReelStage({
33464
33483
  const stripHeight = reelVisibleRows * reelRowHeight;
33465
33484
  const centerOffsetPx = (reelVisibleRows - 1) / 2 * reelRowHeight;
33466
33485
  const P = pool.length;
33467
- const leadIn = 32;
33468
33486
  const tapeRows = Math.max(
33469
- reelVisibleRows + leadIn + 1,
33470
- Math.ceil((reelY + stripHeight) / reelRowHeight) + leadIn + 1
33487
+ reelVisibleRows + REEL_LEAD_IN + 1,
33488
+ Math.ceil((reelY + stripHeight) / reelRowHeight) + REEL_LEAD_IN + 1
33471
33489
  );
33472
33490
  const tape = Array.from({ length: tapeRows }, (_, i) => {
33473
- const poolIdx = P === 0 ? 0 : ((i - leadIn) % P + P) % P;
33491
+ const poolIdx = P === 0 ? 0 : ((i - REEL_LEAD_IN) % P + P) % P;
33474
33492
  return pool[poolIdx];
33475
33493
  });
33476
- const effectiveY = reelY + leadIn * reelRowHeight;
33477
- 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";
33478
33496
  return /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
33479
33497
  "div",
33480
33498
  {
@@ -33519,6 +33537,7 @@ function ReelStage({
33519
33537
  ) : /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
33520
33538
  "div",
33521
33539
  {
33540
+ ref: tapeRef,
33522
33541
  "aria-hidden": "true",
33523
33542
  style: {
33524
33543
  position: "absolute",
@@ -33734,6 +33753,9 @@ function RandomPlayerPicker({
33734
33753
  showStats = true,
33735
33754
  renderWinner,
33736
33755
  spinRef,
33756
+ resetRef,
33757
+ onPoolChange,
33758
+ draftCompleteCaption = "Draft complete \u2014 reset to spin again",
33737
33759
  intensity = "normal",
33738
33760
  enableHaptics = false,
33739
33761
  tickSoundUrl,
@@ -33748,6 +33770,7 @@ function RandomPlayerPicker({
33748
33770
  const dark = useDarkMode(darkMode);
33749
33771
  const reducedMotion = usePrefersReducedMotion();
33750
33772
  const intensityCfg = INTENSITY_CONFIG[intensity];
33773
+ const physicsDriven = intensity === "dramatic" && !reducedMotion;
33751
33774
  const [pool, setPool] = (0, import_react73.useState)(participants);
33752
33775
  const [rotation, setRotation] = (0, import_react73.useState)(0);
33753
33776
  const [reelY, setReelY] = (0, import_react73.useState)(0);
@@ -33760,10 +33783,17 @@ function RandomPlayerPicker({
33760
33783
  const particleTimer = (0, import_react73.useRef)(null);
33761
33784
  const popTimer = (0, import_react73.useRef)(null);
33762
33785
  const tickInterval = (0, import_react73.useRef)(null);
33786
+ const pendingPopIdxRef = (0, import_react73.useRef)(null);
33787
+ const removeWinnersRef = (0, import_react73.useRef)(removeWinners);
33763
33788
  const tickAudioRef = (0, import_react73.useRef)(null);
33764
33789
  const winAudioRef = (0, import_react73.useRef)(null);
33765
33790
  const containerRef = (0, import_react73.useRef)(null);
33766
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);
33767
33797
  (0, import_react73.useEffect)(() => {
33768
33798
  if (typeof window === "undefined" || !tickSoundUrl) {
33769
33799
  tickAudioRef.current = null;
@@ -33798,10 +33828,33 @@ function RandomPlayerPicker({
33798
33828
  winAudioRef.current = null;
33799
33829
  };
33800
33830
  }, [winSoundUrl, soundVolume]);
33831
+ (0, import_react73.useEffect)(() => {
33832
+ removeWinnersRef.current = removeWinners;
33833
+ }, [removeWinners]);
33801
33834
  (0, import_react73.useEffect)(() => {
33802
33835
  const ids = participants.map((p) => p.id).join("|");
33803
33836
  const structuralChange = ids !== prevIdsRef.current;
33804
33837
  if (structuralChange) {
33838
+ if (winnerTimer.current) {
33839
+ clearTimeout(winnerTimer.current);
33840
+ winnerTimer.current = null;
33841
+ }
33842
+ if (tickInterval.current) {
33843
+ clearTimeout(tickInterval.current);
33844
+ tickInterval.current = null;
33845
+ }
33846
+ if (popTimer.current) {
33847
+ clearTimeout(popTimer.current);
33848
+ popTimer.current = null;
33849
+ }
33850
+ if (rafRef.current) {
33851
+ cancelAnimationFrame(rafRef.current);
33852
+ rafRef.current = null;
33853
+ }
33854
+ visualRotationRef.current = null;
33855
+ visualReelYRef.current = null;
33856
+ pendingPopIdxRef.current = null;
33857
+ setSpinning(false);
33805
33858
  setPool(participants);
33806
33859
  setHistory([]);
33807
33860
  setWinner(null);
@@ -33858,33 +33911,50 @@ function RandomPlayerPicker({
33858
33911
  [dark]
33859
33912
  );
33860
33913
  const spin = (0, import_react73.useCallback)(() => {
33861
- if (spinning || n < 2) return;
33914
+ if (spinning) return;
33915
+ let activePool = pool;
33916
+ if (popTimer.current) {
33917
+ clearTimeout(popTimer.current);
33918
+ popTimer.current = null;
33919
+ const pendingIdx = pendingPopIdxRef.current;
33920
+ pendingPopIdxRef.current = null;
33921
+ if (pendingIdx != null && pendingIdx >= 0 && pendingIdx < pool.length && removeWinnersRef.current) {
33922
+ activePool = pool.filter((_, i) => i !== pendingIdx);
33923
+ setPool(activePool);
33924
+ setWinner(null);
33925
+ }
33926
+ }
33927
+ const activeN = activePool.length;
33928
+ if (activeN < 2) return;
33929
+ const activeAnglePerWedge = 2 * Math.PI / activeN;
33930
+ const activeRotation = rotation;
33931
+ const activeReelY = reelY;
33862
33932
  onSpinStart?.();
33863
- const winnerIdx = weightedIndex(pool);
33933
+ const winnerIdx = weightedIndex(activePool);
33864
33934
  const minRev = minRevolutions + intensityCfg.revsBoost;
33865
33935
  const maxRev = maxRevolutions + intensityCfg.revsBoost;
33866
33936
  const minFullRevs = Math.ceil(minRev);
33867
33937
  const maxFullRevs = Math.max(minFullRevs, Math.floor(maxRev));
33868
33938
  const revolutions = minFullRevs + Math.floor(Math.random() * (maxFullRevs - minFullRevs + 1));
33869
- const wedgeMidRad = winnerIdx * anglePerWedge + anglePerWedge / 2;
33939
+ const wedgeMidRad = winnerIdx * activeAnglePerWedge + activeAnglePerWedge / 2;
33870
33940
  const wedgeMidDeg = wedgeMidRad * 180 / Math.PI;
33871
- const wheelWobble = (Math.random() - 0.5) * (anglePerWedge * 180 / Math.PI) * 0.6;
33872
- const currentMod = (rotation % 360 + 360) % 360;
33941
+ const wheelWobble = (Math.random() - 0.5) * (activeAnglePerWedge * 180 / Math.PI) * 0.6;
33942
+ const currentMod = (activeRotation % 360 + 360) % 360;
33873
33943
  const targetMod = ((-wedgeMidDeg + wheelWobble) % 360 + 360) % 360;
33874
33944
  let deltaMod = targetMod - currentMod;
33875
33945
  if (deltaMod < 0) deltaMod += 360;
33876
- const finalRotation = rotation + deltaMod + revolutions * 360;
33946
+ const finalRotation = activeRotation + deltaMod + revolutions * 360;
33877
33947
  const centerOffsetRows = (reelVisibleRows - 1) / 2;
33878
33948
  const centerOffsetPx = centerOffsetRows * reelRowHeight;
33879
33949
  const currentCenterRow = Math.round(
33880
- (reelY + centerOffsetPx) / reelRowHeight
33950
+ (activeReelY + centerOffsetPx) / reelRowHeight
33881
33951
  );
33882
- const currentCenterMod = mod(currentCenterRow, n);
33883
- const targetRowOffset = (winnerIdx - currentCenterMod + n) % n;
33884
- const reelRevRows = Math.round(revolutions * n);
33885
- const finalReelY = reelY + (reelRevRows + targetRowOffset) * reelRowHeight;
33886
- const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, n) : wheelIndexAtPointer(finalRotation, n);
33887
- const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < pool.length ? settledWinnerIdx : winnerIdx;
33952
+ const currentCenterMod = mod(currentCenterRow, activeN);
33953
+ const targetRowOffset = (winnerIdx - currentCenterMod + activeN) % activeN;
33954
+ const reelRevRows = Math.round(revolutions * activeN);
33955
+ const finalReelY = activeReelY + (reelRevRows + targetRowOffset) * reelRowHeight;
33956
+ const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, activeN) : wheelIndexAtPointer(finalRotation, activeN);
33957
+ const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < activePool.length ? settledWinnerIdx : winnerIdx;
33888
33958
  const effectiveDuration = reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult);
33889
33959
  setSpinning(true);
33890
33960
  setWinner(null);
@@ -33894,10 +33964,59 @@ function RandomPlayerPicker({
33894
33964
  } else {
33895
33965
  setRotation(finalRotation);
33896
33966
  }
33897
- if ((onTick || tickAudioRef.current) && !reducedMotion) {
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) {
33898
34017
  const wedges = mode === "reel" ? Math.max(1, reelRevRows + targetRowOffset) : (() => {
33899
34018
  const totalDelta = deltaMod + revolutions * 360;
33900
- const wedgeDeg = anglePerWedge * 180 / Math.PI;
34019
+ const wedgeDeg = activeAnglePerWedge * 180 / Math.PI;
33901
34020
  return Math.max(
33902
34021
  1,
33903
34022
  Math.round(totalDelta / Math.max(wedgeDeg, 1))
@@ -33912,16 +34031,7 @@ function RandomPlayerPicker({
33912
34031
  const eased = 1 - Math.pow(1 - progress, 3);
33913
34032
  const interval = startInterval + (endInterval - startInterval) * eased;
33914
34033
  tickInterval.current = setTimeout(() => {
33915
- onTick?.();
33916
- const ta = tickAudioRef.current;
33917
- if (ta) {
33918
- try {
33919
- ta.currentTime = 0;
33920
- void ta.play().catch(() => {
33921
- });
33922
- } catch {
33923
- }
33924
- }
34034
+ fireTick();
33925
34035
  elapsed += interval;
33926
34036
  schedule();
33927
34037
  }, interval);
@@ -33929,7 +34039,7 @@ function RandomPlayerPicker({
33929
34039
  schedule();
33930
34040
  }
33931
34041
  winnerTimer.current = setTimeout(() => {
33932
- const picked = pool[finalWinnerIdx];
34042
+ const picked = activePool[finalWinnerIdx];
33933
34043
  if (!picked) {
33934
34044
  setSpinning(false);
33935
34045
  return;
@@ -33975,9 +34085,13 @@ function RandomPlayerPicker({
33975
34085
  particleTimer.current = setTimeout(() => setParticles([]), 2400);
33976
34086
  }
33977
34087
  if (removeWinners) {
34088
+ pendingPopIdxRef.current = finalWinnerIdx;
33978
34089
  popTimer.current = setTimeout(() => {
34090
+ popTimer.current = null;
34091
+ pendingPopIdxRef.current = null;
34092
+ if (!removeWinnersRef.current) return;
33979
34093
  setPool((p) => p.filter((_, i) => i !== finalWinnerIdx));
33980
- setWinner(null);
34094
+ if (activePool.length - 1 >= 2) setWinner(null);
33981
34095
  setRotation(0);
33982
34096
  setReelY(0);
33983
34097
  }, 2800);
@@ -33986,8 +34100,6 @@ function RandomPlayerPicker({
33986
34100
  }, [
33987
34101
  pool,
33988
34102
  spinning,
33989
- n,
33990
- anglePerWedge,
33991
34103
  rotation,
33992
34104
  reelY,
33993
34105
  mode,
@@ -34002,6 +34114,7 @@ function RandomPlayerPicker({
34002
34114
  maxRevolutions,
34003
34115
  history,
34004
34116
  reducedMotion,
34117
+ physicsDriven,
34005
34118
  enableHaptics,
34006
34119
  intensityCfg.particles,
34007
34120
  intensityCfg.durationMult,
@@ -34013,16 +34126,25 @@ function RandomPlayerPicker({
34013
34126
  if (spinRef && spinRef.current === spin) spinRef.current = null;
34014
34127
  };
34015
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]);
34016
34136
  (0, import_react73.useEffect)(
34017
34137
  () => () => {
34018
34138
  if (winnerTimer.current) clearTimeout(winnerTimer.current);
34019
34139
  if (particleTimer.current) clearTimeout(particleTimer.current);
34020
34140
  if (popTimer.current) clearTimeout(popTimer.current);
34021
34141
  if (tickInterval.current) clearTimeout(tickInterval.current);
34142
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
34022
34143
  },
34023
34144
  []
34024
34145
  );
34025
34146
  (0, import_react73.useEffect)(() => {
34147
+ if (trigger === "external") return;
34026
34148
  const el = containerRef.current;
34027
34149
  if (!el) return;
34028
34150
  const h = (e) => {
@@ -34033,8 +34155,28 @@ function RandomPlayerPicker({
34033
34155
  };
34034
34156
  el.addEventListener("keydown", h);
34035
34157
  return () => el.removeEventListener("keydown", h);
34036
- }, [spin]);
34158
+ }, [spin, trigger]);
34037
34159
  const reset = (0, import_react73.useCallback)(() => {
34160
+ if (winnerTimer.current) {
34161
+ clearTimeout(winnerTimer.current);
34162
+ winnerTimer.current = null;
34163
+ }
34164
+ if (tickInterval.current) {
34165
+ clearTimeout(tickInterval.current);
34166
+ tickInterval.current = null;
34167
+ }
34168
+ if (popTimer.current) {
34169
+ clearTimeout(popTimer.current);
34170
+ popTimer.current = null;
34171
+ }
34172
+ if (rafRef.current) {
34173
+ cancelAnimationFrame(rafRef.current);
34174
+ rafRef.current = null;
34175
+ }
34176
+ visualRotationRef.current = null;
34177
+ visualReelYRef.current = null;
34178
+ pendingPopIdxRef.current = null;
34179
+ setSpinning(false);
34038
34180
  setPool(participants);
34039
34181
  setHistory([]);
34040
34182
  setWinner(null);
@@ -34042,9 +34184,17 @@ function RandomPlayerPicker({
34042
34184
  setReelY(0);
34043
34185
  setParticles([]);
34044
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]);
34045
34193
  const isIdle = n < 2;
34194
+ const drainedByDraft = isIdle && participants.length >= 2;
34195
+ const effectiveIdleCaption = drainedByDraft ? draftCompleteCaption : placeholderCaption;
34046
34196
  const placeholderDiscColor = dark ? "#2a3344" : "#e2e8f0";
34047
- if (isIdle && placeholderMode === "message") {
34197
+ if (isIdle && !drainedByDraft && placeholderMode === "message") {
34048
34198
  return /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
34049
34199
  "div",
34050
34200
  {
@@ -34066,6 +34216,8 @@ function RandomPlayerPicker({
34066
34216
  const shakeKeyframe = `rppShake_${intensity}`;
34067
34217
  const glowKeyframe = `rppGlow_${intensity}`;
34068
34218
  const pulseKeyframe = `rppPulse_${intensity}`;
34219
+ const displayRotation = visualRotationRef.current ?? rotation;
34220
+ const displayReelY = visualReelYRef.current ?? reelY;
34069
34221
  return /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
34070
34222
  "div",
34071
34223
  {
@@ -34112,13 +34264,33 @@ function RandomPlayerPicker({
34112
34264
  [data-rpp-anim] { animation: none !important; }
34113
34265
  }
34114
34266
  ` }),
34115
- mode === "reel" ? /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
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)(
34116
34285
  ReelStage,
34117
34286
  {
34118
34287
  pool,
34119
34288
  winner,
34120
34289
  spinning,
34121
34290
  reelY,
34291
+ displayY: displayReelY,
34292
+ cssTransition: !physicsDriven,
34293
+ tapeRef: reelTapeRef,
34122
34294
  reelVisibleRows,
34123
34295
  reelRowHeight,
34124
34296
  size,
@@ -34127,7 +34299,7 @@ function RandomPlayerPicker({
34127
34299
  dark,
34128
34300
  isIdle,
34129
34301
  placeholderMode,
34130
- placeholderCaption,
34302
+ placeholderCaption: effectiveIdleCaption,
34131
34303
  placeholderDiscColor,
34132
34304
  spinDuration,
34133
34305
  intensityCfg,
@@ -34184,6 +34356,7 @@ function RandomPlayerPicker({
34184
34356
  /* @__PURE__ */ (0, import_jsx_runtime116.jsxs)(
34185
34357
  "svg",
34186
34358
  {
34359
+ ref: wheelSvgRef,
34187
34360
  width: size,
34188
34361
  height: size,
34189
34362
  "aria-hidden": "true",
@@ -34192,8 +34365,8 @@ function RandomPlayerPicker({
34192
34365
  top: 36,
34193
34366
  left: 0,
34194
34367
  transformOrigin: `${cx}px ${cy}px`,
34195
- transform: `rotate(${rotation}deg)`,
34196
- 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",
34197
34370
  filter: `drop-shadow(0 8px 28px ${dark ? "rgba(0,0,0,0.55)" : "rgba(0,0,0,0.15)"})`
34198
34371
  },
34199
34372
  children: [
@@ -34346,7 +34519,7 @@ function RandomPlayerPicker({
34346
34519
  pointerEvents: "none",
34347
34520
  zIndex: 3
34348
34521
  },
34349
- children: placeholderCaption
34522
+ children: effectiveIdleCaption
34350
34523
  }
34351
34524
  ),
34352
34525
  particles.map((p) => /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(
@@ -38487,27 +38660,28 @@ function GameTimer({
38487
38660
  const now = performance.now();
38488
38661
  const delta = now - lastTickRef.current;
38489
38662
  lastTickRef.current = now;
38490
- setElapsedMs((prev) => {
38491
- const next = prev + delta;
38492
- if (mode === "countdown" && next >= durationMs) {
38493
- setRunning(false);
38494
- setCompleted(true);
38495
- onComplete?.();
38496
- return durationMs;
38663
+ const next = elapsedRef.current + delta;
38664
+ if (mode === "countdown" && next >= durationMs) {
38665
+ elapsedRef.current = durationMs;
38666
+ setElapsedMs(durationMs);
38667
+ setRunning(false);
38668
+ setCompleted(true);
38669
+ onComplete?.();
38670
+ return;
38671
+ }
38672
+ if (mode === "countdown") {
38673
+ const secLeft = Math.ceil((durationMs - next) / 1e3);
38674
+ if (secLeft <= warningAt && !warningFiredRef.current) {
38675
+ warningFiredRef.current = true;
38676
+ onWarning?.();
38497
38677
  }
38498
- if (mode === "countdown") {
38499
- const secLeft = Math.ceil((durationMs - next) / 1e3);
38500
- if (secLeft <= warningAt && !warningFiredRef.current) {
38501
- warningFiredRef.current = true;
38502
- onWarning?.();
38503
- }
38504
- if (secLeft <= dangerAt && !dangerFiredRef.current) {
38505
- dangerFiredRef.current = true;
38506
- onDanger?.();
38507
- }
38678
+ if (secLeft <= dangerAt && !dangerFiredRef.current) {
38679
+ dangerFiredRef.current = true;
38680
+ onDanger?.();
38508
38681
  }
38509
- return next;
38510
- });
38682
+ }
38683
+ elapsedRef.current = next;
38684
+ setElapsedMs(next);
38511
38685
  rafRef.current = requestAnimationFrame(tick);
38512
38686
  }, [mode, durationMs, warningAt, dangerAt, onComplete, onWarning, onDanger]);
38513
38687
  (0, import_react76.useEffect)(() => {
@@ -38528,6 +38702,7 @@ function GameTimer({
38528
38702
  }, []);
38529
38703
  const reset = (0, import_react76.useCallback)(() => {
38530
38704
  setRunning(false);
38705
+ elapsedRef.current = 0;
38531
38706
  setElapsedMs(0);
38532
38707
  setCompleted(false);
38533
38708
  setLaps([]);
@@ -39824,8 +39999,10 @@ function TeamGenerator({
39824
39999
  minPlayersPerTeam = 2,
39825
40000
  maxPlayers = Number.POSITIVE_INFINITY,
39826
40001
  defaultNames = [],
40002
+ defaultTeams,
39827
40003
  onNamesChange,
39828
40004
  onGenerate,
40005
+ onTeamsChange,
39829
40006
  onTeamCountChange,
39830
40007
  labels,
39831
40008
  ariaLabel = "Team generator",
@@ -39840,12 +40017,17 @@ function TeamGenerator({
39840
40017
  const [names, setNames] = (0, import_react78.useState)(defaultNames);
39841
40018
  const [input, setInput] = (0, import_react78.useState)("");
39842
40019
  const [teamCount, setTeamCount] = (0, import_react78.useState)(initialCount);
39843
- const [teams, setTeams] = (0, import_react78.useState)(null);
40020
+ const [teams, setTeams] = (0, import_react78.useState)(
40021
+ defaultTeams && defaultTeams.length > 0 ? defaultTeams : null
40022
+ );
39844
40023
  const inputRef = (0, import_react78.useRef)(null);
39845
40024
  const minNames = teamCount * minPlayersPerTeam;
39846
40025
  (0, import_react78.useEffect)(() => {
39847
40026
  onNamesChange?.(names);
39848
40027
  }, [names, onNamesChange]);
40028
+ (0, import_react78.useEffect)(() => {
40029
+ onTeamsChange?.(teams);
40030
+ }, [teams, onTeamsChange]);
39849
40031
  const addName = (0, import_react78.useCallback)(() => {
39850
40032
  const trimmed = input.trim();
39851
40033
  if (!trimmed) return;
@@ -40467,6 +40649,8 @@ function RoundRobinScheduler({
40467
40649
  theme: theme5 = "slate",
40468
40650
  darkMode,
40469
40651
  minTeams = 3,
40652
+ maxTeams,
40653
+ nameMaxLength,
40470
40654
  defaultTeams = [],
40471
40655
  resumeRounds,
40472
40656
  onTeamsChange,
@@ -40489,39 +40673,41 @@ function RoundRobinScheduler({
40489
40673
  (0, import_react79.useEffect)(() => {
40490
40674
  onTeamsChange?.(teams);
40491
40675
  }, [teams, onTeamsChange]);
40492
- const addTeams = (0, import_react79.useCallback)((raw) => {
40493
- const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40494
- if (names.length === 0) return;
40495
- setTeams((prev) => {
40496
- const existing = new Set(prev.map((t) => t.toLowerCase()));
40497
- const next = [...prev];
40498
- for (const name of names) {
40499
- if (!existing.has(name.toLowerCase())) {
40500
- existing.add(name.toLowerCase());
40501
- next.push(name);
40502
- }
40503
- }
40504
- return next;
40505
- });
40506
- setInput("");
40507
- setRounds([]);
40508
- }, []);
40509
- const importTeams = (0, import_react79.useCallback)((incoming) => {
40510
- if (incoming.length === 0) return;
40511
- setTeams((prev) => {
40676
+ const mergeNames = (0, import_react79.useCallback)(
40677
+ (prev, incoming) => {
40512
40678
  const existing = new Set(prev.map((t) => t.toLowerCase()));
40513
40679
  const next = [...prev];
40514
- for (const name of incoming) {
40680
+ for (const rawName of incoming) {
40681
+ if (maxTeams != null && next.length >= maxTeams) break;
40682
+ const name = nameMaxLength != null ? rawName.slice(0, nameMaxLength) : rawName;
40515
40683
  const key = name.toLowerCase();
40516
- if (!existing.has(key)) {
40684
+ if (name.length > 0 && !existing.has(key)) {
40517
40685
  existing.add(key);
40518
40686
  next.push(name);
40519
40687
  }
40520
40688
  }
40521
40689
  return next;
40522
- });
40523
- setRounds([]);
40524
- }, []);
40690
+ },
40691
+ [maxTeams, nameMaxLength]
40692
+ );
40693
+ const addTeams = (0, import_react79.useCallback)(
40694
+ (raw) => {
40695
+ const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40696
+ if (names.length === 0) return;
40697
+ setTeams((prev) => mergeNames(prev, names));
40698
+ setInput("");
40699
+ setRounds([]);
40700
+ },
40701
+ [mergeNames]
40702
+ );
40703
+ const importTeams = (0, import_react79.useCallback)(
40704
+ (incoming) => {
40705
+ if (incoming.length === 0) return;
40706
+ setTeams((prev) => mergeNames(prev, incoming));
40707
+ setRounds([]);
40708
+ },
40709
+ [mergeNames]
40710
+ );
40525
40711
  const handleKeyDown = (0, import_react79.useCallback)(
40526
40712
  (e) => {
40527
40713
  if (e.key === "Enter") {
@@ -41010,6 +41196,8 @@ function Scoreboard({
41010
41196
  onTeamsChange,
41011
41197
  onRoundAdvance,
41012
41198
  onNewGame,
41199
+ onReset,
41200
+ nameMaxLength,
41013
41201
  labels,
41014
41202
  ariaLabel = "Scoreboard",
41015
41203
  className
@@ -41084,7 +41272,8 @@ function Scoreboard({
41084
41272
  setRound(1);
41085
41273
  setHistory([]);
41086
41274
  setHistoryOpen(false);
41087
- }, []);
41275
+ onReset?.();
41276
+ }, [onReset]);
41088
41277
  const newGame = (0, import_react80.useCallback)(() => {
41089
41278
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41090
41279
  setRound(1);
@@ -41306,6 +41495,7 @@ function Scoreboard({
41306
41495
  {
41307
41496
  type: "text",
41308
41497
  value: team.name,
41498
+ maxLength: nameMaxLength,
41309
41499
  onChange: (e) => updateName(index, e.target.value),
41310
41500
  style: nameInputStyle,
41311
41501
  "aria-label": `Team ${index + 1} name`