jaml-ui 0.17.0 → 0.17.2

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,11 @@
1
+ export type JamlSpeedometerStatus = "idle" | "booting" | "running" | "completed" | "cancelled" | "error";
2
+ export interface JamlSpeedometerProps {
3
+ seedsPerSecond: number;
4
+ totalSearched: bigint | number;
5
+ matchingSeeds: bigint | number;
6
+ status: JamlSpeedometerStatus;
7
+ }
8
+ /**
9
+ * Compact live-search stats strip for MCP/app chrome.
10
+ */
11
+ export declare function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, }: JamlSpeedometerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { JimboColorOption } from "../ui/tokens.js";
4
+ const C = JimboColorOption;
5
+ function formatCount(value) {
6
+ return Number(value).toLocaleString();
7
+ }
8
+ function formatSpeed(value) {
9
+ if (!Number.isFinite(value) || value <= 0)
10
+ return "0/s";
11
+ if (value >= 1_000_000)
12
+ return `${(value / 1_000_000).toFixed(1)}M/s`;
13
+ if (value >= 1_000)
14
+ return `${(value / 1_000).toFixed(1)}K/s`;
15
+ return `${Math.round(value)}/s`;
16
+ }
17
+ /**
18
+ * Compact live-search stats strip for MCP/app chrome.
19
+ */
20
+ export function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, }) {
21
+ const active = status === "running" || status === "booting";
22
+ const tone = status === "error" ? C.RED : active ? C.GOLD : C.GREY;
23
+ return (_jsxs("div", { style: {
24
+ display: "flex",
25
+ alignItems: "center",
26
+ gap: 8,
27
+ color: tone,
28
+ fontSize: 11,
29
+ fontFamily: "var(--font-sans, m6x11plus), monospace",
30
+ whiteSpace: "nowrap",
31
+ }, children: [_jsx("span", { children: status }), _jsx("span", { children: formatSpeed(seedsPerSecond) }), _jsxs("span", { children: [formatCount(totalSearched), " searched"] }), _jsxs("span", { children: [formatCount(matchingSeeds), " matches"] })] }));
32
+ }
package/dist/index.d.ts CHANGED
@@ -16,6 +16,8 @@ export { CardFan, type CardFanProps } from "./components/CardFan.js";
16
16
  export { RealStandardcard, type CardSuit, type CardRank, type CardEnhancement, type CardSeal, type CardEdition, } from "./components/Standardcard.js";
17
17
  export { DeckSprite, DECK_SPRITE_POS, STAKE_SPRITE_POS, type DeckSpriteProps, } from "./components/DeckSprite.js";
18
18
  export { MotelyVersionBadge, type MotelyVersionBadgeProps, type MotelyCapabilities, } from "./components/MotelyVersionBadge.js";
19
+ export { JamlSpeedometer, type JamlSpeedometerProps, type JamlSpeedometerStatus, } from "./components/JamlSpeedometer.js";
20
+ export { Showcase, type ShowcaseFilter, type ShowcaseLiveStats, type ShowcaseProps, type ShowcaseRecentFind, } from "./ui/showcase.js";
19
21
  export { extractVisualJamlItems, type JamlPreviewGroups, type JamlPreviewItem, type JamlPreviewSection, type JamlPreviewVisualType, } from "./utils/jamlMapPreview.js";
20
22
  export { useMotelyStream, type StreamItem, type StreamState } from "./hooks/useShopStream.js";
21
23
  export { useSearch, type SearchResult, type SearchStatus, type UseSearchState, } from "./hooks/useSearch.js";
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ export { CardFan } from "./components/CardFan.js";
17
17
  export { RealStandardcard, } from "./components/Standardcard.js";
18
18
  export { DeckSprite, DECK_SPRITE_POS, STAKE_SPRITE_POS, } from "./components/DeckSprite.js";
19
19
  export { MotelyVersionBadge, } from "./components/MotelyVersionBadge.js";
20
+ export { JamlSpeedometer, } from "./components/JamlSpeedometer.js";
21
+ export { Showcase, } from "./ui/showcase.js";
20
22
  export { extractVisualJamlItems, } from "./utils/jamlMapPreview.js";
21
23
  export { useMotelyStream } from "./hooks/useShopStream.js";
22
24
  export { useSearch, } from "./hooks/useSearch.js";
