@fab1o978/react-ui 0.1.3 → 0.1.5

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.
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/components/SlidingCounter/SlidingCounter.tsx
7
+
8
+ // src/components/SlidingCounter/SlidingCounter.module.scss
9
+ var SlidingCounter_module_default = {
10
+ counter: "SlidingCounter_module_counter2",
11
+ sign: "SlidingCounter_module_sign2",
12
+ separator: "SlidingCounter_module_separator2",
13
+ reelWindow: "SlidingCounter_module_reelWindow2",
14
+ sm: "SlidingCounter_module_sm2",
15
+ md: "SlidingCounter_module_md2",
16
+ lg: "SlidingCounter_module_lg2",
17
+ reel: "SlidingCounter_module_reel2",
18
+ digitCell: "SlidingCounter_module_digitCell2"
19
+ };
20
+
21
+ // src/utils/color.ts
22
+ function hexToHsl(hex) {
23
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
24
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
25
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
26
+ const max = Math.max(r, g, b);
27
+ const min = Math.min(r, g, b);
28
+ const l = (max + min) / 2;
29
+ let h = 0;
30
+ let s = 0;
31
+ if (max !== min) {
32
+ const d = max - min;
33
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
34
+ switch (max) {
35
+ case r:
36
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
37
+ break;
38
+ case g:
39
+ h = ((b - r) / d + 2) / 6;
40
+ break;
41
+ case b:
42
+ h = ((r - g) / d + 4) / 6;
43
+ break;
44
+ }
45
+ }
46
+ return [h * 360, s * 100, l * 100];
47
+ }
48
+ function hslToHex(h, s, l) {
49
+ const sl = s / 100;
50
+ const ll = l / 100;
51
+ const a = sl * Math.min(ll, 1 - ll);
52
+ const f = (n) => {
53
+ const k = (n + h / 30) % 12;
54
+ const color = ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
55
+ return Math.round(255 * color).toString(16).padStart(2, "0");
56
+ };
57
+ return `#${f(0)}${f(8)}${f(4)}`;
58
+ }
59
+ function hslToRgb(h, s, l) {
60
+ const sl = s / 100;
61
+ const ll = l / 100;
62
+ const a = sl * Math.min(ll, 1 - ll);
63
+ const f = (n) => {
64
+ const k = (n + h / 30) % 12;
65
+ return Math.round(255 * (ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)));
66
+ };
67
+ return { r: f(0), g: f(8), b: f(4) };
68
+ }
69
+ function hslToRgba(h, s, l, alpha) {
70
+ const { r, g, b } = hslToRgb(h, s, l);
71
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
72
+ }
73
+ function deriveAccent(hex) {
74
+ const [h, s, l] = hexToHsl(hex);
75
+ return {
76
+ base: hex,
77
+ dot: hslToHex(h, s, Math.min(l + 12, 95)),
78
+ track: hex,
79
+ ring: hex,
80
+ glow: hslToRgba(h, s, l, 0.3),
81
+ light: hslToHex(h, Math.max(s - 20, 0), Math.min(l + 45, 96))
82
+ };
83
+ }
84
+ function accentToCssVars(tokens, prefix) {
85
+ return {
86
+ [`--${prefix}-accent`]: tokens.base,
87
+ [`--${prefix}-accent-dot`]: tokens.dot,
88
+ [`--${prefix}-accent-track`]: tokens.track,
89
+ [`--${prefix}-accent-ring`]: tokens.ring,
90
+ [`--${prefix}-accent-glow`]: tokens.glow,
91
+ [`--${prefix}-accent-light`]: tokens.light
92
+ };
93
+ }
94
+ var COPIES = 3;
95
+ var TOTAL = COPIES * 10;
96
+ var DigitReel = ({ digit, direction, size }) => {
97
+ const posRef = react.useRef(10 + digit);
98
+ const prevDigitRef = react.useRef(digit);
99
+ const [pos, setPos] = react.useState(10 + digit);
100
+ const [animate, setAnimate] = react.useState(true);
101
+ react.useEffect(() => {
102
+ if (prevDigitRef.current === digit) return;
103
+ const prev = prevDigitRef.current;
104
+ let next = posRef.current;
105
+ if (direction === "up") {
106
+ next += digit > prev ? digit - prev : 10 - prev + digit;
107
+ } else {
108
+ next -= digit < prev ? prev - digit : prev + 10 - digit;
109
+ }
110
+ posRef.current = next;
111
+ prevDigitRef.current = digit;
112
+ setAnimate(true);
113
+ setPos(next);
114
+ }, [digit, direction]);
115
+ const handleTransitionEnd = () => {
116
+ const normalized = 10 + (posRef.current % 10 + 10) % 10;
117
+ posRef.current = normalized;
118
+ setAnimate(false);
119
+ setPos(normalized);
120
+ };
121
+ const translateY = -(pos / TOTAL) * 100;
122
+ const cells = Array.from(
123
+ { length: COPIES },
124
+ () => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
125
+ ).flat();
126
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${SlidingCounter_module_default.reelWindow} ${SlidingCounter_module_default[size]}`, children: /* @__PURE__ */ jsxRuntime.jsx(
127
+ "div",
128
+ {
129
+ className: SlidingCounter_module_default.reel,
130
+ style: {
131
+ transform: `translateY(${translateY}%)`,
132
+ transition: animate ? void 0 : "none"
133
+ },
134
+ onTransitionEnd: handleTransitionEnd,
135
+ children: cells.map((d, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: SlidingCounter_module_default.digitCell, children: d }, i))
136
+ }
137
+ ) });
138
+ };
139
+ function splitDigits(value, minDigits, decimals) {
140
+ const abs = Math.abs(value);
141
+ const fixed = abs.toFixed(decimals);
142
+ const [intPart, decPart = ""] = fixed.split(".");
143
+ const intDigits = intPart.split("").map(Number);
144
+ const padded = intDigits.length < minDigits ? [...Array(minDigits - intDigits.length).fill(0), ...intDigits] : intDigits;
145
+ const decDigits = decPart.split("").map(Number);
146
+ return { intDigits: padded, decDigits };
147
+ }
148
+ var SlidingCounter = ({
149
+ value,
150
+ minDigits = 1,
151
+ decimals = 0,
152
+ decimalSeparator = ".",
153
+ size = "md",
154
+ accent,
155
+ className
156
+ }) => {
157
+ const accentVars = accent ? accentToCssVars(deriveAccent(accent), "sliding-counter") : {};
158
+ const safeValue = isFinite(value) ? value : 0;
159
+ const prevValueRef = react.useRef(safeValue);
160
+ const direction = safeValue >= prevValueRef.current ? "up" : "down";
161
+ prevValueRef.current = safeValue;
162
+ const { intDigits, decDigits } = splitDigits(safeValue, minDigits, decimals);
163
+ return /* @__PURE__ */ jsxRuntime.jsxs(
164
+ "div",
165
+ {
166
+ className: [SlidingCounter_module_default.counter, SlidingCounter_module_default[size], className ?? ""].filter(Boolean).join(" "),
167
+ style: accentVars,
168
+ "aria-label": safeValue.toFixed(decimals),
169
+ children: [
170
+ safeValue < 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: SlidingCounter_module_default.sign, children: "\u2212" }),
171
+ intDigits.map((digit, i) => /* @__PURE__ */ jsxRuntime.jsx(DigitReel, { digit, direction, size }, `int-${intDigits.length - i}`)),
172
+ decimals > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: SlidingCounter_module_default.separator, children: decimalSeparator }),
173
+ decDigits.map((digit, i) => /* @__PURE__ */ jsxRuntime.jsx(DigitReel, { digit, direction, size }, `dec-${i}`))
174
+ ]
175
+ }
176
+ );
177
+ };
178
+
179
+ exports.SlidingCounter = SlidingCounter;
180
+ //# sourceMappingURL=index.cjs.map
181
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SlidingCounter/SlidingCounter.module.scss","../../../src/utils/color.ts","../../../src/components/SlidingCounter/SlidingCounter.tsx"],"names":["counter","sign","separator","reelWindow","sm","md","lg","reel","digitCell","useRef","useState","useEffect","jsx","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["@import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\");\n:root {\n --accent: #3b82f6;\n --accent-dot: rgb(82.52, 145, 247.08);\n --accent-track: #3b82f6;\n --accent-ring: #3b82f6;\n --accent-glow: rgba(59, 130, 246, 0.3);\n --accent-light: rgb(157.485, 188.725, 239.765);\n --color-primary-50: rgb(169.3455, 194.6925, 236.1045);\n --color-primary-100: rgb(149.4545, 184.4575, 241.6455);\n --color-primary-400: rgb(82.52, 145, 247.08);\n --color-primary-500: #3b82f6;\n --color-primary-600: rgb(29.8390243902, 111.4024390244, 244.6609756098);\n --color-primary-700: rgb(10.712195122, 95.2195121951, 233.287804878);\n --color-neutral-0: #ffffff;\n --color-neutral-100: #f3f4f6;\n --color-neutral-200: #e5e7eb;\n --color-neutral-400: #9ca3af;\n --color-neutral-600: #4b5563;\n --color-neutral-900: #111827;\n --color-success-500: #22c55e;\n --color-success-600: #16a34a;\n --color-danger-500: #ef4444;\n --color-danger-600: #dc2626;\n --color-warning-500: #f59e0b;\n --color-warning-600: #d97706;\n --font-family-base: \"Inter\", system-ui, -apple-system, sans-serif;\n --font-size-xs: 0.75rem;\n --font-size-sm: 0.875rem;\n --font-size-md: 1rem;\n --font-size-lg: 1.125rem;\n --spacing-1: 0.25rem;\n --spacing-2: 0.5rem;\n --spacing-3: 0.75rem;\n --spacing-4: 1rem;\n --radius-sm: 0.25rem;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --radius-full: 9999px;\n --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n --transition-fast: 150ms ease;\n --color-surface: #ffffff;\n --color-surface-elevated: #ffffff;\n}\n\n[data-theme=dark] {\n --color-neutral-0: #0f172a;\n --color-neutral-100: #1e293b;\n --color-neutral-200: #334155;\n --color-neutral-400: #94a3b8;\n --color-neutral-600: #cbd5e1;\n --color-neutral-900: #f8fafc;\n --color-surface: #1e293b;\n --color-surface-elevated: #273549;\n --color-primary-50: #172554;\n --color-primary-100: #1e3a8a;\n --accent-light: #172554;\n --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.4);\n --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.5), 0 2px 4px -2px rgb(0 0 0 / 0.4);\n}\n\n.counter {\n display: inline-flex;\n align-items: center;\n gap: 1px;\n font-family: var(--font-family-base);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: var(--sliding-counter-accent-dot, var(--accent-dot));\n user-select: none;\n}\n\n.sign {\n line-height: 1;\n margin-right: 2px;\n}\n\n.separator {\n line-height: 1;\n margin: 0 1px;\n opacity: 0.5;\n align-self: flex-end;\n padding-bottom: 0.5em;\n}\n\n.reelWindow {\n overflow: hidden;\n position: relative;\n}\n.reelWindow.sm {\n height: 1.6rem;\n width: 0.85rem;\n font-size: 1.25rem;\n line-height: 1.6rem;\n}\n.reelWindow.md {\n height: 2.6rem;\n width: 1.35rem;\n font-size: 2rem;\n line-height: 2.6rem;\n}\n.reelWindow.lg {\n height: 3.8rem;\n width: 2rem;\n font-size: 3rem;\n line-height: 3.8rem;\n}\n\n.reel {\n display: flex;\n flex-direction: column;\n transition: transform 420ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.digitCell {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.reelWindow.sm .digitCell {\n height: 1.6rem;\n width: 0.85rem;\n}\n.reelWindow.md .digitCell {\n height: 2.6rem;\n width: 1.35rem;\n}\n.reelWindow.lg .digitCell {\n height: 3.8rem;\n width: 2rem;\n}\n/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3N0eWxlcy90b2tlbnMuc2NzcyIsIlNsaWRpbmdDb3VudGVyLm1vZHVsZS5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVRO0FBS1I7RUFFRTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7RUFHQTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7RUFHQTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7RUFHQTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7RUFHQTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBR0E7RUFDQTtFQUNBO0VBQ0E7RUFHQTtFQUNBO0VBQ0E7RUFDQTtFQUdBO0VBQ0E7RUFHQTtFQUdBO0VBQ0E7OztBQVFGO0VBRUU7RUFDQTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBR0E7RUFDQTtFQUdBO0VBQ0E7RUFJQTtFQUdBO0VBQ0E7OztBQ3pGRjtFQUNFO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7OztBQUlGO0VBQ0U7RUFDQTs7O0FBR0Y7RUFDRTtFQUNBO0VBQ0E7RUFDQTtFQUNBOzs7QUFJRjtFQUNFO0VBQ0E7O0FBR0U7RUFDRTtFQUNBO0VBQ0E7RUFDQTs7QUFKRjtFQUNFO0VBQ0E7RUFDQTtFQUNBOztBQUpGO0VBQ0U7RUFDQTtFQUNBO0VBQ0E7OztBQU1OO0VBQ0U7RUFDQTtFQUNBOzs7QUFJRjtFQUNFO0VBQ0E7RUFDQTtFQUNBOztBQUdFO0VBQ0U7RUFDQTs7QUFGRjtFQUNFO0VBQ0E7O0FBRkY7RUFDRTtFQUNBIiwic291cmNlc0NvbnRlbnQiOlsiQHVzZSBcInNhc3M6Y29sb3JcIjtcblxuQGltcG9ydCB1cmwoXCJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2NzczI/ZmFtaWx5PUludGVyOndnaHRANDAwOzUwMDs2MDAmZGlzcGxheT1zd2FwXCIpO1xuXG4vLyBBY2NlbnQgZGkgZGVmYXVsdCDigJQgY2FtYmlhIHF1aSBwZXIgYWdnaW9ybmFyZSB0dXR0YSBsYSBsaWJyZXJpYVxuJGFjY2VudDogIzNiODJmNjtcblxuOnJvb3Qge1xuICAvLyBBY2NlbnQgc2NhbGUg4oCUIGRlcml2YXRhIGRhICRhY2NlbnQgYSBjb21waWxlIHRpbWVcbiAgLS1hY2NlbnQ6ICAgICAgICAjeyRhY2NlbnR9O1xuICAtLWFjY2VudC1kb3Q6ICAgICN7Y29sb3Iuc2NhbGUoJGFjY2VudCwgJGxpZ2h0bmVzczogMTIlKX07XG4gIC0tYWNjZW50LXRyYWNrOiAgI3skYWNjZW50fTtcbiAgLS1hY2NlbnQtcmluZzogICAjeyRhY2NlbnR9O1xuICAtLWFjY2VudC1nbG93OiAgICN7Y29sb3IuY2hhbmdlKCRhY2NlbnQsICRhbHBoYTogMC4zKX07XG4gIC0tYWNjZW50LWxpZ2h0OiAgI3tjb2xvci5zY2FsZSgkYWNjZW50LCAkbGlnaHRuZXNzOiA0NSUsICRzYXR1cmF0aW9uOiAtMjAlKX07XG5cbiAgLy8gUHJpbWFyeSBzY2FsZSAobWFudGVudXRhIHBlciByZXRyb2NvbXBhdGliaWxpdMOgIGNvbiBCdXR0b24sIEJhZGdlLCBJbnB1dClcbiAgLS1jb2xvci1wcmltYXJ5LTUwOiAgI3tjb2xvci5zY2FsZSgkYWNjZW50LCAkbGlnaHRuZXNzOiA0OSUsICRzYXR1cmF0aW9uOiAtMzAlKX07XG4gIC0tY29sb3ItcHJpbWFyeS0xMDA6ICN7Y29sb3Iuc2NhbGUoJGFjY2VudCwgJGxpZ2h0bmVzczogNDIlLCAkc2F0dXJhdGlvbjogLTE1JSl9O1xuICAtLWNvbG9yLXByaW1hcnktNDAwOiAje2NvbG9yLnNjYWxlKCRhY2NlbnQsICRsaWdodG5lc3M6IDEyJSl9O1xuICAtLWNvbG9yLXByaW1hcnktNTAwOiAjeyRhY2NlbnR9O1xuICAtLWNvbG9yLXByaW1hcnktNjAwOiAje2NvbG9yLnNjYWxlKCRhY2NlbnQsICRsaWdodG5lc3M6IC0xMCUpfTtcbiAgLS1jb2xvci1wcmltYXJ5LTcwMDogI3tjb2xvci5zY2FsZSgkYWNjZW50LCAkbGlnaHRuZXNzOiAtMjAlKX07XG5cbiAgLy8gTmV1dHJhbFxuICAtLWNvbG9yLW5ldXRyYWwtMDogICAjZmZmZmZmO1xuICAtLWNvbG9yLW5ldXRyYWwtMTAwOiAjZjNmNGY2O1xuICAtLWNvbG9yLW5ldXRyYWwtMjAwOiAjZTVlN2ViO1xuICAtLWNvbG9yLW5ldXRyYWwtNDAwOiAjOWNhM2FmO1xuICAtLWNvbG9yLW5ldXRyYWwtNjAwOiAjNGI1NTYzO1xuICAtLWNvbG9yLW5ldXRyYWwtOTAwOiAjMTExODI3O1xuXG4gIC8vIFNlbWFudGljXG4gIC0tY29sb3Itc3VjY2Vzcy01MDA6ICMyMmM1NWU7XG4gIC0tY29sb3Itc3VjY2Vzcy02MDA6ICMxNmEzNGE7XG4gIC0tY29sb3ItZGFuZ2VyLTUwMDogICNlZjQ0NDQ7XG4gIC0tY29sb3ItZGFuZ2VyLTYwMDogICNkYzI2MjY7XG4gIC0tY29sb3Itd2FybmluZy01MDA6ICNmNTllMGI7XG4gIC0tY29sb3Itd2FybmluZy02MDA6ICNkOTc3MDY7XG5cbiAgLy8gVHlwb2dyYXBoeVxuICAtLWZvbnQtZmFtaWx5LWJhc2U6IFwiSW50ZXJcIiwgc3lzdGVtLXVpLCAtYXBwbGUtc3lzdGVtLCBzYW5zLXNlcmlmO1xuICAtLWZvbnQtc2l6ZS14czogMC43NXJlbTtcbiAgLS1mb250LXNpemUtc206IDAuODc1cmVtO1xuICAtLWZvbnQtc2l6ZS1tZDogMXJlbTtcbiAgLS1mb250LXNpemUtbGc6IDEuMTI1cmVtO1xuXG4gIC8vIFNwYWNpbmdcbiAgLS1zcGFjaW5nLTE6IDAuMjVyZW07XG4gIC0tc3BhY2luZy0yOiAwLjVyZW07XG4gIC0tc3BhY2luZy0zOiAwLjc1cmVtO1xuICAtLXNwYWNpbmctNDogMXJlbTtcblxuICAvLyBSYWRpdXNcbiAgLS1yYWRpdXMtc206ICAgMC4yNXJlbTtcbiAgLS1yYWRpdXMtbWQ6ICAgMC4zNzVyZW07XG4gIC0tcmFkaXVzLWxnOiAgIDAuNXJlbTtcbiAgLS1yYWRpdXMtZnVsbDogOTk5OXB4O1xuXG4gIC8vIFNoYWRvd3NcbiAgLS1zaGFkb3ctc206IDAgMXB4IDJweCAwIHJnYigwIDAgMCAvIDAuMDUpO1xuICAtLXNoYWRvdy1tZDogMCA0cHggNnB4IC0xcHggcmdiKDAgMCAwIC8gMC4xKSwgMCAycHggNHB4IC0ycHggcmdiKDAgMCAwIC8gMC4xKTtcblxuICAvLyBUcmFuc2l0aW9uc1xuICAtLXRyYW5zaXRpb24tZmFzdDogMTUwbXMgZWFzZTtcblxuICAvLyBTdXJmYWNlIChiYWNrZ3JvdW5kIGRpIGNhcmQvcGFuZWwpXG4gIC0tY29sb3Itc3VyZmFjZTogICAgICAgICAgI2ZmZmZmZjtcbiAgLS1jb2xvci1zdXJmYWNlLWVsZXZhdGVkOiAjZmZmZmZmO1xufVxuXG4vLyDilIDilIDilIAgRGFyayB0aGVtZSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbi8vIEFwcGxpY2F0byB2aWEgZGF0YS10aGVtZT1cImRhcmtcIiBzdWxsJ2VsZW1lbnRvIHJvb3QgKGdlc3RpdG8gZGEgU3Rvcnlib29rIGRlY29yYXRvclxuLy8gZSBkYWxsJ2FwcCBjb25zdW1lcikuIEkgY29tcG9uZW50aSBub24gZGV2b25vIGZhcmUgbnVsbGEgZGkgc3BlY2lhbGU6IGJhc3RhIGNoZVxuLy8gdXNpbm8gaSB0b2tlbiB2YXIoKSBlIHJpY2V2ZXJhbm5vIGF1dG9tYXRpY2FtZW50ZSBpIHZhbG9yaSBkYXJrLlxuXG5bZGF0YS10aGVtZT1cImRhcmtcIl0ge1xuICAvLyBOZXV0cmFsIChzY2FsYSBpbnZlcnRpdGEpXG4gIC0tY29sb3ItbmV1dHJhbC0wOiAgICMwZjE3MmE7XG4gIC0tY29sb3ItbmV1dHJhbC0xMDA6ICMxZTI5M2I7XG4gIC0tY29sb3ItbmV1dHJhbC0yMDA6ICMzMzQxNTU7XG4gIC0tY29sb3ItbmV1dHJhbC00MDA6ICM5NGEzYjg7XG4gIC0tY29sb3ItbmV1dHJhbC02MDA6ICNjYmQ1ZTE7XG4gIC0tY29sb3ItbmV1dHJhbC05MDA6ICNmOGZhZmM7XG5cbiAgLy8gU3VyZmFjZVxuICAtLWNvbG9yLXN1cmZhY2U6ICAgICAgICAgICMxZTI5M2I7XG4gIC0tY29sb3Itc3VyZmFjZS1lbGV2YXRlZDogIzI3MzU0OTtcblxuICAvLyBQcmltYXJ5IHNjYWxlIGRhcmsgKHBlciBpbCBkZWZhdWx0IGFjY2VudCBibHUg4oCUIGxlIHNmdW1hdHVyZSBjaGlhcmUgZGl2ZW50YW5vIHNjdXJlKVxuICAtLWNvbG9yLXByaW1hcnktNTA6ICAjMTcyNTU0O1xuICAtLWNvbG9yLXByaW1hcnktMTAwOiAjMWUzYThhO1xuICAvLyA0MDAgLyA1MDAgLyA2MDAgLyA3MDAgcmltYW5nb25vIGludmFyaWF0aSAoYWJiYXN0YW56YSBzYXR1cmkgc3UgYmcgc2N1cm8pXG5cbiAgLy8gQWNjZW50IGxpZ2h0OiB2YXJpYW50ZSBzY3VyYVxuICAtLWFjY2VudC1saWdodDogIzE3MjU1NDtcblxuICAvLyBTaGFkb3dzIChwacO5IGZvcnRpIHN1IGJnIHNjdXJvKVxuICAtLXNoYWRvdy1zbTogMCAxcHggMnB4IDAgcmdiKDAgMCAwIC8gMC40KTtcbiAgLS1zaGFkb3ctbWQ6IDAgNHB4IDZweCAtMXB4IHJnYigwIDAgMCAvIDAuNSksIDAgMnB4IDRweCAtMnB4IHJnYigwIDAgMCAvIDAuNCk7XG59XG4iLCJAdXNlIFwiLi4vLi4vc3R5bGVzL3Rva2Vuc1wiO1xuXG4vLyDilIDilIAgU2l6ZSBtYXAg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4kc2l6ZXM6IChcbiAgc206IChmb250OiAxLjI1cmVtLCBjZWxsOiAxLjZyZW0sIHdpZHRoOiAwLjg1cmVtKSxcbiAgbWQ6IChmb250OiAycmVtLCAgICBjZWxsOiAyLjZyZW0sIHdpZHRoOiAxLjM1cmVtKSxcbiAgbGc6IChmb250OiAzcmVtLCAgICBjZWxsOiAzLjhyZW0sIHdpZHRoOiAycmVtKSxcbik7XG5cbi8vIOKUgOKUgCBDb3VudGVyIHdyYXBwZXIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4uY291bnRlciB7XG4gIGRpc3BsYXk6IGlubGluZS1mbGV4O1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICBnYXA6IDFweDtcbiAgZm9udC1mYW1pbHk6IHZhcigtLWZvbnQtZmFtaWx5LWJhc2UpO1xuICBmb250LXdlaWdodDogNjAwO1xuICBmb250LXZhcmlhbnQtbnVtZXJpYzogdGFidWxhci1udW1zO1xuICBjb2xvcjogdmFyKC0tc2xpZGluZy1jb3VudGVyLWFjY2VudC1kb3QsIHZhcigtLWFjY2VudC1kb3QpKTtcbiAgdXNlci1zZWxlY3Q6IG5vbmU7XG59XG5cbi8vIOKUgOKUgCBTaWduIChtaW51cykgYW5kIGRlY2ltYWwgc2VwYXJhdG9yIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuLnNpZ24ge1xuICBsaW5lLWhlaWdodDogMTtcbiAgbWFyZ2luLXJpZ2h0OiAycHg7XG59XG5cbi5zZXBhcmF0b3Ige1xuICBsaW5lLWhlaWdodDogMTtcbiAgbWFyZ2luOiAwIDFweDtcbiAgb3BhY2l0eTogMC41O1xuICBhbGlnbi1zZWxmOiBmbGV4LWVuZDtcbiAgcGFkZGluZy1ib3R0b206IDAuNWVtO1xufVxuXG4vLyDilIDilIAgUmVlbCB3aW5kb3cg4oCUIGNsaXBzIHRvIG9uZSBkaWdpdCBoZWlnaHQg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4ucmVlbFdpbmRvdyB7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcblxuICBAZWFjaCAkbmFtZSwgJG1hcCBpbiAkc2l6ZXMge1xuICAgICYuI3skbmFtZX0ge1xuICAgICAgaGVpZ2h0OiBtYXAtZ2V0KCRtYXAsIGNlbGwpO1xuICAgICAgd2lkdGg6IG1hcC1nZXQoJG1hcCwgd2lkdGgpO1xuICAgICAgZm9udC1zaXplOiBtYXAtZ2V0KCRtYXAsIGZvbnQpO1xuICAgICAgbGluZS1oZWlnaHQ6IG1hcC1nZXQoJG1hcCwgY2VsbCk7XG4gICAgfVxuICB9XG59XG5cbi8vIOKUgOKUgCBUaGUgc2Nyb2xsaW5nIGNvbHVtbiDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbi5yZWVsIHtcbiAgZGlzcGxheTogZmxleDtcbiAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcbiAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDQyMG1zIGN1YmljLWJlemllcigwLjQsIDAsIDAuMiwgMSk7XG59XG5cbi8vIOKUgOKUgCBTaW5nbGUgZGlnaXQgY2VsbCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbi5kaWdpdENlbGwge1xuICBkaXNwbGF5OiBmbGV4O1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgZmxleC1zaHJpbms6IDA7XG5cbiAgQGVhY2ggJG5hbWUsICRtYXAgaW4gJHNpemVzIHtcbiAgICAucmVlbFdpbmRvdy4jeyRuYW1lfSAmIHtcbiAgICAgIGhlaWdodDogbWFwLWdldCgkbWFwLCBjZWxsKTtcbiAgICAgIHdpZHRoOiBtYXAtZ2V0KCRtYXAsIHdpZHRoKTtcbiAgICB9XG4gIH1cbn1cbiJdfQ== */","export function hexToHsl(hex: string): [number, number, number] {\n const r = parseInt(hex.slice(1, 3), 16) / 255;\n const g = parseInt(hex.slice(3, 5), 16) / 255;\n const b = parseInt(hex.slice(5, 7), 16) / 255;\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n let h = 0;\n let s = 0;\n\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;\n case g: h = ((b - r) / d + 2) / 6; break;\n case b: h = ((r - g) / d + 4) / 6; break;\n }\n }\n\n return [h * 360, s * 100, l * 100];\n}\n\nexport function hslToHex(h: number, s: number, l: number): string {\n const sl = s / 100;\n const ll = l / 100;\n const a = sl * Math.min(ll, 1 - ll);\n const f = (n: number) => {\n const k = (n + h / 30) % 12;\n const color = ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);\n return Math.round(255 * color).toString(16).padStart(2, \"0\");\n };\n return `#${f(0)}${f(8)}${f(4)}`;\n}\n\nexport function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {\n const sl = s / 100;\n const ll = l / 100;\n const a = sl * Math.min(ll, 1 - ll);\n const f = (n: number) => {\n const k = (n + h / 30) % 12;\n return Math.round(255 * (ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)));\n };\n return { r: f(0), g: f(8), b: f(4) };\n}\n\nexport function rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n const rr = r / 255, gg = g / 255, bb = b / 255;\n const max = Math.max(rr, gg, bb);\n const min = Math.min(rr, gg, bb);\n const l = (max + min) / 2;\n let h = 0, s = 0;\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case rr: h = ((gg - bb) / d + (gg < bb ? 6 : 0)) / 6; break;\n case gg: h = ((bb - rr) / d + 2) / 6; break;\n case bb: h = ((rr - gg) / d + 4) / 6; break;\n }\n }\n return [h * 360, s * 100, l * 100];\n}\n\nfunction hslToRgba(h: number, s: number, l: number, alpha: number): string {\n const { r, g, b } = hslToRgb(h, s, l);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nexport interface AccentTokens {\n base: string;\n dot: string;\n track: string;\n ring: string;\n glow: string;\n light: string;\n}\n\nexport interface ColorValue {\n hex: string;\n hexa: string;\n hsl: string;\n hsla: string;\n rgb: string;\n rgba: string;\n h: number;\n s: number;\n l: number;\n a: number;\n}\n\nexport function buildColorValue(h: number, s: number, l: number, a: number): ColorValue {\n const { r, g, b } = hslToRgb(h, s, l);\n const hex = hslToHex(h, s, l);\n const alphaHex = Math.round(a * 255).toString(16).padStart(2, \"0\");\n const hR = Math.round(h), sR = Math.round(s), lR = Math.round(l);\n const aR = Math.round(a * 100) / 100;\n return {\n hex,\n hexa: `${hex}${alphaHex}`,\n hsl: `hsl(${hR}, ${sR}%, ${lR}%)`,\n hsla: `hsla(${hR}, ${sR}%, ${lR}%, ${aR})`,\n rgb: `rgb(${r}, ${g}, ${b})`,\n rgba: `rgba(${r}, ${g}, ${b}, ${aR})`,\n h, s, l, a,\n };\n}\n\nexport function deriveAccent(hex: string): AccentTokens {\n const [h, s, l] = hexToHsl(hex);\n return {\n base: hex,\n dot: hslToHex(h, s, Math.min(l + 12, 95)),\n track: hex,\n ring: hex,\n glow: hslToRgba(h, s, l, 0.3),\n light: hslToHex(h, Math.max(s - 20, 0), Math.min(l + 45, 96)),\n };\n}\n\nexport function accentToCssVars(\n tokens: AccentTokens,\n prefix: string\n): Record<string, string> {\n return {\n [`--${prefix}-accent`]: tokens.base,\n [`--${prefix}-accent-dot`]: tokens.dot,\n [`--${prefix}-accent-track`]: tokens.track,\n [`--${prefix}-accent-ring`]: tokens.ring,\n [`--${prefix}-accent-glow`]: tokens.glow,\n [`--${prefix}-accent-light`]: tokens.light,\n };\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport styles from \"./SlidingCounter.module.scss\";\nimport { deriveAccent, accentToCssVars } from \"../../utils/color\";\n\nexport type SlidingCounterSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface SlidingCounterProps {\n value: number;\n /** Minimum integer digits shown (zero-padded on the left) */\n minDigits?: number;\n /** Number of decimal digits to display. 0 = no decimals. */\n decimals?: number;\n /** Decimal separator character */\n decimalSeparator?: string;\n size?: SlidingCounterSize;\n /** Custom accent color — overrides the global theme for this instance */\n accent?: string;\n className?: string;\n}\n\ninterface DigitReelProps {\n digit: number;\n direction: \"up\" | \"down\";\n size: SlidingCounterSize;\n}\n\n// 3 copies of 0-9: [0-9 | 0-9 | 0-9] — middle copy is the \"home\" position.\n// After each transition we silently snap back to middle to keep the range usable indefinitely.\nconst COPIES = 3;\nconst TOTAL = COPIES * 10; // 30 cells\n\nconst DigitReel: React.FC<DigitReelProps> = ({ digit, direction, size }) => {\n const posRef = useRef(10 + digit);\n const prevDigitRef = useRef(digit);\n const [pos, setPos] = useState(10 + digit);\n const [animate, setAnimate] = useState(true);\n\n useEffect(() => {\n if (prevDigitRef.current === digit) return;\n\n const prev = prevDigitRef.current;\n let next = posRef.current;\n\n if (direction === \"up\") {\n next += digit > prev ? digit - prev : 10 - prev + digit;\n } else {\n next -= digit < prev ? prev - digit : prev + 10 - digit;\n }\n\n posRef.current = next;\n prevDigitRef.current = digit;\n setAnimate(true);\n setPos(next);\n }, [digit, direction]);\n\n const handleTransitionEnd = () => {\n const normalized = 10 + (((posRef.current % 10) + 10) % 10);\n posRef.current = normalized;\n setAnimate(false);\n setPos(normalized);\n };\n\n const translateY = -(pos / TOTAL) * 100;\n\n const cells = Array.from({ length: COPIES }, () =>\n [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n ).flat();\n\n return (\n <div className={`${styles.reelWindow} ${styles[size]}`}>\n <div\n className={styles.reel}\n style={{\n transform: `translateY(${translateY}%)`,\n transition: animate ? undefined : \"none\",\n }}\n onTransitionEnd={handleTransitionEnd}\n >\n {cells.map((d, i) => (\n <div key={i} className={styles.digitCell}>\n {d}\n </div>\n ))}\n </div>\n </div>\n );\n};\n\n/** Split a number into integer and decimal digit arrays with explicit decimal places. */\nfunction splitDigits(\n value: number,\n minDigits: number,\n decimals: number\n): { intDigits: number[]; decDigits: number[] } {\n const abs = Math.abs(value);\n const fixed = abs.toFixed(decimals);\n const [intPart, decPart = \"\"] = fixed.split(\".\");\n\n const intDigits = intPart.split(\"\").map(Number);\n const padded =\n intDigits.length < minDigits\n ? [...Array(minDigits - intDigits.length).fill(0), ...intDigits]\n : intDigits;\n\n const decDigits = decPart.split(\"\").map(Number);\n\n return { intDigits: padded, decDigits };\n}\n\nexport const SlidingCounter: React.FC<SlidingCounterProps> = ({\n value,\n minDigits = 1,\n decimals = 0,\n decimalSeparator = \".\",\n size = \"md\",\n accent,\n className,\n}) => {\n const accentVars = accent\n ? accentToCssVars(deriveAccent(accent), \"sliding-counter\")\n : {};\n\n const safeValue = isFinite(value) ? value : 0;\n\n const prevValueRef = useRef(safeValue);\n const direction = safeValue >= prevValueRef.current ? \"up\" : \"down\";\n prevValueRef.current = safeValue;\n\n const { intDigits, decDigits } = splitDigits(safeValue, minDigits, decimals);\n\n return (\n <div\n className={[styles.counter, styles[size], className ?? \"\"]\n .filter(Boolean)\n .join(\" \")}\n style={accentVars as React.CSSProperties}\n aria-label={safeValue.toFixed(decimals)}\n >\n {safeValue < 0 && <span className={styles.sign}>−</span>}\n\n {intDigits.map((digit, i) => (\n <DigitReel key={`int-${intDigits.length - i}`} digit={digit} direction={direction} size={size} />\n ))}\n\n {decimals > 0 && (\n <span className={styles.separator}>{decimalSeparator}</span>\n )}\n\n {decDigits.map((digit, i) => (\n <DigitReel key={`dec-${i}`} digit={digit} direction={direction} size={size} />\n ))}\n </div>\n );\n};\n"]}
@@ -0,0 +1,133 @@
1
+ @import "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap";
2
+
3
+ /* src/components/SlidingCounter/SlidingCounter.module.scss */
4
+ :root {
5
+ --accent: #3b82f6;
6
+ --accent-dot: rgb(82.52, 145, 247.08);
7
+ --accent-track: #3b82f6;
8
+ --accent-ring: #3b82f6;
9
+ --accent-glow: rgba(59, 130, 246, 0.3);
10
+ --accent-light: rgb(157.485, 188.725, 239.765);
11
+ --color-primary-50: rgb(169.3455, 194.6925, 236.1045);
12
+ --color-primary-100: rgb(149.4545, 184.4575, 241.6455);
13
+ --color-primary-400: rgb(82.52, 145, 247.08);
14
+ --color-primary-500: #3b82f6;
15
+ --color-primary-600: rgb(29.8390243902, 111.4024390244, 244.6609756098);
16
+ --color-primary-700: rgb(10.712195122, 95.2195121951, 233.287804878);
17
+ --color-neutral-0: #ffffff;
18
+ --color-neutral-100: #f3f4f6;
19
+ --color-neutral-200: #e5e7eb;
20
+ --color-neutral-400: #9ca3af;
21
+ --color-neutral-600: #4b5563;
22
+ --color-neutral-900: #111827;
23
+ --color-success-500: #22c55e;
24
+ --color-success-600: #16a34a;
25
+ --color-danger-500: #ef4444;
26
+ --color-danger-600: #dc2626;
27
+ --color-warning-500: #f59e0b;
28
+ --color-warning-600: #d97706;
29
+ --font-family-base:
30
+ "Inter",
31
+ system-ui,
32
+ -apple-system,
33
+ sans-serif;
34
+ --font-size-xs: 0.75rem;
35
+ --font-size-sm: 0.875rem;
36
+ --font-size-md: 1rem;
37
+ --font-size-lg: 1.125rem;
38
+ --spacing-1: 0.25rem;
39
+ --spacing-2: 0.5rem;
40
+ --spacing-3: 0.75rem;
41
+ --spacing-4: 1rem;
42
+ --radius-sm: 0.25rem;
43
+ --radius-md: 0.375rem;
44
+ --radius-lg: 0.5rem;
45
+ --radius-full: 9999px;
46
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
47
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
48
+ --transition-fast: 150ms ease;
49
+ --color-surface: #ffffff;
50
+ --color-surface-elevated: #ffffff;
51
+ }
52
+ [data-theme=dark] {
53
+ --color-neutral-0: #0f172a;
54
+ --color-neutral-100: #1e293b;
55
+ --color-neutral-200: #334155;
56
+ --color-neutral-400: #94a3b8;
57
+ --color-neutral-600: #cbd5e1;
58
+ --color-neutral-900: #f8fafc;
59
+ --color-surface: #1e293b;
60
+ --color-surface-elevated: #273549;
61
+ --color-primary-50: #172554;
62
+ --color-primary-100: #1e3a8a;
63
+ --accent-light: #172554;
64
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.4);
65
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.5), 0 2px 4px -2px rgb(0 0 0 / 0.4);
66
+ }
67
+ .SlidingCounter_module_counter2 {
68
+ display: inline-flex;
69
+ align-items: center;
70
+ gap: 1px;
71
+ font-family: var(--font-family-base);
72
+ font-weight: 600;
73
+ font-variant-numeric: tabular-nums;
74
+ color: var(--sliding-counter-accent-dot, var(--accent-dot));
75
+ user-select: none;
76
+ }
77
+ .SlidingCounter_module_sign2 {
78
+ line-height: 1;
79
+ margin-right: 2px;
80
+ }
81
+ .SlidingCounter_module_separator2 {
82
+ line-height: 1;
83
+ margin: 0 1px;
84
+ opacity: 0.5;
85
+ align-self: flex-end;
86
+ padding-bottom: 0.5em;
87
+ }
88
+ .SlidingCounter_module_reelWindow2 {
89
+ overflow: hidden;
90
+ position: relative;
91
+ }
92
+ .SlidingCounter_module_reelWindow2.SlidingCounter_module_sm2 {
93
+ height: 1.6rem;
94
+ width: 0.85rem;
95
+ font-size: 1.25rem;
96
+ line-height: 1.6rem;
97
+ }
98
+ .SlidingCounter_module_reelWindow2.SlidingCounter_module_md2 {
99
+ height: 2.6rem;
100
+ width: 1.35rem;
101
+ font-size: 2rem;
102
+ line-height: 2.6rem;
103
+ }
104
+ .SlidingCounter_module_reelWindow2.SlidingCounter_module_lg2 {
105
+ height: 3.8rem;
106
+ width: 2rem;
107
+ font-size: 3rem;
108
+ line-height: 3.8rem;
109
+ }
110
+ .SlidingCounter_module_reel2 {
111
+ display: flex;
112
+ flex-direction: column;
113
+ transition: transform 420ms cubic-bezier(0.4, 0, 0.2, 1);
114
+ }
115
+ .SlidingCounter_module_digitCell2 {
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ flex-shrink: 0;
120
+ }
121
+ .SlidingCounter_module_reelWindow2.SlidingCounter_module_sm2 .SlidingCounter_module_digitCell2 {
122
+ height: 1.6rem;
123
+ width: 0.85rem;
124
+ }
125
+ .SlidingCounter_module_reelWindow2.SlidingCounter_module_md2 .SlidingCounter_module_digitCell2 {
126
+ height: 2.6rem;
127
+ width: 1.35rem;
128
+ }
129
+ .SlidingCounter_module_reelWindow2.SlidingCounter_module_lg2 .SlidingCounter_module_digitCell2 {
130
+ height: 3.8rem;
131
+ width: 2rem;
132
+ }
133
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/styles/tokens.scss","../../../src/components/SlidingCounter/SlidingCounter.module.scss"],"sourcesContent":["@use \"sass:color\";\n\n@import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\");\n\n// Accent di default — cambia qui per aggiornare tutta la libreria\n$accent: #3b82f6;\n\n:root {\n // Accent scale — derivata da $accent a compile time\n --accent: #{$accent};\n --accent-dot: #{color.scale($accent, $lightness: 12%)};\n --accent-track: #{$accent};\n --accent-ring: #{$accent};\n --accent-glow: #{color.change($accent, $alpha: 0.3)};\n --accent-light: #{color.scale($accent, $lightness: 45%, $saturation: -20%)};\n\n // Primary scale (mantenuta per retrocompatibilità con Button, Badge, Input)\n --color-primary-50: #{color.scale($accent, $lightness: 49%, $saturation: -30%)};\n --color-primary-100: #{color.scale($accent, $lightness: 42%, $saturation: -15%)};\n --color-primary-400: #{color.scale($accent, $lightness: 12%)};\n --color-primary-500: #{$accent};\n --color-primary-600: #{color.scale($accent, $lightness: -10%)};\n --color-primary-700: #{color.scale($accent, $lightness: -20%)};\n\n // Neutral\n --color-neutral-0: #ffffff;\n --color-neutral-100: #f3f4f6;\n --color-neutral-200: #e5e7eb;\n --color-neutral-400: #9ca3af;\n --color-neutral-600: #4b5563;\n --color-neutral-900: #111827;\n\n // Semantic\n --color-success-500: #22c55e;\n --color-success-600: #16a34a;\n --color-danger-500: #ef4444;\n --color-danger-600: #dc2626;\n --color-warning-500: #f59e0b;\n --color-warning-600: #d97706;\n\n // Typography\n --font-family-base: \"Inter\", system-ui, -apple-system, sans-serif;\n --font-size-xs: 0.75rem;\n --font-size-sm: 0.875rem;\n --font-size-md: 1rem;\n --font-size-lg: 1.125rem;\n\n // Spacing\n --spacing-1: 0.25rem;\n --spacing-2: 0.5rem;\n --spacing-3: 0.75rem;\n --spacing-4: 1rem;\n\n // Radius\n --radius-sm: 0.25rem;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --radius-full: 9999px;\n\n // Shadows\n --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n\n // Transitions\n --transition-fast: 150ms ease;\n\n // Surface (background di card/panel)\n --color-surface: #ffffff;\n --color-surface-elevated: #ffffff;\n}\n\n// ─── Dark theme ────────────────────────────────────────────────────────────────\n// Applicato via data-theme=\"dark\" sull'elemento root (gestito da Storybook decorator\n// e dall'app consumer). I componenti non devono fare nulla di speciale: basta che\n// usino i token var() e riceveranno automaticamente i valori dark.\n\n[data-theme=\"dark\"] {\n // Neutral (scala invertita)\n --color-neutral-0: #0f172a;\n --color-neutral-100: #1e293b;\n --color-neutral-200: #334155;\n --color-neutral-400: #94a3b8;\n --color-neutral-600: #cbd5e1;\n --color-neutral-900: #f8fafc;\n\n // Surface\n --color-surface: #1e293b;\n --color-surface-elevated: #273549;\n\n // Primary scale dark (per il default accent blu — le sfumature chiare diventano scure)\n --color-primary-50: #172554;\n --color-primary-100: #1e3a8a;\n // 400 / 500 / 600 / 700 rimangono invariati (abbastanza saturi su bg scuro)\n\n // Accent light: variante scura\n --accent-light: #172554;\n\n // Shadows (più forti su bg scuro)\n --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.4);\n --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.5), 0 2px 4px -2px rgb(0 0 0 / 0.4);\n}\n","@use \"../../styles/tokens\";\n\n// ── Size map ────────────────────────────────────────────────────────────────\n$sizes: (\n sm: (font: 1.25rem, cell: 1.6rem, width: 0.85rem),\n md: (font: 2rem, cell: 2.6rem, width: 1.35rem),\n lg: (font: 3rem, cell: 3.8rem, width: 2rem),\n);\n\n// ── Counter wrapper ─────────────────────────────────────────────────────────\n.counter {\n display: inline-flex;\n align-items: center;\n gap: 1px;\n font-family: var(--font-family-base);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: var(--sliding-counter-accent-dot, var(--accent-dot));\n user-select: none;\n}\n\n// ── Sign (minus) and decimal separator ──────────────────────────────────────\n.sign {\n line-height: 1;\n margin-right: 2px;\n}\n\n.separator {\n line-height: 1;\n margin: 0 1px;\n opacity: 0.5;\n align-self: flex-end;\n padding-bottom: 0.5em;\n}\n\n// ── Reel window — clips to one digit height ─────────────────────────────────\n.reelWindow {\n overflow: hidden;\n position: relative;\n\n @each $name, $map in $sizes {\n &.#{$name} {\n height: map-get($map, cell);\n width: map-get($map, width);\n font-size: map-get($map, font);\n line-height: map-get($map, cell);\n }\n }\n}\n\n// ── The scrolling column ─────────────────────────────────────────────────────\n.reel {\n display: flex;\n flex-direction: column;\n transition: transform 420ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n// ── Single digit cell ────────────────────────────────────────────────────────\n.digitCell {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n\n @each $name, $map in $sizes {\n .reelWindow.#{$name} & {\n height: map-get($map, cell);\n width: map-get($map, width);\n }\n }\n}\n"],"mappings":";;;AAOA;AAEE,YAAA;AACA,gBAAA,IAAA,KAAA,EAAA,GAAA,EAAA;AACA,kBAAA;AACA,iBAAA;AACA,iBAAA,KAAA,EAAA,EAAA,GAAA,EAAA,GAAA,EAAA;AACA,kBAAA,IAAA,OAAA,EAAA,OAAA,EAAA;AAGA,sBAAA,IAAA,QAAA,EAAA,QAAA,EAAA;AACA,uBAAA,IAAA,QAAA,EAAA,QAAA,EAAA;AACA,uBAAA,IAAA,KAAA,EAAA,GAAA,EAAA;AACA,uBAAA;AACA,uBAAA,IAAA,aAAA,EAAA,cAAA,EAAA;AACA,uBAAA,IAAA,YAAA,EAAA,aAAA,EAAA;AAGA,qBAAA;AACA,uBAAA;AACA,uBAAA;AACA,uBAAA;AACA,uBAAA;AACA,uBAAA;AAGA,uBAAA;AACA,uBAAA;AACA,sBAAA;AACA,sBAAA;AACA,uBAAA;AACA,uBAAA;AAGA;IAAA,OAAA;IAAA,SAAA;IAAA,aAAA;IAAA;AACA,kBAAA;AACA,kBAAA;AACA,kBAAA;AACA,kBAAA;AAGA,eAAA;AACA,eAAA;AACA,eAAA;AACA,eAAA;AAGA,eAAA;AACA,eAAA;AACA,eAAA;AACA,iBAAA;AAGA,eAAA,EAAA,IAAA,IAAA,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA;AACA,eAAA,EAAA,IAAA,IAAA,KAAA,IAAA,EAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,IAAA,IAAA,KAAA,IAAA,EAAA,EAAA,EAAA,EAAA;AAGA,qBAAA,MAAA;AAGA,mBAAA;AACA,4BAAA;;AAQF,CAAA;AAEE,qBAAA;AACA,uBAAA;AACA,uBAAA;AACA,uBAAA;AACA,uBAAA;AACA,uBAAA;AAGA,mBAAA;AACA,4BAAA;AAGA,sBAAA;AACA,uBAAA;AAIA,kBAAA;AAGA,eAAA,EAAA,IAAA,IAAA,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA;AACA,eAAA,EAAA,IAAA,IAAA,KAAA,IAAA,EAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,IAAA,IAAA,KAAA,IAAA,EAAA,EAAA,EAAA,EAAA;;ACzFF,CAAAA;AACE,WAAA;AACA,eAAA;AACA,OAAA;AACA,eAAA,IAAA;AACA,eAAA;AACA,wBAAA;AACA,SAAA,IAAA,4BAAA,EAAA,IAAA;AACA,eAAA;;AAIF,CAAAC;AACE,eAAA;AACA,gBAAA;;AAGF,CAAAC;AACE,eAAA;AACA,UAAA,EAAA;AACA,WAAA;AACA,cAAA;AACA,kBAAA;;AAIF,CAAAC;AACE,YAAA;AACA,YAAA;;AAGE,CALJA,iCAKI,CAAAC;AACE,UAAA;AACA,SAAA;AACA,aAAA;AACA,eAAA;;AAJF,CALJD,iCAKI,CAAAE;AACE,UAAA;AACA,SAAA;AACA,aAAA;AACA,eAAA;;AAJF,CALJF,iCAKI,CAAAG;AACE,UAAA;AACA,SAAA;AACA,aAAA;AACA,eAAA;;AAMN,CAAAC;AACE,WAAA;AACA,kBAAA;AACA,cAAA,UAAA,MAAA,aAAA,GAAA,EAAA,CAAA,EAAA,GAAA,EAAA;;AAIF,CAAAC;AACE,WAAA;AACA,eAAA;AACA,mBAAA;AACA,eAAA;;AAGE,CA7BJL,iCA6BI,CAxBAC,0BAwBA,CAPJI;AAQM,UAAA;AACA,SAAA;;AAFF,CA7BJL,iCA6BI,CAxBAE,0BAwBA,CAPJG;AAQM,UAAA;AACA,SAAA;;AAFF,CA7BJL,iCA6BI,CAxBAG,0BAwBA,CAPJE;AAQM,UAAA;AACA,SAAA;;","names":["counter","sign","separator","reelWindow","sm","md","lg","reel","digitCell"]}
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ type SlidingCounterSize = "sm" | "md" | "lg";
4
+ interface SlidingCounterProps {
5
+ value: number;
6
+ /** Minimum integer digits shown (zero-padded on the left) */
7
+ minDigits?: number;
8
+ /** Number of decimal digits to display. 0 = no decimals. */
9
+ decimals?: number;
10
+ /** Decimal separator character */
11
+ decimalSeparator?: string;
12
+ size?: SlidingCounterSize;
13
+ /** Custom accent color — overrides the global theme for this instance */
14
+ accent?: string;
15
+ className?: string;
16
+ }
17
+ declare const SlidingCounter: React.FC<SlidingCounterProps>;
18
+
19
+ export { SlidingCounter, type SlidingCounterProps, type SlidingCounterSize };
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ type SlidingCounterSize = "sm" | "md" | "lg";
4
+ interface SlidingCounterProps {
5
+ value: number;
6
+ /** Minimum integer digits shown (zero-padded on the left) */
7
+ minDigits?: number;
8
+ /** Number of decimal digits to display. 0 = no decimals. */
9
+ decimals?: number;
10
+ /** Decimal separator character */
11
+ decimalSeparator?: string;
12
+ size?: SlidingCounterSize;
13
+ /** Custom accent color — overrides the global theme for this instance */
14
+ accent?: string;
15
+ className?: string;
16
+ }
17
+ declare const SlidingCounter: React.FC<SlidingCounterProps>;
18
+
19
+ export { SlidingCounter, type SlidingCounterProps, type SlidingCounterSize };
@@ -0,0 +1,179 @@
1
+ import { useRef, useState, useEffect } from 'react';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ // src/components/SlidingCounter/SlidingCounter.tsx
5
+
6
+ // src/components/SlidingCounter/SlidingCounter.module.scss
7
+ var SlidingCounter_module_default = {
8
+ counter: "SlidingCounter_module_counter2",
9
+ sign: "SlidingCounter_module_sign2",
10
+ separator: "SlidingCounter_module_separator2",
11
+ reelWindow: "SlidingCounter_module_reelWindow2",
12
+ sm: "SlidingCounter_module_sm2",
13
+ md: "SlidingCounter_module_md2",
14
+ lg: "SlidingCounter_module_lg2",
15
+ reel: "SlidingCounter_module_reel2",
16
+ digitCell: "SlidingCounter_module_digitCell2"
17
+ };
18
+
19
+ // src/utils/color.ts
20
+ function hexToHsl(hex) {
21
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
22
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
23
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
24
+ const max = Math.max(r, g, b);
25
+ const min = Math.min(r, g, b);
26
+ const l = (max + min) / 2;
27
+ let h = 0;
28
+ let s = 0;
29
+ if (max !== min) {
30
+ const d = max - min;
31
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
32
+ switch (max) {
33
+ case r:
34
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
35
+ break;
36
+ case g:
37
+ h = ((b - r) / d + 2) / 6;
38
+ break;
39
+ case b:
40
+ h = ((r - g) / d + 4) / 6;
41
+ break;
42
+ }
43
+ }
44
+ return [h * 360, s * 100, l * 100];
45
+ }
46
+ function hslToHex(h, s, l) {
47
+ const sl = s / 100;
48
+ const ll = l / 100;
49
+ const a = sl * Math.min(ll, 1 - ll);
50
+ const f = (n) => {
51
+ const k = (n + h / 30) % 12;
52
+ const color = ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
53
+ return Math.round(255 * color).toString(16).padStart(2, "0");
54
+ };
55
+ return `#${f(0)}${f(8)}${f(4)}`;
56
+ }
57
+ function hslToRgb(h, s, l) {
58
+ const sl = s / 100;
59
+ const ll = l / 100;
60
+ const a = sl * Math.min(ll, 1 - ll);
61
+ const f = (n) => {
62
+ const k = (n + h / 30) % 12;
63
+ return Math.round(255 * (ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)));
64
+ };
65
+ return { r: f(0), g: f(8), b: f(4) };
66
+ }
67
+ function hslToRgba(h, s, l, alpha) {
68
+ const { r, g, b } = hslToRgb(h, s, l);
69
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
70
+ }
71
+ function deriveAccent(hex) {
72
+ const [h, s, l] = hexToHsl(hex);
73
+ return {
74
+ base: hex,
75
+ dot: hslToHex(h, s, Math.min(l + 12, 95)),
76
+ track: hex,
77
+ ring: hex,
78
+ glow: hslToRgba(h, s, l, 0.3),
79
+ light: hslToHex(h, Math.max(s - 20, 0), Math.min(l + 45, 96))
80
+ };
81
+ }
82
+ function accentToCssVars(tokens, prefix) {
83
+ return {
84
+ [`--${prefix}-accent`]: tokens.base,
85
+ [`--${prefix}-accent-dot`]: tokens.dot,
86
+ [`--${prefix}-accent-track`]: tokens.track,
87
+ [`--${prefix}-accent-ring`]: tokens.ring,
88
+ [`--${prefix}-accent-glow`]: tokens.glow,
89
+ [`--${prefix}-accent-light`]: tokens.light
90
+ };
91
+ }
92
+ var COPIES = 3;
93
+ var TOTAL = COPIES * 10;
94
+ var DigitReel = ({ digit, direction, size }) => {
95
+ const posRef = useRef(10 + digit);
96
+ const prevDigitRef = useRef(digit);
97
+ const [pos, setPos] = useState(10 + digit);
98
+ const [animate, setAnimate] = useState(true);
99
+ useEffect(() => {
100
+ if (prevDigitRef.current === digit) return;
101
+ const prev = prevDigitRef.current;
102
+ let next = posRef.current;
103
+ if (direction === "up") {
104
+ next += digit > prev ? digit - prev : 10 - prev + digit;
105
+ } else {
106
+ next -= digit < prev ? prev - digit : prev + 10 - digit;
107
+ }
108
+ posRef.current = next;
109
+ prevDigitRef.current = digit;
110
+ setAnimate(true);
111
+ setPos(next);
112
+ }, [digit, direction]);
113
+ const handleTransitionEnd = () => {
114
+ const normalized = 10 + (posRef.current % 10 + 10) % 10;
115
+ posRef.current = normalized;
116
+ setAnimate(false);
117
+ setPos(normalized);
118
+ };
119
+ const translateY = -(pos / TOTAL) * 100;
120
+ const cells = Array.from(
121
+ { length: COPIES },
122
+ () => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
123
+ ).flat();
124
+ return /* @__PURE__ */ jsx("div", { className: `${SlidingCounter_module_default.reelWindow} ${SlidingCounter_module_default[size]}`, children: /* @__PURE__ */ jsx(
125
+ "div",
126
+ {
127
+ className: SlidingCounter_module_default.reel,
128
+ style: {
129
+ transform: `translateY(${translateY}%)`,
130
+ transition: animate ? void 0 : "none"
131
+ },
132
+ onTransitionEnd: handleTransitionEnd,
133
+ children: cells.map((d, i) => /* @__PURE__ */ jsx("div", { className: SlidingCounter_module_default.digitCell, children: d }, i))
134
+ }
135
+ ) });
136
+ };
137
+ function splitDigits(value, minDigits, decimals) {
138
+ const abs = Math.abs(value);
139
+ const fixed = abs.toFixed(decimals);
140
+ const [intPart, decPart = ""] = fixed.split(".");
141
+ const intDigits = intPart.split("").map(Number);
142
+ const padded = intDigits.length < minDigits ? [...Array(minDigits - intDigits.length).fill(0), ...intDigits] : intDigits;
143
+ const decDigits = decPart.split("").map(Number);
144
+ return { intDigits: padded, decDigits };
145
+ }
146
+ var SlidingCounter = ({
147
+ value,
148
+ minDigits = 1,
149
+ decimals = 0,
150
+ decimalSeparator = ".",
151
+ size = "md",
152
+ accent,
153
+ className
154
+ }) => {
155
+ const accentVars = accent ? accentToCssVars(deriveAccent(accent), "sliding-counter") : {};
156
+ const safeValue = isFinite(value) ? value : 0;
157
+ const prevValueRef = useRef(safeValue);
158
+ const direction = safeValue >= prevValueRef.current ? "up" : "down";
159
+ prevValueRef.current = safeValue;
160
+ const { intDigits, decDigits } = splitDigits(safeValue, minDigits, decimals);
161
+ return /* @__PURE__ */ jsxs(
162
+ "div",
163
+ {
164
+ className: [SlidingCounter_module_default.counter, SlidingCounter_module_default[size], className ?? ""].filter(Boolean).join(" "),
165
+ style: accentVars,
166
+ "aria-label": safeValue.toFixed(decimals),
167
+ children: [
168
+ safeValue < 0 && /* @__PURE__ */ jsx("span", { className: SlidingCounter_module_default.sign, children: "\u2212" }),
169
+ intDigits.map((digit, i) => /* @__PURE__ */ jsx(DigitReel, { digit, direction, size }, `int-${intDigits.length - i}`)),
170
+ decimals > 0 && /* @__PURE__ */ jsx("span", { className: SlidingCounter_module_default.separator, children: decimalSeparator }),
171
+ decDigits.map((digit, i) => /* @__PURE__ */ jsx(DigitReel, { digit, direction, size }, `dec-${i}`))
172
+ ]
173
+ }
174
+ );
175
+ };
176
+
177
+ export { SlidingCounter };
178
+ //# sourceMappingURL=index.js.map
179
+ //# sourceMappingURL=index.js.map