virtual-ui-lib 1.0.71 → 1.0.73

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Virtual UI
2
2
 
3
- Simple **React UI Component Library** with customizable components.
3
+ A modern and customizable **React UI Component Library** designed for fast development and clean design systems.
4
4
 
5
5
  ---
6
6
 
7
- ## Install
7
+ ## 📦 Install
8
8
 
9
9
  ```bash
10
10
  npm install virtual-ui-lib
@@ -12,209 +12,348 @@ npm install virtual-ui-lib
12
12
 
13
13
  ---
14
14
 
15
- ## Usage
15
+ ## 🚀 Usage
16
16
 
17
17
  ```jsx
18
18
  import {
19
- Button,
20
- Card,
21
- Input,
22
- Badge,
23
- Alert,
24
- Avatar,
25
- Spinner,
26
- Modal
19
+ Navbar,
20
+ Sidebar,
21
+ AvatarCard,
22
+ PricingCard,
23
+ Loader,
24
+ OTPInput
27
25
  } from "virtual-ui-lib"
28
26
 
29
27
  function App() {
30
28
  return (
31
29
  <>
32
- <Button text="Click me" />
30
+ <Navbar />
31
+ <Sidebar />
32
+
33
+ <div style={{ padding: "20px" }}>
34
+ <AvatarCard />
35
+ <PricingCard />
36
+ <OTPInput />
37
+ <Loader />
38
+ </div>
39
+ </>
40
+ )
41
+ }
42
+ ```
43
+
44
+ ---
33
45
 
34
- <Card title="Hello" text="Card example">
35
- <Button text="Buy" />
36
- </Card>
46
+ # 🧩 Components
47
+
48
+ Virtual UI includes the following components:
49
+
50
+ * AvatarCard
51
+ * BackgroundImageSlider
52
+ * Charts
53
+ * ColorPicker
54
+ * FileUpload
55
+ * Footer
56
+ * ImageCard
57
+ * ImageSlider
58
+ * InvoiceCard
59
+ * Loader
60
+ * Navbar
61
+ * NotificationToast
62
+ * OTPInput
63
+ * PageLoader
64
+ * PricingCard
65
+ * RatingStars
66
+ * Sidebar
37
67
 
38
- <Input placeholder="Enter name" />
68
+ ---
39
69
 
40
- <Badge text="New" />
70
+ # 📘 Component Details
41
71
 
42
- <Alert text="Saved successfully" />
72
+ ---
43
73
 
44
- <Avatar />
74
+ ## AvatarCard
45
75
 
46
- <Spinner />
47
- </>
48
- )
49
- }
76
+ ```jsx
77
+ <AvatarCard />
50
78
  ```
51
79
 
80
+ ### Props
81
+
82
+ | Prop | Description |
83
+ | ----- | ------------ |
84
+ | name | User name |
85
+ | image | Avatar image |
86
+ | role | User role |
87
+ | size | Card size |
88
+
52
89
  ---
53
90
 
54
- # Components
91
+ ## Navbar
92
+
93
+ ```jsx
94
+ <Navbar />
95
+ ```
55
96
 
56
- Virtual UI currently includes the following components:
97
+ ### Props
57
98
 
58
- * Button
59
- * Card
60
- * Input
61
- * Badge
62
- * Alert
63
- * Avatar
64
- * Spinner
65
- * Modal
99
+ | Prop | Description |
100
+ | ----- | ---------------- |
101
+ | logo | Logo text/image |
102
+ | links | Navigation links |
103
+ | fixed | Sticky navbar |
66
104
 
67
105
  ---
68
106
 
69
- # Button
107
+ ## Sidebar
70
108
 
71
109
  ```jsx
72
- <Button text="Click me" />
110
+ <Sidebar />
73
111
  ```
74
112
 
75
113
  ### Props
76
114
 
77
- | Prop | Description |
78
- | -------- | ---------------- |
79
- | text | Button text |
80
- | bg | Background color |
81
- | color | Text color |
82
- | size | sm / md / lg |
83
- | width | Button width |
84
- | radius | Border radius |
85
- | shadow | Box shadow |
86
- | disabled | Disable button |
87
- | icon | Add icon |
115
+ | Prop | Description |
116
+ | --------- | -------------- |
117
+ | items | Sidebar items |
118
+ | collapsed | Collapse state |
88
119
 
89
120
  ---
90
121
 
91
- # Card
122
+ ## PricingCard
92
123
 
93
124
  ```jsx
94
- <Card title="Card Title" text="Card description" />
125
+ <PricingCard />
95
126
  ```
96
127
 
97
128
  ### Props
98
129
 
99
- | Prop | Description |
100
- | -------- | ------------------------- |
101
- | title | Card title |
102
- | text | Card description |
103
- | width | Card width |
104
- | radius | Border radius |
105
- | shadow | Box shadow |
106
- | children | Extra content inside card |
130
+ | Prop | Description |
131
+ | ----------- | ---------------- |
132
+ | title | Plan name |
133
+ | price | Plan price |
134
+ | features | List of features |
135
+ | highlighted | Highlight plan |
107
136
 
108
137
  ---
109
138
 
110
- # Input
139
+ ## OTPInput
111
140
 
112
141
  ```jsx
113
- <Input placeholder="Enter name" />
142
+ <OTPInput length={6} />
114
143
  ```
115
144
 
116
145
  ### Props
117
146
 
118
- | Prop | Description |
119
- | ----------- | -------------------- |
120
- | placeholder | Input placeholder |
121
- | width | Input width |
122
- | radius | Border radius |
123
- | shadow | Box shadow |
124
- | value | Input value |
125
- | onChange | Input change handler |
147
+ | Prop | Description |
148
+ | -------- | ------------------ |
149
+ | length | Number of digits |
150
+ | onChange | OTP change handler |
126
151
 
127
152
  ---
128
153
 
129
- # Badge
154
+ ## Loader
130
155
 
131
156
  ```jsx