@@ -0,0 +1,1040 @@
1
+ /* ═══════════════════════════════════════════════════════════════════════════
2
+ jimbo.css — Jimbo Design System
3
+ Balatro-inspired design tokens + component classes.
4
+ Eyedropped from actual game shader output — not Lua hex values.
5
+ ═══════════════════════════════════════════════════════════════════════════ */
6
+
7
+ /* ── Design Tokens (CSS Custom Properties) ─────────────────────────────── */
8
+
9
+ :root {
10
+ /* Colors — eyedropped from Balatro's rendered shader output */
11
+ --j-red: #ff4c40;
12
+ --j-blue: #0093ff;
13
+ --j-green: #429f79;
14
+ --j-orange: #ff9800;
15
+ --j-gold: #e4b643;
16
+ --j-purple: #9e74ce;
17
+
18
+ --j-dark-red: #a02721;
19
+ --j-dark-blue: #0057a1;
20
+ --j-dark-orange: #a05b00;
21
+ --j-dark-green: #215f46;
22
+ --j-dark-purple: #5e437e;
23
+
24
+ --j-dark-grey: #3a5055;
25
+ --j-darkest: #1e2b2d;
26
+ --j-grey: #708386;
27
+ --j-teal-grey: #404c4e;
28
+
29
+ --j-panel-edge: #1e2e32;
30
+ --j-inner-border: #334461;
31
+ --j-border-silver: #b9c2d2;
32
+ --j-border-south: #777e89;
33
+
34
+ --j-gold-text: #e4b643;
35
+ --j-green-text: #35bd86;
36
+ --j-orange-text: #ff8f00;
37
+ --j-white: #ffffff;
38
+ --j-black: #000000;
39
+
40
+ --j-tarot-btn: #9e74ce;
41
+ --j-planet-btn: #00a7ca;
42
+ --j-spectral-btn: #2e76fd;
43
+ --j-tarot-btn-dark: #5e437e;
44
+ --j-planet-btn-dark: #00657c;
45
+ --j-spectral-btn-dark: #14449e;
46
+
47
+ /* Typography */
48
+ --j-font: 'm6x11plus', 'Courier New', monospace;
49
+ --j-text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
50
+
51
+ /* Spacing */
52
+ --j-space-xs: 2px;
53
+ --j-space-sm: 4px;
54
+ --j-space-md: 8px;
55
+ --j-space-lg: 12px;
56
+ --j-space-xl: 16px;
57
+
58
+ /* Radii — Balatro is chunky, not bubbly. Never > 10px. */
59
+ --j-radius-sm: 4px;
60
+ --j-radius-md: 6px;
61
+ --j-radius-lg: 8px;
62
+ --j-radius-pill: 10px;
63
+
64
+ /* Animation */
65
+ --j-ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
66
+ --j-press-y: 3px;
67
+ --j-press-speed: 55ms;
68
+ }
69
+
70
+
71
+ /* ── Base Text ─────────────────────────────────────────────────────────── */
72
+
73
+ .j-text {
74
+ font-family: var(--j-font);
75
+ font-weight: 400;
76
+ line-height: 1.2;
77
+ text-shadow: var(--j-text-shadow);
78
+ color: var(--j-white);
79
+ }
80
+ .j-text--no-shadow { text-shadow: none; }
81
+ .j-text--upper {
82
+ text-transform: uppercase;
83
+ letter-spacing: 0.08em;
84
+ }
85
+
86
+ /* Sizes (from DESIGN.md typography scale) */
87
+ .j-text--display { font-size: 26px; letter-spacing: 0.04em; line-height: 1; }
88
+ .j-text--xl { font-size: 24px; letter-spacing: 0.04em; }
89
+ .j-text--lg { font-size: 18px; letter-spacing: 0.04em; }
90
+ .j-text--heading { font-size: 14px; letter-spacing: 0.08em; line-height: 1.2; }
91
+ .j-text--md { font-size: 14px; }
92
+ .j-text--sm { font-size: 12px; }
93
+ .j-text--body { font-size: 11px; letter-spacing: 0.05em; line-height: 1.3; }
94
+ .j-text--xs { font-size: 10px; }
95
+ .j-text--label { font-size: 9px; letter-spacing: 0.1em; line-height: 1; text-transform: uppercase; }
96
+ .j-text--micro { font-size: 8px; letter-spacing: 0.08em; line-height: 1; }
97
+
98
+ /* Tones */
99
+ .j-text--default { color: var(--j-white); }
100
+ .j-text--mult,
101
+ .j-text--red { color: var(--j-red); }
102
+ .j-text--chips,
103
+ .j-text--blue { color: var(--j-blue); }
104
+ .j-text--gold { color: var(--j-gold-text); }
105
+ .j-text--green { color: var(--j-green-text); }
106
+ .j-text--orange { color: var(--j-orange-text); }
107
+ .j-text--purple { color: var(--j-purple); }
108
+ .j-text--grey { color: var(--j-grey); }
109
+
110
+
111
+ /* ── Panel ─────────────────────────────────────────────────────────────── */
112
+
113
+ .j-panel {
114
+ background-color: var(--j-dark-grey);
115
+ border: 2px solid var(--j-border-silver);
116
+ border-bottom-color: var(--j-border-south);
117
+ border-radius: var(--j-radius-md);
118
+ box-shadow: 0 3px 0 0 rgba(0, 0, 0, 0.55),
119
+ inset 0 0 0 1px rgba(255, 255, 255, 0.04);
120
+ padding: var(--j-space-lg);
121
+ display: flex;
122
+ flex-direction: column;
123
+ align-items: stretch;
124
+ overflow: hidden;
125
+ position: relative;
126
+ }
127
+
128
+ .j-panel__body {
129
+ flex: 1;
130
+ overflow: auto;
131
+ }
132
+
133
+ .j-panel__back {
134
+ margin-top: var(--j-space-lg);
135
+ padding-top: var(--j-space-md);
136
+ flex-shrink: 0;
137
+ }
138
+
139
+ .j-inner-panel {
140
+ background-color: var(--j-inner-border);
141
+ border: 2px solid var(--j-panel-edge);
142
+ border-radius: var(--j-radius-md);
143
+ padding: var(--j-space-lg);
144
+ }
145
+
146
+
147
+ /* ── Button ────────────────────────────────────────────────────────────── */
148
+ /* Chunky 3D press: colored underside via offset shadow div,
149
+ press sinks the face +3px and collapses the shadow. */
150
+
151
+ .j-btn {
152
+ display: inline-block;
153
+ position: relative;
154
+ cursor: pointer;
155
+ -webkit-user-select: none;
156
+ user-select: none;
157
+ }
158
+ .j-btn--full { width: 100%; }
159
+ .j-btn--disabled {
160
+ opacity: 0.55;
161
+ cursor: not-allowed;
162
+ }
163
+
164
+ .j-btn__shadow {
165
+ position: absolute;
166
+ left: 1px;
167
+ top: 3px;
168
+ right: -1px;
169
+ bottom: -3px;
170
+ border-radius: var(--j-radius-md);
171
+ background: var(--j-btn-shadow-color, var(--j-dark-orange));
172
+ transition: opacity var(--j-press-speed) linear;
173
+ }
174
+ .j-btn[data-pressed="true"] .j-btn__shadow { opacity: 0; }
175
+
176
+ .j-btn__face {
177
+ position: relative;
178
+ border-radius: var(--j-radius-md);
179
+ background: var(--j-btn-face-color, var(--j-orange));
180
+ text-align: center;
181
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
182
+ transform: translate(0, 0);
183
+ transition: transform var(--j-press-speed) linear;
184
+ }
185
+ .j-btn[data-pressed="true"] .j-btn__face {
186
+ transform: translate(1px, 3px);
187
+ }
188
+
189
+ /* Sizes */
190
+ .j-btn--xs .j-btn__face { padding: 2px 8px; }
191
+ .j-btn--sm .j-btn__face { padding: 4px 10px; }
192
+ .j-btn--md .j-btn__face { padding: 6px 14px; }
193
+ .j-btn--lg .j-btn__face { padding: 10px 18px; }
194
+
195
+ /* Tone colors — set via CSS custom properties */
196
+ .j-btn--orange { --j-btn-face-color: var(--j-orange); --j-btn-shadow-color: var(--j-dark-orange); }
197
+ .j-btn--red { --j-btn-face-color: var(--j-red); --j-btn-shadow-color: var(--j-dark-red); }
198
+ .j-btn--blue { --j-btn-face-color: var(--j-blue); --j-btn-shadow-color: var(--j-dark-blue); }
199
+ .j-btn--green { --j-btn-face-color: var(--j-green); --j-btn-shadow-color: var(--j-dark-green); }
200
+ .j-btn--gold { --j-btn-face-color: var(--j-gold); --j-btn-shadow-color: #8a6a1e; }
201
+ .j-btn--grey { --j-btn-face-color: var(--j-dark-grey); --j-btn-shadow-color: var(--j-darkest); }
202
+
203
+
204
+ /* ── Badge ─────────────────────────────────────────────────────────────── */
205
+
206
+ .j-badge {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ border-radius: var(--j-radius-sm);
210
+ font-family: var(--j-font);
211
+ font-weight: 400;
212
+ text-transform: uppercase;
213
+ letter-spacing: 0.04em;
214
+ white-space: nowrap;
215
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
216
+ }
217
+ .j-badge--sm { padding: 2px 6px; font-size: 10px; }
218
+ .j-badge--md { padding: 4px 8px; font-size: 12px; }
219
+
220
+ /* Badge tones */
221
+ .j-badge--dark { background: var(--j-darkest); color: var(--j-white); border: 1px solid var(--j-panel-edge); }
222
+ .j-badge--blue { background: var(--j-blue); color: var(--j-white); border: 1px solid var(--j-black); }
223
+ .j-badge--red { background: var(--j-red); color: var(--j-white); border: 1px solid var(--j-black); }
224
+ .j-badge--green { background: var(--j-green); color: var(--j-black); border: 1px solid var(--j-black); }
225
+ .j-badge--gold { background: var(--j-gold); color: var(--j-black); border: 1px solid var(--j-black); }
226
+ .j-badge--grey { background: var(--j-grey); color: var(--j-white); border: 1px solid var(--j-black); }
227
+ .j-badge--orange { background: var(--j-orange); color: var(--j-white); border: 1px solid var(--j-black); }
228
+ .j-badge--purple { background: var(--j-purple); color: var(--j-white); border: 1px solid var(--j-black); }
229
+
230
+
231
+ /* ── Tabs ──────────────────────────────────────────────────────────────── */
232
+
233
+ .j-tabs {
234
+ display: flex;
235
+ gap: var(--j-space-md);
236
+ align-items: flex-end;
237
+ flex-wrap: wrap;
238
+ }
239
+
240
+ .j-tab {
241
+ display: flex;
242
+ flex-direction: column;
243
+ align-items: center;
244
+ position: relative;
245
+ }
246
+
247
+ .j-tab__indicator {
248
+ margin-bottom: var(--j-space-sm);
249
+ }
250
+ .j-tab__indicator[data-active="true"] {
251
+ animation: jimbo-bounce 0.8s cubic-bezier(0.68, 0, 0.68, 1) infinite;
252
+ }
253
+ .j-tab__indicator[data-active="false"] {
254
+ visibility: hidden;
255
+ }
256
+
257
+ .j-tab__btn {
258
+ border: none;
259
+ cursor: pointer;
260
+ border-radius: var(--j-radius-lg);
261
+ padding: 4px 12px;
262
+ background-color: var(--j-red);
263
+ box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.3);
264
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
265
+ transition: none;
266
+ }
267
+ .j-tab__btn:hover,
268
+ .j-tab__btn:active {
269
+ background-color: var(--j-dark-red);
270
+ }
271
+ .j-tab__btn[data-pressed="true"] {
272
+ transform: translateY(2px);
273
+ box-shadow: none;
274
+ }
275
+
276
+ @keyframes jimbo-bounce {
277
+ 0%, 100% { transform: translateY(0); }
278
+ 50% { transform: translateY(-3px); }
279
+ }
280
+
281
+ /* Vertical tabs */
282
+ .j-vtabs {
283
+ display: flex;
284
+ flex-direction: column;
285
+ gap: var(--j-space-sm);
286
+ }
287
+
288
+ .j-vtab {
289
+ border: none;
290
+ cursor: pointer;
291
+ border-radius: 8px 0 0 8px;
292
+ padding: 16px 8px;
293
+ background-color: var(--j-red);
294
+ writing-mode: vertical-rl;
295
+ text-orientation: mixed;
296
+ transform: rotate(180deg);
297
+ transition: none;
298
+ }
299
+
300
+
301
+ /* ── Toggle List ──────────────────────────────────────────────────────── */
302
+
303
+ .j-toggle-list {
304
+ display: flex;
305
+ flex-direction: column;
306
+ gap: var(--j-space-sm);
307
+ }
308
+
309
+ .j-toggle-list__title {
310
+ font-family: var(--j-font);
311
+ font-size: 12px;
312
+ color: var(--j-grey);
313
+ text-transform: uppercase;
314
+ letter-spacing: 0.04em;
315
+ margin-bottom: var(--j-space-sm);
316
+ }
317
+
318
+ .j-toggle-item {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: var(--j-space-md);
322
+ padding: 6px 8px;
323
+ background: rgba(255, 255, 255, 0.05);
324
+ border: 1px solid rgba(0, 0, 0, 0.2);
325
+ border-radius: var(--j-radius-sm);
326
+ cursor: pointer;
327
+ font-family: var(--j-font);
328
+ color: var(--j-white);
329
+ text-transform: uppercase;
330
+ letter-spacing: 0.04em;
331
+ text-align: left;
332
+ justify-content: flex-start;
333
+ }
334
+
335
+ .j-toggle-check {
336
+ width: 10px;
337
+ height: 10px;
338
+ flex-shrink: 0;
339
+ border: 1px solid var(--j-dark-grey);
340
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
341
+ }
342
+ .j-toggle-check[data-on="true"] { background: var(--j-orange); }
343
+ .j-toggle-check[data-on="false"] { background: var(--j-darkest); }
344
+
345
+
346
+ /* ── Tooltip ──────────────────────────────────────────────────────────── */
347
+
348
+ .j-tooltip {
349
+ position: fixed;
350
+ max-width: 280px;
351
+ padding: 6px 10px;
352
+ border-radius: var(--j-radius-md);
353
+ background: var(--j-darkest);
354
+ border: 2px solid var(--j-border-silver);
355
+ box-shadow: 0 2px 0 rgba(0, 0, 0, 0.8);
356
+ color: var(--j-white);
357
+ pointer-events: none;
358
+ z-index: 10000;
359
+ transition: opacity 120ms ease;
360
+ }
361
+
362
+
363
+ /* ── Flank Nav ────────────────────────────────────────────────────────── */
364
+
365
+ .j-flank {
366
+ display: flex;
367
+ align-items: stretch;
368
+ justify-content: center;
369
+ gap: var(--j-space-md);
370
+ width: 100%;
371
+ position: relative;
372
+ }
373
+
374
+ .j-flank__content {
375
+ position: relative;
376
+ flex: 1;
377
+ min-width: 0;
378
+ display: flex;
379
+ flex-direction: column;
380
+ }
381
+
382
+ .j-flank__btn {
383
+ flex-shrink: 0;
384
+ width: 40px;
385
+ margin: var(--j-press-y) 0;
386
+ border: none;
387
+ border-radius: var(--j-radius-lg);
388
+ cursor: pointer;
389
+ background-color: var(--j-red);
390
+ color: var(--j-white);
391
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
392
+ display: flex;
393
+ align-items: center;
394
+ justify-content: center;
395
+ box-shadow: 0 var(--j-press-y) 0 0 var(--j-dark-red);
396
+ transition: transform var(--j-press-speed) ease,
397
+ box-shadow var(--j-press-speed) ease,
398
+ background-color var(--j-press-speed) ease;
399
+ }
400
+ .j-flank__btn[data-pressed="true"] {
401
+ transform: translateY(var(--j-press-y));
402
+ box-shadow: none;
403
+ }
404
+ .j-flank__btn:disabled {
405
+ background-color: var(--j-dark-red);
406
+ cursor: default;
407
+ box-shadow: none;
408
+ }
409
+
410
+
411
+ /* ── Copy Row ─────────────────────────────────────────────────────────── */
412
+
413
+ .j-copy-row {
414
+ display: flex;
415
+ flex-direction: column;
416
+ gap: var(--j-space-sm);
417
+ }
418
+
419
+ .j-copy-row__field {
420
+ display: flex;
421
+ align-items: center;
422
+ gap: var(--j-space-md);
423
+ }
424
+
425
+ .j-copy-row__value {
426
+ flex: 1;
427
+ padding: 6px 10px;
428
+ background: var(--j-darkest);
429
+ border: 2px solid var(--j-panel-edge);
430
+ border-radius: var(--j-radius-sm);
431
+ word-break: break-all;
432
+ }
433
+
434
+ .j-copy-row__btn {
435
+ font-family: var(--j-font);
436
+ font-size: 11px;
437
+ letter-spacing: 0.08em;
438
+ border-radius: var(--j-radius-sm);
439
+ padding: 4px 12px;
440
+ cursor: pointer;
441
+ flex-shrink: 0;
442
+ transition: color 0.15s, background 0.15s, border-color 0.15s;
443
+ }
444
+ .j-copy-row__btn[data-copied="false"] {
445
+ color: var(--j-gold-text);
446
+ background: rgba(228, 182, 67, 0.12);
447
+ border: 1px solid var(--j-gold-text);
448
+ }
449
+ .j-copy-row__btn[data-copied="true"] {
450
+ color: var(--j-green-text);
451
+ background: rgba(53, 189, 134, 0.12);
452
+ border: 1px solid var(--j-green-text);
453
+ }
454
+
455
+
456
+ /* ── Code Block ───────────────────────────────────────────────────────── */
457
+
458
+ .j-code-block {
459
+ background-color: var(--j-darkest);
460
+ border: 2px solid var(--j-panel-edge);
461
+ border-radius: var(--j-radius-lg);
462
+ box-shadow: 0 3px 0 0 rgba(0, 0, 0, 0.5);
463
+ overflow: hidden;
464
+ display: flex;
465
+ flex-direction: column;
466
+ }
467
+
468
+ .j-code-block__header {
469
+ padding: 8px 12px;
470
+ display: flex;
471
+ align-items: center;
472
+ justify-content: space-between;
473
+ border-bottom: 1px solid var(--j-inner-border);
474
+ }
475
+
476
+ .j-code-block__meta {
477
+ display: flex;
478
+ gap: 8px;
479
+ align-items: center;
480
+ }
481
+
482
+ .j-code-block__filename {
483
+ font-size: 10px;
484
+ opacity: 0.6;
485
+ }
486
+
487
+ .j-code-block__lang {
488
+ font-size: 9px;
489
+ padding: 1px 6px;
490
+ border-radius: 3px;
491
+ background: rgba(0, 0, 0, 0.4);
492
+ color: #60a5fa;
493
+ }
494
+
495
+ .j-code-block__copy {
496
+ padding: 4px;
497
+ background: none;
498
+ border: none;
499
+ cursor: pointer;
500
+ display: flex;
501
+ }
502
+ .j-code-block__copy[data-copied="false"] { color: rgba(255, 255, 255, 0.5); }
503
+ .j-code-block__copy[data-copied="true"] { color: #4ade80; }
504
+
505
+ .j-code-block__pre {
506
+ padding: 12px;
507
+ overflow-x: auto;
508
+ font-family: monospace;
509
+ font-size: 0.875rem;
510
+ line-height: 1.6;
511
+ color: #f6f0d5;
512
+ margin: 0;
513
+ }
514
+
515
+
516
+ /* ── Filter Bar ───────────────────────────────────────────────────────── */
517
+
518
+ .j-filter-bar {
519
+ display: flex;
520
+ gap: 24px;
521
+ padding: var(--j-space-xl);
522
+ background-color: var(--j-dark-grey);
523
+ border: 4px solid var(--j-border-silver);
524
+ box-shadow: 0 3px 0 0 var(--j-border-south);
525
+ border-radius: var(--j-radius-lg);
526
+ position: relative;
527
+ flex-wrap: wrap;
528
+ }
529
+
530
+ .j-filter-bar__field {
531
+ flex: 1;
532
+ min-width: 200px;
533
+ position: relative;
534
+ margin-top: 10px;
535
+ }
536
+
537
+ .j-filter-bar__pill {
538
+ position: absolute;
539
+ top: -14px;
540
+ left: 16px;
541
+ background-color: var(--j-red);
542
+ border: 2px solid var(--j-dark-red);
543
+ border-radius: var(--j-radius-md);
544
+ padding: 4px 12px;
545
+ z-index: 2;
546
+ }
547
+
548
+ .j-filter-bar__input {
549
+ width: 100%;
550
+ padding: 14px 16px 14px 48px;
551
+ background-color: var(--j-darkest);
552
+ border: none;
553
+ border-bottom: 4px solid var(--j-panel-edge);
554
+ border-radius: var(--j-radius-lg);
555
+ color: var(--j-white);
556
+ font-family: var(--j-font);
557
+ font-size: 20px;
558
+ letter-spacing: 0.08em;
559
+ outline: none;
560
+ }
561
+
562
+ .j-filter-bar__search-icon {
563
+ position: absolute;
564
+ left: 0;
565
+ top: 0;
566
+ bottom: 0;
567
+ width: 48px;
568
+ display: flex;
569
+ align-items: center;
570
+ justify-content: center;
571
+ pointer-events: none;
572
+ color: var(--j-blue);
573
+ z-index: 1;
574
+ }
575
+
576
+ .j-filter-bar__select {
577
+ appearance: none;
578
+ -webkit-appearance: none;
579
+ -moz-appearance: none;
580
+ background-color: var(--j-orange);
581
+ color: var(--j-white);
582
+ border: none;
583
+ border-bottom: 4px solid var(--j-dark-orange);
584
+ border-radius: var(--j-radius-lg);
585
+ cursor: pointer;
586
+ font-family: var(--j-font);
587
+ font-size: 18px;
588
+ letter-spacing: 0.08em;
589
+ padding: 14px 48px 14px 24px;
590
+ min-width: 200px;
591
+ text-align: center;
592
+ outline: none;
593
+ }
594
+
595
+ .j-filter-bar__sort-icon {
596
+ position: absolute;
597
+ right: 16px;
598
+ top: 50%;
599
+ transform: translateY(-50%);
600
+ pointer-events: none;
601
+ color: var(--j-white);
602
+ opacity: 0.85;
603
+ }
604
+
605
+
606
+ /* ── Modal ────────────────────────────────────────────────────────────── */
607
+
608
+ .j-modal-overlay {
609
+ position: fixed;
610
+ inset: 0;
611
+ z-index: 50;
612
+ display: flex;
613
+ align-items: center;
614
+ justify-content: center;
615
+ padding: var(--j-space-xl);
616
+ background: rgba(0, 0, 0, 0.7);
617
+ }
618
+
619
+ .j-modal {
620
+ width: 100%;
621
+ max-width: 375px;
622
+ max-height: 90vh;
623
+ display: flex;
624
+ flex-direction: column;
625
+ }
626
+
627
+ .j-modal__title {
628
+ text-align: center;
629
+ margin: 0 0 var(--j-space-xl);
630
+ }
631
+
632
+
633
+ /* ── Footer ───────────────────────────────────────────────────────────── */
634
+
635
+ .j-footer {
636
+ width: 100%;
637
+ transition: opacity 200ms;
638
+ }
639
+ .j-footer--hidden {
640
+ pointer-events: none;
641
+ opacity: 0;
642
+ }
643
+
644
+ .j-footer__bar {
645
+ width: 100%;
646
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
647
+ background: rgba(0, 0, 0, 0.9);
648
+ padding: 0 1rem 3px;
649
+ text-align: center;
650
+ }
651
+
652
+ .j-footer__text {
653
+ font-family: var(--j-font);
654
+ font-size: clamp(11px, 0.8vw + 8px, 14px);
655
+ display: flex;
656
+ flex-wrap: wrap;
657
+ align-items: center;
658
+ justify-content: center;
659
+ gap: 0 0.5rem;
660
+ color: white;
661
+ margin: 0;
662
+ }
663
+
664
+ .j-footer__link {
665
+ color: var(--j-gold);
666
+ text-decoration: none;
667
+ }
668
+
669
+
670
+ /* ── Floating ─────────────────────────────────────────────────────────── */
671
+
672
+ .j-floating { position: absolute; z-index: 20; }
673
+
674
+
675
+ /* ── Background (canvas is styled inline because it's fixed fullscreen) ── */
676
+
677
+
678
+ /* ── GlowRing ─────────────────────────────────────────────────────────── */
679
+
680
+ .j-glow--must {
681
+ box-shadow: 0 0 0 2px var(--j-blue), 0 0 10px var(--j-blue);
682
+ animation: j-glow-pulse 1.6s ease-in-out infinite;
683
+ }
684
+ .j-glow--should {
685
+ box-shadow: 0 0 0 2px var(--j-gold), 0 0 10px var(--j-gold);
686
+ animation: j-glow-pulse 1.6s ease-in-out infinite;
687
+ }
688
+
689
+ @keyframes j-glow-pulse {
690
+ 0%, 100% { opacity: 0.55; }
691
+ 50% { opacity: 1; }
692
+ }
693
+
694
+
695
+ /* ── Utility ──────────────────────────────────────────────────────────── */
696
+
697
+ .j-flex { display: flex; }
698
+ .j-flex-col { display: flex; flex-direction: column; }
699
+ .j-flex-wrap { flex-wrap: wrap; }
700
+ .j-items-center { align-items: center; }
701
+ .j-justify-center { justify-content: center; }
702
+ .j-gap-xs { gap: var(--j-space-xs); }
703
+ .j-gap-sm { gap: var(--j-space-sm); }
704
+ .j-gap-md { gap: var(--j-space-md); }
705
+ .j-gap-lg { gap: var(--j-space-lg); }
706
+ .j-gap-xl { gap: var(--j-space-xl); }
707
+ .j-w-full { width: 100%; }
708
+ .j-shrink-0 { flex-shrink: 0; }
709
+ .j-flex-1 { flex: 1; }
710
+ .j-min-w-0 { min-width: 0; }
711
+ .j-text-center { text-align: center; }
712
+ .j-overflow-hidden { overflow: hidden; }
713
+ .j-overflow-auto { overflow: auto; }
714
+ .j-relative { position: relative; }
715
+
716
+
717
+ /* ── Seed Input ───────────────────────────────────────────────────────── */
718
+
719
+ .j-seed-input {
720
+ display: flex;
721
+ flex-direction: column;
722
+ gap: var(--j-space-sm);
723
+ }
724
+
725
+ .j-seed-input__field {
726
+ width: 100%;
727
+ padding: 8px 12px;
728
+ border-radius: var(--j-radius-md);
729
+ border: 2px solid var(--j-panel-edge);
730
+ background: var(--j-darkest);
731
+ color: var(--j-gold-text);
732
+ font-size: 18px;
733
+ font-family: var(--j-font);
734
+ font-weight: 400;
735
+ letter-spacing: 0.12em;
736
+ text-transform: uppercase;
737
+ text-shadow: var(--j-text-shadow);
738
+ outline: none;
739
+ transition: border-color 100ms ease;
740
+ box-sizing: border-box;
741
+ }
742
+ .j-seed-input__field::placeholder {
743
+ color: var(--j-grey);
744
+ opacity: 0.6;
745
+ text-transform: uppercase;
746
+ letter-spacing: 0.06em;
747
+ font-size: 12px;
748
+ }
749
+ .j-seed-input__field:focus {
750
+ border-color: var(--j-gold);
751
+ }
752
+ .j-seed-input__field[data-valid="true"] {
753
+ border-color: var(--j-green);
754
+ }
755
+ .j-seed-input__field[data-valid="false"] {
756
+ border-color: var(--j-red);
757
+ }
758
+ .j-seed-input__field[data-valid="partial"] {
759
+ border-color: var(--j-panel-edge);
760
+ }
761
+
762
+ .j-seed-input__hint {
763
+ font-family: var(--j-font);
764
+ font-size: 9px;
765
+ letter-spacing: 0.08em;
766
+ color: var(--j-grey);
767
+ text-shadow: var(--j-text-shadow);
768
+ }
769
+
770
+
771
+ /* ── Aesthetic Selector ──────────────────────────────────────────────── */
772
+
773
+ .j-aesthetic-selector {
774
+ display: flex;
775
+ flex-direction: column;
776
+ gap: var(--j-space-sm);
777
+ }
778
+
779
+ .j-aesthetic-selector__list {
780
+ display: flex;
781
+ flex-wrap: wrap;
782
+ gap: var(--j-space-sm);
783
+ }
784
+
785
+ .j-aesthetic-pill {
786
+ padding: 4px 10px;
787
+ border-radius: var(--j-radius-md);
788
+ border: 2px solid var(--j-panel-edge);
789
+ background: var(--j-darkest);
790
+ color: var(--j-grey);
791
+ cursor: pointer;
792
+ font-size: 11px;
793
+ font-family: var(--j-font);
794
+ font-weight: 400;
795
+ letter-spacing: 0.04em;
796
+ text-shadow: var(--j-text-shadow);
797
+ transition: border-color 100ms ease, background 100ms ease, color 100ms ease;
798
+ box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.3);
799
+ }
800
+ .j-aesthetic-pill:hover {
801
+ border-color: var(--j-gold);
802
+ color: var(--j-gold-text);
803
+ }
804
+ .j-aesthetic-pill[data-active="true"] {
805
+ border-color: var(--j-gold);
806
+ background: rgba(228, 182, 67, 0.13);
807
+ color: var(--j-gold-text);
808
+ box-shadow: 0 0 0 1px var(--j-gold), 0 2px 0 0 rgba(0, 0, 0, 0.3);
809
+ }
810
+
811
+
812
+ /* ── Showcase ─────────────────────────────────────────────────────────── */
813
+
814
+ .j-showcase {
815
+ width: 100%;
816
+ height: 100%;
817
+ background: var(--j-darkest);
818
+ display: flex;
819
+ flex-direction: column;
820
+ font-family: var(--j-font);
821
+ color: var(--j-white);
822
+ overflow: hidden;
823
+ }
824
+
825
+ .j-showcase__scroll {
826
+ flex: 1;
827
+ min-height: 0;
828
+ overflow-y: auto;
829
+ padding: 18px 14px 10px;
830
+ }
831
+
832
+ .j-showcase__wordmark {
833
+ text-align: center;
834
+ margin-bottom: 18px;
835
+ }
836
+
837
+ .j-showcase__wordmark-title {
838
+ font-size: 32px;
839
+ letter-spacing: 3px;
840
+ line-height: 1;
841
+ color: var(--j-gold-text);
842
+ text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);
843
+ }
844
+
845
+ .j-showcase__wordmark-sub {
846
+ font-size: 14px;
847
+ letter-spacing: 4px;
848
+ color: var(--j-grey);
849
+ margin-top: 4px;
850
+ text-shadow: var(--j-text-shadow);
851
+ }
852
+
853
+ .j-showcase__stats {
854
+ background: var(--j-dark-grey);
855
+ border-radius: var(--j-radius-md);
856
+ padding: 10px;
857
+ border: 2px solid var(--j-panel-edge);
858
+ box-shadow: 0 2px 0 var(--j-black);
859
+ display: grid;
860
+ grid-template-columns: 1fr 1fr 1fr;
861
+ gap: var(--j-space-md);
862
+ text-align: center;
863
+ margin-bottom: var(--j-space-xl);
864
+ }
865
+
866
+ .j-showcase__stat-value {
867
+ font-size: 16px;
868
+ color: var(--j-gold-text);
869
+ text-shadow: var(--j-text-shadow);
870
+ }
871
+
872
+ .j-showcase__stat-label {
873
+ font-size: 9px;
874
+ color: var(--j-grey);
875
+ letter-spacing: 2px;
876
+ margin-top: 2px;
877
+ }
878
+
879
+ .j-showcase__section-header {
880
+ display: flex;
881
+ align-items: center;
882
+ gap: var(--j-space-md);
883
+ margin-bottom: var(--j-space-md);
884
+ }
885
+
886
+ .j-showcase__section-tag {
887
+ font-size: 11px;
888
+ letter-spacing: 2px;
889
+ padding: 2px 8px;
890
+ color: var(--j-white);
891
+ border-radius: 3px;
892
+ text-shadow: var(--j-text-shadow);
893
+ }
894
+
895
+ .j-showcase__section-rule {
896
+ flex: 1;
897
+ height: 2px;
898
+ border-radius: 1px;
899
+ }
900
+
901
+ .j-showcase__filter-list {
902
+ display: flex;
903
+ flex-direction: column;
904
+ gap: var(--j-space-md);
905
+ margin-bottom: var(--j-space-xl);
906
+ }
907
+
908
+ .j-showcase__filter-card {
909
+ background: var(--j-dark-grey);
910
+ border-radius: var(--j-radius-md);
911
+ padding: 10px;
912
+ box-shadow: 0 2px 0 var(--j-black);
913
+ display: flex;
914
+ align-items: center;
915
+ gap: 10px;
916
+ cursor: pointer;
917
+ }
918
+
919
+ .j-showcase__filter-sprites {
920
+ display: flex;
921
+ gap: 2px;
922
+ }
923
+
924
+ .j-showcase__filter-sprite {
925
+ width: 30px;
926
+ height: 40px;
927
+ display: flex;
928
+ align-items: center;
929
+ justify-content: center;
930
+ }
931
+
932
+ .j-showcase__filter-info {
933
+ flex: 1;
934
+ min-width: 0;
935
+ }
936
+
937
+ .j-showcase__filter-name {
938
+ font-size: 13px;
939
+ color: var(--j-white);
940
+ letter-spacing: 1px;
941
+ text-shadow: var(--j-text-shadow);
942
+ overflow: hidden;
943
+ text-overflow: ellipsis;
944
+ white-space: nowrap;
945
+ }
946
+
947
+ .j-showcase__filter-author {
948
+ font-size: 9px;
949
+ color: var(--j-gold-text);
950
+ letter-spacing: 1px;
951
+ margin-top: 2px;
952
+ }
953
+
954
+ .j-showcase__filter-hits {
955
+ text-align: right;
956
+ }
957
+
958
+ .j-showcase__filter-hits-value {
959
+ font-size: 14px;
960
+ text-shadow: var(--j-text-shadow);
961
+ }
962
+
963
+ .j-showcase__filter-hits-label {
964
+ font-size: 8px;
965
+ color: var(--j-grey);
966
+ letter-spacing: 1px;
967
+ }
968
+
969
+ .j-showcase__recent {
970
+ background: var(--j-dark-grey);
971
+ border-radius: var(--j-radius-md);
972
+ padding: 8px 10px;
973
+ border: 2px solid var(--j-panel-edge);
974
+ box-shadow: 0 2px 0 var(--j-black);
975
+ font-size: 11px;
976
+ color: var(--j-grey);
977
+ letter-spacing: 1px;
978
+ line-height: 1.7;
979
+ }
980
+
981
+ .j-showcase__actions {
982
+ padding: 8px 10px 10px;
983
+ border-top: 2px solid var(--j-black);
984
+ background: var(--j-dark-grey);
985
+ display: flex;
986
+ flex-direction: column;
987
+ gap: 6px;
988
+ }
989
+
990
+
991
+ /* ── Footer suit animation ────────────────────────────────────────────── */
992
+
993
+ .j-footer__suits {
994
+ display: inline-flex;
995
+ align-items: center;
996
+ }
997
+
998
+ .j-footer__suit-stage {
999
+ position: relative;
1000
+ display: inline-block;
1001
+ width: 1.5em;
1002
+ height: 1em;
1003
+ vertical-align: middle;
1004
+ }
1005
+
1006
+ .j-footer__suit-char {
1007
+ position: absolute;
1008
+ inset: 0;
1009
+ display: inline-flex;
1010
+ align-items: center;
1011
+ justify-content: center;
1012
+ opacity: 0;
1013
+ animation-duration: 5s;
1014
+ animation-delay: 0s;
1015
+ animation-iteration-count: infinite;
1016
+ animation-timing-function: ease-out;
1017
+ }
1018
+
1019
+
1020
+ /* ── Copy row label ───────────────────────────────────────────────────── */
1021
+
1022
+ .j-copy-row__label {
1023
+ letter-spacing: 2px;
1024
+ }
1025
+
1026
+
1027
+ /* ── Motely version badge ─────────────────────────────────────────────── */
1028
+
1029
+ .j-motely-badge {
1030
+ display: inline-flex;
1031
+ align-items: center;
1032
+ gap: 6px;
1033
+ }
1034
+
1035
+ .j-motely-badge--chip {
1036
+ padding: 3px 8px;
1037
+ border-radius: var(--j-radius-sm);
1038
+ background: var(--j-darkest);
1039
+ border: 1px solid var(--j-panel-edge);
1040
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaml-ui",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,12 +37,13 @@
37
37
  "assets/*.png",
38
38
  "assets/fonts",
39
39
  "fonts.css",
40
+ "dist/ui/jimbo.css",
40
41
  "README.md",
41
42
  "DESIGN.md",
42
43
  "LICENSE"
43
44
  ],
44
45
  "scripts": {
45
- "build": "tsc --pretty false",
46
+ "build": "tsc --pretty false && node -e \"const fs=require('fs');fs.mkdirSync('dist/ui',{recursive:true});fs.copyFileSync('src/ui/jimbo.css','dist/ui/jimbo.css')\"",
46
47
  "dev": "tsc --watch",
47
48
  "demo": "vite --config demo/vite.config.ts",
48
49
  "typecheck": "tsc --noEmit --pretty false",
@@ -116,7 +117,7 @@
116
117
  "@types/three": "^0.184.0",
117
118
  "@vitejs/plugin-react": "^5.0.4",
118
119
  "monaco-editor": "^0.55.1",
119
- "motely-wasm": "^14.3.1",
120
+ "motely-wasm": "^14.2.41",
120
121
  "react": "^19.2.4",
121
122
  "react-dom": "^19.2.4",
122
123
  "react-icons": "^5.6.0",