jaml-ui 0.2.0 → 0.3.1
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 +36 -28
- package/dist/components/AnalyzerExplorer.d.ts +44 -0
- package/dist/components/AnalyzerExplorer.js +451 -0
- package/dist/components/JamlIde.d.ts +17 -0
- package/dist/components/JamlIde.js +61 -0
- package/dist/components/JamlIdeToolbar.d.ts +8 -0
- package/dist/components/JamlIdeToolbar.js +36 -0
- package/dist/components/JamlMapPreview.d.ts +10 -0
- package/dist/components/JamlMapPreview.js +117 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/motely.d.ts +1 -0
- package/dist/motely.js +1 -0
- package/dist/motelyDisplay.d.ts +629 -0
- package/dist/motelyDisplay.js +187 -0
- package/dist/utils/jamlMapPreview.d.ts +12 -0
- package/dist/utils/jamlMapPreview.js +105 -0
- package/package.json +19 -31
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# jaml-ui
|
|
2
2
|
|
|
3
|
-
Balatro
|
|
3
|
+
React components and utilities for Balatro/JAML. Pair with **`motely-wasm`** when you need the engine (peer). Update both in consumers with normal **`pnpm update`** / semver like any other deps.
|
|
4
4
|
|
|
5
5
|
## Package shape
|
|
6
6
|
|
|
@@ -17,8 +17,6 @@ Balatro rendering components, sprite metadata, and optional Motely helpers for R
|
|
|
17
17
|
npm install jaml-ui react react-dom
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
**Latest published major features:** `0.2.x` adds `jaml-ui/r3f` (Three.js card meshes). If you need `0.2.0` from npm and hit Windows publish issues, see [PUBLISHING.md](./PUBLISHING.md) and use GitHub Actions or Linux to `npm publish`.
|
|
21
|
-
|
|
22
20
|
If you want the Motely-specific helpers too:
|
|
23
21
|
|
|
24
22
|
```bash
|
|
@@ -47,6 +45,39 @@ export function Example() {
|
|
|
47
45
|
}
|
|
48
46
|
```
|
|
49
47
|
|
|
48
|
+
## JAML preview
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
"use client";
|
|
52
|
+
|
|
53
|
+
import { JamlMapPreview } from "jaml-ui";
|
|
54
|
+
|
|
55
|
+
export function PreviewExample({ jaml }: { jaml: string }) {
|
|
56
|
+
return <JamlMapPreview jaml={jaml} title="JAML Intent Preview" />;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Lightweight JAML IDE shell
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
"use client";
|
|
64
|
+
|
|
65
|
+
import { useState } from "react";
|
|
66
|
+
import { JamlIde } from "jaml-ui";
|
|
67
|
+
|
|
68
|
+
export function IdeExample() {
|
|
69
|
+
const [jaml, setJaml] = useState("must:\n joker: Blueprint");
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<JamlIde
|
|
73
|
+
jaml={jaml}
|
|
74
|
+
onChange={setJaml}
|
|
75
|
+
results={[]}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
50
81
|
## Asset handling
|
|
51
82
|
|
|
52
83
|
By default, `jaml-ui` resolves its packaged sprite assets from the package `assets/` directory using `import.meta.url`.
|
|
@@ -90,34 +121,9 @@ const itemName = decodeMotelyItemName(0x5001);
|
|
|
90
121
|
const enumKey = motelyItemTypeName(0x5001);
|
|
91
122
|
```
|
|
92
123
|
|
|
93
|
-
## Three.js / R3F (optional)
|
|
94
|
-
|
|
95
|
-
`jaml-ui/r3f` provides **magnetic-tilt** meshes for playing cards and jokers. Peer dependencies (install in your app):
|
|
96
|
-
|
|
97
|
-
- `three`
|
|
98
|
-
- `@react-three/fiber`
|
|
99
|
-
- `@react-spring/three`
|
|
100
|
-
|
|
101
|
-
```tsx
|
|
102
|
-
"use client";
|
|
103
|
-
|
|
104
|
-
import { BalatroPlayingCard3D, BalatroJokerPreview3D } from "jaml-ui/r3f";
|
|
105
|
-
|
|
106
|
-
// In an existing <Canvas>: BalatroPlayingCard3D with card={{ suit, rank, ... }}
|
|
107
|
-
// Standalone joker preview:
|
|
108
|
-
export function Preview() {
|
|
109
|
-
return <BalatroJokerPreview3D displayName="Blueprint" />;
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Atlas URLs default to packaged sprites; override with `setJamlAssetBaseUrl("/your/public/images/")` or per-component `deckUrl` / `jokersImageUrl` props.
|
|
114
|
-
|
|
115
|
-
Bundled data: `balatro-jokers.json` ships in `dist/data/` for joker grid lookup.
|
|
116
|
-
|
|
117
124
|
## Next.js notes
|
|
118
125
|
|
|
119
126
|
- The root `jaml-ui` entry is client-oriented and preserves the `"use client"` boundary for component consumers.
|
|
120
|
-
- Add `jaml-ui/r3f` to `transpilePackages` when you use the R3F entry.
|
|
121
127
|
- Import pure helpers from `jaml-ui/core` when you want server-safe metadata and asset utilities.
|
|
122
128
|
- If you are consuming `jaml-ui` from a local workspace package in a Next.js app, you may need:
|
|
123
129
|
|
|
@@ -135,3 +141,5 @@ export default nextConfig;
|
|
|
135
141
|
## Browser-first runtime direction
|
|
136
142
|
|
|
137
143
|
`jaml-ui` is designed for browser/React consumers. The optional `jaml-ui/motely` entry targets plain `motely-wasm` and does not assume threaded WASM, SAB, or COEP setup.
|
|
144
|
+
|
|
145
|
+
The built-in `JamlIde` intentionally stays lightweight. Rich editor integrations like Monaco, custom language servers, or extension-host-specific tooling should live in app-level packages on top of `jaml-ui`, not in the base renderer package.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type AnalyzerShopItem } from "./GameCard.js";
|
|
2
|
+
export interface AnalyzerBadge {
|
|
3
|
+
label: string;
|
|
4
|
+
tone?: "default" | "accent" | "muted";
|
|
5
|
+
}
|
|
6
|
+
export interface AnalyzerFact {
|
|
7
|
+
label: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AnalyzerItem extends AnalyzerShopItem {
|
|
11
|
+
desired?: boolean;
|
|
12
|
+
badges?: AnalyzerBadge[];
|
|
13
|
+
detail?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AnalyzerAnteView {
|
|
16
|
+
ante: number;
|
|
17
|
+
boss?: string;
|
|
18
|
+
voucher?: string;
|
|
19
|
+
smallBlindTag?: string;
|
|
20
|
+
bigBlindTag?: string;
|
|
21
|
+
packs?: string[];
|
|
22
|
+
shop?: AnalyzerItem[];
|
|
23
|
+
facts?: AnalyzerFact[];
|
|
24
|
+
}
|
|
25
|
+
export interface AnalyzerHighlight {
|
|
26
|
+
id: string;
|
|
27
|
+
ante: number;
|
|
28
|
+
title: string;
|
|
29
|
+
subtitle?: string;
|
|
30
|
+
desired?: boolean;
|
|
31
|
+
item?: AnalyzerItem;
|
|
32
|
+
boss?: string;
|
|
33
|
+
voucher?: string;
|
|
34
|
+
tag?: string;
|
|
35
|
+
badges?: AnalyzerBadge[];
|
|
36
|
+
}
|
|
37
|
+
export interface AnalyzerExplorerProps {
|
|
38
|
+
antes: AnalyzerAnteView[];
|
|
39
|
+
highlights?: AnalyzerHighlight[];
|
|
40
|
+
visibleAntes?: number;
|
|
41
|
+
totalAntes?: number;
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function AnalyzerExplorer({ antes, highlights, visibleAntes, totalAntes, className, }: AnalyzerExplorerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { JamlBoss, JamlGameCard, JamlTag, JamlVoucher, resolveAnalyzerShopItem, } from "./GameCard.js";
|
|
5
|
+
export function AnalyzerExplorer({ antes, highlights = [], visibleAntes, totalAntes, className = "", }) {
|
|
6
|
+
const [currentAnte, setCurrentAnte] = useState(antes[0]?.ante ?? 0);
|
|
7
|
+
const scrollRef = useRef(null);
|
|
8
|
+
const anteRefs = useRef(new Map());
|
|
9
|
+
const highlightRefs = useRef(new Map());
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
setCurrentAnte(antes[0]?.ante ?? 0);
|
|
12
|
+
}, [antes]);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const root = scrollRef.current;
|
|
15
|
+
if (!root) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const observer = new IntersectionObserver((entries) => {
|
|
19
|
+
const mostVisibleEntry = entries
|
|
20
|
+
.filter((entry) => entry.isIntersecting)
|
|
21
|
+
.sort((left, right) => right.intersectionRatio - left.intersectionRatio)[0];
|
|
22
|
+
if (!mostVisibleEntry) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const ante = Number(mostVisibleEntry.target.dataset.ante);
|
|
26
|
+
if (!Number.isNaN(ante)) {
|
|
27
|
+
setCurrentAnte(ante);
|
|
28
|
+
}
|
|
29
|
+
}, {
|
|
30
|
+
root,
|
|
31
|
+
threshold: [0.45, 0.72, 0.9],
|
|
32
|
+
});
|
|
33
|
+
for (const [, element] of anteRefs.current) {
|
|
34
|
+
observer.observe(element);
|
|
35
|
+
}
|
|
36
|
+
return () => observer.disconnect();
|
|
37
|
+
}, [antes]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const activeHighlight = highlights.find((highlight) => highlight.ante === currentAnte);
|
|
40
|
+
if (!activeHighlight) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const element = highlightRefs.current.get(activeHighlight.id);
|
|
44
|
+
element?.scrollIntoView({
|
|
45
|
+
behavior: "smooth",
|
|
46
|
+
inline: "center",
|
|
47
|
+
block: "nearest",
|
|
48
|
+
});
|
|
49
|
+
}, [currentAnte, highlights]);
|
|
50
|
+
const scrollToAnte = useCallback((ante) => {
|
|
51
|
+
anteRefs.current.get(ante)?.scrollIntoView({
|
|
52
|
+
behavior: "smooth",
|
|
53
|
+
block: "start",
|
|
54
|
+
});
|
|
55
|
+
}, []);
|
|
56
|
+
const currentAnteIndex = antes.findIndex((ante) => ante.ante === currentAnte);
|
|
57
|
+
const previousAnte = currentAnteIndex > 0 ? antes[currentAnteIndex - 1]?.ante ?? null : null;
|
|
58
|
+
const nextAnte = currentAnteIndex >= 0 && currentAnteIndex < antes.length - 1 ? antes[currentAnteIndex + 1]?.ante ?? null : null;
|
|
59
|
+
const shownAntes = visibleAntes ?? antes.length;
|
|
60
|
+
const availableAntes = totalAntes ?? shownAntes;
|
|
61
|
+
return (_jsxs("div", { className: className, style: styles.root, children: [highlights.length > 0 ? (_jsxs("section", { style: styles.highlightSection, children: [_jsxs("div", { style: styles.highlightHeader, children: [_jsx("span", { style: styles.highlightTitle, children: "Highlights" }), _jsx("span", { style: styles.highlightSubtitle, children: "Swipe, tap, jump" })] }), _jsx("div", { style: styles.highlightRail, children: highlights.map((highlight) => {
|
|
62
|
+
const isActive = highlight.ante === currentAnte;
|
|
63
|
+
return (_jsxs("button", { ref: (element) => {
|
|
64
|
+
if (element) {
|
|
65
|
+
highlightRefs.current.set(highlight.id, element);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
highlightRefs.current.delete(highlight.id);
|
|
69
|
+
}
|
|
70
|
+
}, type: "button", onClick: () => scrollToAnte(highlight.ante), style: isActive ? styles.highlightCardActive : styles.highlightCard, children: [_jsx("div", { style: styles.highlightVisual, children: renderHighlightVisual(highlight) }), _jsxs("div", { style: styles.highlightMeta, children: [_jsxs("span", { style: styles.highlightAnte, children: ["Ante ", highlight.ante] }), _jsx("span", { style: styles.highlightLabel, children: highlight.title }), highlight.subtitle ? (_jsx("span", { style: styles.highlightDescription, children: highlight.subtitle })) : null, _jsx(BadgeRow, { badges: highlight.badges, desired: highlight.desired })] })] }, highlight.id));
|
|
71
|
+
}) })] })) : null, _jsxs("div", { style: styles.navBar, children: [_jsx("button", { type: "button", onClick: () => previousAnte !== null && scrollToAnte(previousAnte), disabled: previousAnte === null, style: {
|
|
72
|
+
...styles.navButton,
|
|
73
|
+
opacity: previousAnte !== null ? 1 : 0.25,
|
|
74
|
+
}, children: "\u25B2" }), _jsxs("div", { style: styles.navLabel, children: ["Ante ", currentAnte, _jsxs("span", { style: styles.navSubLabel, children: ["of ", shownAntes, availableAntes > shownAntes ? ` / ${availableAntes}` : ""] })] }), _jsx("button", { type: "button", onClick: () => nextAnte !== null && scrollToAnte(nextAnte), disabled: nextAnte === null, style: {
|
|
75
|
+
...styles.navButton,
|
|
76
|
+
opacity: nextAnte !== null ? 1 : 0.25,
|
|
77
|
+
}, children: "\u25BC" })] }), _jsx("div", { ref: scrollRef, style: styles.scrollRegion, children: antes.map((ante) => (_jsx("div", { "data-ante": ante.ante, ref: (element) => {
|
|
78
|
+
if (element) {
|
|
79
|
+
anteRefs.current.set(ante.ante, element);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
anteRefs.current.delete(ante.ante);
|
|
83
|
+
}
|
|
84
|
+
}, style: styles.antePage, children: _jsx(AnteSection, { ante: ante }) }, ante.ante))) })] }));
|
|
85
|
+
}
|
|
86
|
+
function AnteSection({ ante }) {
|
|
87
|
+
return (_jsxs("section", { style: styles.anteSection, children: [_jsxs("div", { style: styles.anteHeader, children: [_jsxs("span", { style: styles.anteHeading, children: ["Ante ", ante.ante] }), ante.boss ? (_jsxs("div", { style: styles.bossRow, children: [_jsx(JamlBoss, { bossName: ante.boss, scale: 0.62 }), _jsx("span", { style: styles.bossName, children: ante.boss })] })) : null] }), ante.smallBlindTag || ante.bigBlindTag ? (_jsxs(AnalyzerRow, { label: "Tags", children: [ante.smallBlindTag ? (_jsx(CompactCard, { label: "Small", visual: _jsx(JamlTag, { tagName: ante.smallBlindTag, scale: 0.58, hoverTilt: true }), text: ante.smallBlindTag })) : null, ante.bigBlindTag ? (_jsx(CompactCard, { label: "Big", visual: _jsx(JamlTag, { tagName: ante.bigBlindTag, scale: 0.58, hoverTilt: true }), text: ante.bigBlindTag })) : null] })) : null, ante.voucher ? (_jsx(AnalyzerRow, { label: "Voucher", children: _jsx(CompactCard, { visual: _jsx(JamlVoucher, { voucherName: ante.voucher, scale: 0.58, hoverTilt: true }), text: ante.voucher }) })) : null, ante.shop && ante.shop.length > 0 ? (_jsx(AnalyzerRow, { label: "Shop", dense: true, children: ante.shop.map((item) => (_jsx(ResolvedItemCard, { item: item }, `${ante.ante}-${item.id}-${item.name}`))) })) : null, ante.packs && ante.packs.length > 0 ? (_jsx(AnalyzerRow, { label: "Packs", children: ante.packs.map((pack) => (_jsx("div", { style: styles.packChip, children: pack }, `${ante.ante}-${pack}`))) })) : null, ante.facts && ante.facts.length > 0 ? (_jsx(AnalyzerRow, { label: "Facts", children: ante.facts.map((fact) => (_jsxs("div", { style: styles.factCard, children: [_jsx("span", { style: styles.factLabel, children: fact.label }), _jsx("span", { style: styles.factValue, children: fact.value })] }, `${ante.ante}-${fact.label}-${fact.value}`))) })) : null] }));
|
|
88
|
+
}
|
|
89
|
+
function AnalyzerRow({ label, children, dense = false, }) {
|
|
90
|
+
return (_jsxs("div", { style: styles.row, children: [_jsx("div", { style: styles.rowLabel, children: label }), _jsx("div", { style: dense ? styles.denseGrid : styles.cardFlow, children: children })] }));
|
|
91
|
+
}
|
|
92
|
+
function CompactCard({ label, visual, text, }) {
|
|
93
|
+
return (_jsxs("div", { style: styles.compactCard, children: [label ? _jsx("span", { style: styles.compactLabel, children: label }) : null, visual, _jsx("span", { style: styles.itemText, children: text })] }));
|
|
94
|
+
}
|
|
95
|
+
function ResolvedItemCard({ item }) {
|
|
96
|
+
const resolved = resolveAnalyzerShopItem(item, 0.58);
|
|
97
|
+
const wrapperStyle = item.desired ? styles.itemCardDesired : styles.itemCard;
|
|
98
|
+
if (resolved.kind === "joker" || resolved.kind === "consumable" || resolved.kind === "playing") {
|
|
99
|
+
return (_jsxs("div", { style: wrapperStyle, children: [_jsx(JamlGameCard, { card: resolved.card, type: resolved.type, hoverTilt: true }), _jsx("span", { style: styles.itemText, children: item.name }), _jsx(BadgeRow, { badges: item.badges, desired: item.desired }), item.detail ? _jsx("span", { style: styles.itemDetail, children: item.detail }) : null] }));
|
|
100
|
+
}
|
|
101
|
+
if (resolved.kind === "voucher") {
|
|
102
|
+
return (_jsxs("div", { style: wrapperStyle, children: [_jsx(JamlVoucher, { voucherName: resolved.voucherName, scale: 0.58, hoverTilt: true }), _jsx("span", { style: styles.itemText, children: item.name }), _jsx(BadgeRow, { badges: item.badges, desired: item.desired }), item.detail ? _jsx("span", { style: styles.itemDetail, children: item.detail }) : null] }));
|
|
103
|
+
}
|
|
104
|
+
return (_jsxs("div", { style: wrapperStyle, children: [_jsx("div", { style: styles.packChip, children: item.name }), _jsx(BadgeRow, { badges: item.badges, desired: item.desired }), item.detail ? _jsx("span", { style: styles.itemDetail, children: item.detail }) : null] }));
|
|
105
|
+
}
|
|
106
|
+
function BadgeRow({ badges, desired = false }) {
|
|
107
|
+
const combinedBadges = desired
|
|
108
|
+
? [{ label: "desired", tone: "accent" }, ...(badges ?? [])]
|
|
109
|
+
: (badges ?? []);
|
|
110
|
+
if (combinedBadges.length === 0) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return (_jsx("div", { style: styles.badgeRow, children: combinedBadges.map((badge) => {
|
|
114
|
+
const badgeStyle = badge.tone === "accent"
|
|
115
|
+
? styles.badgeAccent
|
|
116
|
+
: badge.tone === "muted"
|
|
117
|
+
? styles.badgeMuted
|
|
118
|
+
: styles.badge;
|
|
119
|
+
return (_jsx("span", { style: badgeStyle, children: badge.label }, `${badge.label}-${badge.tone ?? "default"}`));
|
|
120
|
+
}) }));
|
|
121
|
+
}
|
|
122
|
+
function renderHighlightVisual(highlight) {
|
|
123
|
+
if (highlight.item) {
|
|
124
|
+
return _jsx(ResolvedItemCard, { item: highlight.item });
|
|
125
|
+
}
|
|
126
|
+
if (highlight.boss) {
|
|
127
|
+
return _jsx(JamlBoss, { bossName: highlight.boss, scale: 0.92, hoverTilt: true });
|
|
128
|
+
}
|
|
129
|
+
if (highlight.voucher) {
|
|
130
|
+
return _jsx(JamlVoucher, { voucherName: highlight.voucher, scale: 0.92, hoverTilt: true });
|
|
131
|
+
}
|
|
132
|
+
if (highlight.tag) {
|
|
133
|
+
return _jsx(JamlTag, { tagName: highlight.tag, scale: 0.92, hoverTilt: true });
|
|
134
|
+
}
|
|
135
|
+
return _jsx("div", { style: styles.packChip, children: highlight.title });
|
|
136
|
+
}
|
|
137
|
+
const styles = {
|
|
138
|
+
root: {
|
|
139
|
+
display: "flex",
|
|
140
|
+
flexDirection: "column",
|
|
141
|
+
height: "100%",
|
|
142
|
+
minHeight: 0,
|
|
143
|
+
},
|
|
144
|
+
highlightSection: {
|
|
145
|
+
padding: "10px 16px 8px",
|
|
146
|
+
borderBottom: "1px solid #1a1a34",
|
|
147
|
+
background: "#0f0f22",
|
|
148
|
+
},
|
|
149
|
+
highlightHeader: {
|
|
150
|
+
display: "flex",
|
|
151
|
+
alignItems: "center",
|
|
152
|
+
justifyContent: "space-between",
|
|
153
|
+
gap: 12,
|
|
154
|
+
marginBottom: 8,
|
|
155
|
+
},
|
|
156
|
+
highlightTitle: {
|
|
157
|
+
fontSize: 11,
|
|
158
|
+
color: "#6f6fa1",
|
|
159
|
+
textTransform: "uppercase",
|
|
160
|
+
letterSpacing: "0.08em",
|
|
161
|
+
},
|
|
162
|
+
highlightSubtitle: {
|
|
163
|
+
fontSize: 11,
|
|
164
|
+
color: "#6f6fa1",
|
|
165
|
+
},
|
|
166
|
+
highlightRail: {
|
|
167
|
+
display: "flex",
|
|
168
|
+
gap: 10,
|
|
169
|
+
overflowX: "auto",
|
|
170
|
+
paddingBottom: 4,
|
|
171
|
+
scrollSnapType: "x mandatory",
|
|
172
|
+
},
|
|
173
|
+
highlightCard: {
|
|
174
|
+
minWidth: 150,
|
|
175
|
+
maxWidth: 150,
|
|
176
|
+
display: "flex",
|
|
177
|
+
flexDirection: "column",
|
|
178
|
+
gap: 6,
|
|
179
|
+
padding: 8,
|
|
180
|
+
borderRadius: 12,
|
|
181
|
+
border: "1px solid #2a2a55",
|
|
182
|
+
background: "#131326",
|
|
183
|
+
scrollSnapAlign: "center",
|
|
184
|
+
cursor: "pointer",
|
|
185
|
+
},
|
|
186
|
+
highlightCardActive: {
|
|
187
|
+
minWidth: 150,
|
|
188
|
+
maxWidth: 150,
|
|
189
|
+
display: "flex",
|
|
190
|
+
flexDirection: "column",
|
|
191
|
+
gap: 6,
|
|
192
|
+
padding: 8,
|
|
193
|
+
borderRadius: 12,
|
|
194
|
+
border: "1px solid rgba(245,200,66,0.45)",
|
|
195
|
+
background: "rgba(245,200,66,0.08)",
|
|
196
|
+
scrollSnapAlign: "center",
|
|
197
|
+
cursor: "pointer",
|
|
198
|
+
},
|
|
199
|
+
highlightVisual: {
|
|
200
|
+
display: "flex",
|
|
201
|
+
justifyContent: "center",
|
|
202
|
+
alignItems: "flex-start",
|
|
203
|
+
minHeight: 88,
|
|
204
|
+
},
|
|
205
|
+
highlightMeta: {
|
|
206
|
+
display: "flex",
|
|
207
|
+
flexDirection: "column",
|
|
208
|
+
gap: 3,
|
|
209
|
+
alignItems: "flex-start",
|
|
210
|
+
},
|
|
211
|
+
highlightAnte: {
|
|
212
|
+
fontSize: 10,
|
|
213
|
+
color: "#a855f7",
|
|
214
|
+
fontWeight: 700,
|
|
215
|
+
textTransform: "uppercase",
|
|
216
|
+
letterSpacing: "0.08em",
|
|
217
|
+
},
|
|
218
|
+
highlightLabel: {
|
|
219
|
+
fontSize: 12,
|
|
220
|
+
color: "#ececff",
|
|
221
|
+
fontWeight: 700,
|
|
222
|
+
lineHeight: 1.2,
|
|
223
|
+
textAlign: "left",
|
|
224
|
+
},
|
|
225
|
+
highlightDescription: {
|
|
226
|
+
fontSize: 11,
|
|
227
|
+
color: "#8e8eb6",
|
|
228
|
+
lineHeight: 1.2,
|
|
229
|
+
textAlign: "left",
|
|
230
|
+
},
|
|
231
|
+
navBar: {
|
|
232
|
+
display: "flex",
|
|
233
|
+
alignItems: "center",
|
|
234
|
+
justifyContent: "center",
|
|
235
|
+
gap: 12,
|
|
236
|
+
padding: "8px 16px",
|
|
237
|
+
background: "#0f0f22",
|
|
238
|
+
borderBottom: "1px solid #1a1a34",
|
|
239
|
+
},
|
|
240
|
+
navButton: {
|
|
241
|
+
background: "none",
|
|
242
|
+
border: "1px solid #2a2a55",
|
|
243
|
+
borderRadius: 6,
|
|
244
|
+
color: "#9898c0",
|
|
245
|
+
fontSize: 14,
|
|
246
|
+
padding: "4px 10px",
|
|
247
|
+
cursor: "pointer",
|
|
248
|
+
},
|
|
249
|
+
navLabel: {
|
|
250
|
+
fontSize: 12,
|
|
251
|
+
fontWeight: 700,
|
|
252
|
+
color: "#a855f7",
|
|
253
|
+
textTransform: "uppercase",
|
|
254
|
+
letterSpacing: "0.08em",
|
|
255
|
+
minWidth: 130,
|
|
256
|
+
textAlign: "center",
|
|
257
|
+
},
|
|
258
|
+
navSubLabel: {
|
|
259
|
+
color: "#5a5a88",
|
|
260
|
+
fontWeight: 500,
|
|
261
|
+
textTransform: "none",
|
|
262
|
+
letterSpacing: "normal",
|
|
263
|
+
fontSize: 11,
|
|
264
|
+
marginLeft: 4,
|
|
265
|
+
},
|
|
266
|
+
scrollRegion: {
|
|
267
|
+
flex: 1,
|
|
268
|
+
overflowY: "auto",
|
|
269
|
+
overflowX: "hidden",
|
|
270
|
+
scrollSnapType: "y mandatory",
|
|
271
|
+
scrollBehavior: "smooth",
|
|
272
|
+
},
|
|
273
|
+
antePage: {
|
|
274
|
+
scrollSnapAlign: "start",
|
|
275
|
+
scrollSnapStop: "always",
|
|
276
|
+
minHeight: "100%",
|
|
277
|
+
boxSizing: "border-box",
|
|
278
|
+
paddingBottom: 16,
|
|
279
|
+
borderBottom: "1px solid #1a1a34",
|
|
280
|
+
},
|
|
281
|
+
anteSection: {
|
|
282
|
+
paddingBottom: 8,
|
|
283
|
+
},
|
|
284
|
+
anteHeader: {
|
|
285
|
+
display: "flex",
|
|
286
|
+
alignItems: "center",
|
|
287
|
+
gap: 12,
|
|
288
|
+
padding: "8px 16px",
|
|
289
|
+
},
|
|
290
|
+
anteHeading: {
|
|
291
|
+
fontSize: 12,
|
|
292
|
+
fontWeight: 700,
|
|
293
|
+
color: "#a855f7",
|
|
294
|
+
textTransform: "uppercase",
|
|
295
|
+
letterSpacing: "0.1em",
|
|
296
|
+
},
|
|
297
|
+
bossRow: {
|
|
298
|
+
display: "flex",
|
|
299
|
+
alignItems: "center",
|
|
300
|
+
gap: 6,
|
|
301
|
+
},
|
|
302
|
+
bossName: {
|
|
303
|
+
fontSize: 13,
|
|
304
|
+
color: "#e84040",
|
|
305
|
+
fontWeight: 600,
|
|
306
|
+
},
|
|
307
|
+
row: {
|
|
308
|
+
padding: "4px 16px 8px",
|
|
309
|
+
},
|
|
310
|
+
rowLabel: {
|
|
311
|
+
fontSize: 10,
|
|
312
|
+
color: "#5a5a88",
|
|
313
|
+
textTransform: "uppercase",
|
|
314
|
+
letterSpacing: "0.07em",
|
|
315
|
+
marginBottom: 6,
|
|
316
|
+
},
|
|
317
|
+
cardFlow: {
|
|
318
|
+
display: "flex",
|
|
319
|
+
flexWrap: "wrap",
|
|
320
|
+
gap: 8,
|
|
321
|
+
alignItems: "flex-start",
|
|
322
|
+
},
|
|
323
|
+
denseGrid: {
|
|
324
|
+
display: "grid",
|
|
325
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(82px, 1fr))",
|
|
326
|
+
gap: 8,
|
|
327
|
+
alignItems: "start",
|
|
328
|
+
},
|
|
329
|
+
compactCard: {
|
|
330
|
+
display: "flex",
|
|
331
|
+
flexDirection: "column",
|
|
332
|
+
alignItems: "center",
|
|
333
|
+
gap: 4,
|
|
334
|
+
minWidth: 62,
|
|
335
|
+
},
|
|
336
|
+
compactLabel: {
|
|
337
|
+
padding: "2px 6px",
|
|
338
|
+
borderRadius: 999,
|
|
339
|
+
background: "#18182e",
|
|
340
|
+
color: "#7f7fa7",
|
|
341
|
+
border: "1px solid #2a2a55",
|
|
342
|
+
fontSize: 9,
|
|
343
|
+
fontWeight: 700,
|
|
344
|
+
textTransform: "uppercase",
|
|
345
|
+
letterSpacing: "0.05em",
|
|
346
|
+
},
|
|
347
|
+
itemCard: {
|
|
348
|
+
display: "flex",
|
|
349
|
+
flexDirection: "column",
|
|
350
|
+
alignItems: "center",
|
|
351
|
+
gap: 4,
|
|
352
|
+
width: "100%",
|
|
353
|
+
minWidth: 0,
|
|
354
|
+
},
|
|
355
|
+
itemCardDesired: {
|
|
356
|
+
display: "flex",
|
|
357
|
+
flexDirection: "column",
|
|
358
|
+
alignItems: "center",
|
|
359
|
+
gap: 4,
|
|
360
|
+
width: "100%",
|
|
361
|
+
minWidth: 0,
|
|
362
|
+
padding: "4px 4px 6px",
|
|
363
|
+
borderRadius: 10,
|
|
364
|
+
border: "1px solid rgba(245,200,66,0.4)",
|
|
365
|
+
background: "rgba(245,200,66,0.08)",
|
|
366
|
+
},
|
|
367
|
+
itemText: {
|
|
368
|
+
fontSize: 10,
|
|
369
|
+
color: "#8e8eb6",
|
|
370
|
+
textAlign: "center",
|
|
371
|
+
lineHeight: 1.2,
|
|
372
|
+
maxWidth: 84,
|
|
373
|
+
overflow: "hidden",
|
|
374
|
+
textOverflow: "ellipsis",
|
|
375
|
+
whiteSpace: "nowrap",
|
|
376
|
+
},
|
|
377
|
+
itemDetail: {
|
|
378
|
+
fontSize: 10,
|
|
379
|
+
color: "#d8d8ea",
|
|
380
|
+
textAlign: "center",
|
|
381
|
+
lineHeight: 1.2,
|
|
382
|
+
},
|
|
383
|
+
badgeRow: {
|
|
384
|
+
display: "flex",
|
|
385
|
+
flexWrap: "wrap",
|
|
386
|
+
gap: 4,
|
|
387
|
+
justifyContent: "center",
|
|
388
|
+
},
|
|
389
|
+
badge: {
|
|
390
|
+
padding: "2px 6px",
|
|
391
|
+
borderRadius: 999,
|
|
392
|
+
background: "#202043",
|
|
393
|
+
color: "#c7c7ef",
|
|
394
|
+
border: "1px solid #35356d",
|
|
395
|
+
fontSize: 9,
|
|
396
|
+
fontWeight: 700,
|
|
397
|
+
textTransform: "uppercase",
|
|
398
|
+
letterSpacing: "0.05em",
|
|
399
|
+
},
|
|
400
|
+
badgeAccent: {
|
|
401
|
+
padding: "2px 6px",
|
|
402
|
+
borderRadius: 999,
|
|
403
|
+
background: "rgba(245,200,66,0.2)",
|
|
404
|
+
color: "#f5c842",
|
|
405
|
+
border: "1px solid rgba(245,200,66,0.35)",
|
|
406
|
+
fontSize: 9,
|
|
407
|
+
fontWeight: 700,
|
|
408
|
+
textTransform: "uppercase",
|
|
409
|
+
letterSpacing: "0.05em",
|
|
410
|
+
},
|
|
411
|
+
badgeMuted: {
|
|
412
|
+
padding: "2px 6px",
|
|
413
|
+
borderRadius: 999,
|
|
414
|
+
background: "#18182e",
|
|
415
|
+
color: "#7f7fa7",
|
|
416
|
+
border: "1px solid #2a2a55",
|
|
417
|
+
fontSize: 9,
|
|
418
|
+
fontWeight: 700,
|
|
419
|
+
textTransform: "uppercase",
|
|
420
|
+
letterSpacing: "0.05em",
|
|
421
|
+
},
|
|
422
|
+
packChip: {
|
|
423
|
+
fontSize: 12,
|
|
424
|
+
color: "#5a5a88",
|
|
425
|
+
background: "#131326",
|
|
426
|
+
border: "1px solid #2a2a55",
|
|
427
|
+
borderRadius: 6,
|
|
428
|
+
padding: "4px 8px",
|
|
429
|
+
},
|
|
430
|
+
factCard: {
|
|
431
|
+
display: "flex",
|
|
432
|
+
flexDirection: "column",
|
|
433
|
+
gap: 2,
|
|
434
|
+
minWidth: 110,
|
|
435
|
+
padding: "8px 10px",
|
|
436
|
+
borderRadius: 10,
|
|
437
|
+
background: "#131326",
|
|
438
|
+
border: "1px solid #2a2a55",
|
|
439
|
+
},
|
|
440
|
+
factLabel: {
|
|
441
|
+
fontSize: 10,
|
|
442
|
+
color: "#7f7fa7",
|
|
443
|
+
textTransform: "uppercase",
|
|
444
|
+
letterSpacing: "0.07em",
|
|
445
|
+
},
|
|
446
|
+
factValue: {
|
|
447
|
+
fontSize: 12,
|
|
448
|
+
color: "#ececff",
|
|
449
|
+
fontWeight: 700,
|
|
450
|
+
},
|
|
451
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type JamlIdeMode } from "./JamlIdeToolbar.js";
|
|
3
|
+
export interface JamlIdeSearchResult {
|
|
4
|
+
seed: string;
|
|
5
|
+
score?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface JamlIdeProps {
|
|
8
|
+
jaml: string;
|
|
9
|
+
onChange: (jaml: string) => void;
|
|
10
|
+
defaultMode?: JamlIdeMode;
|
|
11
|
+
searchResults?: JamlIdeSearchResult[];
|
|
12
|
+
className?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
actions?: React.ReactNode;
|
|
15
|
+
codePlaceholder?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function JamlIde({ jaml, onChange, defaultMode, searchResults, className, title, actions, codePlaceholder, }: JamlIdeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
5
|
+
import { JamlIdeToolbar } from "./JamlIdeToolbar.js";
|
|
6
|
+
function ResultsView({ results }) {
|
|
7
|
+
if (results.length === 0) {
|
|
8
|
+
return (_jsx("div", { style: {
|
|
9
|
+
border: "1px dashed rgba(255,255,255,0.18)",
|
|
10
|
+
borderRadius: 12,
|
|
11
|
+
padding: 14,
|
|
12
|
+
fontSize: 12,
|
|
13
|
+
opacity: 0.72,
|
|
14
|
+
background: "rgba(255,255,255,0.03)",
|
|
15
|
+
}, children: "No results yet." }));
|
|
16
|
+
}
|
|
17
|
+
return (_jsx("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: results.map((result, index) => (_jsxs("div", { style: {
|
|
18
|
+
display: "flex",
|
|
19
|
+
alignItems: "center",
|
|
20
|
+
justifyContent: "space-between",
|
|
21
|
+
gap: 12,
|
|
22
|
+
borderRadius: 12,
|
|
23
|
+
border: "1px solid rgba(255,255,255,0.08)",
|
|
24
|
+
background: "rgba(255,255,255,0.03)",
|
|
25
|
+
padding: "10px 12px",
|
|
26
|
+
}, children: [_jsx("div", { style: { fontWeight: 700, letterSpacing: 0.4 }, children: result.seed }), _jsx("div", { style: { fontSize: 12, opacity: 0.7 }, children: result.score !== undefined ? result.score : "-" })] }, `${result.seed}-${index}`))) }));
|
|
27
|
+
}
|
|
28
|
+
export function JamlIde({ jaml, onChange, defaultMode = "code", searchResults = [], className = "", title = "JAML IDE", actions, codePlaceholder = "Enter JAML...", }) {
|
|
29
|
+
const [mode, setMode] = useState(defaultMode);
|
|
30
|
+
const results = useMemo(() => searchResults, [searchResults]);
|
|
31
|
+
return (_jsxs("div", { className: className, style: {
|
|
32
|
+
display: "flex",
|
|
33
|
+
flexDirection: "column",
|
|
34
|
+
minHeight: 420,
|
|
35
|
+
borderRadius: 16,
|
|
36
|
+
overflow: "hidden",
|
|
37
|
+
border: "1px solid rgba(255,255,255,0.08)",
|
|
38
|
+
background: "#17181c",
|
|
39
|
+
color: "#f5f5f5",
|
|
40
|
+
}, children: [_jsxs("div", { style: {
|
|
41
|
+
display: "flex",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
justifyContent: "space-between",
|
|
44
|
+
gap: 12,
|
|
45
|
+
padding: "12px 14px",
|
|
46
|
+
borderBottom: "1px solid rgba(255,255,255,0.08)",
|
|
47
|
+
background: "rgba(255,255,255,0.03)",
|
|
48
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, fontWeight: 800 }, children: title }), _jsx("div", { style: { fontSize: 11, opacity: 0.66 }, children: "Reusable JAML authoring and preview surface." })] }), actions ? _jsx("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: actions }) : null] }), _jsx(JamlIdeToolbar, { mode: mode, onModeChange: setMode, resultCount: results.length }), _jsxs("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: [mode === "code" ? (_jsx("textarea", { title: "JAML IDE Editor", value: jaml, onChange: (event) => onChange(event.target.value), placeholder: codePlaceholder, spellCheck: false, autoCapitalize: "off", autoCorrect: "off", style: {
|
|
49
|
+
width: "100%",
|
|
50
|
+
minHeight: 320,
|
|
51
|
+
resize: "vertical",
|
|
52
|
+
border: 0,
|
|
53
|
+
outline: 0,
|
|
54
|
+
padding: 16,
|
|
55
|
+
background: "transparent",
|
|
56
|
+
color: "inherit",
|
|
57
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
58
|
+
fontSize: 13,
|
|
59
|
+
lineHeight: 1.7,
|
|
60
|
+
} })) : null, mode === "map" ? _jsx(JamlMapPreview, { jaml: jaml }) : null, mode === "results" ? _jsx("div", { style: { padding: 16 }, children: _jsx(ResultsView, { results: results }) }) : null] })] }));
|
|
61
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type JamlIdeMode = "code" | "map" | "results";
|
|
2
|
+
export interface JamlIdeToolbarProps {
|
|
3
|
+
mode: JamlIdeMode;
|
|
4
|
+
onModeChange: (mode: JamlIdeMode) => void;
|
|
5
|
+
resultCount?: number;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function JamlIdeToolbar({ mode, onModeChange, resultCount, className }: JamlIdeToolbarProps): import("react/jsx-runtime").JSX.Element;
|