jaml-ui 0.21.3 → 0.21.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.
- package/DESIGN.md +36 -6
- package/dist/assets.d.ts +0 -2
- package/dist/assets.js +7 -29
- package/dist/components/JamlAnalyzerFullscreen.d.ts +1 -1
- package/dist/components/JamlAnalyzerFullscreen.js +5 -81
- package/dist/components/JamlCurator.js +1 -1
- package/dist/components/JamlSpeedometer.d.ts +7 -2
- package/dist/components/JamlSpeedometer.js +8 -15
- package/dist/components/jamlMap/JamlMapEditor.js +42 -38
- package/dist/components/jamlMap/JokerPicker.js +2 -2
- package/dist/components/jamlMap/MysterySlot.js +4 -4
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/hooks/useSearch.d.ts +2 -1
- package/dist/hooks/useSearch.js +111 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/SpriteMapper.d.ts +10 -0
- package/dist/lib/SpriteMapper.js +48 -0
- package/dist/lib/cardParser.d.ts +8 -0
- package/dist/lib/cardParser.js +65 -0
- package/dist/lib/classes/BuyMetaData.d.ts +11 -0
- package/dist/lib/classes/BuyMetaData.js +1 -0
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.js +15 -0
- package/dist/lib/const.d.ts +61 -0
- package/dist/lib/const.js +521 -0
- package/dist/lib/data/constants.d.ts +11 -0
- package/dist/lib/data/constants.js +17 -0
- package/dist/lib/hooks/useDragScroll.d.ts +4 -0
- package/dist/lib/hooks/useDragScroll.js +48 -0
- package/dist/lib/hooks/useJamlFilter.d.ts +48 -0
- package/dist/lib/hooks/useJamlFilter.js +219 -0
- package/dist/lib/hooks/useSeedAnalyzer.d.ts +6 -0
- package/dist/lib/hooks/useSeedAnalyzer.js +48 -0
- package/dist/lib/jaml/jamlCompletion.d.ts +12 -0
- package/dist/lib/jaml/jamlCompletion.js +13 -0
- package/dist/lib/jaml/jamlData.d.ts +3 -0
- package/dist/lib/jaml/jamlData.js +8 -0
- package/dist/lib/jaml/jamlObjectives.d.ts +13 -0
- package/dist/lib/jaml/jamlObjectives.js +97 -0
- package/dist/lib/jaml/jamlParser.d.ts +14 -0
- package/dist/lib/jaml/jamlParser.js +47 -0
- package/dist/lib/jaml/jamlPresets.d.ts +8 -0
- package/dist/lib/jaml/jamlPresets.js +61 -0
- package/dist/lib/jaml/jamlSchema.d.ts +54 -0
- package/dist/lib/jaml/jamlSchema.js +91 -0
- package/dist/lib/parseDailyRitual.d.ts +45 -0
- package/dist/lib/parseDailyRitual.js +69 -0
- package/dist/lib/tts/getRevealPos.d.ts +5 -0
- package/dist/lib/tts/getRevealPos.js +16 -0
- package/dist/lib/tts/splitTtsDisplay.d.ts +19 -0
- package/dist/lib/tts/splitTtsDisplay.js +35 -0
- package/dist/lib/types.d.ts +121 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/motelyDisplay.js +10 -17
- package/dist/ui/JimboIconButton.d.ts +10 -0
- package/dist/ui/JimboIconButton.js +28 -0
- package/dist/ui/JimboInputModal.d.ts +13 -0
- package/dist/ui/JimboInputModal.js +60 -0
- package/dist/ui/JimboSelect.d.ts +18 -0
- package/dist/ui/JimboSelect.js +43 -0
- package/dist/ui/PanelSplitter.d.ts +7 -0
- package/dist/ui/PanelSplitter.js +76 -0
- package/dist/ui/ide/AgnosticSeedCard.d.ts +19 -0
- package/dist/ui/ide/AgnosticSeedCard.js +48 -0
- package/dist/ui/ide/DeckSprite.d.ts +1 -0
- package/dist/ui/ide/DeckSprite.js +2 -0
- package/dist/ui/ide/JamlBuilder.d.ts +1 -0
- package/dist/ui/ide/JamlBuilder.js +112 -0
- package/dist/ui/ide/JamlEditor.d.ts +7 -0
- package/dist/ui/ide/JamlEditor.js +496 -0
- package/dist/ui/ide/JamlEditorMonaco.d.ts +8 -0
- package/dist/ui/ide/JamlEditorMonaco.js +78 -0
- package/dist/ui/ide/WasmStatus.d.ts +1 -0
- package/dist/ui/ide/WasmStatus.js +42 -0
- package/dist/ui/jimbo.css +338 -32
- package/dist/ui/jimboApp.d.ts +12 -0
- package/dist/ui/jimboApp.js +15 -0
- package/dist/ui/jimboInfoCard.d.ts +31 -0
- package/dist/ui/jimboInfoCard.js +26 -0
- package/dist/ui/jimboInset.d.ts +9 -0
- package/dist/ui/jimboInset.js +9 -0
- package/dist/ui/jimboSectionHeader.d.ts +11 -0
- package/dist/ui/jimboSectionHeader.js +9 -0
- package/dist/ui/jimboStatGrid.d.ts +13 -0
- package/dist/ui/jimboStatGrid.js +9 -0
- package/dist/ui/jimboTabs.d.ts +1 -1
- package/dist/ui/jimboWordmark.d.ts +10 -0
- package/dist/ui/jimboWordmark.js +9 -0
- package/dist/ui/mascot/JammySpeechBox.d.ts +9 -0
- package/dist/ui/mascot/JammySpeechBox.js +30 -0
- package/dist/ui/mascot/SeedMascot.d.ts +37 -0
- package/dist/ui/mascot/SeedMascot.js +17 -0
- package/dist/ui/mascot/index.d.ts +3 -0
- package/dist/ui/mascot/index.js +3 -0
- package/dist/ui/mascot/menuConfig.d.ts +102 -0
- package/dist/ui/mascot/menuConfig.js +12 -0
- package/dist/ui/panel.d.ts +1 -1
- package/dist/ui/panel.js +3 -25
- package/dist/ui/radial/RadialBadge.d.ts +17 -0
- package/dist/ui/radial/RadialBadge.js +43 -0
- package/dist/ui/radial/RadialBreadcrumb.d.ts +12 -0
- package/dist/ui/radial/RadialBreadcrumb.js +18 -0
- package/dist/ui/radial/RadialButton.d.ts +61 -0
- package/dist/ui/radial/RadialButton.js +102 -0
- package/dist/ui/radial/RadialMenu.d.ts +38 -0
- package/dist/ui/radial/RadialMenu.js +168 -0
- package/dist/ui/radial/RadialPill.d.ts +18 -0
- package/dist/ui/radial/RadialPill.js +15 -0
- package/dist/ui/radial/index.d.ts +16 -0
- package/dist/ui/radial/index.js +18 -0
- package/dist/ui/radial/radialMenuStore.d.ts +31 -0
- package/dist/ui/radial/radialMenuStore.js +122 -0
- package/dist/ui/radial/radialMenuViewport.d.ts +6 -0
- package/dist/ui/radial/radialMenuViewport.js +59 -0
- package/dist/ui/radial/useRadialMenu.d.ts +35 -0
- package/dist/ui/radial/useRadialMenu.js +107 -0
- package/dist/ui/showcase.d.ts +14 -6
- package/dist/ui/showcase.js +13 -21
- package/dist/ui/tokens.d.ts +5 -19
- package/dist/ui/tokens.js +5 -21
- package/dist/ui.d.ts +14 -0
- package/dist/ui.js +15 -0
- package/package.json +145 -146
package/DESIGN.md
CHANGED
|
@@ -124,7 +124,7 @@ components:
|
|
|
124
124
|
|
|
125
125
|
Jimbo is the design system for Balatro seed finder tools (JAML-UI, WeeJoker, Seed Finder). It recreates the cozy, tactile, chunky feel of LocalThunk's Balatro — dark panels with silver borders, 3D-press buttons, pixel typography, juice animations. Everything feels like a physical object you can poke.
|
|
126
126
|
|
|
127
|
-
The system is built **Mobile First
|
|
127
|
+
The system is built **Mobile First** for the **iPhone SE viewport: 375×667px**. This is a HARD constraint — not a minimum, it is THE target. All app screens must fit within 375×667 with **NO SCROLLING**. Content must be designed to fit, not overflow. No fat padding, no bloated margins — every pixel earns its place. If content doesn't fit, redesign it to be more compact or split it into a separate view.
|
|
128
128
|
|
|
129
129
|
## Colors
|
|
130
130
|
|
|
@@ -155,12 +155,40 @@ Contrast is critical. NEVER make grey text on top of a grey background. If using
|
|
|
155
155
|
|
|
156
156
|
## Layout
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
Hard target: **375×667px (iPhone SE portrait)**. No scrolling on app-level screens. Components must fit within the viewport. Snap-scrolling is ONLY allowed for internal content areas that are explicitly paginated (e.g., ante pages within a fullscreen analyzer). Top-level app views must never scroll.
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
**Thumb Zone Rule:** Back buttons, navigation buttons, and primary actions ALWAYS go at the **bottom** of the screen. Users hold their phone with one hand and tap with their thumb — bottom-positioned buttons are always in reach. NEVER put back/nav buttons at the top of a mobile screen. Back buttons are ALWAYS orange and labeled "Back".
|
|
161
|
+
|
|
162
|
+
**Button Consistency:** All footer buttons use `lg` size across every screen. No mixing sizes in footers.
|
|
163
|
+
|
|
164
|
+
Horizontal swipe for seed navigation. NO visible scrollbars globally — use `::-webkit-scrollbar { display: none; }` and `-ms-overflow-style: none`.
|
|
161
165
|
|
|
162
166
|
Panels use 2px solid borders with border-silver on top/sides and border-south on bottom, creating a subtle 3D card effect. Inner shadow: `inset 0 0 0 1px rgba(255,255,255,0.04)`. Outer shadow: `0 2px 0 #000`.
|
|
163
167
|
|
|
168
|
+
### Responsive Breakpoints (Container Queries)
|
|
169
|
+
|
|
170
|
+
The j-app shell uses CSS **container queries** (not media queries) because it may live inside a mobile browser, a Claude MCP artifact, or a centered desktop preview. The container determines the layout, not the viewport.
|
|
171
|
+
|
|
172
|
+
| Name | Container Width | Height | Scroll | Usage |
|
|
173
|
+
|---|---|---|---|---|
|
|
174
|
+
| `compact` | ≤ 400px | Fixed 667px | Never | iPhone SE. Default. |
|
|
175
|
+
| `cozy` | 401–750px | Flexible | Vertical OK | MCP inline artifacts, wider phones. |
|
|
176
|
+
| `wide` | 751px+ | Flexible | Vertical OK | Tablet/desktop (future). |
|
|
177
|
+
|
|
178
|
+
Default is `compact` (375×667 locked). Add `<JimboApp fluid>` or `.j-app--fluid` to unlock for MCP/desktop contexts. This lets the container stretch to fill its parent (up to 750px) and activates `@container jimbo` queries in CSS.
|
|
179
|
+
|
|
180
|
+
**What changes compact → cozy:**
|
|
181
|
+
- Height constraint lifts — content determines height
|
|
182
|
+
- Vertical scroll becomes OK (host manages scroll)
|
|
183
|
+
- Padding bumps from `--j-space-lg` to `--j-space-xl`
|
|
184
|
+
- Stat grid values bump to 20px
|
|
185
|
+
- Info card titles bump to 14px
|
|
186
|
+
- Section header tags bump to 12px
|
|
187
|
+
- Buttons STAY `lg` — thumb zone rule still applies
|
|
188
|
+
- Footer STAYS bottom-anchored
|
|
189
|
+
|
|
190
|
+
**What does NOT change:** Colors, fonts, components, spacing tokens, button behavior, animation. The design language is identical — only density shifts.
|
|
191
|
+
|
|
164
192
|
## Elevation & Depth
|
|
165
193
|
|
|
166
194
|
Buttons have a colored "underside" via box-shadow (not blur). On press, translateY increases by 2-3px and the shadow collapses — the button physically sinks. On hover, apply a tiny brightness bump (no lift).
|
|
@@ -169,9 +197,9 @@ Panels sit on a dark south-shadow (`0 3px 0 rgba(0,0,0,0.55)`). Translucent pane
|
|
|
169
197
|
|
|
170
198
|
JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]` with a 1.6s pulse animation. Must = blue glow, should = gold/green glow.
|
|
171
199
|
|
|
172
|
-
|
|
200
|
+
**Button:** Chunky 3D press. DO NOT ADD A COLORED EDGE/BORDER. Buttons rely entirely on a solid `box-shadow` to create the colored "underside" depth. Hover brightness bump. Press sinks +2-3px and the box-shadow collapses to 0. Variants: primary (red), secondary (blue), back (orange), default (grey). NO GOLD BUTTONS. Sizes via padding, not font-size. Easing: `cubic-bezier(0.34, 1.56, 0.64, 1)`.
|
|
173
201
|
|
|
174
|
-
**
|
|
202
|
+
**Badge (JimboBadge):** Badges indicate status and DO NOT CLICK. They are strictly flat. They have a colored background and flat borders, but NO 3D BOX-SHADOW and NO PRESS ANIMATIONS. Variants: dark, blue, red, green, orange, purple, grey. NO GOLD BADGES.
|
|
175
203
|
|
|
176
204
|
**Panel:** Dark grey (#3a5055) background, 2px solid border (silver top/sides, south bottom), border-radius 6px. Inner highlight: `inset 0 0 0 1px rgba(255,255,255,0.04)`. Drop: `0 2px 0 #000`.
|
|
177
205
|
|
|
@@ -194,8 +222,9 @@ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]`
|
|
|
194
222
|
## Do's and Don'ts
|
|
195
223
|
|
|
196
224
|
- DO use m6x11plus for everything except code/monospace.
|
|
197
|
-
- DO design for
|
|
225
|
+
- DO design for 375×667px (iPhone SE). Everything must fit — no scrolling.
|
|
198
226
|
- DO use translateY + box-shadow for button depth. Not CSS 3D transforms.
|
|
227
|
+
- DO put back buttons and nav actions at the **bottom** of the screen (thumb zone).
|
|
199
228
|
- DON'T use font-weight bold or heavy. m6x11plus is single-weight.
|
|
200
229
|
- DON'T use ALL CAPS. It is considered an embellishment and ruins the aesthetic.
|
|
201
230
|
- DON'T put grey text on top of a grey background.
|
|
@@ -203,4 +232,5 @@ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]`
|
|
|
203
232
|
- DON'T add visible scrollbars. Vertical magnetic snap-scroll + horizontal swipe only.
|
|
204
233
|
- DON'T use rounded corners larger than 10px. Balatro is chunky, not bubbly.
|
|
205
234
|
- DON'T use blur-based shadows for depth. Use solid colored box-shadows 80% opaque.
|
|
235
|
+
- DON'T put back/nav buttons at the top of a screen. They go at the BOTTOM.
|
|
206
236
|
- DON'T use redundant JS wrappers for `motely-wasm`. Import globally and `motely.boot()` once. Use `?worker&inline` for search workers rather than blob strings, and do not prop-drill `motelyWasmUrl`.
|
package/dist/assets.d.ts
CHANGED
|
@@ -15,6 +15,4 @@ export declare const JAML_ASSET_FILES: {
|
|
|
15
15
|
export type JamlAssetKey = keyof typeof JAML_ASSET_FILES;
|
|
16
16
|
export type JamlAssetFile = (typeof JAML_ASSET_FILES)[JamlAssetKey];
|
|
17
17
|
export declare function setJamlAssetBaseUrl(baseUrl: string | null | undefined): void;
|
|
18
|
-
export declare function clearJamlAssetBaseUrl(): void;
|
|
19
18
|
export declare function resolveJamlAssetUrl(asset: JamlAssetKey | JamlAssetFile): string;
|
|
20
|
-
export declare function getDefaultJamlAssetUrlMap(): Readonly<Record<JamlAssetKey, string>>;
|
package/dist/assets.js
CHANGED
|
@@ -13,28 +13,13 @@ export const JAML_ASSET_FILES = {
|
|
|
13
13
|
font: "fonts/m6x11plusplus.otf",
|
|
14
14
|
};
|
|
15
15
|
const assetKeyByFileName = Object.fromEntries(Object.entries(JAML_ASSET_FILES).map(([key, fileName]) => [fileName, key]));
|
|
16
|
-
const defaultAssetUrls = {
|
|
17
|
-
deck: new URL(`../assets/${JAML_ASSET_FILES.deck}`, import.meta.url).href,
|
|
18
|
-
blinds: new URL(`../assets/${JAML_ASSET_FILES.blinds}`, import.meta.url).href,
|
|
19
|
-
boosters: new URL(`../assets/${JAML_ASSET_FILES.boosters}`, import.meta.url).href,
|
|
20
|
-
editions: new URL(`../assets/${JAML_ASSET_FILES.editions}`, import.meta.url).href,
|
|
21
|
-
enhancers: new URL(`../assets/${JAML_ASSET_FILES.enhancers}`, import.meta.url).href,
|
|
22
|
-
jokers: new URL(`../assets/${JAML_ASSET_FILES.jokers}`, import.meta.url).href,
|
|
23
|
-
tarots: new URL(`../assets/${JAML_ASSET_FILES.tarots}`, import.meta.url).href,
|
|
24
|
-
vouchers: new URL(`../assets/${JAML_ASSET_FILES.vouchers}`, import.meta.url).href,
|
|
25
|
-
stickers: new URL(`../assets/${JAML_ASSET_FILES.stickers}`, import.meta.url).href,
|
|
26
|
-
tags: new URL(`../assets/${JAML_ASSET_FILES.tags}`, import.meta.url).href,
|
|
27
|
-
stakes: new URL(`../assets/${JAML_ASSET_FILES.stakes}`, import.meta.url).href,
|
|
28
|
-
font: new URL(`../assets/${JAML_ASSET_FILES.font}`, import.meta.url).href,
|
|
29
|
-
};
|
|
16
|
+
const defaultAssetUrls = Object.fromEntries(Object.entries(JAML_ASSET_FILES).map(([key, fileName]) => [key, new URL(`../assets/${fileName}`, import.meta.url).href]));
|
|
30
17
|
let customAssetBaseUrl = null;
|
|
31
18
|
function normalizeBaseUrl(baseUrl) {
|
|
32
|
-
|
|
33
|
-
if (trimmed.length === 0) {
|
|
34
|
-
throw new Error("Jaml asset base URL must not be empty.");
|
|
35
|
-
}
|
|
36
|
-
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
19
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
37
20
|
}
|
|
21
|
+
// `new URL(file, base)` requires `base` to be absolute. weejoker.app passes
|
|
22
|
+
// "/assets" (relative path), so we fall back to string concatenation for that case.
|
|
38
23
|
function joinAssetUrl(baseUrl, fileName) {
|
|
39
24
|
const normalized = normalizeBaseUrl(baseUrl);
|
|
40
25
|
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(normalized) || normalized.startsWith("//")) {
|
|
@@ -50,9 +35,6 @@ export function setJamlAssetBaseUrl(baseUrl) {
|
|
|
50
35
|
const trimmed = baseUrl.trim();
|
|
51
36
|
customAssetBaseUrl = trimmed.length === 0 ? null : normalizeBaseUrl(trimmed);
|
|
52
37
|
}
|
|
53
|
-
export function clearJamlAssetBaseUrl() {
|
|
54
|
-
customAssetBaseUrl = null;
|
|
55
|
-
}
|
|
56
38
|
export function resolveJamlAssetUrl(asset) {
|
|
57
39
|
const assetKey = asset in JAML_ASSET_FILES
|
|
58
40
|
? asset
|
|
@@ -60,11 +42,7 @@ export function resolveJamlAssetUrl(asset) {
|
|
|
60
42
|
if (!assetKey) {
|
|
61
43
|
throw new Error(`Unknown Jaml asset '${asset}'.`);
|
|
62
44
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return defaultAssetUrls[assetKey];
|
|
67
|
-
}
|
|
68
|
-
export function getDefaultJamlAssetUrlMap() {
|
|
69
|
-
return defaultAssetUrls;
|
|
45
|
+
return customAssetBaseUrl
|
|
46
|
+
? joinAssetUrl(customAssetBaseUrl, JAML_ASSET_FILES[assetKey])
|
|
47
|
+
: defaultAssetUrls[assetKey];
|
|
70
48
|
}
|
|
@@ -25,6 +25,6 @@ export interface JamlAnalyzerFullscreenProps {
|
|
|
25
25
|
/** Custom top page to render as Slide 0 */
|
|
26
26
|
topPage?: React.ReactNode;
|
|
27
27
|
}
|
|
28
|
-
export declare function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams,
|
|
28
|
+
export declare function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, chunkSize, className, topPage, }: JamlAnalyzerFullscreenProps): import("react/jsx-runtime").JSX.Element;
|
|
29
29
|
export type { AnalyzerItem };
|
|
30
30
|
export { ANALYZER_STREAM_META, type AnalyzerStreamKey } from "../hooks/analyzerStreamRegistry.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
3
|
-
import { useCallback, useMemo, useRef
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
4
4
|
import { JamlBoss, JamlGameCard, JamlTag, JamlVoucher, resolveAnalyzerShopItem } from "./GameCard.js";
|
|
5
5
|
import { useMotelyStream } from "../hooks/useShopStream.js";
|
|
6
6
|
import { useInfiniteScroll } from "../hooks/useIntersectionObserver.js";
|
|
@@ -17,16 +17,10 @@ const TONE_COLORS = {
|
|
|
17
17
|
default: C.GOLD_TEXT,
|
|
18
18
|
};
|
|
19
19
|
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
20
|
-
export function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams,
|
|
21
|
-
const
|
|
22
|
-
const effectiveEnabled = enabledStreams ?? internalEnabled;
|
|
23
|
-
const setEnabled = useCallback((next) => {
|
|
24
|
-
setInternalEnabled(next);
|
|
25
|
-
onEnabledStreamsChange?.(next);
|
|
26
|
-
}, [onEnabledStreamsChange]);
|
|
20
|
+
export function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, chunkSize = 12, className = "", topPage, }) {
|
|
21
|
+
const effectiveEnabled = enabledStreams ?? DEFAULT_ENABLED_STREAMS;
|
|
27
22
|
const { currentAnte, scrollRef, scrollToAnte, registerAnteRef } = useAnteTracker(antes);
|
|
28
|
-
|
|
29
|
-
return (_jsxs("div", { className: className, style: styles.root, children: [_jsxs("div", { ref: scrollRef, style: styles.scroller, children: [topPage ? topPage : jaml && (_jsxs("section", { style: { ...styles.section, scrollSnapAlign: "start", justifyContent: 'center' }, children: [_jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: styles.anteLabel, children: "JAML" }), _jsx("div", { style: styles.anteNumber, children: "MAP" })] }), _jsx(JamlMapPreview, { jaml: jaml, tallyColumns: tallyColumns, tallyLabels: tallyLabels }), _jsx("div", { style: { marginTop: 24, textAlign: 'center', opacity: 0.6 }, children: _jsx(JimboText, { size: "xs", tone: "grey", children: "Scroll down to explore seed details" }) })] })), antes.map((ante) => (_jsx(AnteSection, { ante: ante, live: live, enabledStreams: effectiveEnabled, chunkSize: chunkSize, registerRef: (el) => registerAnteRef(ante.ante, el) }, ante.ante)))] }), _jsx(SideRail, { antes: antes.map((a) => a.ante), currentAnte: currentAnte, onJump: scrollToAnte }), !hidePicker && (_jsx(StreamPicker, { enabled: effectiveEnabled, onChange: setEnabled, open: pickerOpen, onToggle: () => setPickerOpen((v) => !v) }))] }));
|
|
23
|
+
return (_jsxs("div", { className: className, style: styles.root, children: [_jsxs("div", { ref: scrollRef, style: styles.scroller, children: [topPage ? topPage : jaml && (_jsxs("section", { style: { ...styles.section, scrollSnapAlign: "start", justifyContent: 'center' }, children: [_jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: styles.anteLabel, children: "JAML" }), _jsx("div", { style: styles.anteNumber, children: "MAP" })] }), _jsx(JamlMapPreview, { jaml: jaml, tallyColumns: tallyColumns, tallyLabels: tallyLabels }), _jsx("div", { style: { marginTop: 24, textAlign: 'center', opacity: 0.6 }, children: _jsx(JimboText, { size: "xs", tone: "grey", children: "Scroll down to explore seed details" }) })] })), antes.map((ante) => (_jsx(AnteSection, { ante: ante, live: live, enabledStreams: effectiveEnabled, chunkSize: chunkSize, registerRef: (el) => registerAnteRef(ante.ante, el) }, ante.ante)))] }), _jsx(SideRail, { antes: antes.map((a) => a.ante), currentAnte: currentAnte, onJump: scrollToAnte })] }));
|
|
30
24
|
}
|
|
31
25
|
function AnteSection({ ante, live, enabledStreams, chunkSize, registerRef }) {
|
|
32
26
|
return (_jsxs("section", { ref: registerRef, "data-ante": ante.ante, style: styles.section, children: [_jsxs("header", { style: styles.header, children: [_jsxs("div", { children: [_jsx("div", { style: styles.anteLabel, children: "Ante" }), _jsx("div", { style: styles.anteNumber, children: ante.ante })] }), ante.voucher && (_jsxs("div", { style: styles.voucherBlock, children: [_jsx(JamlVoucher, { voucherName: ante.voucher, scale: 0.85 }), _jsx("div", { style: styles.voucherCaption, children: ante.voucher })] }))] }), _jsxs("div", { style: styles.blindRow, children: [_jsx(BlindCell, { label: "Small", tag: ante.smallBlindTag }), _jsx(BlindCell, { label: "Big", tag: ante.bigBlindTag }), ante.boss && (_jsxs("div", { style: styles.bossCell, children: [_jsx("div", { style: styles.cellLabel, children: "Boss" }), _jsx(JamlBoss, { bossName: ante.boss, scale: 0.7 }), _jsx("div", { style: styles.cellCaption, children: ante.boss })] }))] }), ante.packs && ante.packs.length > 0 && (_jsxs("div", { style: styles.streamLane, children: [_jsx("div", { style: styles.streamLabel, children: "Packs" }), _jsx("div", { style: styles.packRow, children: ante.packs.map((pack, i) => (_jsx("div", { style: styles.packPill, children: pack }, `${ante.ante}-pack-${i}`))) })] })), enabledStreams.map((key) => {
|
|
@@ -99,28 +93,6 @@ function SideRail({ antes, currentAnte, onJump }) {
|
|
|
99
93
|
} }, ante));
|
|
100
94
|
}) }));
|
|
101
95
|
}
|
|
102
|
-
function StreamPicker({ enabled, onChange, open, onToggle }) {
|
|
103
|
-
const enabledSet = new Set(enabled);
|
|
104
|
-
const all = Object.values(ANALYZER_STREAM_META);
|
|
105
|
-
function toggle(key) {
|
|
106
|
-
const next = new Set(enabledSet);
|
|
107
|
-
if (next.has(key))
|
|
108
|
-
next.delete(key);
|
|
109
|
-
else
|
|
110
|
-
next.add(key);
|
|
111
|
-
onChange(all.map((m) => m.key).filter((k) => next.has(k)));
|
|
112
|
-
}
|
|
113
|
-
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: onToggle, style: styles.pickerButton, "aria-label": "Toggle stream picker", children: open ? "✕" : "≡" }), open && (_jsxs("div", { style: styles.pickerPanel, children: [_jsx("div", { style: styles.pickerHeader, children: "Streams" }), all.map((meta) => {
|
|
114
|
-
const isOn = enabledSet.has(meta.key);
|
|
115
|
-
const tone = TONE_COLORS[meta.tone] ?? TONE_COLORS.default;
|
|
116
|
-
return (_jsxs("button", { type: "button", onClick: () => toggle(meta.key), style: {
|
|
117
|
-
...styles.pickerChip,
|
|
118
|
-
borderColor: isOn ? tone : withAlpha(C.WHITE, 0.15),
|
|
119
|
-
color: isOn ? tone : C.GREY,
|
|
120
|
-
background: isOn ? withAlpha(tone, 0.1) : "transparent",
|
|
121
|
-
}, children: [isOn ? "●" : "○", " ", meta.label] }, meta.key));
|
|
122
|
-
})] }))] }));
|
|
123
|
-
}
|
|
124
96
|
const styles = {
|
|
125
97
|
root: {
|
|
126
98
|
position: "relative",
|
|
@@ -287,53 +259,5 @@ const styles = {
|
|
|
287
259
|
padding: 0,
|
|
288
260
|
transition: "transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease",
|
|
289
261
|
},
|
|
290
|
-
pickerButton: {
|
|
291
|
-
position: "absolute",
|
|
292
|
-
top: 12,
|
|
293
|
-
right: 12,
|
|
294
|
-
width: 32,
|
|
295
|
-
height: 32,
|
|
296
|
-
borderRadius: 4,
|
|
297
|
-
border: `1px solid ${withAlpha(C.WHITE, 0.2)}`,
|
|
298
|
-
background: withAlpha(C.DARK_GREY, 0.85),
|
|
299
|
-
color: C.WHITE,
|
|
300
|
-
fontSize: 16,
|
|
301
|
-
cursor: "pointer",
|
|
302
|
-
zIndex: 6,
|
|
303
|
-
fontFamily: "inherit",
|
|
304
|
-
},
|
|
305
|
-
pickerPanel: {
|
|
306
|
-
position: "absolute",
|
|
307
|
-
top: 50,
|
|
308
|
-
right: 12,
|
|
309
|
-
width: 220,
|
|
310
|
-
maxHeight: "70vh",
|
|
311
|
-
overflowY: "auto",
|
|
312
|
-
padding: 10,
|
|
313
|
-
background: withAlpha(C.DARK_GREY, 0.95),
|
|
314
|
-
border: `1px solid ${withAlpha(C.WHITE, 0.15)}`,
|
|
315
|
-
borderRadius: 6,
|
|
316
|
-
display: "flex",
|
|
317
|
-
flexDirection: "column",
|
|
318
|
-
gap: 4,
|
|
319
|
-
zIndex: 6,
|
|
320
|
-
backdropFilter: "blur(4px)",
|
|
321
|
-
},
|
|
322
|
-
pickerHeader: {
|
|
323
|
-
fontSize: 10,
|
|
324
|
-
color: C.GREY,
|
|
325
|
-
letterSpacing: "0.16em",
|
|
326
|
-
marginBottom: 4,
|
|
327
|
-
},
|
|
328
|
-
pickerChip: {
|
|
329
|
-
padding: "6px 10px",
|
|
330
|
-
border: "1px solid",
|
|
331
|
-
borderRadius: 4,
|
|
332
|
-
fontSize: 11,
|
|
333
|
-
fontFamily: "inherit",
|
|
334
|
-
cursor: "pointer",
|
|
335
|
-
textAlign: "left",
|
|
336
|
-
transition: "all 0.12s ease",
|
|
337
|
-
},
|
|
338
262
|
};
|
|
339
263
|
export { ANALYZER_STREAM_META } from "../hooks/analyzerStreamRegistry.js";
|
|
@@ -59,5 +59,5 @@ export function JamlCurator({}) {
|
|
|
59
59
|
padding: "16px 12px 24px",
|
|
60
60
|
boxSizing: "border-box",
|
|
61
61
|
borderBottom: `2px solid ${C.GOLD}`,
|
|
62
|
-
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(JimboText, { size: "lg", tone: "gold", children: "JAML Curator" }), _jsx(JimboButton, { tone: isSearching ? "red" : "green", size: "sm", onClick: handleSearch, children: isSearching ? "STOP" : "SEARCH" })] }), _jsx("div", { style: { flex: 1, minHeight: 0, overflowY: 'auto' }, className: "hide-scrollbar", children: _jsx(JamlMapEditor, { onChange: handleMapChange }) }), _jsx("div", { style: { flexShrink: 0 }, children: _jsx(JamlSpeedometer, { status: search.status, seedsPerSecond: search.seedsPerSecond, totalSearched: search.totalSearched, matchingSeeds: search.matchingSeeds }) }), _jsx("div", { style: { flexShrink: 0 }, children: _jsx(JimboPanel, { children: search.results.length === 0 ? (_jsx(JimboText, { size: "sm", tone: "grey", className: "j-text-center", children: isSearching ? "Searching..." : "No results yet." })) : (_jsxs("div", { className: "j-flex-col j-gap-sm", children: [_jsxs("div", { className: "j-flex j-items-center j-justify-between", children: [_jsx(JimboText, { size: "xs", tone: "grey", children: "SEED MATCHES" }), _jsxs(JimboText, { size: "xs", tone: "gold", children: [search.matchingSeeds, " FOUND"] })] }), _jsx(JimboFlankNav, { canPrev: resultIndex > 0, canNext: resultIndex < search.results.length - 1, onPrev: () => setResultIndex(i => Math.max(0, i - 1)), onNext: () => setResultIndex(i => Math.min(search.results.length - 1, i + 1)), children: _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "lg", tone: "gold", style: { letterSpacing: 2 }, children: currentSeed }), _jsx(JimboButton, { tone: "
|
|
62
|
+
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(JimboText, { size: "lg", tone: "gold", children: "JAML Curator" }), _jsx(JimboButton, { tone: isSearching ? "red" : "green", size: "sm", onClick: handleSearch, children: isSearching ? "STOP" : "SEARCH" })] }), _jsx("div", { style: { flex: 1, minHeight: 0, overflowY: 'auto' }, className: "hide-scrollbar", children: _jsx(JamlMapEditor, { onChange: handleMapChange }) }), _jsx("div", { style: { flexShrink: 0 }, children: _jsx(JamlSpeedometer, { status: search.status, seedsPerSecond: search.seedsPerSecond, totalSearched: search.totalSearched, matchingSeeds: search.matchingSeeds }) }), _jsx("div", { style: { flexShrink: 0 }, children: _jsx(JimboPanel, { children: search.results.length === 0 ? (_jsx(JimboText, { size: "sm", tone: "grey", className: "j-text-center", children: isSearching ? "Searching..." : "No results yet." })) : (_jsxs("div", { className: "j-flex-col j-gap-sm", children: [_jsxs("div", { className: "j-flex j-items-center j-justify-between", children: [_jsx(JimboText, { size: "xs", tone: "grey", children: "SEED MATCHES" }), _jsxs(JimboText, { size: "xs", tone: "gold", children: [search.matchingSeeds, " FOUND"] })] }), _jsx(JimboFlankNav, { canPrev: resultIndex > 0, canNext: resultIndex < search.results.length - 1, onPrev: () => setResultIndex(i => Math.max(0, i - 1)), onNext: () => setResultIndex(i => Math.min(search.results.length - 1, i + 1)), children: _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "lg", tone: "gold", style: { letterSpacing: 2 }, children: currentSeed }), _jsx(JimboButton, { tone: "blue", size: "xs", children: "Copy Seed" })] }) }), _jsx(JimboText, { size: "micro", tone: "grey", className: "j-text-center", style: { opacity: 0.7, marginTop: 8 }, children: "\u25BC SWIPE DOWN FOR ANTES \u25BC" })] })) }) })] }) }) }));
|
|
63
63
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
export type JamlSpeedometerStatus = "idle" | "booting" | "running" | "completed" | "cancelled" | "error";
|
|
2
3
|
export interface JamlSpeedometerProps {
|
|
3
4
|
seedsPerSecond: number;
|
|
4
5
|
totalSearched: bigint | number;
|
|
5
6
|
matchingSeeds: bigint | number;
|
|
6
7
|
status: JamlSpeedometerStatus;
|
|
8
|
+
className?: string;
|
|
9
|
+
style?: React.CSSProperties;
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
|
-
* Compact live-search stats strip
|
|
12
|
+
* Compact live-search stats strip — NOT a car speedometer.
|
|
13
|
+
* Three stat cells in a row: speed | searched | matches.
|
|
14
|
+
* Uses j-stat-grid CSS class from jimbo.css.
|
|
10
15
|
*/
|
|
11
|
-
export declare function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, }: JamlSpeedometerProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, className, style, }: JamlSpeedometerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
const C = JimboColorOption;
|
|
3
|
+
import { JimboText } from "../ui/jimboText.js";
|
|
5
4
|
function formatCount(value) {
|
|
6
5
|
return Number(value).toLocaleString();
|
|
7
6
|
}
|
|
8
7
|
function formatSpeed(value) {
|
|
9
8
|
if (!Number.isFinite(value) || value <= 0)
|
|
10
|
-
return "
|
|
9
|
+
return "—";
|
|
11
10
|
if (value >= 1_000_000)
|
|
12
11
|
return `${(value / 1_000_000).toFixed(1)}M/s`;
|
|
13
12
|
if (value >= 1_000)
|
|
@@ -15,18 +14,12 @@ function formatSpeed(value) {
|
|
|
15
14
|
return `${Math.round(value)}/s`;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
* Compact live-search stats strip
|
|
17
|
+
* Compact live-search stats strip — NOT a car speedometer.
|
|
18
|
+
* Three stat cells in a row: speed | searched | matches.
|
|
19
|
+
* Uses j-stat-grid CSS class from jimbo.css.
|
|
19
20
|
*/
|
|
20
|
-
export function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, }) {
|
|
21
|
+
export function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, className = "", style, }) {
|
|
21
22
|
const active = status === "running" || status === "booting";
|
|
22
|
-
const
|
|
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"] })] }));
|
|
23
|
+
const statusTone = status === "error" ? "red" : active ? "green" : "grey";
|
|
24
|
+
return (_jsxs("div", { className: `j-stat-grid ${className}`, style: style, children: [_jsxs("div", { children: [_jsx("div", { className: "j-stat-grid__value", children: _jsx(JimboText, { size: "md", tone: active ? "gold" : "grey", children: formatSpeed(seedsPerSecond) }) }), _jsx("div", { className: "j-stat-grid__label", children: "speed" })] }), _jsxs("div", { children: [_jsx("div", { className: "j-stat-grid__value", children: _jsx(JimboText, { size: "md", tone: "white", children: formatCount(totalSearched) }) }), _jsx("div", { className: "j-stat-grid__label", children: "searched" })] }), _jsxs("div", { children: [_jsx("div", { className: "j-stat-grid__value", children: _jsx(JimboText, { size: "md", tone: Number(matchingSeeds) > 0 ? "green" : "grey", children: formatCount(matchingSeeds) }) }), _jsx("div", { className: "j-stat-grid__label", children: "matches" })] })] }));
|
|
32
25
|
}
|
|
@@ -92,15 +92,15 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
92
92
|
const handleOverlayClose = useCallback(() => {
|
|
93
93
|
setActiveSlot(null);
|
|
94
94
|
}, []);
|
|
95
|
-
const
|
|
95
|
+
const jamlText = useMemo(() => buildJamlText(antesState), [antesState]);
|
|
96
96
|
useEffect(() => {
|
|
97
|
-
onChange?.(
|
|
98
|
-
}, [
|
|
97
|
+
onChange?.(jamlText);
|
|
98
|
+
}, [jamlText, onChange]);
|
|
99
99
|
const renderSlot = (anteIndex, id, width, sheetType, forceCategory) => {
|
|
100
100
|
const sel = (antesState[anteIndex] || {})[id];
|
|
101
101
|
return (_jsx(MysterySlot, { zone: sel ? sel.zone : currentZone, sheetType: sheetType, selection: sel, width: width, onTap: () => handleSlotTap(anteIndex, id, forceCategory), onClear: sel ? () => handleSlotClear(anteIndex, id) : undefined, style: { flexShrink: 0 } }, id));
|
|
102
102
|
};
|
|
103
|
-
return (_jsxs("div", { style: { width: "100%", height: "100%", display: "flex", flexDirection: "column" }, children: [_jsxs("div", { style: { position: "sticky", top: 0, zIndex: 10, background: C.DARKEST, padding: "max(32px, env(safe-area-inset-top, 32px)) 0 8px 0", borderBottom: `2px solid ${C.PANEL_EDGE}` }, children: [_jsx(JimboText, { size: "md", tone: "white",
|
|
103
|
+
return (_jsxs("div", { style: { width: "100%", height: "100%", display: "flex", flexDirection: "column" }, children: [_jsxs("div", { style: { position: "sticky", top: 0, zIndex: 10, background: C.DARKEST, padding: "max(32px, env(safe-area-inset-top, 32px)) 0 8px 0", borderBottom: `2px solid ${C.PANEL_EDGE}` }, children: [_jsx(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 12 }, children: "JAML VISUAL BUILDER" }), _jsx("div", { className: "j-flex j-gap-sm", style: { justifyContent: "center" }, children: ["must", "should", "mustnot"].map((z) => (_jsx(JimboButton, { tone: currentZone === z ? ZONE_TONE[z] : "blue", size: "sm", onClick: () => setCurrentZone(z), style: { opacity: currentZone === z ? 1 : 0.4 }, children: ZONE_LABEL[z] }, z))) })] }), _jsx("div", { className: "hide-scrollbar", style: {
|
|
104
104
|
flex: 1,
|
|
105
105
|
overflowY: "auto",
|
|
106
106
|
scrollSnapType: "y mandatory",
|
|
@@ -113,7 +113,7 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
113
113
|
flexDirection: "column",
|
|
114
114
|
gap: 24,
|
|
115
115
|
borderBottom: `2px solid ${C.DARK_GREY}`
|
|
116
|
-
}, children: [_jsxs(JimboText, { size: "md", tone: "white",
|
|
116
|
+
}, children: [_jsxs(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 8 }, children: ["ANTE ", a] }), _jsxs("div", { className: "j-flex j-justify-between j-items-end", children: [_jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "VOUCHER" }), renderSlot(a, `ante_${a}_voucher`, 42, "Vouchers", "voucher")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "SMALL" }), renderSlot(a, `ante_${a}_tag_small`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "BIG" }), renderSlot(a, `ante_${a}_tag_big`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "BOSS" }), renderSlot(a, `ante_${a}_boss`, 42, "BlindChips", "boss")] })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "SHOP ITEMS" }), _jsx("div", { className: "j-flex hide-scrollbar j-gap-sm", style: { overflowX: "auto", paddingBottom: 8 }, children: [1, 2, 3, 4, 5, 6, 7, 8].map(i => renderSlot(a, `ante_${a}_shop_${i}`, 52, "Jokers")) })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "PACKS" }), _jsx("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: [1, 2, 3, 4, 5, 6].map(i => renderSlot(a, `ante_${a}_pack_${i}`, 64, "Boosters", "pack")) })] })] }, a))) }), _jsx(JimboModal, { open: activeSlot !== null, onClose: handlePickerCancel, title: pickerFlow === "category" ? "Select Category" : undefined, className: "j-picker-modal", children: activeSlot !== null && (pickerFlow === "category" ? (_jsx(CategoryMenu, { onSelect: handleCategorySelect })) : pickerFlow === "joker" ? (_jsx(JokerPicker, { onSelect: handleItemSelect, onCancel: handlePickerCancel })) : (_jsx(CategoryPicker, { config: CATEGORY_CONFIG_MAP[pickerFlow], onSelect: handleItemSelect, onCancel: handlePickerCancel }))) })] }));
|
|
117
117
|
}
|
|
118
118
|
// ─── Category Selection Menu ─────────────────────────────────────────────────
|
|
119
119
|
function CategoryMenu({ onSelect, }) {
|
|
@@ -126,45 +126,49 @@ function CategoryMenu({ onSelect, }) {
|
|
|
126
126
|
overflowY: "auto",
|
|
127
127
|
}, children: CATEGORIES.map((cat) => (_jsx(JimboButton, { tone: cat.tone, size: "sm", fullWidth: true, onClick: () => onSelect(cat.key), children: _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, width: "100%", textAlign: "left" }, children: [_jsx(JimboSprite, { name: cat.sprite, sheet: cat.sheet, width: 24 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 1 }, children: [_jsx("span", { style: { fontSize: 11 }, children: cat.label }), _jsx("span", { style: { fontSize: 8, opacity: 0.7, letterSpacing: "0.04em", lineHeight: 1, whiteSpace: "normal" }, children: cat.hint })] })] }) }, cat.key))) }));
|
|
128
128
|
}
|
|
129
|
-
// ─── Build
|
|
130
|
-
function
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
// ─── Build JAML text from slots ──────────────────────────────────────────────
|
|
130
|
+
function buildJamlText(antes) {
|
|
131
|
+
const byZone = {
|
|
132
|
+
must: {}, should: {}, mustnot: {}
|
|
133
|
+
};
|
|
134
134
|
for (const [anteStr, selections] of Object.entries(antes)) {
|
|
135
135
|
const anteNum = parseInt(anteStr, 10);
|
|
136
|
-
// Group by zone
|
|
137
|
-
const byZone = {
|
|
138
|
-
must: {}, should: {}, mustnot: {}
|
|
139
|
-
};
|
|
140
136
|
for (const sel of Object.values(selections)) {
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
const zone = sel.zone;
|
|
138
|
+
const key = sel.clauseKey;
|
|
139
|
+
if (!byZone[zone][key]) {
|
|
140
|
+
byZone[zone][key] = [];
|
|
141
|
+
}
|
|
142
|
+
const existing = byZone[zone][key].find(item => item.value === sel.value);
|
|
143
|
+
if (existing) {
|
|
144
|
+
if (!existing.antes.includes(anteNum))
|
|
145
|
+
existing.antes.push(anteNum);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
byZone[zone][key].push({ value: sel.value, antes: [anteNum] });
|
|
143
149
|
}
|
|
144
|
-
byZone[sel.zone][sel.clauseKey].push(sel.value);
|
|
145
150
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
}
|
|
152
|
+
let lines = [];
|
|
153
|
+
lines.push("name: My Custom Seed Map");
|
|
154
|
+
lines.push("author: JamlBuilder");
|
|
155
|
+
lines.push("description: Auto-generated from the visual editor.");
|
|
156
|
+
lines.push("deck: Red");
|
|
157
|
+
lines.push("stake: White");
|
|
158
|
+
for (const [zone, label] of [["must", "must"], ["should", "should"], ["mustnot", "mustNot"]]) {
|
|
159
|
+
const clauses = byZone[zone];
|
|
160
|
+
if (Object.keys(clauses).length === 0)
|
|
161
|
+
continue;
|
|
162
|
+
lines.push(`${label}:`);
|
|
163
|
+
for (const [key, items] of Object.entries(clauses)) {
|
|
164
|
+
for (const item of items) {
|
|
165
|
+
lines.push(` - ${key}: ${item.value}`);
|
|
166
|
+
// Only emit `antes:` if it's not all 8 antes (simplification, or just emit it)
|
|
167
|
+
if (item.antes.length < 8) {
|
|
168
|
+
lines.push(` antes: [${item.antes.sort((a, b) => a - b).join(", ")}]`);
|
|
169
|
+
}
|
|
153
170
|
}
|
|
154
|
-
if (z === "must")
|
|
155
|
-
must.push(obj);
|
|
156
|
-
else if (z === "should")
|
|
157
|
-
should.push(obj);
|
|
158
|
-
else if (z === "mustnot")
|
|
159
|
-
mustNot.push(obj);
|
|
160
171
|
}
|
|
161
172
|
}
|
|
162
|
-
|
|
163
|
-
if (must.length > 0)
|
|
164
|
-
result.must = must;
|
|
165
|
-
if (should.length > 0)
|
|
166
|
-
result.should = should;
|
|
167
|
-
if (mustNot.length > 0)
|
|
168
|
-
result.mustNot = mustNot;
|
|
169
|
-
return result;
|
|
173
|
+
return lines.join("\n") + "\n";
|
|
170
174
|
}
|
|
@@ -60,7 +60,7 @@ const RARITY_META = {
|
|
|
60
60
|
common: { label: "Common", tone: "blue", hint: "Found in shops and Buffoon Packs" },
|
|
61
61
|
uncommon: { label: "Uncommon", tone: "green", hint: "Found in shops and Buffoon Packs" },
|
|
62
62
|
rare: { label: "Rare", tone: "red", hint: "Found in shops and Buffoon Packs" },
|
|
63
|
-
legendary: { label: "Legendary", tone: "
|
|
63
|
+
legendary: { label: "Legendary", tone: "tarot", hint: "Spawns from The Soul only!" },
|
|
64
64
|
};
|
|
65
65
|
export function JokerPicker({ onSelect, onCancel }) {
|
|
66
66
|
const [step, setStep] = useState("rarity");
|
|
@@ -112,7 +112,7 @@ export function JokerPicker({ onSelect, onCancel }) {
|
|
|
112
112
|
justifyContent: "space-between",
|
|
113
113
|
padding: "8px 10px",
|
|
114
114
|
borderBottom: `2px solid ${C.PANEL_EDGE}`,
|
|
115
|
-
}, children: [_jsx(JimboButton, { tone: "
|
|
115
|
+
}, children: [_jsx(JimboButton, { tone: "orange", size: "xs", onClick: () => setStep("rarity"), children: "\u2190 Back" }), _jsxs(JimboText, { size: "md", children: [RARITY_META[selectedRarity].label, " Jokers"] }), _jsx("div", { style: { width: 44 } })] }), _jsxs("div", { className: "j-flex j-gap-sm", style: { padding: "8px 10px 4px" }, children: [_jsx("input", { className: "j-seed-input__field", type: "text", placeholder: "Search jokers...", value: search, onChange: (e) => setSearch(e.target.value), style: { fontSize: 13, padding: "6px 10px", textTransform: "none", letterSpacing: "0.04em" } }), _jsx(JimboButton, { tone: "orange", size: "sm", onClick: handleAnySelect, children: "Any" })] }), selectedRarity === "legendary" && (_jsx("div", { className: "j-inner-panel", style: { margin: "4px 10px 6px", padding: "6px 10px" }, children: _jsx(JimboText, { size: "xs", tone: "purple", children: "Legendary jokers spawn from The Soul. Find it in Arcana Pack, Spectral Pack, Charm Tag, or Ethereal Tag only!" }) })), _jsxs("div", { style: {
|
|
116
116
|
display: "grid",
|
|
117
117
|
gridTemplateColumns: "repeat(auto-fill, minmax(64px, 1fr))",
|
|
118
118
|
gap: 6,
|
|
@@ -51,10 +51,10 @@ export function MysterySlot({ zone, sheetType, selection, width = 56, onTap, onC
|
|
|
51
51
|
const nx = Math.max(-1, Math.min(1, ((e.clientX - rect.left) / rect.width - 0.5) * 2));
|
|
52
52
|
const ny = Math.max(-1, Math.min(1, ((e.clientY - rect.top) / rect.height - 0.5) * 2));
|
|
53
53
|
setTilt({
|
|
54
|
-
rx: ny * -
|
|
55
|
-
ry: nx *
|
|
56
|
-
tx: nx * -
|
|
57
|
-
ty: ny * -
|
|
54
|
+
rx: ny * -8, // subtle max 8deg tilt
|
|
55
|
+
ry: nx * 8,
|
|
56
|
+
tx: nx * -2, // subtle shift
|
|
57
|
+
ty: ny * -2,
|
|
58
58
|
});
|
|
59
59
|
};
|
|
60
60
|
const handleMouseLeave = () => {
|
package/dist/core.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { JAML_ASSET_FILES,
|
|
1
|
+
export { JAML_ASSET_FILES, resolveJamlAssetUrl, setJamlAssetBaseUrl, type JamlAssetFile, type JamlAssetKey, } from "./assets.js";
|
|
2
2
|
export { Layer, type LayerOptions } from "./render/Layer.js";
|
|
3
3
|
export { getSpriteData, type SpriteData, type SpriteSheetType } from "./sprites/spriteMapper.js";
|
|
4
4
|
export { SPRITE_SHEETS, JOKERS, JOKER_FACES, TAROTS_AND_PLANETS, CONSUMABLE_FACES, VOUCHERS, BOSSES, TAGS, BOOSTER_PACKS, EDITION_MAP, STICKER_MAP, RANK_MAP, SUIT_MAP, ENHANCER_MAP, SEAL_MAP, type SpritePos, type SpriteEntry, type SpriteSheetInfo, } from "./sprites/spriteData.js";
|
package/dist/core.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { JAML_ASSET_FILES,
|
|
1
|
+
export { JAML_ASSET_FILES, resolveJamlAssetUrl, setJamlAssetBaseUrl, } from "./assets.js";
|
|
2
2
|
export { Layer } from "./render/Layer.js";
|
|
3
3
|
export { getSpriteData } from "./sprites/spriteMapper.js";
|
|
4
4
|
export { SPRITE_SHEETS, JOKERS, JOKER_FACES, TAROTS_AND_PLANETS, CONSUMABLE_FACES, VOUCHERS, BOSSES, TAGS, BOOSTER_PACKS, EDITION_MAP, STICKER_MAP, RANK_MAP, SUIT_MAP, ENHANCER_MAP, SEAL_MAP, } from "./sprites/spriteData.js";
|
|
@@ -13,11 +13,12 @@ export interface UseSearchState {
|
|
|
13
13
|
seedsPerSecond: number;
|
|
14
14
|
tallyLabels: string[];
|
|
15
15
|
}
|
|
16
|
-
export declare function useSearch(): {
|
|
16
|
+
export declare function useSearch(motelyWasmUrl?: string): {
|
|
17
17
|
start: (jaml: string, count: number) => void;
|
|
18
18
|
startAesthetic: (jaml: string, aesthetic: number) => void;
|
|
19
19
|
startSeedList: (jaml: string, seeds: string[]) => void;
|
|
20
20
|
startKeyword: (jaml: string, keywords: string, padding?: string) => void;
|
|
21
|
+
startSequential: (jaml: string, startSeed: string, endSeed?: string) => void;
|
|
21
22
|
cancel: () => void;
|
|
22
23
|
clearError: () => void;
|
|
23
24
|
fetchTallyLabels: (jaml: string) => void;
|