@digilogiclabs/saas-factory-ui 2.3.0 → 2.4.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: {
@@ -33760,6 +33771,8 @@ function RandomPlayerPicker({
33760
33771
  const particleTimer = (0, import_react73.useRef)(null);
33761
33772
  const popTimer = (0, import_react73.useRef)(null);
33762
33773
  const tickInterval = (0, import_react73.useRef)(null);
33774
+ const pendingPopIdxRef = (0, import_react73.useRef)(null);
33775
+ const removeWinnersRef = (0, import_react73.useRef)(removeWinners);
33763
33776
  const tickAudioRef = (0, import_react73.useRef)(null);
33764
33777
  const winAudioRef = (0, import_react73.useRef)(null);
33765
33778
  const containerRef = (0, import_react73.useRef)(null);
@@ -33798,10 +33811,27 @@ function RandomPlayerPicker({
33798
33811
  winAudioRef.current = null;
33799
33812
  };
33800
33813
  }, [winSoundUrl, soundVolume]);
33814
+ (0, import_react73.useEffect)(() => {
33815
+ removeWinnersRef.current = removeWinners;
33816
+ }, [removeWinners]);
33801
33817
  (0, import_react73.useEffect)(() => {
33802
33818
  const ids = participants.map((p) => p.id).join("|");
33803
33819
  const structuralChange = ids !== prevIdsRef.current;
33804
33820
  if (structuralChange) {
33821
+ if (winnerTimer.current) {
33822
+ clearTimeout(winnerTimer.current);
33823
+ winnerTimer.current = null;
33824
+ }
33825
+ if (tickInterval.current) {
33826
+ clearTimeout(tickInterval.current);
33827
+ tickInterval.current = null;
33828
+ }
33829
+ if (popTimer.current) {
33830
+ clearTimeout(popTimer.current);
33831
+ popTimer.current = null;
33832
+ }
33833
+ pendingPopIdxRef.current = null;
33834
+ setSpinning(false);
33805
33835
  setPool(participants);
33806
33836
  setHistory([]);
33807
33837
  setWinner(null);
@@ -33858,33 +33888,50 @@ function RandomPlayerPicker({
33858
33888
  [dark]
33859
33889
  );
33860
33890
  const spin = (0, import_react73.useCallback)(() => {
33861
- if (spinning || n < 2) return;
33891
+ if (spinning) return;
33892
+ let activePool = pool;
33893
+ if (popTimer.current) {
33894
+ clearTimeout(popTimer.current);
33895
+ popTimer.current = null;
33896
+ const pendingIdx = pendingPopIdxRef.current;
33897
+ pendingPopIdxRef.current = null;
33898
+ if (pendingIdx != null && pendingIdx >= 0 && pendingIdx < pool.length && removeWinnersRef.current) {
33899
+ activePool = pool.filter((_, i) => i !== pendingIdx);
33900
+ setPool(activePool);
33901
+ setWinner(null);
33902
+ }
33903
+ }
33904
+ const activeN = activePool.length;
33905
+ if (activeN < 2) return;
33906
+ const activeAnglePerWedge = 2 * Math.PI / activeN;
33907
+ const activeRotation = rotation;
33908
+ const activeReelY = reelY;
33862
33909
  onSpinStart?.();
33863
- const winnerIdx = weightedIndex(pool);
33910
+ const winnerIdx = weightedIndex(activePool);
33864
33911
  const minRev = minRevolutions + intensityCfg.revsBoost;
33865
33912
  const maxRev = maxRevolutions + intensityCfg.revsBoost;
33866
33913
  const minFullRevs = Math.ceil(minRev);
33867
33914
  const maxFullRevs = Math.max(minFullRevs, Math.floor(maxRev));
33868
33915
  const revolutions = minFullRevs + Math.floor(Math.random() * (maxFullRevs - minFullRevs + 1));
33869
- const wedgeMidRad = winnerIdx * anglePerWedge + anglePerWedge / 2;
33916
+ const wedgeMidRad = winnerIdx * activeAnglePerWedge + activeAnglePerWedge / 2;
33870
33917
  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;
33918
+ const wheelWobble = (Math.random() - 0.5) * (activeAnglePerWedge * 180 / Math.PI) * 0.6;
33919
+ const currentMod = (activeRotation % 360 + 360) % 360;
33873
33920
  const targetMod = ((-wedgeMidDeg + wheelWobble) % 360 + 360) % 360;
33874
33921
  let deltaMod = targetMod - currentMod;
33875
33922
  if (deltaMod < 0) deltaMod += 360;
33876
- const finalRotation = rotation + deltaMod + revolutions * 360;
33923
+ const finalRotation = activeRotation + deltaMod + revolutions * 360;
33877
33924
  const centerOffsetRows = (reelVisibleRows - 1) / 2;
33878
33925
  const centerOffsetPx = centerOffsetRows * reelRowHeight;
33879
33926
  const currentCenterRow = Math.round(
33880
- (reelY + centerOffsetPx) / reelRowHeight
33927
+ (activeReelY + centerOffsetPx) / reelRowHeight
33881
33928
  );
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;
33929
+ const currentCenterMod = mod(currentCenterRow, activeN);
33930
+ const targetRowOffset = (winnerIdx - currentCenterMod + activeN) % activeN;
33931
+ const reelRevRows = Math.round(revolutions * activeN);
33932
+ const finalReelY = activeReelY + (reelRevRows + targetRowOffset) * reelRowHeight;
33933
+ const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, activeN) : wheelIndexAtPointer(finalRotation, activeN);
33934
+ const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < activePool.length ? settledWinnerIdx : winnerIdx;
33888
33935
  const effectiveDuration = reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult);
33889
33936
  setSpinning(true);
33890
33937
  setWinner(null);
@@ -33897,7 +33944,7 @@ function RandomPlayerPicker({
33897
33944
  if ((onTick || tickAudioRef.current) && !reducedMotion) {
33898
33945
  const wedges = mode === "reel" ? Math.max(1, reelRevRows + targetRowOffset) : (() => {
33899
33946
  const totalDelta = deltaMod + revolutions * 360;
33900
- const wedgeDeg = anglePerWedge * 180 / Math.PI;
33947
+ const wedgeDeg = activeAnglePerWedge * 180 / Math.PI;
33901
33948
  return Math.max(
33902
33949
  1,
33903
33950
  Math.round(totalDelta / Math.max(wedgeDeg, 1))
@@ -33929,7 +33976,7 @@ function RandomPlayerPicker({
33929
33976
  schedule();
33930
33977
  }
33931
33978
  winnerTimer.current = setTimeout(() => {
33932
- const picked = pool[finalWinnerIdx];
33979
+ const picked = activePool[finalWinnerIdx];
33933
33980
  if (!picked) {
33934
33981
  setSpinning(false);
33935
33982
  return;
@@ -33975,7 +34022,11 @@ function RandomPlayerPicker({
33975
34022
  particleTimer.current = setTimeout(() => setParticles([]), 2400);
33976
34023
  }
33977
34024
  if (removeWinners) {
34025
+ pendingPopIdxRef.current = finalWinnerIdx;
33978
34026
  popTimer.current = setTimeout(() => {
34027
+ popTimer.current = null;
34028
+ pendingPopIdxRef.current = null;
34029
+ if (!removeWinnersRef.current) return;
33979
34030
  setPool((p) => p.filter((_, i) => i !== finalWinnerIdx));
33980
34031
  setWinner(null);
33981
34032
  setRotation(0);
@@ -33986,8 +34037,6 @@ function RandomPlayerPicker({
33986
34037
  }, [
33987
34038
  pool,
33988
34039
  spinning,
33989
- n,
33990
- anglePerWedge,
33991
34040
  rotation,
33992
34041
  reelY,
33993
34042
  mode,
@@ -34023,6 +34072,7 @@ function RandomPlayerPicker({
34023
34072
  []
34024
34073
  );
34025
34074
  (0, import_react73.useEffect)(() => {
34075
+ if (trigger === "external") return;
34026
34076
  const el = containerRef.current;
34027
34077
  if (!el) return;
34028
34078
  const h = (e) => {
@@ -34033,8 +34083,22 @@ function RandomPlayerPicker({
34033
34083
  };
34034
34084
  el.addEventListener("keydown", h);
34035
34085
  return () => el.removeEventListener("keydown", h);
34036
- }, [spin]);
34086
+ }, [spin, trigger]);
34037
34087
  const reset = (0, import_react73.useCallback)(() => {
34088
+ if (winnerTimer.current) {
34089
+ clearTimeout(winnerTimer.current);
34090
+ winnerTimer.current = null;
34091
+ }
34092
+ if (tickInterval.current) {
34093
+ clearTimeout(tickInterval.current);
34094
+ tickInterval.current = null;
34095
+ }
34096
+ if (popTimer.current) {
34097
+ clearTimeout(popTimer.current);
34098
+ popTimer.current = null;
34099
+ }
34100
+ pendingPopIdxRef.current = null;
34101
+ setSpinning(false);
34038
34102
  setPool(participants);
34039
34103
  setHistory([]);
34040
34104
  setWinner(null);
@@ -38487,27 +38551,28 @@ function GameTimer({
38487
38551
  const now = performance.now();
38488
38552
  const delta = now - lastTickRef.current;
38489
38553
  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;
38554
+ const next = elapsedRef.current + delta;
38555
+ if (mode === "countdown" && next >= durationMs) {
38556
+ elapsedRef.current = durationMs;
38557
+ setElapsedMs(durationMs);
38558
+ setRunning(false);
38559
+ setCompleted(true);
38560
+ onComplete?.();
38561
+ return;
38562
+ }
38563
+ if (mode === "countdown") {
38564
+ const secLeft = Math.ceil((durationMs - next) / 1e3);
38565
+ if (secLeft <= warningAt && !warningFiredRef.current) {
38566
+ warningFiredRef.current = true;
38567
+ onWarning?.();
38497
38568
  }
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
- }
38569
+ if (secLeft <= dangerAt && !dangerFiredRef.current) {
38570
+ dangerFiredRef.current = true;
38571
+ onDanger?.();
38508
38572
  }
38509
- return next;
38510
- });
38573
+ }
38574
+ elapsedRef.current = next;
38575
+ setElapsedMs(next);
38511
38576
  rafRef.current = requestAnimationFrame(tick);
38512
38577
  }, [mode, durationMs, warningAt, dangerAt, onComplete, onWarning, onDanger]);
38513
38578
  (0, import_react76.useEffect)(() => {
@@ -38528,6 +38593,7 @@ function GameTimer({
38528
38593
  }, []);
38529
38594
  const reset = (0, import_react76.useCallback)(() => {
38530
38595
  setRunning(false);
38596
+ elapsedRef.current = 0;
38531
38597
  setElapsedMs(0);
38532
38598
  setCompleted(false);
38533
38599
  setLaps([]);
@@ -40467,6 +40533,8 @@ function RoundRobinScheduler({
40467
40533
  theme: theme5 = "slate",
40468
40534
  darkMode,
40469
40535
  minTeams = 3,
40536
+ maxTeams,
40537
+ nameMaxLength,
40470
40538
  defaultTeams = [],
40471
40539
  resumeRounds,
40472
40540
  onTeamsChange,
@@ -40489,39 +40557,41 @@ function RoundRobinScheduler({
40489
40557
  (0, import_react79.useEffect)(() => {
40490
40558
  onTeamsChange?.(teams);
40491
40559
  }, [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) => {
40560
+ const mergeNames = (0, import_react79.useCallback)(
40561
+ (prev, incoming) => {
40496
40562
  const existing = new Set(prev.map((t) => t.toLowerCase()));
40497
40563
  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) => {
40512
- const existing = new Set(prev.map((t) => t.toLowerCase()));
40513
- const next = [...prev];
40514
- for (const name of incoming) {
40564
+ for (const rawName of incoming) {
40565
+ if (maxTeams != null && next.length >= maxTeams) break;
40566
+ const name = nameMaxLength != null ? rawName.slice(0, nameMaxLength) : rawName;
40515
40567
  const key = name.toLowerCase();
40516
- if (!existing.has(key)) {
40568
+ if (name.length > 0 && !existing.has(key)) {
40517
40569
  existing.add(key);
40518
40570
  next.push(name);
40519
40571
  }
40520
40572
  }
40521
40573
  return next;
40522
- });
40523
- setRounds([]);
40524
- }, []);
40574
+ },
40575
+ [maxTeams, nameMaxLength]
40576
+ );
40577
+ const addTeams = (0, import_react79.useCallback)(
40578
+ (raw) => {
40579
+ const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40580
+ if (names.length === 0) return;
40581
+ setTeams((prev) => mergeNames(prev, names));
40582
+ setInput("");
40583
+ setRounds([]);
40584
+ },
40585
+ [mergeNames]
40586
+ );
40587
+ const importTeams = (0, import_react79.useCallback)(
40588
+ (incoming) => {
40589
+ if (incoming.length === 0) return;
40590
+ setTeams((prev) => mergeNames(prev, incoming));
40591
+ setRounds([]);
40592
+ },
40593
+ [mergeNames]
40594
+ );
40525
40595
  const handleKeyDown = (0, import_react79.useCallback)(
40526
40596
  (e) => {
40527
40597
  if (e.key === "Enter") {
@@ -41010,6 +41080,8 @@ function Scoreboard({
41010
41080
  onTeamsChange,
41011
41081
  onRoundAdvance,
41012
41082
  onNewGame,
41083
+ onReset,
41084
+ nameMaxLength,
41013
41085
  labels,
41014
41086
  ariaLabel = "Scoreboard",
41015
41087
  className
@@ -41084,7 +41156,8 @@ function Scoreboard({
41084
41156
  setRound(1);
41085
41157
  setHistory([]);
41086
41158
  setHistoryOpen(false);
41087
- }, []);
41159
+ onReset?.();
41160
+ }, [onReset]);
41088
41161
  const newGame = (0, import_react80.useCallback)(() => {
41089
41162
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41090
41163
  setRound(1);
@@ -41306,6 +41379,7 @@ function Scoreboard({
41306
41379
  {
41307
41380
  type: "text",
41308
41381
  value: team.name,
41382
+ maxLength: nameMaxLength,
41309
41383
  onChange: (e) => updateName(index, e.target.value),
41310
41384
  style: nameInputStyle,
41311
41385
  "aria-label": `Team ${index + 1} name`