132
- <Badge text="New" />
157
+ <Loader />
133
158
  ```
134
159
 
135
160
  ### Props
136
161
 
137
- | Prop | Description |
138
- | ----- | ---------------- |
139
- | text | Badge text |
140
- | bg | Background color |
141
- | color | Text color |
162
+ | Prop | Description |
163
+ | ----- | ------------ |
164
+ | size | Loader size |
165
+ | color | Loader color |
142
166
 
143
167
  ---
144
168
 
145
- # Alert
169
+ ## NotificationToast
146
170
 
147
171
  ```jsx
148
- <Alert text="Saved successfully" />
172
+ <NotificationToast message="Saved!" />
149
173
  ```
150
174
 
151
175
  ### Props
152
176
 
153
- | Prop | Description |
154
- | ----- | ---------------- |
155
- | text | Alert message |
156
- | bg | Background color |
157
- | color | Text color |
177
+ | Prop | Description |
178
+ | ------- | ---------------------- |
179
+ | message | Toast text |
180
+ | type | success / error / info |
181
+
182
+ ---
183
+
184
+ ## Charts
185
+
186
+ ```jsx
187
+ <Charts />
188
+ ```
189
+
190
+ ### Props
191
+
192
+ | Prop | Description |
193
+ | ---- | ---------------- |
194
+ | type | bar / line / pie |
195
+ | data | Chart data |
196
+
197
+ ---
198
+
199
+ ## ColorPicker
200
+
201
+ ```jsx
202
+ <ColorPicker />
203
+ ```
204
+
205
+ ### Props
206
+
207
+ | Prop | Description |
208
+ | -------- | -------------- |
209
+ | value | Selected color |
210
+ | onChange | Change handler |
211
+
212
+ ---
213
+
214
+ ## FileUpload
215
+
216
+ ```jsx
217
+ <FileUpload />
218
+ ```
219
+
220
+ ### Props
221
+
222
+ | Prop | Description |
223
+ | -------- | -------------------- |
224
+ | onUpload | Upload handler |
225
+ | multiple | Allow multiple files |
226
+
227
+ ---
228
+
229
+ ## ImageCard
230
+
231
+ ```jsx
232
+ <ImageCard />
233
+ ```
234
+
235
+ ### Props
236
+
237
+ | Prop | Description |
238
+ | ----- | ------------ |
239
+ | src | Image source |
240
+ | title | Image title |
241
+
242
+ ---
243
+
244
+ ## ImageSlider
245
+
246
+ ```jsx
247
+ <ImageSlider />
248
+ ```
249
+
250
+ ### Props
251
+
252
+ | Prop | Description |
253
+ | -------- | ----------- |
254
+ | images | Image array |
255
+ | autoPlay | Auto slide |
256
+
257
+ ---
258
+
259
+ ## BackgroundImageSlider
260
+
261
+ ```jsx
262
+ <BackgroundImageSlider />
263
+ ```
264
+
265
+ ### Props
266
+
267
+ | Prop | Description |
268
+ | ------- | ----------------- |
269
+ | images | Background images |
270
+ | overlay | Overlay color |
271
+
272
+ ---
273
+
274
+ ## InvoiceCard
275
+
276
+ ```jsx
277
+ <InvoiceCard />
278
+ ```
279
+
280
+ ### Props
281
+
282
+ | Prop | Description |
283
+ | --------- | -------------- |
284
+ | invoiceId | Invoice ID |
285
+ | amount | Amount |
286
+ | status | Paid / Pending |
158
287
 
159
288
  ---
160
289
 
161
- # Avatar
290
+ ## RatingStars
162
291
 
163
292
  ```jsx
164
- <Avatar />
293
+ <RatingStars rating={4} />
165
294
  ```
166
295
 
167
296
  ### Props
168
297
 
169
- | Prop | Description |
170
- | ------ | ------------- |
171
- | src | Avatar image |
172
- | size | Avatar size |
173
- | radius | Border radius |
298
+ | Prop | Description |
299
+ | -------- | --------------- |
300
+ | rating | Number of stars |
301
+ | onChange | Change handler |
174
302
 
175
303
  ---
176
304
 
177
- # Spinner
305
+ ## PageLoader
178
306
 
179
307
  ```jsx
180
- <Spinner />
308
+ <PageLoader />
181
309
  ```
182
310
 
183
311
  ### Props
184
312
 
185
- | Prop | Description |
186
- | ------ | ------------- |
187
- | size | Spinner size |
188
- | color | Spinner color |
189
- | border | Border width |
313
+ | Prop | Description |
314
+ | ---------- | ---------------- |
315
+ | fullScreen | Full page loader |
190
316
 
191
317
  ---
192
318
 
193
- # Modal
319
+ ## Footer
194
320
 
195
321
  ```jsx
