@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.
@@ -30226,10 +30226,10 @@ function TiltCard({
30226
30226
  // src/components/ui/dice-roller/index.tsx
30227
30227
  import {
30228
30228
  useCallback as useCallback41,
30229
- useEffect as useEffect52,
30229
+ useEffect as useEffect51,
30230
30230
  useMemo as useMemo26,
30231
30231
  useRef as useRef40,
30232
- useState as useState67
30232
+ useState as useState66
30233
30233
  } from "react";
30234
30234
 
30235
30235
  // src/hooks/web/use-dark-mode.ts
@@ -30279,21 +30279,6 @@ function useDarkMode(forced) {
30279
30279
  return dark;
30280
30280
  }
30281
30281
 
30282
- // src/hooks/web/use-prefers-reduced-motion.ts
30283
- import { useEffect as useEffect51, useState as useState66 } from "react";
30284
- function usePrefersReducedMotion() {
30285
- const [reduced, setReduced] = useState66(false);
30286
- useEffect51(() => {
30287
- if (typeof window === "undefined" || !window.matchMedia) return;
30288
- const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
30289
- setReduced(mq.matches);
30290
- const read = () => setReduced(mq.matches);
30291
- mq.addEventListener("change", read);
30292
- return () => mq.removeEventListener("change", read);
30293
- }, []);
30294
- return reduced;
30295
- }
30296
-
30297
30282
  // src/components/ui/dice-roller/index.tsx
30298
30283
  import { Fragment as Fragment30, jsx as jsx113, jsxs as jsxs86 } from "react/jsx-runtime";
30299
30284
  var PRESETS = {
@@ -30366,14 +30351,35 @@ var PIP_POS = {
30366
30351
  [75, 75]
30367
30352
  ]
30368
30353
  };
30369
- var SHOW_ROT = {
30354
+ var D6_FACE_PLACEMENTS = {
30370
30355
  1: { x: 0, y: 0 },
30371
- 2: { x: 0, y: 180 },
30372
- 3: { x: 0, y: -90 },
30373
- 4: { x: 0, y: 90 },
30374
- 5: { x: -90, y: 0 },
30375
- 6: { x: 90, y: 0 }
30356
+ 2: { x: 0, y: 90 },
30357
+ 3: { x: 90, y: 0 },
30358
+ 4: { x: -90, y: 0 },
30359
+ 5: { x: 0, y: -90 },
30360
+ 6: { x: 0, y: 180 }
30361
+ };
30362
+ function d6LandingRotation(value) {
30363
+ const p = D6_FACE_PLACEMENTS[value];
30364
+ return {
30365
+ x: Math.abs(p.x) === 180 ? 180 : -p.x || 0,
30366
+ y: Math.abs(p.y) === 180 ? 180 : -p.y || 0
30367
+ };
30368
+ }
30369
+ var POLY_SHAPES = {
30370
+ 4: { points: "50,8 93,86 7,86", textY: 62, fontScale: 0.3 },
30371
+ 8: { points: "50,4 96,50 50,96 4,50", textY: 51, fontScale: 0.33 },
30372
+ 10: { points: "50,4 93,44 50,96 7,44", textY: 47, fontScale: 0.3 },
30373
+ 12: { points: "50,4 95,37 78,93 22,93 5,37", textY: 54, fontScale: 0.33 },
30374
+ 20: {
30375
+ points: "50,3 91,26 91,74 50,97 9,74 9,26",
30376
+ textY: 51,
30377
+ fontScale: 0.33
30378
+ }
30376
30379
  };
30380
+ function formatDieValue(v, sides) {
30381
+ return sides > 8 && (v === 6 || v === 9) ? `${v}.` : String(v);
30382
+ }
30377
30383
  var MAX_COUNT = 6;
30378
30384
  var TUMBLE_MS = 950;
30379
30385
  var SETTLE_MS = 1100;
@@ -30491,8 +30497,8 @@ function simulateDice(arenaW, arenaH, diceSize, starts, numDice) {
30491
30497
  return frames;
30492
30498
  }
30493
30499
  function useArenaBounds(ref) {
30494
- const [bounds, setBounds] = useState67({ w: 400, h: 260 });
30495
- useEffect52(() => {
30500
+ const [bounds, setBounds] = useState66({ w: 400, h: 260 });
30501
+ useEffect51(() => {
30496
30502
  if (!ref.current) return;
30497
30503
  const el = ref.current;
30498
30504
  const update = () => setBounds({ w: el.offsetWidth, h: el.offsetHeight });
@@ -30525,6 +30531,7 @@ function Face({ value, pipColor, pipShadow, pipDiameter }) {
30525
30531
  }
30526
30532
  function DiceRoller({
30527
30533
  count: count2 = 2,
30534
+ sides = 6,
30528
30535
  theme: theme5 = "red",
30529
30536
  darkMode,
30530
30537
  size = 80,
@@ -30546,13 +30553,13 @@ function DiceRoller({
30546
30553
  }) {
30547
30554
  const clampedCount = Math.max(1, Math.min(MAX_COUNT, count2));
30548
30555
  const dark = useDarkMode(darkMode);
30549
- const reducedMotion = usePrefersReducedMotion();
30550
- const [rolling, setRolling] = useState67(false);
30551
- const [results, setResults] = useState67(null);
30552
- const [history, setHistory] = useState67([]);
30556
+ const [rolling, setRolling] = useState66(false);
30557
+ const [results, setResults] = useState66(null);
30558
+ const [history, setHistory] = useState66([]);
30553
30559
  const dieWrapperRefs = useRef40([]);
30554
30560
  const cubeRefs = useRef40([]);
30555
30561
  const shadowRefs = useRef40([]);
30562
+ const numeralRefs = useRef40([]);
30556
30563
  const currentPositionsRef = useRef40(null);
30557
30564
  const rollingRef = useRef40(false);
30558
30565
  const dieSetters = useRef40([]);
@@ -30582,6 +30589,15 @@ function DiceRoller({
30582
30589
  }
30583
30590
  return shadowSetters.current[i];
30584
30591
  };
30592
+ const numeralSetters = useRef40([]);
30593
+ const getNumeralSetter = (i) => {
30594
+ if (!numeralSetters.current[i]) {
30595
+ numeralSetters.current[i] = (el) => {
30596
+ numeralRefs.current[i] = el;
30597
+ };
30598
+ }
30599
+ return numeralSetters.current[i];
30600
+ };
30585
30601
  const timersRef = useRef40([]);
30586
30602
  const rafHandleRef = useRef40(null);
30587
30603
  const arenaRef = useRef40(null);
@@ -30634,15 +30650,15 @@ function DiceRoller({
30634
30650
  const pipDiameter = Math.round(effectiveSize * 0.2);
30635
30651
  const faceRadius = Math.round(effectiveSize * 0.07);
30636
30652
  const arenaHeight = arenaHeightProp ?? Math.max(effectiveSize * 3, 200);
30637
- const faceTransforms = useMemo26(
30638
- () => [
30639
- `translateZ(${half}px)`,
30640
- `rotateY(180deg) translateZ(${half}px)`,
30641
- `rotateY(-90deg) translateZ(${half}px)`,
30642
- `rotateY(90deg) translateZ(${half}px)`,
30643
- `rotateX(90deg) translateZ(${half}px)`,
30644
- `rotateX(-90deg) translateZ(${half}px)`
30645
- ],
30653
+ const faceTransformFor = useCallback41(
30654
+ (value) => {
30655
+ const p = D6_FACE_PLACEMENTS[value];
30656
+ const parts = [];
30657
+ if (p.x !== 0) parts.push(`rotateX(${p.x}deg)`);
30658
+ if (p.y !== 0) parts.push(`rotateY(${p.y}deg)`);
30659
+ parts.push(`translateZ(${half}px)`);
30660
+ return parts.join(" ");
30661
+ },
30646
30662
  [half]
30647
30663
  );
30648
30664
  const defaultPositions = useCallback41(
@@ -30664,15 +30680,23 @@ function DiceRoller({
30664
30680
  [half]
30665
30681
  );
30666
30682
  const writeCubeRotation = useCallback41(
30667
- (i, rx, ry, rz, withTransition) => {
30683
+ (i, rx, ry, rz, withTransition, tiltZ = 0) => {
30668
30684
  const el = cubeRefs.current[i];
30669
30685
  if (!el) return;
30670
30686
  el.style.transition = withTransition ? "transform 0.95s cubic-bezier(0.12,0.8,0.22,1)" : "none";
30671
- el.style.transform = `rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`;
30687
+ el.style.transform = `${tiltZ ? `rotateZ(${tiltZ}deg) ` : ""}rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`;
30672
30688
  },
30673
30689
  []
30674
30690
  );
30675
- useEffect52(() => {
30691
+ useEffect51(() => {
30692
+ if (timersRef.current.length > 0) {
30693
+ timersRef.current.forEach((t) => clearTimeout(t));
30694
+ timersRef.current = [];
30695
+ }
30696
+ if (rollingRef.current) {
30697
+ rollingRef.current = false;
30698
+ setRolling(false);
30699
+ }
30676
30700
  const rawW = arenaRef.current?.offsetWidth ?? 0;
30677
30701
  const rawH = arenaRef.current?.offsetHeight ?? 0;
30678
30702
  const w = rawW > 20 ? rawW : arena.w > 20 ? arena.w : 400;
@@ -30698,19 +30722,23 @@ function DiceRoller({
30698
30722
  setResults(null);
30699
30723
  }, [
30700
30724
  clampedCount,
30725
+ sides,
30701
30726
  effectiveSize,
30702
30727
  arena.w,
30703
30728
  arenaHeight,
30704
30729
  defaultPositions,
30705
30730
  half
30706
30731
  ]);
30732
+ useEffect51(() => {
30733
+ setHistory([]);
30734
+ }, [sides]);
30707
30735
  const roll = useCallback41(() => {
30708
30736
  if (rollingRef.current) return;
30709
30737
  rollingRef.current = true;
30710
30738
  setRolling(true);
30711
30739
  const vals = Array.from(
30712
30740
  { length: clampedCount },
30713
- () => Math.floor(Math.random() * 6) + 1
30741
+ () => Math.floor(Math.random() * sides) + 1
30714
30742
  );
30715
30743
  timersRef.current.forEach((t) => clearTimeout(t));
30716
30744
  timersRef.current = [];
@@ -30725,13 +30753,17 @@ function DiceRoller({
30725
30753
  const starts = currentPositionsRef.current ?? defaultPositions(w, h, clampedCount);
30726
30754
  const frames = simulateDice(w, h, effectiveSize, starts, clampedCount);
30727
30755
  for (let i = 0; i < clampedCount; i++) {
30728
- writeCubeRotation(
30729
- i,
30730
- (Math.random() - 0.5) * 720,
30731
- (Math.random() - 0.5) * 720,
30732
- (Math.random() - 0.5) * 720,
30733
- false
30734
- );
30756
+ if (sides === 6) {
30757
+ writeCubeRotation(
30758
+ i,
30759
+ (Math.random() - 0.5) * 720,
30760
+ (Math.random() - 0.5) * 720,
30761
+ (Math.random() - 0.5) * 720,
30762
+ false
30763
+ );
30764
+ } else {
30765
+ writeCubeRotation(i, 0, 0, (Math.random() - 0.5) * 720, false);
30766
+ }
30735
30767
  const el = dieWrapperRefs.current[i];
30736
30768
  if (el) el.style.transition = "none";
30737
30769
  const shadow = shadowRefs.current[i];
@@ -30764,18 +30796,47 @@ function DiceRoller({
30764
30796
  }, capturedStep * stepMs);
30765
30797
  timersRef.current.push(timer);
30766
30798
  }
30799
+ if (sides !== 6) {
30800
+ const flickerTicks = 8;
30801
+ const flickerStep = Math.max(60, Math.floor(TUMBLE_MS / flickerTicks));
30802
+ for (let f = 1; f <= flickerTicks; f++) {
30803
+ const t = setTimeout(() => {
30804
+ for (let i = 0; i < clampedCount; i++) {
30805
+ const el = numeralRefs.current[i];
30806
+ if (el)
30807
+ el.textContent = formatDieValue(
30808
+ Math.floor(Math.random() * sides) + 1,
30809
+ sides
30810
+ );
30811
+ }
30812
+ }, f * flickerStep);
30813
+ timersRef.current.push(t);
30814
+ }
30815
+ }
30767
30816
  requestAnimationFrame(() => {
30768
30817
  requestAnimationFrame(() => {
30769
30818
  for (let i = 0; i < clampedCount; i++) {
30770
- const tgt = SHOW_ROT[vals[i]];
30771
- const lx = tgt.x + (Math.random() > 0.5 ? 720 : -720);
30772
- const ly = tgt.y;
30773
- const lz = (Math.random() > 0.5 ? 1 : -1) * 360;
30774
- writeCubeRotation(i, lx, ly, lz, true);
30819
+ const tilt = (Math.random() - 0.5) * 18;
30820
+ if (sides === 6) {
30821
+ const tgt = d6LandingRotation(vals[i]);
30822
+ const lx = tgt.x + (Math.random() > 0.5 ? 720 : -720);
30823
+ const ly = tgt.y;
30824
+ const lz = (Math.random() > 0.5 ? 1 : -1) * 360;
30825
+ writeCubeRotation(i, lx, ly, lz, true, tilt);
30826
+ } else {
30827
+ const lz = (Math.random() > 0.5 ? 1 : -1) * 720 + tilt;
30828
+ writeCubeRotation(i, 0, 0, lz, true);
30829
+ }
30775
30830
  }
30776
30831
  });
30777
30832
  });
30778
30833
  const settleTimer = setTimeout(() => {
30834
+ if (sides !== 6) {
30835
+ for (let i = 0; i < clampedCount; i++) {
30836
+ const el = numeralRefs.current[i];
30837
+ if (el) el.textContent = formatDieValue(vals[i], sides);
30838
+ }
30839
+ }
30779
30840
  setResults(vals);
30780
30841
  setRolling(false);
30781
30842
  rollingRef.current = false;
@@ -30788,28 +30849,28 @@ function DiceRoller({
30788
30849
  timersRef.current.push(settleTimer);
30789
30850
  }, [
30790
30851
  clampedCount,
30852
+ sides,
30791
30853
  defaultPositions,
30792
30854
  effectiveSize,
30793
30855
  half,
30794
- reducedMotion,
30795
30856
  onRoll,
30796
30857
  writeCubeRotation,
30797
30858
  writeDiePosition,
30798
30859
  showHistory,
30799
30860
  historyMax
30800
30861
  ]);
30801
- useEffect52(() => {
30862
+ useEffect51(() => {
30802
30863
  if (rollRef) rollRef.current = roll;
30803
30864
  return () => {
30804
30865
  if (rollRef && rollRef.current === roll) rollRef.current = null;
30805
30866
  };
30806
30867
  }, [roll, rollRef]);
30807
- useEffect52(() => {
30868
+ useEffect51(() => {
30808
30869
  if (!autoRoll) return;
30809
30870
  const t = setTimeout(roll, 300);
30810
30871
  return () => clearTimeout(t);
30811
30872
  }, []);
30812
- useEffect52(
30873
+ useEffect51(
30813
30874
  () => () => {
30814
30875
  timersRef.current.forEach((t) => clearTimeout(t));
30815
30876
  timersRef.current = [];
@@ -30833,7 +30894,7 @@ function DiceRoller({
30833
30894
  {
30834
30895
  ref: arenaRef,
30835
30896
  role: "img",
30836
- "aria-label": ariaLabel ?? `${clampedCount} dice`,
30897
+ "aria-label": ariaLabel ?? `${clampedCount} ${sides === 6 ? "dice" : `${sides}-sided dice`}`,
30837
30898
  "aria-live": "polite",
30838
30899
  style: {
30839
30900
  position: "relative",
@@ -30896,7 +30957,7 @@ function DiceRoller({
30896
30957
  transformStyle: "preserve-3d",
30897
30958
  willChange: "transform"
30898
30959
  },
30899
- children: [1, 2, 3, 4, 5, 6].map((val, fi) => /* @__PURE__ */ jsx113(
30960
+ children: sides === 6 ? [1, 2, 3, 4, 5, 6].map((val) => /* @__PURE__ */ jsx113(
30900
30961
  "div",
30901
30962
  {
30902
30963
  style: {
@@ -30906,7 +30967,7 @@ function DiceRoller({
30906
30967
  background: colors.face,
30907
30968
  border: `1px solid ${colors.faceBorder}`,
30908
30969
  borderRadius: faceRadius,
30909
- transform: faceTransforms[fi],
30970
+ transform: faceTransformFor(val),
30910
30971
  transition: "background 0.3s, border-color 0.3s"
30911
30972
  },
30912
30973
  children: /* @__PURE__ */ jsx113(
@@ -30920,7 +30981,64 @@ function DiceRoller({
30920
30981
  )
30921
30982
  },
30922
30983
  val
30923
- ))
30984
+ )) : (() => {
30985
+ const shape = POLY_SHAPES[sides];
30986
+ const shown = results?.[d] ?? sides;
30987
+ return /* @__PURE__ */ jsxs86(
30988
+ "svg",
30989
+ {
30990
+ viewBox: "0 0 100 100",
30991
+ width: "100%",
30992
+ height: "100%",
30993
+ style: { display: "block", overflow: "visible" },
30994
+ "aria-hidden": "true",
30995
+ children: [
30996
+ /* @__PURE__ */ jsx113(
30997
+ "polygon",
30998
+ {
30999
+ points: shape.points,
31000
+ fill: colors.face,
31001
+ stroke: colors.faceBorder,
31002
+ strokeWidth: 2.5,
31003
+ strokeLinejoin: "round",
31004
+ style: { transition: "fill 0.3s, stroke 0.3s" }
31005
+ }
31006
+ ),
31007
+ /* @__PURE__ */ jsx113(
31008
+ "polygon",
31009
+ {
31010
+ points: shape.points,
31011
+ fill: "none",
31012
+ stroke: colors.faceBorder,
31013
+ strokeWidth: 1.5,
31014
+ strokeLinejoin: "round",
31015
+ opacity: 0.55,
31016
+ transform: "translate(50 50) scale(0.76) translate(-50 -50)"
31017
+ }
31018
+ ),
31019
+ /* @__PURE__ */ jsx113(
31020
+ "text",
31021
+ {
31022
+ ref: getNumeralSetter(d),
31023
+ x: 50,
31024
+ y: shape.textY,
31025
+ textAnchor: "middle",
31026
+ dominantBaseline: "central",
31027
+ fontSize: 100 * shape.fontScale,
31028
+ fontWeight: 800,
31029
+ fill: colors.pip,
31030
+ style: {
31031
+ fontVariantNumeric: "tabular-nums",
31032
+ transition: "fill 0.3s",
31033
+ userSelect: "none"
31034
+ },
31035
+ children: formatDieValue(shown, sides)
31036
+ }
31037
+ )
31038
+ ]
31039
+ }
31040
+ );
31041
+ })()
30924
31042
  }
30925
31043
  )
30926
31044
  ] }, d);
@@ -31053,10 +31171,10 @@ DiceRoller.displayName = "DiceRoller";
31053
31171
 
31054
31172
  // src/components/ui/bracket-generator/index.tsx
31055
31173
  import {
31056
- useState as useState68,
31174
+ useState as useState67,
31057
31175
  useCallback as useCallback42,
31058
31176
  useRef as useRef41,
31059
- useEffect as useEffect53,
31177
+ useEffect as useEffect52,
31060
31178
  useMemo as useMemo27,
31061
31179
  useLayoutEffect as useLayoutEffect2
31062
31180
  } from "react";
@@ -31279,7 +31397,7 @@ function computeLayout(rounds, firstRoundCount) {
31279
31397
  };
31280
31398
  }
31281
31399
  function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31282
- const [transform, setTransform] = useState68({
31400
+ const [transform, setTransform] = useState67({
31283
31401
  x: 0,
31284
31402
  y: 0,
31285
31403
  scale: 1
@@ -31299,7 +31417,7 @@ function usePanZoom(viewportRef, contentSize, minZoom, maxZoom) {
31299
31417
  },
31300
31418
  [contentSize]
31301
31419
  );
31302
- useEffect53(() => {
31420
+ useEffect52(() => {
31303
31421
  const el = viewportRef.current;
31304
31422
  if (!el) return;
31305
31423
  const onWheel = (e) => {
@@ -31530,12 +31648,12 @@ function BracketGenerator({
31530
31648
  ariaLabel = "Tournament bracket"
31531
31649
  }) {
31532
31650
  const dark = useDarkMode(darkMode);
31533
- const [bracketState, setBracketState] = useState68({
31651
+ const [bracketState, setBracketState] = useState67({
31534
31652
  matches: [],
31535
31653
  rounds: 0,
31536
31654
  champion: null
31537
31655
  });
31538
- const [simulating, setSimulating] = useState68(false);
31656
+ const [simulating, setSimulating] = useState67(false);
31539
31657
  const autoRef = useRef41(null);
31540
31658
  const viewportRef = useRef41(null);
31541
31659
  const effectiveParticipants = useMemo27(() => {
@@ -31590,7 +31708,7 @@ function BracketGenerator({
31590
31708
  minZoom,
31591
31709
  maxZoom
31592
31710
  );
31593
- const [miniMapHidden, setMiniMapHidden] = useState68(false);
31711
+ const [miniMapHidden, setMiniMapHidden] = useState67(false);
31594
31712
  const miniMapDragRef = useRef41(false);
31595
31713
  const resumeRef = useRef41(resumeMatches);
31596
31714
  const hydrateFromResume = useCallback42(() => {
@@ -31724,7 +31842,7 @@ function BracketGenerator({
31724
31842
  []
31725
31843
  );
31726
31844
  const prevBracketStateRef = useRef41(bracketState);
31727
- useEffect53(() => {
31845
+ useEffect52(() => {
31728
31846
  const prev = prevBracketStateRef.current;
31729
31847
  const next = bracketState;
31730
31848
  if (prev === next) return;
@@ -31748,7 +31866,7 @@ function BracketGenerator({
31748
31866
  [effectiveParticipants, seedingStrategy]
31749
31867
  );
31750
31868
  const seededKeyRef = useRef41(null);
31751
- useEffect53(() => {
31869
+ useEffect52(() => {
31752
31870
  if (seededKeyRef.current === structuralKey) {
31753
31871
  setBracketState((prev) => {
31754
31872
  if (!prev.matches.length) return prev;
@@ -31779,7 +31897,7 @@ function BracketGenerator({
31779
31897
  seedBracket();
31780
31898
  }
31781
31899
  }, [structuralKey, effectiveParticipants, hydrateFromResume, seedBracket]);
31782
- useEffect53(() => {
31900
+ useEffect52(() => {
31783
31901
  return () => {
31784
31902
  if (autoRef.current) clearTimeout(autoRef.current);
31785
31903
  };
@@ -31790,7 +31908,7 @@ function BracketGenerator({
31790
31908
  return () => clearTimeout(timer);
31791
31909
  }
31792
31910
  }, [contentSize.w]);
31793
- useEffect53(() => {
31911
+ useEffect52(() => {
31794
31912
  if (autoSimulate && bracketState.matches.length && !bracketState.champion) {
31795
31913
  const timer = setTimeout(autoPlay, 500);
31796
31914
  return () => clearTimeout(timer);
@@ -32511,10 +32629,10 @@ function BracketGenerator({
32511
32629
  // src/components/ui/coin-flipper/index.tsx
32512
32630
  import {
32513
32631
  useCallback as useCallback43,
32514
- useEffect as useEffect54,
32632
+ useEffect as useEffect53,
32515
32633
  useMemo as useMemo28,
32516
32634
  useRef as useRef42,
32517
- useState as useState69
32635
+ useState as useState68
32518
32636
  } from "react";
32519
32637
  import { jsx as jsx115, jsxs as jsxs88 } from "react/jsx-runtime";
32520
32638
  var PRESETS3 = {
@@ -32699,10 +32817,10 @@ function CoinFlipper({
32699
32817
  }) {
32700
32818
  const dark = useDarkMode(darkMode);
32701
32819
  const tc = typeof theme5 === "string" ? PRESETS3[theme5] || PRESETS3.gold : theme5;
32702
- const [result, setResult] = useState69(null);
32703
- const [flipping, setFlipping] = useState69(false);
32704
- const [history, setHistory] = useState69([]);
32705
- const [stats, setStats] = useState69({ heads: 0, tails: 0 });
32820
+ const [result, setResult] = useState68(null);
32821
+ const [flipping, setFlipping] = useState68(false);
32822
+ const [history, setHistory] = useState68([]);
32823
+ const [stats, setStats] = useState68({ heads: 0, tails: 0 });
32706
32824
  const flipIdRef = useRef42(0);
32707
32825
  const coinRef = useRef42(null);
32708
32826
  const shadowRef = useRef42(null);
@@ -32758,16 +32876,19 @@ function CoinFlipper({
32758
32876
  }, frames.length * stepMs);
32759
32877
  timersRef.current.push(settleTimer);
32760
32878
  }, [flipping, onFlip, historyMax]);
32761
- useEffect54(() => {
32879
+ useEffect53(() => {
32762
32880
  if (flipRef) flipRef.current = flip;
32881
+ return () => {
32882
+ if (flipRef && flipRef.current === flip) flipRef.current = null;
32883
+ };
32763
32884
  }, [flip, flipRef]);
32764
- useEffect54(() => {
32885
+ useEffect53(() => {
32765
32886
  if (autoFlip) {
32766
32887
  const timer = setTimeout(flip, 300);
32767
32888
  return () => clearTimeout(timer);
32768
32889
  }
32769
32890
  }, []);
32770
- useEffect54(() => {
32891
+ useEffect53(() => {
32771
32892
  return () => {
32772
32893
  timersRef.current.forEach(clearTimeout);
32773
32894
  };
@@ -33210,6 +33331,23 @@ import {
33210
33331
  useRef as useRef43,
33211
33332
  useState as useState70
33212
33333
  } from "react";
33334
+
33335
+ // src/hooks/web/use-prefers-reduced-motion.ts
33336
+ import { useEffect as useEffect54, useState as useState69 } from "react";
33337
+ function usePrefersReducedMotion() {
33338
+ const [reduced, setReduced] = useState69(false);
33339
+ useEffect54(() => {
33340
+ if (typeof window === "undefined" || !window.matchMedia) return;
33341
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
33342
+ setReduced(mq.matches);
33343
+ const read = () => setReduced(mq.matches);
33344
+ mq.addEventListener("change", read);
33345
+ return () => mq.removeEventListener("change", read);
33346
+ }, []);
33347
+ return reduced;
33348
+ }
33349
+
33350
+ // src/components/ui/random-player-picker/index.tsx
33213
33351
  import { Fragment as Fragment31, jsx as jsx116, jsxs as jsxs89 } from "react/jsx-runtime";
33214
33352
  var PRESETS4 = {
33215
33353
  blue: {
@@ -33657,6 +33795,8 @@ function RandomPlayerPicker({
33657
33795
  const particleTimer = useRef43(null);
33658
33796
  const popTimer = useRef43(null);
33659
33797
  const tickInterval = useRef43(null);
33798
+ const pendingPopIdxRef = useRef43(null);
33799
+ const removeWinnersRef = useRef43(removeWinners);
33660
33800
  const tickAudioRef = useRef43(null);
33661
33801
  const winAudioRef = useRef43(null);
33662
33802
  const containerRef = useRef43(null);
@@ -33695,10 +33835,27 @@ function RandomPlayerPicker({
33695
33835
  winAudioRef.current = null;
33696
33836
  };
33697
33837
  }, [winSoundUrl, soundVolume]);
33838
+ useEffect55(() => {
33839
+ removeWinnersRef.current = removeWinners;
33840
+ }, [removeWinners]);
33698
33841
  useEffect55(() => {
33699
33842
  const ids = participants.map((p) => p.id).join("|");
33700
33843
  const structuralChange = ids !== prevIdsRef.current;
33701
33844
  if (structuralChange) {
33845
+ if (winnerTimer.current) {
33846
+ clearTimeout(winnerTimer.current);
33847
+ winnerTimer.current = null;
33848
+ }
33849
+ if (tickInterval.current) {
33850
+ clearTimeout(tickInterval.current);
33851
+ tickInterval.current = null;
33852
+ }
33853
+ if (popTimer.current) {
33854
+ clearTimeout(popTimer.current);
33855
+ popTimer.current = null;
33856
+ }
33857
+ pendingPopIdxRef.current = null;
33858
+ setSpinning(false);
33702
33859
  setPool(participants);
33703
33860
  setHistory([]);
33704
33861
  setWinner(null);
@@ -33755,33 +33912,50 @@ function RandomPlayerPicker({
33755
33912
  [dark]
33756
33913
  );
33757
33914
  const spin = useCallback44(() => {
33758
- if (spinning || n < 2) return;
33915
+ if (spinning) return;
33916
+ let activePool = pool;
33917
+ if (popTimer.current) {
33918
+ clearTimeout(popTimer.current);
33919
+ popTimer.current = null;
33920
+ const pendingIdx = pendingPopIdxRef.current;
33921
+ pendingPopIdxRef.current = null;
33922
+ if (pendingIdx != null && pendingIdx >= 0 && pendingIdx < pool.length && removeWinnersRef.current) {
33923
+ activePool = pool.filter((_, i) => i !== pendingIdx);
33924
+ setPool(activePool);
33925
+ setWinner(null);
33926
+ }
33927
+ }
33928
+ const activeN = activePool.length;
33929
+ if (activeN < 2) return;
33930
+ const activeAnglePerWedge = 2 * Math.PI / activeN;
33931
+ const activeRotation = rotation;
33932
+ const activeReelY = reelY;
33759
33933
  onSpinStart?.();
33760
- const winnerIdx = weightedIndex(pool);
33934
+ const winnerIdx = weightedIndex(activePool);
33761
33935
  const minRev = minRevolutions + intensityCfg.revsBoost;
33762
33936
  const maxRev = maxRevolutions + intensityCfg.revsBoost;
33763
33937
  const minFullRevs = Math.ceil(minRev);
33764
33938
  const maxFullRevs = Math.max(minFullRevs, Math.floor(maxRev));
33765
33939
  const revolutions = minFullRevs + Math.floor(Math.random() * (maxFullRevs - minFullRevs + 1));
33766
- const wedgeMidRad = winnerIdx * anglePerWedge + anglePerWedge / 2;
33940
+ const wedgeMidRad = winnerIdx * activeAnglePerWedge + activeAnglePerWedge / 2;
33767
33941
  const wedgeMidDeg = wedgeMidRad * 180 / Math.PI;
33768
- const wheelWobble = (Math.random() - 0.5) * (anglePerWedge * 180 / Math.PI) * 0.6;
33769
- const currentMod = (rotation % 360 + 360) % 360;
33942
+ const wheelWobble = (Math.random() - 0.5) * (activeAnglePerWedge * 180 / Math.PI) * 0.6;
33943
+ const currentMod = (activeRotation % 360 + 360) % 360;
33770
33944
  const targetMod = ((-wedgeMidDeg + wheelWobble) % 360 + 360) % 360;
33771
33945
  let deltaMod = targetMod - currentMod;
33772
33946
  if (deltaMod < 0) deltaMod += 360;
33773
- const finalRotation = rotation + deltaMod + revolutions * 360;
33947
+ const finalRotation = activeRotation + deltaMod + revolutions * 360;
33774
33948
  const centerOffsetRows = (reelVisibleRows - 1) / 2;
33775
33949
  const centerOffsetPx = centerOffsetRows * reelRowHeight;
33776
33950
  const currentCenterRow = Math.round(
33777
- (reelY + centerOffsetPx) / reelRowHeight
33951
+ (activeReelY + centerOffsetPx) / reelRowHeight
33778
33952
  );
33779
- const currentCenterMod = mod(currentCenterRow, n);
33780
- const targetRowOffset = (winnerIdx - currentCenterMod + n) % n;
33781
- const reelRevRows = Math.round(revolutions * n);
33782
- const finalReelY = reelY + (reelRevRows + targetRowOffset) * reelRowHeight;
33783
- const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, n) : wheelIndexAtPointer(finalRotation, n);
33784
- const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < pool.length ? settledWinnerIdx : winnerIdx;
33953
+ const currentCenterMod = mod(currentCenterRow, activeN);
33954
+ const targetRowOffset = (winnerIdx - currentCenterMod + activeN) % activeN;
33955
+ const reelRevRows = Math.round(revolutions * activeN);
33956
+ const finalReelY = activeReelY + (reelRevRows + targetRowOffset) * reelRowHeight;
33957
+ const settledWinnerIdx = mode === "reel" ? reelIndexAtCenter(finalReelY, centerOffsetPx, reelRowHeight, activeN) : wheelIndexAtPointer(finalRotation, activeN);
33958
+ const finalWinnerIdx = settledWinnerIdx >= 0 && settledWinnerIdx < activePool.length ? settledWinnerIdx : winnerIdx;
33785
33959
  const effectiveDuration = reducedMotion ? 250 : Math.round(spinDuration * intensityCfg.durationMult);
33786
33960
  setSpinning(true);
33787
33961
  setWinner(null);
@@ -33794,7 +33968,7 @@ function RandomPlayerPicker({
33794
33968
  if ((onTick || tickAudioRef.current) && !reducedMotion) {
33795
33969
  const wedges = mode === "reel" ? Math.max(1, reelRevRows + targetRowOffset) : (() => {
33796
33970
  const totalDelta = deltaMod + revolutions * 360;
33797
- const wedgeDeg = anglePerWedge * 180 / Math.PI;
33971
+ const wedgeDeg = activeAnglePerWedge * 180 / Math.PI;
33798
33972
  return Math.max(
33799
33973
  1,
33800
33974
  Math.round(totalDelta / Math.max(wedgeDeg, 1))
@@ -33826,7 +34000,7 @@ function RandomPlayerPicker({
33826
34000
  schedule();
33827
34001
  }
33828
34002
  winnerTimer.current = setTimeout(() => {
33829
- const picked = pool[finalWinnerIdx];
34003
+ const picked = activePool[finalWinnerIdx];
33830
34004
  if (!picked) {
33831
34005
  setSpinning(false);
33832
34006
  return;
@@ -33872,7 +34046,11 @@ function RandomPlayerPicker({
33872
34046
  particleTimer.current = setTimeout(() => setParticles([]), 2400);
33873
34047
  }
33874
34048
  if (removeWinners) {
34049
+ pendingPopIdxRef.current = finalWinnerIdx;
33875
34050
  popTimer.current = setTimeout(() => {
34051
+ popTimer.current = null;
34052
+ pendingPopIdxRef.current = null;
34053
+ if (!removeWinnersRef.current) return;
33876
34054
  setPool((p) => p.filter((_, i) => i !== finalWinnerIdx));
33877
34055
  setWinner(null);
33878
34056
  setRotation(0);
@@ -33883,8 +34061,6 @@ function RandomPlayerPicker({
33883
34061
  }, [
33884
34062
  pool,
33885
34063
  spinning,
33886
- n,
33887
- anglePerWedge,
33888
34064
  rotation,
33889
34065
  reelY,
33890
34066
  mode,
@@ -33920,6 +34096,7 @@ function RandomPlayerPicker({
33920
34096
  []
33921
34097
  );
33922
34098
  useEffect55(() => {
34099
+ if (trigger === "external") return;
33923
34100
  const el = containerRef.current;
33924
34101
  if (!el) return;
33925
34102
  const h = (e) => {
@@ -33930,8 +34107,22 @@ function RandomPlayerPicker({
33930
34107
  };
33931
34108
  el.addEventListener("keydown", h);
33932
34109
  return () => el.removeEventListener("keydown", h);
33933
- }, [spin]);
34110
+ }, [spin, trigger]);
33934
34111
  const reset = useCallback44(() => {
34112
+ if (winnerTimer.current) {
34113
+ clearTimeout(winnerTimer.current);
34114
+ winnerTimer.current = null;
34115
+ }
34116
+ if (tickInterval.current) {
34117
+ clearTimeout(tickInterval.current);
34118
+ tickInterval.current = null;
34119
+ }
34120
+ if (popTimer.current) {
34121
+ clearTimeout(popTimer.current);
34122
+ popTimer.current = null;
34123
+ }
34124
+ pendingPopIdxRef.current = null;
34125
+ setSpinning(false);
33935
34126
  setPool(participants);
33936
34127
  setHistory([]);
33937
34128
  setWinner(null);
@@ -38404,27 +38595,28 @@ function GameTimer({
38404
38595
  const now = performance.now();
38405
38596
  const delta = now - lastTickRef.current;
38406
38597
  lastTickRef.current = now;
38407
- setElapsedMs((prev) => {
38408
- const next = prev + delta;
38409
- if (mode === "countdown" && next >= durationMs) {
38410
- setRunning(false);
38411
- setCompleted(true);
38412
- onComplete?.();
38413
- return durationMs;
38598
+ const next = elapsedRef.current + delta;
38599
+ if (mode === "countdown" && next >= durationMs) {
38600
+ elapsedRef.current = durationMs;
38601
+ setElapsedMs(durationMs);
38602
+ setRunning(false);
38603
+ setCompleted(true);
38604
+ onComplete?.();
38605
+ return;
38606
+ }
38607
+ if (mode === "countdown") {
38608
+ const secLeft = Math.ceil((durationMs - next) / 1e3);
38609
+ if (secLeft <= warningAt && !warningFiredRef.current) {
38610
+ warningFiredRef.current = true;
38611
+ onWarning?.();
38414
38612
  }
38415
- if (mode === "countdown") {
38416
- const secLeft = Math.ceil((durationMs - next) / 1e3);
38417
- if (secLeft <= warningAt && !warningFiredRef.current) {
38418
- warningFiredRef.current = true;
38419
- onWarning?.();
38420
- }
38421
- if (secLeft <= dangerAt && !dangerFiredRef.current) {
38422
- dangerFiredRef.current = true;
38423
- onDanger?.();
38424
- }
38613
+ if (secLeft <= dangerAt && !dangerFiredRef.current) {
38614
+ dangerFiredRef.current = true;
38615
+ onDanger?.();
38425
38616
  }
38426
- return next;
38427
- });
38617
+ }
38618
+ elapsedRef.current = next;
38619
+ setElapsedMs(next);
38428
38620
  rafRef.current = requestAnimationFrame(tick);
38429
38621
  }, [mode, durationMs, warningAt, dangerAt, onComplete, onWarning, onDanger]);
38430
38622
  useEffect59(() => {
@@ -38445,6 +38637,7 @@ function GameTimer({
38445
38637
  }, []);
38446
38638
  const reset = useCallback48(() => {
38447
38639
  setRunning(false);
38640
+ elapsedRef.current = 0;
38448
38641
  setElapsedMs(0);
38449
38642
  setCompleted(false);
38450
38643
  setLaps([]);
@@ -40401,6 +40594,8 @@ function RoundRobinScheduler({
40401
40594
  theme: theme5 = "slate",
40402
40595
  darkMode,
40403
40596
  minTeams = 3,
40597
+ maxTeams,
40598
+ nameMaxLength,
40404
40599
  defaultTeams = [],
40405
40600
  resumeRounds,
40406
40601
  onTeamsChange,
@@ -40423,39 +40618,41 @@ function RoundRobinScheduler({
40423
40618
  useEffect62(() => {
40424
40619
  onTeamsChange?.(teams);
40425
40620
  }, [teams, onTeamsChange]);
40426
- const addTeams = useCallback51((raw) => {
40427
- const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40428
- if (names.length === 0) return;
40429
- setTeams((prev) => {
40621
+ const mergeNames = useCallback51(
40622
+ (prev, incoming) => {
40430
40623
  const existing = new Set(prev.map((t) => t.toLowerCase()));
40431
40624
  const next = [...prev];
40432
- for (const name of names) {
40433
- if (!existing.has(name.toLowerCase())) {
40434
- existing.add(name.toLowerCase());
40435
- next.push(name);
40436
- }
40437
- }
40438
- return next;
40439
- });
40440
- setInput("");
40441
- setRounds([]);
40442
- }, []);
40443
- const importTeams = useCallback51((incoming) => {
40444
- if (incoming.length === 0) return;
40445
- setTeams((prev) => {
40446
- const existing = new Set(prev.map((t) => t.toLowerCase()));
40447
- const next = [...prev];
40448
- for (const name of incoming) {
40625
+ for (const rawName of incoming) {
40626
+ if (maxTeams != null && next.length >= maxTeams) break;
40627
+ const name = nameMaxLength != null ? rawName.slice(0, nameMaxLength) : rawName;
40449
40628
  const key = name.toLowerCase();
40450
- if (!existing.has(key)) {
40629
+ if (name.length > 0 && !existing.has(key)) {
40451
40630
  existing.add(key);
40452
40631
  next.push(name);
40453
40632
  }
40454
40633
  }
40455
40634
  return next;
40456
- });
40457
- setRounds([]);
40458
- }, []);
40635
+ },
40636
+ [maxTeams, nameMaxLength]
40637
+ );
40638
+ const addTeams = useCallback51(
40639
+ (raw) => {
40640
+ const names = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40641
+ if (names.length === 0) return;
40642
+ setTeams((prev) => mergeNames(prev, names));
40643
+ setInput("");
40644
+ setRounds([]);
40645
+ },
40646
+ [mergeNames]
40647
+ );
40648
+ const importTeams = useCallback51(
40649
+ (incoming) => {
40650
+ if (incoming.length === 0) return;
40651
+ setTeams((prev) => mergeNames(prev, incoming));
40652
+ setRounds([]);
40653
+ },
40654
+ [mergeNames]
40655
+ );
40459
40656
  const handleKeyDown = useCallback51(
40460
40657
  (e) => {
40461
40658
  if (e.key === "Enter") {
@@ -40949,6 +41146,8 @@ function Scoreboard({
40949
41146
  onTeamsChange,
40950
41147
  onRoundAdvance,
40951
41148
  onNewGame,
41149
+ onReset,
41150
+ nameMaxLength,
40952
41151
  labels,
40953
41152
  ariaLabel = "Scoreboard",
40954
41153
  className
@@ -41023,7 +41222,8 @@ function Scoreboard({
41023
41222
  setRound(1);
41024
41223
  setHistory([]);
41025
41224
  setHistoryOpen(false);
41026
- }, []);
41225
+ onReset?.();
41226
+ }, [onReset]);
41027
41227
  const newGame = useCallback52(() => {
41028
41228
  setTeams(createDefaultTeams(seedCount, L.teamNamePrefix));
41029
41229
  setRound(1);
@@ -41245,6 +41445,7 @@ function Scoreboard({
41245
41445
  {
41246
41446
  type: "text",
41247
41447
  value: team.name,
41448
+ maxLength: nameMaxLength,
41248
41449
  onChange: (e) => updateName(index, e.target.value),
41249
41450
  style: nameInputStyle,
41250
41451
  "aria-label": `Team ${index + 1} name`
@@ -43343,6 +43544,7 @@ export {
43343
43544
  CommentSystem,
43344
43545
  Container,
43345
43546
  ConversionKPI,
43547
+ D6_FACE_PLACEMENTS,
43346
43548
  DashboardGrid,
43347
43549
  DashboardLayout,
43348
43550
  DashboardWidget,
@@ -43583,6 +43785,7 @@ export {
43583
43785
  createFormStore,
43584
43786
  createOptimizedStore,
43585
43787
  cssAnimations,
43788
+ d6LandingRotation,
43586
43789
  dailyTrendingConfig,
43587
43790
  detectAuthProvider,
43588
43791
  detectDelimiter,