@digilogiclabs/saas-factory-ui 2.2.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/index.mjs CHANGED
@@ -30463,14 +30463,35 @@ var PIP_POS = {
30463
30463
  [75, 75]
30464
30464
  ]
30465
30465
  };
30466
- var SHOW_ROT = {
30466
+ var D6_FACE_PLACEMENTS = {
30467
30467
  1: { x: 0, y: 0 },
30468
- 2: { x: 0, y: 180 },
30469
- 3: { x: 0, y: -90 },
30470
- 4: { x: 0, y: 90 },
30471
- 5: { x: -90, y: 0 },
30472
- 6: { x: 90, y: 0 }
30468
+ 2: { x: 0, y: 90 },
30469
+ 3: { x: 90, y: 0 },
30470
+ 4: { x: -90, y: 0 },
30471
+ 5: { x: 0, y: -90 },
30472
+ 6: { x: 0, y: 180 }
30473
30473
  };
30474
+ function d6LandingRotation(value) {
30475
+ const p = D6_FACE_PLACEMENTS[value];
30476
+ return {
30477
+ x: Math.abs(p.x) === 180 ? 180 : -p.x || 0,
30478
+ y: Math.abs(p.y) === 180 ? 180 : -p.y || 0
30479
+ };
30480
+ }
30481
+ var POLY_SHAPES = {
30482
+ 4: { points: "50,8 93,86 7,86", textY: 62, fontScale: 0.3 },
30483
+ 8: { points: "50,4 96,50 50,96 4,50", textY: 51, fontScale: 0.33 },
30484
+ 10: { points: "50,4 93,44 50,96 7,44", textY: 47, fontScale: 0.3 },
30485
+ 12: { points: "50,4 95,37 78,93 22,93 5,37", textY: 54, fontScale: 0.33 },
30486
+ 20: {
30487
+ points: "50,3 91,26 91,74 50,97 9,74 9,26",
30488
+ textY: 51,
30489
+ fontScale: 0.33
30490
+ }
30491
+ };
30492
+ function formatDieValue(v, sides) {
30493
+ return sides > 8 && (v === 6 || v === 9) ? `${v}.` : String(v);
30494
+ }
30474
30495
  var MAX_COUNT = 6;
30475
30496
  var TUMBLE_MS = 950;
30476
30497
  var SETTLE_MS = 1100;
@@ -30622,6 +30643,7 @@ function Face({ value, pipColor, pipShadow, pipDiameter }) {
30622
30643
  }
30623
30644
  function DiceRoller({
30624
30645
  count: count2 = 2,
30646
+ sides = 6,
30625
30647
  theme: theme5 = "red",
30626
30648
  darkMode,
30627
30649
  size = 80,
@@ -30643,13 +30665,13 @@ function DiceRoller({
30643
30665
  }) {
30644
30666
  const clampedCount = Math.max(1, Math.min(MAX_COUNT, count2));
30645
30667
  const dark = useDarkMode(darkMode);
30646
- const reducedMotion = usePrefersReducedMotion();
30647
30668
  const [rolling, setRolling] = useState70(false);
30648
30669
  const [results, setResults] = useState70(null);
30649
30670
  const [history, setHistory] = useState70([]);
30650
30671
  const dieWrapperRefs = useRef39([]);
30651
30672
  const cubeRefs = useRef39([]);
30652
30673
  const shadowRefs = useRef39([]);
30674
+ const numeralRefs = useRef39([]);
30653
30675
  const currentPositionsRef = useRef39(null);
30654
30676
  const rollingRef = useRef39(false);
30655
30677
  const dieSetters = useRef39([]);
@@ -30679,6 +30701,15 @@ function DiceRoller({
30679
30701
  }
30680
30702
  return shadowSetters.current[i];
30681
30703
  };
30704
+ const numeralSetters = useRef39([]);
30705
+ const getNumeralSetter = (i) => {
30706
+ if (!numeralSetters.current[i]) {
30707
+ numeralSetters.current[i] = (el) => {
30708
+ numeralRefs.current[i] = el;
30709
+ };
30710
+ }
30711
+ return numeralSetters.current[i];
30712
+ };
30682
30713
  const timersRef = useRef39([]);
30683
30714
  const rafHandleRef = useRef39(null);
30684
30715
  const arenaRef = useRef39(null);
@@ -30731,15 +30762,15 @@ function DiceRoller({
30731
30762
  const pipDiameter = Math.round(effectiveSize * 0.2);
30732
30763
  const faceRadius = Math.round(effectiveSize * 0.07);
30733
30764
  const arenaHeight = arenaHeightProp ?? Math.max(effectiveSize * 3, 200);
30734
- const faceTransforms = useMemo28(
30735
- () => [
30736
- `translateZ(${half}px)`,
30737
- `rotateY(180deg) translateZ(${half}px)`,
30738
- `rotateY(-90deg) translateZ(${half}px)`,
30739
- `rotateY(90deg) translateZ(${half}px)`,
30740
- `rotateX(90deg) translateZ(${half}px)`,
30741
- `rotateX(-90deg) translateZ(${half}px)`
30742
- ],
30765
+ const faceTransformFor = useCallback44(
30766
+ (value) => {
30767
+ const p = D6_FACE_PLACEMENTS[value];
30768
+ const parts = [];
30769
+ if (p.x !== 0) parts.push(`rotateX(${p.x}deg)`);
30770
+ if (p.y !== 0) parts.push(`rotateY(${p.y}deg)`);
30771
+ parts.push(`translateZ(${half}px)`);
30772
+ return parts.join(" ");
30773
+ },
30743
30774
  [half]
30744
30775
  );
30745
30776
  const defaultPositions = useCallback44(
@@ -30761,15 +30792,23 @@ function DiceRoller({
30761
30792
  [half]
30762
30793
  );
30763
30794
  const writeCubeRotation = useCallback44(
30764
- (i, rx, ry, rz, withTransition) => {
30795
+ (i, rx, ry, rz, withTransition, tiltZ = 0) => {
30765
30796
  const el = cubeRefs.current[i];
30766
30797
  if (!el) return;
30767
30798
  el.style.transition = withTransition ? "transform 0.95s cubic-bezier(0.12,0.8,0.22,1)" : "none";
30768
- el.style.transform = `rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`;
30799
+ el.style.transform = `${tiltZ ? `rotateZ(${tiltZ}deg) ` : ""}rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`;
30769
30800
  },
30770
30801
  []
30771
30802
  );
30772
30803
  useEffect54(() => {
30804
+ if (timersRef.current.length > 0) {
30805
+ timersRef.current.forEach((t) => clearTimeout(t));
30806
+ timersRef.current = [];
30807
+ }
30808
+ if (rollingRef.current) {
30809
+ rollingRef.current = false;
30810
+ setRolling(false);
30811
+ }
30773
30812
  const rawW = arenaRef.current?.offsetWidth ?? 0;
30774
30813
  const rawH = arenaRef.current?.offsetHeight ?? 0;
30775
30814
  const w = rawW > 20 ? rawW : arena.w > 20 ? arena.w : 400;
@@ -30795,19 +30834,23 @@ function DiceRoller({
30795
30834
  setResults(null);
30796
30835
  }, [
30797
30836
  clampedCount,
30837
+ sides,
30798
30838
  effectiveSize,
30799
30839
  arena.w,
30800
30840
  arenaHeight,
30801
30841
  defaultPositions,
30802
30842
  half
30803
30843
  ]);
30844
+ useEffect54(() => {
30845
+ setHistory([]);
30846
+ }, [sides]);
30804
30847
  const roll = useCallback44(() => {
30805
30848
  if (rollingRef.current) return;
30806
30849
  rollingRef.current = true;
30807
30850
  setRolling(true);
30808
30851
  const vals = Array.from(
30809
30852
  { length: clampedCount },
30810
- () => Math.floor(Math.random() * 6) + 1
30853
+ () => Math.floor(Math.random() * sides) + 1
30811
30854
  );
30812
30855
  timersRef.current.forEach((t) => clearTimeout(t));
30813
30856
  timersRef.current = [];
@@ -30822,13 +30865,17 @@ function DiceRoller({
30822
30865
  const starts = currentPositionsRef.current ?? defaultPositions(w, h, clampedCount);
30823
30866
  const frames = simulateDice(w, h, effectiveSize, starts, clampedCount);
30824
30867
  for (let i = 0; i < clampedCount; i++) {
30825
- writeCubeRotation(
30826
- i,
30827
- (Math.random() - 0.5) * 720,
30828
- (Math.random() - 0.5) * 720,
30829
- (Math.random() - 0.5) * 720,
30830
- false
30831
- );
30868
+ if (sides === 6) {
30869
+ writeCubeRotation(
30870
+ i,
30871
+ (Math.random() - 0.5) * 720,
30872
+ (Math.random() - 0.5) * 720,
30873
+ (Math.random() - 0.5) * 720,
30874
+ false
30875
+ );
30876
+ } else {
30877
+ writeCubeRotation(i, 0, 0, (Math.random() - 0.5) * 720, false);
30878
+ }
30832
30879
  const el = dieWrapperRefs.current[i];
30833
30880
  if (el) el.style.transition = "none";
30834
30881
  const shadow = shadowRefs.current[i];
@@ -30861,18 +30908,47 @@ function DiceRoller({
30861
30908
  }, capturedStep * stepMs);
30862
30909
  timersRef.current.push(timer);
30863
30910
  }
30911
+ if (sides !== 6) {
30912
+ const flickerTicks = 8;
30913
+ const flickerStep = Math.max(60, Math.floor(TUMBLE_MS / flickerTicks));
30914
+ for (let f = 1; f <= flickerTicks; f++) {
30915
+ const t = setTimeout(() => {
30916
+ for (let i = 0; i < clampedCount; i++) {
30917
+ const el = numeralRefs.current[i];
30918
+ if (el)
30919
+ el.textContent = formatDieValue(
30920
+ Math.floor(Math.random() * sides) + 1,
30921
+ sides
30922
+ );
30923
+ }
30924
+ }, f * flickerStep);
30925
+ timersRef.current.push(t);
30926
+ }
30927
+ }
30864
30928
  requestAnimationFrame(() => {
30865
30929
  requestAnimationFrame(() => {
30866
30930
  for (let i = 0; i < clampedCount; i++) {
30867
- const tgt = SHOW_ROT[vals[i]];
30868
- const lx = tgt.x + (Math.random() > 0.5 ? 720 : -720);
30869
- const ly = tgt.y;
30870
- const lz = (Math.random() > 0.5 ? 1 : -1) * 360;
30871
- writeCubeRotation(i, lx, ly, lz, true);
30931
+ const tilt = (Math.random() - 0.5) * 18;
30932
+ if (sides === 6) {
30933
+ const tgt = d6LandingRotation(vals[i]);
30934
+ const lx = tgt.x + (Math.random() > 0.5 ? 720 : -720);
30935
+ const ly = tgt.y;
30936
+ const lz = (Math.random() > 0.5 ? 1 : -1) * 360;
30937
+ writeCubeRotation(i, lx, ly, lz, true, tilt);
30938
+ } else {
30939
+ const lz = (Math.random() > 0.5 ? 1 : -1) * 720 + tilt;
30940
+ writeCubeRotation(i, 0, 0, lz, true);
30941
+ }
30872
30942
  }
30873
30943
  });
30874
30944
  });
30875
30945
  const settleTimer = setTimeout(() => {
30946
+ if (sides !== 6) {
30947
+ for (let i = 0; i < clampedCount; i++) {
30948
+ const el = numeralRefs.current[i];
30949
+ if (el) el.textContent = formatDieValue(vals[i], sides);
30950
+ }
30951
+ }
30876
30952
  setResults(vals);
30877
30953
  setRolling(false);
30878
30954
  rollingRef.current = false;
@@ -30885,10 +30961,10 @@ function DiceRoller({
30885
30961
  timersRef.current.push(settleTimer);
30886
30962
  }, [
30887
30963
  clampedCount,
30964
+ sides,
30888
30965
  defaultPositions,
30889
30966
  effectiveSize,
30890
30967
  half,
30891
- reducedMotion,
30892
30968
  onRoll,
30893
30969
  writeCubeRotation,
30894
30970
  writeDiePosition,
@@ -30930,7 +31006,7 @@ function DiceRoller({
30930
31006
  {
30931
31007
  ref: arenaRef,
30932
31008
  role: "img",
30933
- "aria-label": ariaLabel ?? `${clampedCount} dice`,
31009
+ "aria-label": ariaLabel ?? `${clampedCount} ${sides === 6 ? "dice" : `${sides}-sided dice`}`,
30934
31010
  "aria-live": "polite",
30935
31011
  style: {
30936
31012
  position: "relative",
@@ -30993,7 +31069,7 @@ function DiceRoller({
30993
31069
  transformStyle: "preserve-3d",
30994
31070
  willChange: "transform"
30995
31071
  },
30996
- children: [1, 2, 3, 4, 5, 6].map((val, fi) => /* @__PURE__ */ jsx114(
31072
+ children: sides === 6 ? [1, 2, 3, 4, 5, 6].map((val) => /* @__PURE__ */ jsx114(
30997
31073
  "div",
30998
31074
  {
30999
31075
  style: {
@@ -31003,7 +31079,7 @@ function DiceRoller({
31003
31079
  background: colors.face,
31004
31080
  border: `1px solid ${colors.faceBorder}`,
31005
31081
  borderRadius: faceRadius,
31006
- transform: faceTransforms[fi],
31082
+ transform: faceTransformFor(val),
31007
31083
  transition: "background 0.3s, border-color 0.3s"
31008
31084
  },
31009
31085
  children: /* @__PURE__ */ jsx114(
@@ -31017,7 +31093,64 @@ function DiceRoller({
31017
31093
  )
31018
31094
  },
31019
31095
  val
31020
- ))
31096
+ )) : (() => {
31097
+ const shape = POLY_SHAPES[sides];
31098
+ const shown = results?.[d] ?? sides;
31099
+ return /* @__PURE__ */ jsxs86(
31100
+ "svg",
31101
+ {
31102
+ viewBox: "0 0 100 100",
31103
+ width: "100%",
31104
+ height: "100%",
31105
+ style: { display: "block", overflow: "visible" },
31106
+ "aria-hidden": "true",
31107
+ children: [
31108
+ /* @__PURE__ */ jsx114(
31109
+ "polygon",
31110
+ {
31111
+ points: shape.points,
31112
+ fill: colors.face,
31113
+ stroke: colors.faceBorder,
31114
+ strokeWidth: 2.5,
31115
+ strokeLinejoin: "round",
31116
+ style: { transition: "fill 0.3s, stroke 0.3s" }
31117
+ }
31118
+ ),
31119
+ /* @__PURE__ */ jsx114(
31120
+ "polygon",
31121
+ {
31122
+ points: shape.points,
31123
+ fill: "none",
31124
+ stroke: colors.faceBorder,
31125
+ strokeWidth: 1.5,
31126
+ strokeLinejoin: "round",
31127
+ opacity: 0.55,
31128
+ transform: "translate(50 50) scale(0.76) translate(-50 -50)"
31129
+ }
31130
+ ),
31131
+ /* @__PURE__ */ jsx114(
31132
+ "text",
31133
+ {
31134
+ ref: getNumeralSetter(d),
31135
+ x: 50,
31136
+ y: shape.textY,
31137
+ textAnchor: "middle",
31138
+ dominantBaseline: "central",
31139
+ fontSize: 100 * shape.fontScale,
31140
+ fontWeight: 800,
31141
+ fill: colors.pip,
31142
+ style: {
31143
+ fontVariantNumeric: "tabular-nums",
31144
+ transition: "fill 0.3s",
31145
+ userSelect: "none"
31146
+ },
31147
+ children: formatDieValue(shown, sides)
31148
+ }
31149
+ )
31150
+ ]
31151
+ }
31152
+ );
31153
+ })()
31021
31154
  }
31022
31155
  )
31023
31156
  ] }, d);
@@ -32857,6 +32990,9 @@ function CoinFlipper({
32857
32990
  }, [flipping, onFlip, historyMax]);
32858
32991
  useEffect56(() => {
32859
32992
  if (flipRef) flipRef.current = flip;
32993
+ return () => {
32994
+ if (flipRef && flipRef.current === flip) flipRef.current = null;
32995
+ };
32860
32996
  }, [flip, flipRef]);
32861
32997
  useEffect56(() => {
32862
32998
  if (autoFlip) {
@@ -33754,6 +33890,8 @@ function RandomPlayerPicker({
33754
33890
  const particleTimer = useRef42(null);
33755
33891
  const popTimer = useRef42(null);
33756
33892
  const tickInterval = useRef42(null);
33893
+ const pendingPopIdxRef = useRef42(null);
33894
+ const removeWinnersRef = useRef42(removeWinners);
33757
33895
  const tickAudioRef = useRef42(null);
33758
33896
  const winAudioRef = useRef42(null);
33759
33897
  const containerRef = useRef42(null);
@@ -33792,10 +33930,27 @@ function RandomPlayerPicker({
33792
33930
  winAudioRef.current = null;
33793
33931
  };
33794
33932
  }, [winSoundUrl, soundVolume]);
33933
+ useEffect57(() => {
33934
+ removeWinnersRef.current = removeWinners;
33935
+ }, [removeWinners]);
33795
33936
  useEffect57(() => {
33796
33937
  const ids = participants.map((p) => p.id).join("|");
33797
33938
  const structuralChange = ids !== prevIdsRef.current;
33798
33939
  if (structuralChange) {
33940
+ if (winnerTimer.current) {
33941
+ clearTimeout(winnerTimer.current);
33942
+ winnerTimer.current = null;
33943
+ }
33944
+ if (tickInterval.current) {
33945
+ clearTimeout(tickInterval.current);
33946
+ tickInterval.current = null;
33947
+ }
33948
+ if (popTimer.current) {
33949
+ clearTimeout(popTimer.current);
33950
+ popTimer.current = null;
33951
+ }
33952
+ pendingPopIdxRef.current = null;
33953
+ setSpinning(false);
33799
33954
  setPool(participants);
33800
33955
  setHistory([]);
33801
33956
  setWinner(null);
@@ -33852,33 +34007,50 @@ function RandomPlayerPicker({
33852
34007
  [dark]
33853
34008
  );
33854
34009
  const spin = useCallback47(() => {
33855
- if (spinning || n < 2) return;
34010
+ if (spinning) return;
34011
+ let activePool = pool;
34012
+ if (popTimer.current) {
34013
+ clearTimeout(popTimer.current);
34014
+ popTimer.current = null;
34015
+ const pendingIdx = pendingPopIdxRef.current;
34016
+ pendingPopIdxRef.current = null;
34017
+ if (pendingIdx != null && pendingIdx >= 0 && pendingIdx < pool.length && removeWinnersRef.current) {
34018
+ activePool = pool.filter((_, i) => i !== pendingIdx);
34019
+ setPool(activePool);
34020
+ setWinner(null);
34021
+ }
34022
+ }
34023
+ const activeN = activePool.length;
34024
+ if (activeN < 2) return;
34025
+ const activeAnglePerWedge = 2 * Math.PI / activeN;
34026
+ const activeRotation = rotation;
34027
+ const activeReelY = reelY;
33856
34028
  onSpinStart?.();
33857
- const winnerIdx = weightedIndex(pool);
34029
+ const winnerIdx = weightedIndex(activePool);
33858
34030
  const minRev = minRevolutions + intensityCfg.revsBoost;
33859
34031
  const maxRev = maxRevolutions + intensityCfg.revsBoost;
33860
34032
  const minFullRevs = Math.ceil(minRev);
33861
34033
  const maxFullRevs = Math.max(minFullRevs, Math.floor(maxRev));
33862
34034
  const revolutions = minFullRevs + Math.floor(Math.random() * (maxFullRevs - minFullRevs + 1));
33863
- const wedgeMidRad = winnerIdx * anglePerWedge + anglePerWedge / 2;
34035
+ const wedgeMidRad = winnerIdx * activeAnglePerWedge + activeAnglePerWedge / 2;
33864
34036
  const wedgeMidDeg = wedgeMidRad * 180 / Math.PI;
33865
- const wheelWobble = (Math.random() - 0.5) * (anglePerWedge * 180 / Math.PI) * 0.6;
33866
- const currentMod = (rotation % 360 + 360) % 360;
34037
+ const wheelWobble = (Math.random() - 0.5) * (activeAnglePerWedge * 180 / Math.PI) * 0.6;
34038
+ const currentMod = (activeRotation % 360 + 360) % 360;
33867
34039
  const targetMod = ((-wedgeMidDeg + wheelWobble) % 360 + 360) % 360;
33868
34040
  let deltaMod = targetMod - currentMod;
33869
34041
  if (deltaMod < 0) deltaMod += 360;
33870
- const finalRotation = rotation + deltaMod + revolutions * 360;
34042
+ const finalRotation = activeRotation + deltaMod + revolutions * 360;
33871
34043
  const centerOffsetRows = (reelVisibleRows - 1) / 2;
33872
34044
  const centerOffsetPx = centerOffsetRows * reelRowHeight;
33873
34045
  const currentCenterRow = Math.round(
33874
- (reelY + centerOffsetPx) / reelRowHeight
34046
+ (activeReelY + centerOffsetPx) / reelRowHeight
33875
34047
  );
33876
- const currentCenterMod = mod(currentCenterRow, n);
33877
- const targetRowOffset = (winnerIdx - currentCenterMod + n) % n;
33878
- const reelRevRows = Math.round(revolutions * n);
33879
- const finalReelY = reelY + (reelRevRows + targetRowOffset) * reelRowHeight;
33880
- const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, n) : wheelIndexAtPointer(finalRotation, n);
33881
- const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < pool.length ? settledWinnerIdx : winnerIdx;
34048
+ const currentCenterMod = mod(currentCenterRow, activeN);
34049
+ const targetRowOffset = (winnerIdx - currentCenterMod + activeN) % activeN;
34050
+ const reelRevRows = Math.round(revolutions * activeN);
34051
+ const finalReelY = activeReelY + (reelRevRows + targetRowOffset) * reelRowHeight;
34052
+ const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, activeN) : wheelIndexAtPointer(finalRotation, activeN);
34053
+ const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < activePool.length ? settledWinnerIdx : winnerIdx;
33882
34054
  const effectiveDuration = reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult);
33883
34055
  setSpinning(true);
33884
34056
  setWinner(null);
@@ -33891,7 +34063,7 @@ function RandomPlayerPicker({
33891
34063
  if ((onTick || tickAudioRef.current) && !reducedMotion) {
33892
34064
  const wedges = mode === "reel" ? Math.max(1, reelRevRows + targetRowOffset) : (() => {
33893
34065
  const totalDelta = deltaMod + revolutions * 360;
33894
- const wedgeDeg = anglePerWedge * 180 / Math.PI;
34066
+ const wedgeDeg = activeAnglePerWedge * 180 / Math.PI;
33895
34067
  return Math.max(
33896
34068
  1,
33897
34069
  Math.round(totalDelta / Math.max(wedgeDeg, 1))
@@ -33923,7 +34095,7 @@ function RandomPlayerPicker({
33923
34095
  schedule();
33924
34096
  }
33925
34097
  winnerTimer.current = setTimeout(() => {
33926
- const picked = pool[finalWinnerIdx];
34098
+ const picked = activePool[finalWinnerIdx];
33927
34099
  if (!picked) {
33928
34100
  setSpinning(false);
33929
34101
  return;
@@ -33969,7 +34141,11 @@ function RandomPlayerPicker({
33969
34141
  particleTimer.current = setTimeout(() => setParticles([]), 2400);
33970
34142
  }
33971
34143
  if (removeWinners) {
34144
+ pendingPopIdxRef.current = finalWinnerIdx;
33972
34145
  popTimer.current = setTimeout(() => {
34146
+ popTimer.current = null;
34147
+ pendingPopIdxRef.current = null;
34148
+ if (!removeWinnersRef.current) return;
33973
34149
  setPool((p) => p.filter((_, i) => i !== finalWinnerIdx));
33974
34150
  setWinner(null);
33975
34151
  setRotation(0);
@@ -33980,8 +34156,6 @@ function RandomPlayerPicker({
33980
34156
  }, [
33981
34157
  pool,
33982
34158
  spinning,
33983
- n,
33984
- anglePerWedge,
33985
34159
  rotation,
33986
34160
  reelY,
33987
34161
  mode,
@@ -34017,6 +34191,7 @@ function RandomPlayerPicker({
34017
34191
  []
34018
34192
  );
34019
34193
  useEffect57(() => {
34194
+ if (trigger === "external") return;
34020
34195
  const el = containerRef.current;
34021
34196
  if (!el) return;
34022
34197
  const h = (e) => {
@@ -34027,8 +34202,22 @@ function RandomPlayerPicker({
34027
34202
  };
34028
34203
  el.addEventListener("keydown", h);
34029
34204
  return () => el.removeEventListener("keydown", h);
34030
- }, [spin]);
34205
+ }, [spin, trigger]);
34031
34206
  const reset = useCallback47(() => {
34207
+ if (winnerTimer.current) {
34208
+ clearTimeout(winnerTimer.current);
34209
+ winnerTimer.current = null;
34210
+ }
34211
+ if (tickInterval.current) {
34212
+ clearTimeout(tickInterval.current);
34213
+ tickInterval.current = null;
34214
+ }
34215
+ if (popTimer.current) {
34216
+ clearTimeout(popTimer.current);
34217
+ popTimer.current = null;
34218
+ }
34219
+ pendingPopIdxRef.current = null;
34220
+ setSpinning(false);
34032
34221
  setPool(participants);
34033
34222
  setHistory([]);
34034
34223
  setWinner(null);
@@ -38501,27 +38690,28 @@ function GameTimer({
38501
38690
  const now = performance.now();
38502
38691
  const delta = now - lastTickRef.current;
38503
38692
  lastTickRef.current = now;
38504
- setElapsedMs((prev) => {
38505
- const next = prev + delta;
38506
- if (mode === "countdown" && next >= durationMs) {
38507
- setRunning(false);
38508
- setCompleted(true);
38509
- onComplete?.();
38510
- return durationMs;
38693
+ const next = elapsedRef.current + delta;
38694
+ if (mode === "countdown" && next >= durationMs) {
38695
+ elapsedRef.current = durationMs;
38696
+ setElapsedMs(durationMs);
38697
+ setRunning(false);
38698
+ setCompleted(true);
38699
+ onComplete?.();
38700
+ return;
38701
+ }
38702
+ if (mode === "countdown") {
38703
+ const secLeft = Math.ceil((durationMs - next) / 1e3);
38704
+ if (secLeft <= warningAt && !warningFiredRef.current) {
38705
+ warningFiredRef.current = true;
38706
+ onWarning?.();
38511
38707
  }
38512
- if (mode === "countdown") {
38513
- const secLeft = Math.ceil((durationMs - next) / 1e3);
38514
- if (secLeft <= warningAt && !warningFiredRef.current) {
38515
- warningFiredRef.current = true;
38516
- onWarning?.();
38517
- }
38518
- if (secLeft <= dangerAt && !dangerFiredRef.current) {
38519
- dangerFiredRef.current = true;
38520
- onDanger?.();
38521
- }
38708
+ if (secLeft <= dangerAt && !dangerFiredRef.current) {
38709
+ dangerFiredRef.current = true;
38710
+ onDanger?.();
38522
38711
  }
38523
- return next;
38524
- });
38712
+ }
38713
+ elapsedRef.current = next;
38714
+ setElapsedMs(next);
38525
38715
  rafRef.current = requestAnimationFrame(tick);
38526
38716
  }, [mode, durationMs, warningAt, dangerAt, onComplete, onWarning, onDanger]);
38527
38717
  useEffect61(() => {
@@ -38542,6 +38732,7 @@ function GameTimer({
38542
38732
  }, []);
38543
38733
  const reset = useCallback51(() => {
38544
38734
  setRunning(false);
38735
+ elapsedRef.current = 0;
38545
38736
  setElapsedMs(0);
38546
38737
  setCompleted(false);
38547
38738
  setLaps([]);
@@ -40703,6 +40894,8 @@ function RoundRobinScheduler({
40703
40894
  theme: theme5 = "slate",
40704
40895
  darkMode,
40705
40896
  minTeams = 3,
40897
+ maxTeams,
40898
+ nameMaxLength,
40706
40899
  defaultTeams = [],
40707
40900
  resumeRounds,
40708
40901
  onTeamsChange,
@@ -40725,39 +40918,41 @@ function RoundRobinScheduler({
40725
40918
  useEffect64(() => {
40726
40919
  onTeamsChange?.(teams);
40727
40920
  }, [teams, onTeamsChange]);
40728
- const addTeams = useCallback54((raw) => {
40729
- const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40730
- if (names.length === 0) return;
40731
- setTeams((prev) => {
40921
+ const mergeNames = useCallback54(
40922
+ (prev, incoming) => {
40732
40923
  const existing = new Set(prev.map((t) => t.toLowerCase()));
40733
40924
  const next = [...prev];
40734
- for (const name of names) {
40735
- if (!existing.has(name.toLowerCase())) {
40736
- existing.add(name.toLowerCase());
40737
- next.push(name);
40738
- }
40739
- }
40740
- return next;
40741
- });
40742
- setInput("");
40743
- setRounds([]);
40744
- }, []);
40745
- const importTeams = useCallback54((incoming) => {
40746
- if (incoming.length === 0) return;
40747
- setTeams((prev) => {
40748
- const existing = new Set(prev.map((t) => t.toLowerCase()));
40749
- const next = [...prev];
40750
- for (const name of incoming) {
40925
+ for (const rawName of incoming) {
40926
+ if (maxTeams != null && next.length >= maxTeams) break;
40927
+ const name = nameMaxLength != null ? rawName.slice(0, nameMaxLength) : rawName;
40751
40928
  const key = name.toLowerCase();
40752
- if (!existing.has(key)) {
40929
+ if (name.length > 0 && !existing.has(key)) {
40753
40930
  existing.add(key);
40754
40931
  next.push(name);
40755
40932
  }
40756
40933
  }
40757
40934
  return next;
40758
- });
40759
- setRounds([]);
40760
- }, []);
40935
+ },
40936
+ [maxTeams, nameMaxLength]
40937
+ );
40938
+ const addTeams = useCallback54(
40939
+ (raw) => {
40940
+ const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40941
+ if (names.length === 0) return;
40942
+ setTeams((prev) => mergeNames(prev, names));
40943
+ setInput("");
40944
+ setRounds([]);
40945
+ },
40946
+ [mergeNames]
40947
+ );
40948
+ const importTeams = useCallback54(
40949
+ (incoming) => {
40950
+ if (incoming.length === 0) return;
40951
+ setTeams((prev) => mergeNames(prev, incoming));
40952
+ setRounds([]);
40953
+ },
40954
+ [mergeNames]
40955
+ );
40761
40956
  const handleKeyDown = useCallback54(
40762
40957
  (e) => {
40763
40958
  if (e.key === "Enter") {
@@ -41251,6 +41446,8 @@ function Scoreboard({
41251
41446
  onTeamsChange,
41252
41447
  onRoundAdvance,
41253
41448
  onNewGame,
41449
+ onReset,
41450
+ nameMaxLength,
41254
41451
  labels,
41255
41452
  ariaLabel = "Scoreboard",
41256
41453
  className
@@ -41325,7 +41522,8 @@ function Scoreboard({
41325
41522
  setRound(1);
41326
41523
  setHistory([]);
41327
41524
  setHistoryOpen(false);
41328
- }, []);
41525
+ onReset?.();
41526
+ }, [onReset]);
41329
41527
  const newGame = useCallback55(() => {
41330
41528
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41331
41529
  setRound(1);
@@ -41547,6 +41745,7 @@ function Scoreboard({
41547
41745
  {
41548
41746
  type: "text",
41549
41747
  value: team.name,
41748
+ maxLength: nameMaxLength,
41550
41749
  onChange: (e) => updateName(index, e.target.value),
41551
41750
  style: nameInputStyle,
41552
41751
  "aria-label": `Team ${index + 1} name`
@@ -44885,6 +45084,7 @@ export {
44885
45084
  CommentSystem,
44886
45085
  Container,
44887
45086
  ConversionKPI,
45087
+ D6_FACE_PLACEMENTS,
44888
45088
  DISTANCES,
44889
45089
  DURATIONS,
44890
45090
  DashboardGrid,
@@ -45174,6 +45374,7 @@ export {
45174
45374
  createOptimizedStore,
45175
45375
  createSlideVariant,
45176
45376
  createTransition,
45377
+ d6LandingRotation,
45177
45378
  dailyTrendingConfig,
45178
45379
  detectAuthProvider,
45179
45380
  detectDelimiter,