196
- <Modal open={true} title="Example Modal">
197
- Hello Modal
198
- </Modal>
322
+ <Footer />
199
323
  ```
200
324
 
201
325
  ### Props
202
326
 
203
- | Prop | Description |
204
- | -------- | ----------------- |
205
- | open | Show / hide modal |
206
- | title | Modal title |
207
- | onClose | Close modal |
208
- | children | Modal content |
327
+ | Prop | Description |
328
+ | ----- | ------------ |
329
+ | text | Footer text |
330
+ | links | Footer links |
331
+
332
+ ---
333
+
334
+ # 🤝 Contributing
335
+
336
+ Contributions are welcome!
337
+
338
+ * Fork the repo
339
+ * Create a new branch
340
+ * Submit a pull request
209
341
 
210
342
  ---
211
343
 
212
- # Contributing
344
+ # 📄 License
213
345
 
214
- Feel free to contribute by creating issues or pull requests.
346
+ MIT License
215
347
 
216
348
  ---
217
349
 
218
- # License
350
+ # 💡 Future Improvements
351
+
352
+ * Theme system (dark/light mode)
353
+ * Animation presets
354
+ * Accessibility improvements
355
+ * More enterprise components
356
+
357
+ ---
219
358
 
220
- MIT
359
+ Made with ❤️ for developers
package/dist/index.js CHANGED
@@ -44,7 +44,9 @@ __export(index_exports, {
44
44
  OTPInput: () => OTPInput,
45
45
  PageLoader: () => PageLoader,
46
46
  PricingCard: () => PricingCard,
47
- Sidebar: () => Sidebar
47
+ RatingStars: () => RatingStars,
48
+ Sidebar: () => Sidebar,
49
+ StatCard: () => StatCard
48
50
  });
49
51
  module.exports = __toCommonJS(index_exports);
50
52
 
@@ -2993,6 +2995,338 @@ var ColorPicker = ({
2993
2995
  alignItems: "center"
2994
2996
  } }, /* @__PURE__ */ import_react16.default.createElement("span", { style: { fontSize: "10px", color: "rgba(255,255,255,0.25)", fontWeight: "600", textTransform: "uppercase", letterSpacing: "0.5px" } }, "RGBA"), /* @__PURE__ */ import_react16.default.createElement("span", { style: { fontSize: "10px", fontFamily: "monospace", color: "rgba(255,255,255,0.5)" } }, alpha(color, opacity / 100))));
2995
2997
  };
2998
+
2999
+ // src/components/RatingStars/RatingStars.jsx
3000
+ var import_react17 = __toESM(require("react"));
3001
+ var RatingStars = ({
3002
+ defaultRating = 0,
3003
+ total = 5,
3004
+ size = 32,
3005
+ allowHalf = true,
3006
+ showLabel = true,
3007
+ showCount = true,
3008
+ reviewCount = 0,
3009
+ readOnly = false,
3010
+ accent = "#f59e0b",
3011
+ bg = "#0f172a",
3012
+ radius = "14px",
3013
+ onChange = () => {
3014
+ }
3015
+ }) => {
3016
+ const [rating, setRating] = (0, import_react17.useState)(defaultRating);
3017
+ const [hovered, setHovered] = (0, import_react17.useState)(0);
3018
+ const labels = ["", "Terrible", "Bad", "Okay", "Good", "Excellent"];
3019
+ const halfLabels = { 0.5: "Awful", 1: "Terrible", 1.5: "Very Bad", 2: "Bad", 2.5: "Below Average", 3: "Okay", 3.5: "Above Average", 4: "Good", 4.5: "Great", 5: "Excellent" };
3020
+ const alpha = (hex, op) => {
3021
+ const r = parseInt(hex.slice(1, 3), 16);
3022
+ const g = parseInt(hex.slice(3, 5), 16);
3023
+ const b = parseInt(hex.slice(5, 7), 16);
3024
+ return `rgba(${r},${g},${b},${op})`;
3025
+ };
3026
+ const active = hovered || rating;
3027
+ const handleMouseMove = (e, i) => {
3028
+ if (readOnly) return;
3029
+ if (allowHalf) {
3030
+ const rect = e.currentTarget.getBoundingClientRect();
3031
+ const half = e.clientX - rect.left < rect.width / 2;
3032
+ setHovered(half ? i - 0.5 : i);
3033
+ } else {
3034
+ setHovered(i);
3035
+ }
3036
+ };
3037
+ const handleClick = (e, i) => {
3038
+ if (readOnly) return;
3039
+ let val;
3040
+ if (allowHalf) {
3041
+ const rect = e.currentTarget.getBoundingClientRect();
3042
+ val = e.clientX - rect.left < rect.width / 2 ? i - 0.5 : i;
3043
+ } else {
3044
+ val = i;
3045
+ }
3046
+ setRating(val);
3047
+ onChange(val);
3048
+ };
3049
+ const StarIcon = ({ index }) => {
3050
+ const fill = active >= index ? "full" : active >= index - 0.5 ? "half" : "empty";
3051
+ const id = `half-${index}`;
3052
+ return /* @__PURE__ */ import_react17.default.createElement(
3053
+ "svg",
3054
+ {
3055
+ width: size,
3056
+ height: size,
3057
+ viewBox: "0 0 24 24",
3058
+ style: { cursor: readOnly ? "default" : "pointer", transition: "transform 0.1s", flexShrink: 0 },
3059
+ onMouseMove: (e) => handleMouseMove(e, index),
3060
+ onMouseLeave: () => !readOnly && setHovered(0),
3061
+ onClick: (e) => handleClick(e, index),
3062
+ onMouseEnter: (e) => {
3063
+ if (!readOnly) e.currentTarget.style.transform = "scale(1.15)";
3064
+ }
3065
+ },
3066
+ /* @__PURE__ */ import_react17.default.createElement("defs", null, /* @__PURE__ */ import_react17.default.createElement("linearGradient", { id }, /* @__PURE__ */ import_react17.default.createElement("stop", { offset: "50%", stopColor: accent }), /* @__PURE__ */ import_react17.default.createElement("stop", { offset: "50%", stopColor: "transparent" }))),
3067
+ /* @__PURE__ */ import_react17.default.createElement(
3068
+ "polygon",
3069
+ {
3070
+ points: "12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26",
3071
+ fill: fill === "full" ? accent : fill === "half" ? `url(#${id})` : "transparent",
3072
+ stroke: fill === "empty" ? "rgba(255,255,255,0.15)" : accent,
3073
+ strokeWidth: "1.5"
3074
+ }
3075
+ )
3076
+ );
3077
+ };
3078
+ return /* @__PURE__ */ import_react17.default.createElement("div", { style: {
3079
+ background: bg,
3080
+ borderRadius: radius,
3081
+ padding: "20px 22px",
3082
+ display: "inline-flex",
3083
+ flexDirection: "column",
3084
+ alignItems: "center",
3085
+ gap: "12px",
3086
+ fontFamily: "system-ui, sans-serif",
3087
+ border: "1px solid rgba(255,255,255,0.07)",
3088
+ boxShadow: "0 10px 40px rgba(0,0,0,0.4)"
3089
+ } }, /* @__PURE__ */ import_react17.default.createElement("div", { style: { display: "flex", gap: "4px", alignItems: "center" } }, Array.from({ length: total }, (_, i) => /* @__PURE__ */ import_react17.default.createElement(StarIcon, { key: i + 1, index: i + 1 }))), showLabel && /* @__PURE__ */ import_react17.default.createElement("div", { style: {
3090
+ fontSize: "14px",
3091
+ fontWeight: "700",
3092
+ minHeight: "20px",
3093
+ color: active > 0 ? accent : "rgba(255,255,255,0.2)",
3094
+ transition: "color 0.2s"
3095
+ } }, active > 0 ? allowHalf ? halfLabels[active] : labels[Math.round(active)] : readOnly ? "Not rated" : "Rate this"), (rating > 0 || readOnly) && showCount && /* @__PURE__ */ import_react17.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, /* @__PURE__ */ import_react17.default.createElement("span", { style: {
3096
+ fontSize: "28px",
3097
+ fontWeight: "800",
3098
+ color: "#fff",
3099
+ lineHeight: 1
3100
+ } }, rating.toFixed(1)), /* @__PURE__ */ import_react17.default.createElement("div", null, /* @__PURE__ */ import_react17.default.createElement("div", { style: { display: "flex", gap: "1px", marginBottom: "3px" } }, Array.from({ length: total }, (_, i) => {
3101
+ const fill = rating >= i + 1 ? "full" : rating >= i + 0.5 ? "half" : "empty";
3102
+ const gid = `sm-${i}`;
3103
+ return /* @__PURE__ */ import_react17.default.createElement("svg", { key: i, width: "12", height: "12", viewBox: "0 0 24 24" }, /* @__PURE__ */ import_react17.default.createElement("defs", null, /* @__PURE__ */ import_react17.default.createElement("linearGradient", { id: gid }, /* @__PURE__ */ import_react17.default.createElement("stop", { offset: "50%", stopColor: accent }), /* @__PURE__ */ import_react17.default.createElement("stop", { offset: "50%", stopColor: "transparent" }))), /* @__PURE__ */ import_react17.default.createElement(
3104
+ "polygon",
3105
+ {
3106
+ points: "12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26",
3107
+ fill: fill === "full" ? accent : fill === "half" ? `url(#${gid})` : "transparent",
3108
+ stroke: fill === "empty" ? "rgba(255,255,255,0.15)" : accent,
3109
+ strokeWidth: "1.5"
3110
+ }
3111
+ ));
3112
+ })), reviewCount > 0 && /* @__PURE__ */ import_react17.default.createElement("span", { style: { fontSize: "11px", color: "rgba(255,255,255,0.3)" } }, reviewCount.toLocaleString(), " reviews"))), !readOnly && rating === 0 && /* @__PURE__ */ import_react17.default.createElement("p", { style: { fontSize: "11px", color: "rgba(255,255,255,0.2)", margin: 0 } }, allowHalf ? "Hover to rate \u2022 Half stars supported" : "Click to rate"));
3113
+ };
3114
+
3115
+ // src/components/StatCard/StatCard.jsx
3116
+ var import_react18 = __toESM(require("react"));
3117
+ var StatCard = ({
3118
+ title = "Active Users",
3119
+ value = "128K",
3120
+ numericValue = 128e3,
3121
+ change = "+12.4%",
3122
+ isPositive = true,
3123
+ icon = "\u{1F465}",
3124
+ bg = "#ffffff",
3125
+ accent = "#3b82f6",
3126
+ radius = "16px",
3127
+ showBadge = true,
3128
+ showIcon = true,
3129
+ barPercent = 68
3130
+ }) => {
3131
+ const [count, setCount] = (0, import_react18.useState)(0);
3132
+ const [barWidth, setBarWidth] = (0, import_react18.useState)(0);
3133
+ const [visible, setVisible] = (0, import_react18.useState)(false);
3134
+ const [entered, setEntered] = (0, import_react18.useState)(false);
3135
+ const ref = (0, import_react18.useRef)(null);
3136
+ (0, import_react18.useEffect)(() => {
3137
+ const observer = new IntersectionObserver(
3138
+ ([entry]) => {
3139
+ if (entry.isIntersecting) {
3140
+ setVisible(true);
3141
+ observer.disconnect();
3142
+ }
3143
+ },
3144
+ { threshold: 0.3 }
3145
+ );
3146
+ if (ref.current) observer.observe(ref.current);
3147
+ return () => observer.disconnect();
3148
+ }, []);
3149
+ (0, import_react18.useEffect)(() => {
3150
+ if (!visible) return;
3151
+ setEntered(true);
3152
+ const duration = 1400;
3153
+ const start = performance.now();
3154
+ const step = (now) => {
3155
+ const p = Math.min((now - start) / duration, 1);
3156
+ const eased = 1 - Math.pow(1 - p, 3);
3157
+ setCount(Math.floor(eased * numericValue));
3158
+ if (p < 1) requestAnimationFrame(step);
3159
+ };
3160
+ requestAnimationFrame(step);
3161
+ }, [visible, numericValue]);
3162
+ (0, import_react18.useEffect)(() => {
3163
+ if (!visible) return;
3164
+ const t = setTimeout(() => setBarWidth(barPercent), 200);
3165
+ return () => clearTimeout(t);
3166
+ }, [visible, barPercent]);
3167
+ const formatCount = (n) => {
3168
+ if (numericValue >= 1e6) return (n / 1e6).toFixed(1) + "M";
3169
+ if (numericValue >= 1e3) return (n / 1e3).toFixed(1) + "K";
3170
+ return n.toLocaleString();
3171
+ };
3172
+ const alpha = (hex, op) => {
3173
+ const r = parseInt(hex.slice(1, 3), 16);
3174
+ const g = parseInt(hex.slice(3, 5), 16);
3175
+ const b = parseInt(hex.slice(5, 7), 16);
3176
+ return `rgba(${r},${g},${b},${op})`;
3177
+ };
3178
+ const uid = accent.replace("#", "sc");
3179
+ return /* @__PURE__ */ import_react18.default.createElement(import_react18.default.Fragment, null, /* @__PURE__ */ import_react18.default.createElement("style", null, `
3180
+ @import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;600;700;800&display=swap');
3181
+
3182
+ .sc-${uid} {
3183
+ font-family: 'Sora', sans-serif;
3184
+ background: ${bg};
3185
+ border-radius: ${radius};
3186
+ padding: 24px;
3187
+ display: inline-flex;
3188
+ flex-direction: column;
3189
+ gap: 14px;
3190
+ border: 1px solid rgba(0,0,0,0.07);
3191
+ box-shadow: 0 2px 16px rgba(0,0,0,0.06);
3192
+ min-width: 200px;
3193
+ position: relative;
3194
+ overflow: hidden;
3195
+ opacity: 0;
3196
+ transform: translateY(24px);
3197
+ transition: opacity 0.5s ease, transform 0.5s ease, box-shadow 0.25s ease;
3198
+ }
3199
+
3200
+ .sc-${uid}.entered {
3201
+ opacity: 1;
3202
+ transform: translateY(0);
3203
+ }
3204
+
3205
+ .sc-${uid}:hover {
3206
+ transform: translateY(-5px) !important;
3207
+ box-shadow: 0 16px 40px ${alpha(accent, 0.18)};
3208
+ }
3209
+
3210
+ /* Shimmer sweep on mount */
3211
+ .sc-${uid}::before {
3212
+ content: '';
3213
+ position: absolute;
3214
+ inset: 0;
3215
+ background: linear-gradient(105deg, transparent 40%, ${alpha(accent, 0.08)} 50%, transparent 60%);
3216
+ transform: translateX(-100%);
3217
+ transition: transform 0s;
3218
+ }
3219
+ .sc-${uid}.entered::before {
3220
+ animation: sc-shimmer-${uid} 0.9s ease 0.3s forwards;
3221
+ }
3222
+ @keyframes sc-shimmer-${uid} {
3223
+ to { transform: translateX(200%); }
3224
+ }
3225
+
3226
+ /* Pulse ring on icon */
3227
+ .sc-icon-${uid} {
3228
+ font-size: 1.3rem;
3229
+ background: ${alpha(accent, 0.1)};
3230
+ width: 42px;
3231
+ height: 42px;
3232
+ border-radius: 10px;
3233
+ display: flex;
3234
+ align-items: center;
3235
+ justify-content: center;
3236
+ position: relative;
3237
+ }
3238
+ .sc-icon-${uid}::after {
3239
+ content: '';
3240
+ position: absolute;
3241
+ inset: -4px;
3242
+ border-radius: 14px;
3243
+ border: 2px solid ${alpha(accent, 0.25)};
3244
+ animation: sc-pulse-${uid} 2s ease-in-out infinite;
3245
+ }
3246
+ @keyframes sc-pulse-${uid} {
3247
+ 0%, 100% { transform: scale(1); opacity: 0.6; }
3248
+ 50% { transform: scale(1.12); opacity: 0; }
3249
+ }
3250
+
3251
+ .sc-top-${uid} {
3252
+ display: flex;
3253
+ justify-content: space-between;
3254
+ align-items: center;
3255
+ }
3256
+
3257
+ .sc-badge-up-${uid} {
3258
+ font-size: 0.72rem;
3259
+ font-weight: 700;
3260
+ color: #16a34a;
3261
+ background: #dcfce7;
3262
+ padding: 3px 10px;
3263
+ border-radius: 999px;
3264
+ letter-spacing: 0.02em;
3265
+ animation: sc-badgepop-${uid} 0.4s cubic-bezier(.34,1.56,.64,1) 0.6s both;
3266
+ }
3267
+ .sc-badge-down-${uid} {
3268
+ font-size: 0.72rem;
3269
+ font-weight: 700;
3270
+ color: #dc2626;
3271
+ background: #fee2e2;
3272
+ padding: 3px 10px;
3273
+ border-radius: 999px;
3274
+ letter-spacing: 0.02em;
3275
+ animation: sc-badgepop-${uid} 0.4s cubic-bezier(.34,1.56,.64,1) 0.6s both;
3276
+ }
3277
+ @keyframes sc-badgepop-${uid} {
3278
+ from { transform: scale(0.5); opacity: 0; }
3279
+ to { transform: scale(1); opacity: 1; }
3280
+ }
3281
+
3282
+ .sc-value-${uid} {
3283
+ font-size: 2rem;
3284
+ font-weight: 800;
3285
+ color: #0f172a;
3286
+ line-height: 1;
3287
+ font-variant-numeric: tabular-nums;
3288
+ }
3289
+
3290
+ .sc-label-${uid} {
3291
+ font-size: 0.78rem;
3292
+ font-weight: 600;
3293
+ color: #94a3b8;
3294
+ text-transform: uppercase;
3295
+ letter-spacing: 0.07em;
3296
+ }
3297
+
3298
+ .sc-track-${uid} {
3299
+ height: 5px;
3300
+ border-radius: 999px;
3301
+ background: rgba(0,0,0,0.06);
3302
+ overflow: hidden;
3303
+ }
3304
+ .sc-fill-${uid} {
3305
+ height: 100%;
3306
+ border-radius: 999px;
3307
+ background: linear-gradient(90deg, ${alpha(accent, 0.5)}, ${accent});
3308
+ width: 0%;
3309
+ transition: width 1.2s cubic-bezier(0.22, 1, 0.36, 1) 0.4s;
3310
+ box-shadow: 0 0 8px ${alpha(accent, 0.5)};
3311
+ }
3312
+ `), /* @__PURE__ */ import_react18.default.createElement(
3313
+ "div",
3314
+ {
3315
+ ref,
3316
+ className: `sc-${uid}${entered ? " entered" : ""}`
3317
+ },
3318
+ /* @__PURE__ */ import_react18.default.createElement("div", { className: `sc-top-${uid}` }, showIcon && /* @__PURE__ */ import_react18.default.createElement("div", { className: `sc-icon-${uid}` }, icon), showBadge && /* @__PURE__ */ import_react18.default.createElement("span", { className: isPositive ? `sc-badge-up-${uid}` : `sc-badge-down-${uid}` }, isPositive ? "\u25B2" : "\u25BC", " ", change)),
3319
+ /* @__PURE__ */ import_react18.default.createElement("div", { className: `sc-value-${uid}` }, visible ? formatCount(count) : "0"),
3320
+ /* @__PURE__ */ import_react18.default.createElement("div", { className: `sc-label-${uid}` }, title),
3321
+ /* @__PURE__ */ import_react18.default.createElement("div", { className: `sc-track-${uid}` }, /* @__PURE__ */ import_react18.default.createElement(
3322
+ "div",
3323
+ {
3324
+ className: `sc-fill-${uid}`,
3325
+ style: { width: `${barWidth}%` }
3326
+ }
3327
+ ))
3328
+ ));
3329
+ };
2996
3330
  // Annotate the CommonJS export names for ESM import in node:
2997
3331
  0 && (module.exports = {
2998
3332
  AvatarCard,
@@ -3010,5 +3344,7 @@ var ColorPicker = ({
3010
3344
  OTPInput,
3011
3345
  PageLoader,
3012
3346
  PricingCard,
3013
- Sidebar
3347
+ RatingStars,
3348
+ Sidebar,
3349
+ StatCard
3014
3350
  });
package/dist/index.mjs CHANGED
@@ -2943,6 +2943,338 @@ var ColorPicker = ({
2943
2943
  alignItems: "center"
2944
2944
  } }, /* @__PURE__ */ React16.createElement("span", { style: { fontSize: "10px", color: "rgba(255,255,255,0.25)", fontWeight: "600", textTransform: "uppercase", letterSpacing: "0.5px" } }, "RGBA"), /* @__PURE__ */ React16.createElement("span", { style: { fontSize: "10px", fontFamily: "monospace", color: "rgba(255,255,255,0.5)" } }, alpha(color, opacity / 100))));
2945
2945
  };
2946
+
2947
+ // src/components/RatingStars/RatingStars.jsx
2948
+ import React17, { useState as useState15 } from "react";
2949
+ var RatingStars = ({
2950
+ defaultRating = 0,
2951
+ total = 5,
2952
+ size = 32,
2953
+ allowHalf = true,
2954
+ showLabel = true,
2955
+ showCount = true,
2956
+ reviewCount = 0,
2957
+ readOnly = false,
2958
+ accent = "#f59e0b",
2959
+ bg = "#0f172a",
2960
+ radius = "14px",
2961
+ onChange = () => {
2962
+ }
2963
+ }) => {
2964
+ const [rating, setRating] = useState15(defaultRating);
2965
+ const [hovered, setHovered] = useState15(0);
2966
+ const labels = ["", "Terrible", "Bad", "Okay", "Good", "Excellent"];
2967
+ const halfLabels = { 0.5: "Awful", 1: "Terrible", 1.5: "Very Bad", 2: "Bad", 2.5: "Below Average", 3: "Okay", 3.5: "Above Average", 4: "Good", 4.5: "Great", 5: "Excellent" };
2968
+ const alpha = (hex, op) => {
2969
+ const r = parseInt(hex.slice(1, 3), 16);
2970
+ const g = parseInt(hex.slice(3, 5), 16);
2971
+ const b = parseInt(hex.slice(5, 7), 16);
2972
+ return `rgba(${r},${g},${b},${op})`;
2973
+ };
2974
+ const active = hovered || rating;
2975
+ const handleMouseMove = (e, i) => {
2976
+ if (readOnly) return;
2977
+ if (allowHalf) {
2978
+ const rect = e.currentTarget.getBoundingClientRect();
2979
+ const half = e.clientX - rect.left < rect.width / 2;
2980
+ setHovered(half ? i - 0.5 : i);
2981
+ } else {
2982
+ setHovered(i);
2983
+ }
2984
+ };
2985
+ const handleClick = (e, i) => {
2986
+ if (readOnly) return;
2987
+ let val;
2988
+ if (allowHalf) {
2989
+ const rect = e.currentTarget.getBoundingClientRect();
2990
+ val = e.clientX - rect.left < rect.width / 2 ? i - 0.5 : i;
2991
+ } else {
2992
+ val = i;
2993
+ }
2994
+ setRating(val);
2995
+ onChange(val);
2996
+ };
2997
+ const StarIcon = ({ index }) => {
2998
+ const fill = active >= index ? "full" : active >= index - 0.5 ? "half" : "empty";
2999
+ const id = `half-${index}`;
3000
+ return /* @__PURE__ */ React17.createElement(
3001
+ "svg",
3002
+ {
3003
+ width: size,
3004
+ height: size,
3005
+ viewBox: "0 0 24 24",
3006
+ style: { cursor: readOnly ? "default" : "pointer", transition: "transform 0.1s", flexShrink: 0 },
3007
+ onMouseMove: (e) => handleMouseMove(e, index),
3008
+ onMouseLeave: () => !readOnly && setHovered(0),
3009
+ onClick: (e) => handleClick(e, index),
3010
+ onMouseEnter: (e) => {
3011
+ if (!readOnly) e.currentTarget.style.transform = "scale(1.15)";
3012
+ }
3013
+ },
3014
+ /* @__PURE__ */ React17.createElement("defs", null, /* @__PURE__ */ React17.createElement("linearGradient", { id }, /* @__PURE__ */ React17.createElement("stop", { offset: "50%", stopColor: accent }), /* @__PURE__ */ React17.createElement("stop", { offset: "50%", stopColor: "transparent" }))),
3015
+ /* @__PURE__ */ React17.createElement(
3016
+ "polygon",
3017
+ {
3018
+ points: "12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26",
3019
+ fill: fill === "full" ? accent : fill === "half" ? `url(#${id})` : "transparent",
3020
+ stroke: fill === "empty" ? "rgba(255,255,255,0.15)" : accent,
3021
+ strokeWidth: "1.5"
3022
+ }
3023
+ )
3024
+ );
3025
+ };
3026
+ return /* @__PURE__ */ React17.createElement("div", { style: {
3027
+ background: bg,
3028
+ borderRadius: radius,
3029
+ padding: "20px 22px",
3030
+ display: "inline-flex",
3031
+ flexDirection: "column",
3032
+ alignItems: "center",
3033
+ gap: "12px",
3034
+ fontFamily: "system-ui, sans-serif",
3035
+ border: "1px solid rgba(255,255,255,0.07)",
3036
+ boxShadow: "0 10px 40px rgba(0,0,0,0.4)"
3037
+ } }, /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: "4px", alignItems: "center" } }, Array.from({ length: total }, (_, i) => /* @__PURE__ */ React17.createElement(StarIcon, { key: i + 1, index: i + 1 }))), showLabel && /* @__PURE__ */ React17.createElement("div", { style: {
3038
+ fontSize: "14px",
3039
+ fontWeight: "700",
3040
+ minHeight: "20px",
3041
+ color: active > 0 ? accent : "rgba(255,255,255,0.2)",
3042
+ transition: "color 0.2s"
3043
+ } }, active > 0 ? allowHalf ? halfLabels[active] : labels[Math.round(active)] : readOnly ? "Not rated" : "Rate this"), (rating > 0 || readOnly) && showCount && /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, /* @__PURE__ */ React17.createElement("span", { style: {
3044
+ fontSize: "28px",
3045
+ fontWeight: "800",
3046
+ color: "#fff",
3047
+ lineHeight: 1
3048
+ } }, rating.toFixed(1)), /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: "1px", marginBottom: "3px" } }, Array.from({ length: total }, (_, i) => {
3049
+ const fill = rating >= i + 1 ? "full" : rating >= i + 0.5 ? "half" : "empty";
3050
+ const gid = `sm-${i}`;
3051
+ return /* @__PURE__ */ React17.createElement("svg", { key: i, width: "12", height: "12", viewBox: "0 0 24 24" }, /* @__PURE__ */ React17.createElement("defs", null, /* @__PURE__ */ React17.createElement("linearGradient", { id: gid }, /* @__PURE__ */ React17.createElement("stop", { offset: "50%", stopColor: accent }), /* @__PURE__ */ React17.createElement("stop", { offset: "50%", stopColor: "transparent" }))), /* @__PURE__ */ React17.createElement(
3052
+ "polygon",
3053
+ {
3054
+ points: "12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26",
3055
+ fill: fill === "full" ? accent : fill === "half" ? `url(#${gid})` : "transparent",
3056
+ stroke: fill === "empty" ? "rgba(255,255,255,0.15)" : accent,
3057
+ strokeWidth: "1.5"
3058
+ }
3059
+ ));
3060
+ })), reviewCount > 0 && /* @__PURE__ */ React17.createElement("span", { style: { fontSize: "11px", color: "rgba(255,255,255,0.3)" } }, reviewCount.toLocaleString(), " reviews"))), !readOnly && rating === 0 && /* @__PURE__ */ React17.createElement("p", { style: { fontSize: "11px", color: "rgba(255,255,255,0.2)", margin: 0 } }, allowHalf ? "Hover to rate \u2022 Half stars supported" : "Click to rate"));
3061
+ };
3062
+
3063
+ // src/components/StatCard/StatCard.jsx
3064
+ import React18, { useEffect as useEffect7, useRef as useRef3, useState as useState16 } from "react";
3065
+ var StatCard = ({
3066
+ title = "Active Users",
3067
+ value = "128K",
3068
+ numericValue = 128e3,
3069
+ change = "+12.4%",
3070
+ isPositive = true,
3071
+ icon = "\u{1F465}",
3072
+ bg = "#ffffff",
3073
+ accent = "#3b82f6",
3074
+ radius = "16px",
3075
+ showBadge = true,
3076
+ showIcon = true,
3077
+ barPercent = 68
3078
+ }) => {
3079
+ const [count, setCount] = useState16(0);
3080
+ const [barWidth, setBarWidth] = useState16(0);
3081
+ const [visible, setVisible] = useState16(false);
3082
+ const [entered, setEntered] = useState16(false);
3083
+ const ref = useRef3(null);
3084
+ useEffect7(() => {
3085
+ const observer = new IntersectionObserver(
3086
+ ([entry]) => {
3087
+ if (entry.isIntersecting) {
3088
+ setVisible(true);
3089
+ observer.disconnect();
3090
+ }
3091
+ },
3092
+ { threshold: 0.3 }
3093
+ );
3094
+ if (ref.current) observer.observe(ref.current);
3095
+ return () => observer.disconnect();
3096
+ }, []);
3097
+ useEffect7(() => {
3098
+ if (!visible) return;
3099
+ setEntered(true);
3100
+ const duration = 1400;
3101
+ const start = performance.now();
3102
+ const step = (now) => {
3103
+ const p = Math.min((now - start) / duration, 1);
3104
+ const eased = 1 - Math.pow(1 - p, 3);
3105
+ setCount(Math.floor(eased * numericValue));
3106
+ if (p < 1) requestAnimationFrame(step);
3107
+ };
3108
+ requestAnimationFrame(step);
3109
+ }, [visible, numericValue]);
3110
+ useEffect7(() => {
3111
+ if (!visible) return;
3112
+ const t = setTimeout(() => setBarWidth(barPercent), 200);
3113
+ return () => clearTimeout(t);
3114
+ }, [visible, barPercent]);
3115
+ const formatCount = (n) => {
3116
+ if (numericValue >= 1e6) return (n / 1e6).toFixed(1) + "M";
3117
+ if (numericValue >= 1e3) return (n / 1e3).toFixed(1) + "K";
3118
+ return n.toLocaleString();
3119
+ };
3120
+ const alpha = (hex, op) => {
3121
+ const r = parseInt(hex.slice(1, 3), 16);
3122
+ const g = parseInt(hex.slice(3, 5), 16);
3123
+ const b = parseInt(hex.slice(5, 7), 16);
3124
+ return `rgba(${r},${g},${b},${op})`;
3125
+ };
3126
+ const uid = accent.replace("#", "sc");
3127
+ return /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement("style", null, `
3128
+ @import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;600;700;800&display=swap');
3129
+
3130
+ .sc-${uid} {
3131
+ font-family: 'Sora', sans-serif;
3132
+ background: ${bg};
3133
+ border-radius: ${radius};
3134
+ padding: 24px;
3135
+ display: inline-flex;
3136
+ flex-direction: column;
3137
+ gap: 14px;
3138
+ border: 1px solid rgba(0,0,0,0.07);
3139
+ box-shadow: 0 2px 16px rgba(0,0,0,0.06);
3140
+ min-width: 200px;
3141
+ position: relative;
3142
+ overflow: hidden;
3143
+ opacity: 0;
3144
+ transform: translateY(24px);
3145
+ transition: opacity 0.5s ease, transform 0.5s ease, box-shadow 0.25s ease;
3146
+ }
3147
+
3148
+ .sc-${uid}.entered {
3149
+ opacity: 1;
3150
+ transform: translateY(0);
3151
+ }
3152
+
3153
+ .sc-${uid}:hover {
3154
+ transform: translateY(-5px) !important;
3155
+ box-shadow: 0 16px 40px ${alpha(accent, 0.18)};
3156
+ }
3157
+
3158
+ /* Shimmer sweep on mount */
3159
+ .sc-${uid}::before {
3160
+ content: '';
3161
+ position: absolute;
3162
+ inset: 0;
3163
+ background: linear-gradient(105deg, transparent 40%, ${alpha(accent, 0.08)} 50%, transparent 60%);
3164
+ transform: translateX(-100%);
3165
+ transition: transform 0s;
3166
+ }
3167
+ .sc-${uid}.entered::before {
3168
+ animation: sc-shimmer-${uid} 0.9s ease 0.3s forwards;
3169
+ }
3170
+ @keyframes sc-shimmer-${uid} {
3171
+ to { transform: translateX(200%); }
3172
+ }
3173
+
3174
+ /* Pulse ring on icon */
3175
+ .sc-icon-${uid} {
3176
+ font-size: 1.3rem;
3177
+ background: ${alpha(accent, 0.1)};
3178
+ width: 42px;
3179
+ height: 42px;
3180
+ border-radius: 10px;
3181
+ display: flex;
3182
+ align-items: center;
3183
+ justify-content: center;
3184
+ position: relative;
3185
+ }
3186
+ .sc-icon-${uid}::after {
3187
+ content: '';
3188
+ position: absolute;
3189
+ inset: -4px;
3190
+ border-radius: 14px;
3191
+ border: 2px solid ${alpha(accent, 0.25)};
3192
+ animation: sc-pulse-${uid} 2s ease-in-out infinite;
3193
+ }
3194
+ @keyframes sc-pulse-${uid} {
3195
+ 0%, 100% { transform: scale(1); opacity: 0.6; }
3196
+ 50% { transform: scale(1.12); opacity: 0; }
3197
+ }
3198
+
3199
+ .sc-top-${uid} {
3200
+ display: flex;
3201
+ justify-content: space-between;
3202
+ align-items: center;
3203
+ }
3204
+
3205
+ .sc-badge-up-${uid} {
3206
+ font-size: 0.72rem;
3207
+ font-weight: 700;
3208
+ color: #16a34a;
3209
+ background: #dcfce7;
3210
+ padding: 3px 10px;
3211
+ border-radius: 999px;
3212
+ letter-spacing: 0.02em;
3213
+ animation: sc-badgepop-${uid} 0.4s cubic-bezier(.34,1.56,.64,1) 0.6s both;
3214
+ }
3215
+ .sc-badge-down-${uid} {
3216
+ font-size: 0.72rem;
3217
+ font-weight: 700;
3218
+ color: #dc2626;
3219
+ background: #fee2e2;
3220
+ padding: 3px 10px;
3221
+ border-radius: 999px;
3222
+ letter-spacing: 0.02em;
3223
+ animation: sc-badgepop-${uid} 0.4s cubic-bezier(.34,1.56,.64,1) 0.6s both;
3224
+ }
3225
+ @keyframes sc-badgepop-${uid} {
3226
+ from { transform: scale(0.5); opacity: 0; }
3227
+ to { transform: scale(1); opacity: 1; }
3228
+ }
3229
+
3230
+ .sc-value-${uid} {
3231
+ font-size: 2rem;
3232
+ font-weight: 800;
3233
+ color: #0f172a;
3234
+ line-height: 1;
3235
+ font-variant-numeric: tabular-nums;
3236
+ }
3237
+
3238
+ .sc-label-${uid} {
3239
+ font-size: 0.78rem;
3240
+ font-weight: 600;
3241
+ color: #94a3b8;
3242
+ text-transform: uppercase;
3243
+ letter-spacing: 0.07em;
3244
+ }
3245
+
3246
+ .sc-track-${uid} {
3247
+ height: 5px;
3248
+ border-radius: 999px;
3249
+ background: rgba(0,0,0,0.06);
3250
+ overflow: hidden;
3251
+ }
3252
+ .sc-fill-${uid} {
3253
+ height: 100%;
3254
+ border-radius: 999px;
3255
+ background: linear-gradient(90deg, ${alpha(accent, 0.5)}, ${accent});
3256
+ width: 0%;
3257
+ transition: width 1.2s cubic-bezier(0.22, 1, 0.36, 1) 0.4s;
3258
+ box-shadow: 0 0 8px ${alpha(accent, 0.5)};
3259
+ }
3260
+ `), /* @__PURE__ */ React18.createElement(
3261
+ "div",
3262
+ {
3263
+ ref,
3264
+ className: `sc-${uid}${entered ? " entered" : ""}`
3265
+ },
3266
+ /* @__PURE__ */ React18.createElement("div", { className: `sc-top-${uid}` }, showIcon && /* @__PURE__ */ React18.createElement("div", { className: `sc-icon-${uid}` }, icon), showBadge && /* @__PURE__ */ React18.createElement("span", { className: isPositive ? `sc-badge-up-${uid}` : `sc-badge-down-${uid}` }, isPositive ? "\u25B2" : "\u25BC", " ", change)),
3267
+ /* @__PURE__ */ React18.createElement("div", { className: `sc-value-${uid}` }, visible ? formatCount(count) : "0"),
3268
+ /* @__PURE__ */ React18.createElement("div", { className: `sc-label-${uid}` }, title),
3269
+ /* @__PURE__ */ React18.createElement("div", { className: `sc-track-${uid}` }, /* @__PURE__ */ React18.createElement(
3270
+ "div",
3271
+ {
3272
+ className: `sc-fill-${uid}`,
3273
+ style: { width: `${barWidth}%` }
3274
+ }
3275
+ ))
3276
+ ));
3277
+ };
2946
3278
  export {
2947
3279
  AvatarCard,
2948
3280
  BackgoundImageSlider,
@@ -2959,5 +3291,7 @@ export {
2959
3291
  OTPInput,
2960
3292
  PageLoader,
2961
3293
  PricingCard,
2962
- Sidebar
3294
+ RatingStars,
3295
+ Sidebar,
3296
+ StatCard
2963
3297
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "virtual-ui-lib",
3
- "version": "1.0.71",
3
+ "version": "1.0.73",
4
4
  "description": "Virtual UI React Component Library",
5
5
  "author": "Ankush",
6
6
  "license": "ISC",