jaml-ui 0.4.0 → 0.5.0
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/assets/fonts/m6x11plusplus.otf +0 -0
- package/dist/r3f/Card3D.d.ts +25 -0
- package/dist/r3f/Card3D.js +103 -0
- package/dist/r3f.d.ts +1 -0
- package/dist/r3f.js +1 -0
- package/dist/ui/codeBlock.d.ts +7 -0
- package/dist/ui/codeBlock.js +14 -0
- package/dist/ui/footer.d.ts +5 -0
- package/dist/ui/footer.js +18 -0
- package/dist/ui/panel.d.ts +29 -0
- package/dist/ui/panel.js +83 -0
- package/dist/ui/tokens.d.ts +72 -0
- package/dist/ui/tokens.js +78 -0
- package/dist/ui.d.ts +4 -0
- package/dist/ui.js +4 -0
- package/package.json +38 -10
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SpriteData } from '../sprites/spriteMapper.js';
|
|
2
|
+
export declare const CARD_DIMENSIONS: {
|
|
3
|
+
readonly WIDTH: 0.7;
|
|
4
|
+
readonly HEIGHT: 0.95;
|
|
5
|
+
readonly DEPTH: 0.02;
|
|
6
|
+
};
|
|
7
|
+
export declare const CARD_MAGNET: {
|
|
8
|
+
readonly MAX_TILT_X: 0.36;
|
|
9
|
+
readonly MAX_TILT_Y: 0.42;
|
|
10
|
+
readonly MAX_SHIFT: 0.038;
|
|
11
|
+
readonly TWIST_Z: 0.11;
|
|
12
|
+
readonly LERP_IN: 18;
|
|
13
|
+
readonly LERP_OUT: 10;
|
|
14
|
+
};
|
|
15
|
+
export interface Card3DProps {
|
|
16
|
+
sprite: SpriteData;
|
|
17
|
+
position?: [number, number, number];
|
|
18
|
+
rotation?: [number, number, number];
|
|
19
|
+
selected?: boolean;
|
|
20
|
+
highlighted?: boolean;
|
|
21
|
+
onClick?: () => void;
|
|
22
|
+
onPointerEnter?: () => void;
|
|
23
|
+
onPointerLeave?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare const Card3D: import("react").NamedExoticComponent<Card3DProps>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useMemo, useState, useEffect, memo } from 'react';
|
|
4
|
+
import { useFrame } from '@react-three/fiber';
|
|
5
|
+
import { useSpring, animated } from '@react-spring/three';
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
import { SPRITE_SHEETS } from '../sprites/spriteData.js';
|
|
8
|
+
export const CARD_DIMENSIONS = { WIDTH: 0.7, HEIGHT: 0.95, DEPTH: 0.02 };
|
|
9
|
+
export const CARD_MAGNET = {
|
|
10
|
+
MAX_TILT_X: 0.36,
|
|
11
|
+
MAX_TILT_Y: 0.42,
|
|
12
|
+
MAX_SHIFT: 0.038,
|
|
13
|
+
TWIST_Z: 0.11,
|
|
14
|
+
LERP_IN: 18,
|
|
15
|
+
LERP_OUT: 10,
|
|
16
|
+
};
|
|
17
|
+
const SHEET_KEY_MAP = {
|
|
18
|
+
Jokers: 'jokers',
|
|
19
|
+
Tarots: 'tarots',
|
|
20
|
+
Vouchers: 'vouchers',
|
|
21
|
+
Boosters: 'boosters',
|
|
22
|
+
Enhancers: 'enhancers',
|
|
23
|
+
Editions: 'editions',
|
|
24
|
+
BlindChips: 'blinds',
|
|
25
|
+
tags: 'tags',
|
|
26
|
+
};
|
|
27
|
+
const _textureCache = new Map();
|
|
28
|
+
function useSpriteTexture(sprite) {
|
|
29
|
+
const [texture, setTexture] = useState(null);
|
|
30
|
+
const serial = useRef(0);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const id = ++serial.current;
|
|
33
|
+
const sheet = SPRITE_SHEETS[SHEET_KEY_MAP[sprite.type]];
|
|
34
|
+
const url = sheet.src;
|
|
35
|
+
const cols = sheet.columns;
|
|
36
|
+
const rows = sheet.rows;
|
|
37
|
+
const { x, y } = sprite.pos;
|
|
38
|
+
const applySlice = (base) => {
|
|
39
|
+
const t = base.clone();
|
|
40
|
+
t.repeat.set(1 / cols, 1 / rows);
|
|
41
|
+
t.offset.set(x / cols, (rows - y - 1) / rows);
|
|
42
|
+
t.needsUpdate = true;
|
|
43
|
+
return t;
|
|
44
|
+
};
|
|
45
|
+
if (_textureCache.has(url)) {
|
|
46
|
+
setTexture(applySlice(_textureCache.get(url)));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const loader = new THREE.TextureLoader();
|
|
50
|
+
loader.load(url, (loaded) => {
|
|
51
|
+
if (id !== serial.current)
|
|
52
|
+
return;
|
|
53
|
+
loaded.colorSpace = THREE.SRGBColorSpace;
|
|
54
|
+
loaded.magFilter = THREE.NearestFilter;
|
|
55
|
+
loaded.minFilter = THREE.NearestFilter;
|
|
56
|
+
_textureCache.set(url, loaded);
|
|
57
|
+
setTexture(applySlice(loaded));
|
|
58
|
+
}, undefined, (err) => console.error('[Card3D] texture load failed:', url, err));
|
|
59
|
+
}, [sprite.type, sprite.pos.x, sprite.pos.y]);
|
|
60
|
+
return texture;
|
|
61
|
+
}
|
|
62
|
+
export const Card3D = memo(function Card3D({ sprite, position = [0, 0, 0], rotation = [0, 0, 0], selected = false, highlighted = false, onClick, onPointerEnter, onPointerLeave, }) {
|
|
63
|
+
const tiltRef = useRef(null);
|
|
64
|
+
const target = useRef({ rx: 0, ry: 0, rz: 0, ox: 0, oy: 0 });
|
|
65
|
+
const [hovered, setHovered] = useState(false);
|
|
66
|
+
const texture = useSpriteTexture(sprite);
|
|
67
|
+
const { posY, scale } = useSpring({
|
|
68
|
+
posY: selected ? 0.3 : hovered ? 0.15 : 0,
|
|
69
|
+
scale: hovered ? 1.08 : selected ? 1.05 : 1,
|
|
70
|
+
config: { tension: 300, friction: 20 },
|
|
71
|
+
});
|
|
72
|
+
useFrame((_state, dt) => {
|
|
73
|
+
const g = tiltRef.current;
|
|
74
|
+
if (!g)
|
|
75
|
+
return;
|
|
76
|
+
const t = target.current;
|
|
77
|
+
const rate = hovered ? CARD_MAGNET.LERP_IN : CARD_MAGNET.LERP_OUT;
|
|
78
|
+
const a = 1 - Math.exp(-rate * dt);
|
|
79
|
+
g.rotation.x = THREE.MathUtils.lerp(g.rotation.x, t.rx, a);
|
|
80
|
+
g.rotation.y = THREE.MathUtils.lerp(g.rotation.y, t.ry, a);
|
|
81
|
+
g.rotation.z = THREE.MathUtils.lerp(g.rotation.z, t.rz, a);
|
|
82
|
+
g.position.x = THREE.MathUtils.lerp(g.position.x, t.ox, a);
|
|
83
|
+
g.position.y = THREE.MathUtils.lerp(g.position.y, t.oy, a);
|
|
84
|
+
});
|
|
85
|
+
const glowColor = useMemo(() => highlighted ? '#e4b643' : '#ffffff', [highlighted]);
|
|
86
|
+
const onMove = (e) => {
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
const uv = e.uv;
|
|
89
|
+
if (!uv)
|
|
90
|
+
return;
|
|
91
|
+
const nx = THREE.MathUtils.clamp((uv.x - 0.5) * 2, -1, 1);
|
|
92
|
+
const ny = THREE.MathUtils.clamp((uv.y - 0.5) * 2, -1, 1);
|
|
93
|
+
target.current.ry = -nx * CARD_MAGNET.MAX_TILT_Y;
|
|
94
|
+
target.current.rx = ny * CARD_MAGNET.MAX_TILT_X;
|
|
95
|
+
target.current.rz = -nx * ny * CARD_MAGNET.TWIST_Z;
|
|
96
|
+
target.current.ox = nx * CARD_MAGNET.MAX_SHIFT;
|
|
97
|
+
target.current.oy = -ny * CARD_MAGNET.MAX_SHIFT * 0.65;
|
|
98
|
+
};
|
|
99
|
+
const reset = () => { target.current = { rx: 0, ry: 0, rz: 0, ox: 0, oy: 0 }; };
|
|
100
|
+
if (!texture)
|
|
101
|
+
return null;
|
|
102
|
+
return (_jsx(animated.group, { "position-x": position[0], "position-y": posY.to((y) => position[1] + y), "position-z": position[2], "rotation-x": rotation[0], "rotation-y": rotation[1], "rotation-z": rotation[2], scale: scale, children: _jsxs("group", { ref: tiltRef, children: [highlighted && (_jsx("pointLight", { color: glowColor, intensity: 1.5, distance: 1, position: [0, 0, 0.1] })), _jsxs("mesh", { onClick: (e) => { e.stopPropagation(); onClick?.(); }, onPointerMove: onMove, onPointerEnter: (e) => { e.stopPropagation(); setHovered(true); onPointerEnter?.(); document.body.style.cursor = 'pointer'; }, onPointerLeave: (e) => { e.stopPropagation(); setHovered(false); reset(); onPointerLeave?.(); document.body.style.cursor = 'auto'; }, castShadow: true, receiveShadow: true, children: [_jsx("boxGeometry", { args: [CARD_DIMENSIONS.WIDTH, CARD_DIMENSIONS.HEIGHT, CARD_DIMENSIONS.DEPTH] }), _jsx("meshBasicMaterial", { attach: "material-4", map: texture, toneMapped: false }), _jsx("meshStandardMaterial", { attach: "material-5", color: "#1a1a2e", metalness: 0.2, roughness: 0.8 }), _jsx("meshStandardMaterial", { attach: "material-0", color: "#f5f5dc" }), _jsx("meshStandardMaterial", { attach: "material-1", color: "#f5f5dc" }), _jsx("meshStandardMaterial", { attach: "material-2", color: "#f5f5dc" }), _jsx("meshStandardMaterial", { attach: "material-3", color: "#f5f5dc" })] }), selected && (_jsxs("mesh", { position: [0, 0, -CARD_DIMENSIONS.DEPTH], children: [_jsx("ringGeometry", { args: [0.45, 0.5, 32] }), _jsx("meshBasicMaterial", { color: "#e4b643", transparent: true, opacity: 0.8 })] }))] }) }));
|
|
103
|
+
});
|
package/dist/r3f.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './r3f/Card3D.js';
|
package/dist/r3f.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './r3f/Card3D.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { FiCopy, FiCheck } from 'react-icons/fi';
|
|
5
|
+
import { JimboColorOption } from './tokens.js';
|
|
6
|
+
export function JimboCodeBlock({ code, language, filename, className = '' }) {
|
|
7
|
+
const [copied, setCopied] = useState(false);
|
|
8
|
+
const copy = () => {
|
|
9
|
+
void navigator.clipboard.writeText(code);
|
|
10
|
+
setCopied(true);
|
|
11
|
+
setTimeout(() => setCopied(false), 2000);
|
|
12
|
+
};
|
|
13
|
+
return (_jsxs("div", { className: 'rounded-xl overflow-hidden flex flex-col border-2 ' + className, style: { backgroundColor: JimboColorOption.DARKEST, borderColor: JimboColorOption.PANEL_EDGE, boxShadow: '0 3px 0 0 rgba(0,0,0,0.5)' }, children: [_jsxs("div", { style: { padding: '0.5rem 1rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: `1px solid ${JimboColorOption.INNER_BORDER}` }, children: [_jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [filename && _jsx("span", { style: { fontSize: 10, textTransform: 'uppercase', opacity: 0.6 }, children: filename }), language && _jsx("span", { style: { fontSize: 9, padding: '1px 6px', borderRadius: 3, background: 'rgba(0,0,0,0.4)', color: '#60a5fa', textTransform: 'uppercase' }, children: language })] }), _jsx("button", { onClick: copy, title: "Copy", style: { padding: 4, background: 'none', border: 'none', cursor: 'pointer', color: copied ? '#4ade80' : 'rgba(255,255,255,0.5)', display: 'flex' }, children: copied ? _jsx(FiCheck, { size: 14 }) : _jsx(FiCopy, { size: 14 }) })] }), _jsx("pre", { style: { padding: '1rem', overflowX: 'auto', fontFamily: 'monospace', fontSize: '0.875rem', lineHeight: 1.6, color: '#f6f0d5', margin: 0 }, children: _jsx("code", { children: code }) })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { JimboColorOption } from './tokens.js';
|
|
4
|
+
const SUITS = [
|
|
5
|
+
{ char: '♥️', kf: 'jaml-heart' },
|
|
6
|
+
{ char: '♠️', kf: 'jaml-spade' },
|
|
7
|
+
{ char: '♦️', kf: 'jaml-diamond' },
|
|
8
|
+
{ char: '♣️', kf: 'jaml-club' },
|
|
9
|
+
];
|
|
10
|
+
const CYCLE = '5s';
|
|
11
|
+
export function JimboBalatroFooter({ hidden = false, className = '' }) {
|
|
12
|
+
return (_jsxs("div", { className: ['fixed right-0 bottom-0 left-0 w-screen min-w-full transition-opacity duration-200', hidden ? 'pointer-events-none opacity-0' : 'opacity-100', className].filter(Boolean).join(' '), children: [_jsx("div", { style: { width: '100%', borderTop: '1px solid rgba(255,255,255,0.1)', background: 'rgba(0,0,0,0.9)', padding: '0 1rem 3px', textAlign: 'center' }, children: _jsxs("p", { style: { fontFamily: 'm6x11plus, monospace', fontSize: 'clamp(11px, 0.8vw + 8px, 14px)', display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'center', gap: '0 0.5rem', color: 'white', margin: 0 }, children: [_jsx("span", { children: "Not affiliated with LocalThunk or PlayStack" }), _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }, children: ["Made with", ' ', _jsx("span", { style: { position: 'relative', display: 'inline-block', width: '1.5em', height: '1em', verticalAlign: 'middle' }, children: SUITS.map(({ char, kf }) => (_jsx("span", { style: { position: 'absolute', inset: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', opacity: 0, animationName: kf, animationDuration: CYCLE, animationDelay: '0s', animationIterationCount: 'infinite', animationTimingFunction: 'ease-out' }, children: char }, char))) }), ' ', "for the", ' ', _jsx("a", { href: "https://playbalatro.com", target: "_blank", rel: "noopener noreferrer", style: { color: JimboColorOption.GOLD, textDecoration: 'none' }, children: "Balatro" }), ' ', "community"] })] }) }), _jsx("style", { children: `
|
|
13
|
+
@keyframes jaml-heart { 0%{opacity:0;transform:scale(1)} 1%{opacity:1;transform:scale(1.45)} 3.5%{opacity:1;transform:scale(1)} 61.5%{opacity:1;transform:scale(1)} 62%{opacity:0} 100%{opacity:0} }
|
|
14
|
+
@keyframes jaml-spade { 0%,61.5%{opacity:0} 62%{opacity:1;transform:scale(1.45)} 64.5%{opacity:1;transform:scale(1)} 71.5%{opacity:1} 72%{opacity:0} 100%{opacity:0} }
|
|
15
|
+
@keyframes jaml-diamond { 0%,71.5%{opacity:0} 72%{opacity:1;transform:scale(1.45)} 74.5%{opacity:1;transform:scale(1)} 81.5%{opacity:1} 82%{opacity:0} 100%{opacity:0} }
|
|
16
|
+
@keyframes jaml-club { 0%,81.5%{opacity:0} 82%{opacity:1;transform:scale(1.45)} 84.5%{opacity:1;transform:scale(1)} 95%{opacity:1} 96%{opacity:0} 100%{opacity:0} }
|
|
17
|
+
` })] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ButtonVariant } from './tokens.js';
|
|
3
|
+
export interface JimboPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
sway?: boolean;
|
|
5
|
+
onBack?: () => void;
|
|
6
|
+
backLabel?: string;
|
|
7
|
+
hideBack?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const JimboPanel: React.MemoExoticComponent<({ children, className, sway, onBack, backLabel, hideBack, style, ...props }: JimboPanelProps) => import("react/jsx-runtime").JSX.Element>;
|
|
10
|
+
export interface JimboInnerPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
}
|
|
12
|
+
export declare const JimboInnerPanel: React.MemoExoticComponent<({ children, className, style, ...props }: JimboInnerPanelProps) => import("react/jsx-runtime").JSX.Element>;
|
|
13
|
+
export interface JimboButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
14
|
+
variant?: ButtonVariant;
|
|
15
|
+
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
16
|
+
fullWidth?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function JimboButton({ children, variant, size, fullWidth, className, style, disabled, ...props }: JimboButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function JimboBackButton({ label, ...props }: Omit<JimboButtonProps, 'variant' | 'children'> & {
|
|
20
|
+
label?: string;
|
|
21
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export interface JimboModalProps {
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
open: boolean;
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
title?: string;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function JimboModal({ children, open, onClose, title, className }: JimboModalProps): import("react/jsx-runtime").JSX.Element | null;
|
package/dist/ui/panel.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useRef, memo } from 'react';
|
|
4
|
+
import { JimboColorOption, JIMBO_ANIMATIONS } from './tokens.js';
|
|
5
|
+
export const JimboPanel = memo(({ children, className = '', sway = false, onBack, backLabel = 'Back', hideBack = false, style, ...props }) => {
|
|
6
|
+
const panelRef = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!sway || !panelRef.current)
|
|
9
|
+
return;
|
|
10
|
+
let frame;
|
|
11
|
+
const start = Date.now();
|
|
12
|
+
const el = panelRef.current;
|
|
13
|
+
const tick = () => {
|
|
14
|
+
const t = ((Date.now() - start) % JIMBO_ANIMATIONS.SWAY_DURATION) / JIMBO_ANIMATIONS.SWAY_DURATION * Math.PI * 2;
|
|
15
|
+
el.style.transform = `translate(${Math.sin(t) * JIMBO_ANIMATIONS.SWAY_AMOUNT * 0.3}px, ${Math.sin(t * 0.8) * JIMBO_ANIMATIONS.SWAY_AMOUNT}px)`;
|
|
16
|
+
frame = requestAnimationFrame(tick);
|
|
17
|
+
};
|
|
18
|
+
frame = requestAnimationFrame(tick);
|
|
19
|
+
return () => { cancelAnimationFrame(frame); el.style.transform = ''; };
|
|
20
|
+
}, [sway]);
|
|
21
|
+
return (_jsxs("div", { ref: panelRef, className: 'rounded-xl p-4 flex flex-col items-stretch overflow-hidden ' + className, style: {
|
|
22
|
+
backgroundColor: JimboColorOption.DARK_GREY,
|
|
23
|
+
border: `3px solid ${JimboColorOption.BORDER_SILVER}`,
|
|
24
|
+
boxShadow: `0 3px 0 0 ${JimboColorOption.BORDER_SOUTH}`,
|
|
25
|
+
...style,
|
|
26
|
+
}, ...props, children: [_jsx("div", { className: "flex-1 overflow-auto", children: children }), onBack && !hideBack && (_jsx("div", { className: "mt-4 pt-2 shrink-0", children: _jsx(JimboBackButton, { onClick: onBack, label: backLabel }) }))] }));
|
|
27
|
+
});
|
|
28
|
+
JimboPanel.displayName = 'JimboPanel';
|
|
29
|
+
export const JimboInnerPanel = memo(({ children, className = '', style, ...props }) => (_jsx("div", { className: 'rounded-lg p-3 ' + className, style: { backgroundColor: JimboColorOption.INNER_BORDER, border: `2px solid ${JimboColorOption.PANEL_EDGE}`, ...style }, ...props, children: children })));
|
|
30
|
+
JimboInnerPanel.displayName = 'JimboInnerPanel';
|
|
31
|
+
// ─── Button ──────────────────────────────────────────────────────────────────
|
|
32
|
+
const VARIANT_COLORS = {
|
|
33
|
+
primary: { bg: JimboColorOption.RED, hover: JimboColorOption.DARK_RED, text: '#fff' },
|
|
34
|
+
secondary: { bg: JimboColorOption.BLUE, hover: JimboColorOption.DARK_BLUE, text: '#fff' },
|
|
35
|
+
danger: { bg: JimboColorOption.RED, hover: JimboColorOption.DARK_RED, text: '#fff' },
|
|
36
|
+
back: { bg: JimboColorOption.ORANGE, hover: JimboColorOption.DARK_ORANGE, text: '#fff' },
|
|
37
|
+
ghost: { bg: 'transparent', hover: 'rgba(255,255,255,0.1)', text: '#fff' },
|
|
38
|
+
};
|
|
39
|
+
export function JimboButton({ children, variant = 'primary', size = 'md', fullWidth = false, className = '', style, disabled, ...props }) {
|
|
40
|
+
const [hovered, setHovered] = useState(false);
|
|
41
|
+
const [pressed, setPressed] = useState(false);
|
|
42
|
+
const c = VARIANT_COLORS[variant];
|
|
43
|
+
const pad = { xs: '0.2rem 0.5rem', sm: '0.25rem 0.75rem', md: '0.375rem 1rem', lg: '0.5rem 1.5rem' }[size];
|
|
44
|
+
return (_jsx("button", { disabled: disabled, onMouseEnter: () => { if (!disabled)
|
|
45
|
+
setHovered(true); }, onMouseLeave: () => { setHovered(false); setPressed(false); }, onMouseDown: () => { if (!disabled)
|
|
46
|
+
setPressed(true); }, onMouseUp: () => setPressed(false), className: className, style: {
|
|
47
|
+
fontFamily: 'm6x11plus, monospace',
|
|
48
|
+
backgroundColor: hovered ? c.hover : c.bg,
|
|
49
|
+
color: c.text,
|
|
50
|
+
padding: pad,
|
|
51
|
+
borderRadius: '0.5rem',
|
|
52
|
+
border: 'none',
|
|
53
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
54
|
+
width: fullWidth ? '100%' : undefined,
|
|
55
|
+
opacity: disabled ? 0.5 : 1,
|
|
56
|
+
transform: pressed ? 'translateY(3px)' : 'none',
|
|
57
|
+
boxShadow: pressed ? 'none' : '0 3px 0 0 rgba(0,0,0,0.5)',
|
|
58
|
+
textShadow: '1px 1px 0 rgba(0,0,0,0.8)',
|
|
59
|
+
userSelect: 'none',
|
|
60
|
+
...style,
|
|
61
|
+
}, ...props, children: children }));
|
|
62
|
+
}
|
|
63
|
+
export function JimboBackButton({ label = 'Back', ...props }) {
|
|
64
|
+
return _jsx(JimboButton, { variant: "back", size: "sm", fullWidth: true, ...props, children: label });
|
|
65
|
+
}
|
|
66
|
+
export function JimboModal({ children, open, onClose, title, className }) {
|
|
67
|
+
const [visible, setVisible] = useState(open);
|
|
68
|
+
const [opacity, setOpacity] = useState(open ? 1 : 0);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (open) {
|
|
71
|
+
setVisible(true);
|
|
72
|
+
requestAnimationFrame(() => setOpacity(1));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
setOpacity(0);
|
|
76
|
+
const t = setTimeout(() => setVisible(false), JIMBO_ANIMATIONS.MENU_SINK_DURATION);
|
|
77
|
+
return () => clearTimeout(t);
|
|
78
|
+
}
|
|
79
|
+
}, [open]);
|
|
80
|
+
if (!visible)
|
|
81
|
+
return null;
|
|
82
|
+
return (_jsx("div", { style: { position: 'fixed', inset: 0, zIndex: 50, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem', background: 'rgba(0,0,0,0.7)', opacity, transition: `opacity ${JIMBO_ANIMATIONS.MENU_SINK_DURATION}ms ease` }, onClick: onClose, children: _jsxs(JimboPanel, { sway: true, onBack: onClose, backLabel: "Close", className: 'w-full flex flex-col max-h-[90vh] ' + (className ?? 'max-w-lg'), onClick: (e) => e.stopPropagation(), children: [title && _jsx("h2", { style: { fontFamily: 'm6x11plus, monospace', color: '#fff', textAlign: 'center', margin: '0 0 1rem', fontSize: '1.25rem' }, children: title }), children] }) }));
|
|
83
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Balatro design tokens — colors eyedropped from actual game pixels.
|
|
3
|
+
* Do NOT replace with Lua HEX values; the game's shader pipeline transforms them.
|
|
4
|
+
*/
|
|
5
|
+
export declare const JimboColorOption: {
|
|
6
|
+
readonly RED: "#ff4c40";
|
|
7
|
+
readonly BLUE: "#0093ff";
|
|
8
|
+
readonly GREEN: "#429f79";
|
|
9
|
+
readonly ORANGE: "#ff9800";
|
|
10
|
+
readonly GOLD: "#e4b643";
|
|
11
|
+
readonly PURPLE: "#9e74ce";
|
|
12
|
+
readonly DARK_RED: "#a02721";
|
|
13
|
+
readonly DARK_BLUE: "#0057a1";
|
|
14
|
+
readonly DARK_ORANGE: "#a05b00";
|
|
15
|
+
readonly DARK_GREEN: "#215f46";
|
|
16
|
+
readonly DARK_PURPLE: "#5e437e";
|
|
17
|
+
readonly DARK_GREY: "#3a5055";
|
|
18
|
+
readonly DARKEST: "#1e2b2d";
|
|
19
|
+
readonly GREY: "#708386";
|
|
20
|
+
readonly TEAL_GREY: "#404c4e";
|
|
21
|
+
readonly PANEL_EDGE: "#1e2e32";
|
|
22
|
+
readonly INNER_BORDER: "#334461";
|
|
23
|
+
readonly BORDER_SILVER: "#b9c2d2";
|
|
24
|
+
readonly BORDER_SOUTH: "#777e89";
|
|
25
|
+
readonly GOLD_TEXT: "#e4b643";
|
|
26
|
+
readonly GREEN_TEXT: "#35bd86";
|
|
27
|
+
readonly ORANGE_TEXT: "#ff8f00";
|
|
28
|
+
readonly WHITE: "#ffffff";
|
|
29
|
+
readonly BLACK: "#000000";
|
|
30
|
+
readonly TAROT_BUTTON: "#9e74ce";
|
|
31
|
+
readonly PLANET_BUTTON: "#00a7ca";
|
|
32
|
+
readonly SPECTRAL_BUTTON: "#2e76fd";
|
|
33
|
+
readonly TAROT_BUTTON_DARK: "#5e437e";
|
|
34
|
+
readonly PLANET_BUTTON_DARK: "#00657c";
|
|
35
|
+
readonly SPECTRAL_BUTTON_DARK: "#14449e";
|
|
36
|
+
};
|
|
37
|
+
export declare const JAML_COLORS: {
|
|
38
|
+
readonly RED: "#ff4c40";
|
|
39
|
+
readonly BLUE: "#0093ff";
|
|
40
|
+
readonly GREEN: "#429f79";
|
|
41
|
+
readonly ORANGE: "#ff9800";
|
|
42
|
+
readonly PURPLE: "#9e74ce";
|
|
43
|
+
readonly WHITE: "#ffffff";
|
|
44
|
+
readonly DARK_RED: "#a02721";
|
|
45
|
+
readonly DARK_BLUE: "#0057a1";
|
|
46
|
+
readonly DARK_ORANGE: "#a05b00";
|
|
47
|
+
readonly DARK_GREEN: "#215f46";
|
|
48
|
+
readonly DARK_PURPLE: "#5e437e";
|
|
49
|
+
};
|
|
50
|
+
export type JimboPaletteColor = keyof typeof JimboColorOption;
|
|
51
|
+
export declare function withAlpha(hex: string, alpha: number): string;
|
|
52
|
+
export declare const JIMBO_ANIMATIONS: {
|
|
53
|
+
readonly JUICE_UP_SCALE: 1.05;
|
|
54
|
+
readonly JUICE_DOWN_SCALE: 1;
|
|
55
|
+
readonly JUICE_DURATION: 150;
|
|
56
|
+
readonly JUICE_EASING: "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
57
|
+
readonly SWAY_AMOUNT: 1.5;
|
|
58
|
+
readonly SWAY_DURATION: 4000;
|
|
59
|
+
readonly PRESS_TRANSLATE_Y: 2;
|
|
60
|
+
readonly PRESS_DURATION: 50;
|
|
61
|
+
readonly CARD_TILT_MAX: 6;
|
|
62
|
+
readonly MENU_SINK_DURATION: 200;
|
|
63
|
+
readonly MENU_RISE_DURATION: 300;
|
|
64
|
+
readonly LETTER_POP_RATE: 3;
|
|
65
|
+
readonly LETTER_BUMP_RATE: 2.666;
|
|
66
|
+
};
|
|
67
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'back' | 'ghost';
|
|
68
|
+
export declare const BUTTON_COLORS: Record<ButtonVariant, {
|
|
69
|
+
bg: string;
|
|
70
|
+
hover: string;
|
|
71
|
+
text: string;
|
|
72
|
+
}>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Balatro design tokens — colors eyedropped from actual game pixels.
|
|
3
|
+
* Do NOT replace with Lua HEX values; the game's shader pipeline transforms them.
|
|
4
|
+
*/
|
|
5
|
+
export const JimboColorOption = {
|
|
6
|
+
RED: '#ff4c40',
|
|
7
|
+
BLUE: '#0093ff',
|
|
8
|
+
GREEN: '#429f79',
|
|
9
|
+
ORANGE: '#ff9800',
|
|
10
|
+
GOLD: '#e4b643',
|
|
11
|
+
PURPLE: '#9e74ce',
|
|
12
|
+
DARK_RED: '#a02721',
|
|
13
|
+
DARK_BLUE: '#0057a1',
|
|
14
|
+
DARK_ORANGE: '#a05b00',
|
|
15
|
+
DARK_GREEN: '#215f46',
|
|
16
|
+
DARK_PURPLE: '#5e437e',
|
|
17
|
+
DARK_GREY: '#3a5055',
|
|
18
|
+
DARKEST: '#1e2b2d',
|
|
19
|
+
GREY: '#708386',
|
|
20
|
+
TEAL_GREY: '#404c4e',
|
|
21
|
+
PANEL_EDGE: '#1e2e32',
|
|
22
|
+
INNER_BORDER: '#334461',
|
|
23
|
+
BORDER_SILVER: '#b9c2d2',
|
|
24
|
+
BORDER_SOUTH: '#777e89',
|
|
25
|
+
GOLD_TEXT: '#e4b643',
|
|
26
|
+
GREEN_TEXT: '#35bd86',
|
|
27
|
+
ORANGE_TEXT: '#ff8f00',
|
|
28
|
+
WHITE: '#ffffff',
|
|
29
|
+
BLACK: '#000000',
|
|
30
|
+
TAROT_BUTTON: '#9e74ce',
|
|
31
|
+
PLANET_BUTTON: '#00a7ca',
|
|
32
|
+
SPECTRAL_BUTTON: '#2e76fd',
|
|
33
|
+
TAROT_BUTTON_DARK: '#5e437e',
|
|
34
|
+
PLANET_BUTTON_DARK: '#00657c',
|
|
35
|
+
SPECTRAL_BUTTON_DARK: '#14449e',
|
|
36
|
+
};
|
|
37
|
+
export const JAML_COLORS = {
|
|
38
|
+
RED: JimboColorOption.RED,
|
|
39
|
+
BLUE: JimboColorOption.BLUE,
|
|
40
|
+
GREEN: JimboColorOption.GREEN,
|
|
41
|
+
ORANGE: JimboColorOption.ORANGE,
|
|
42
|
+
PURPLE: JimboColorOption.PURPLE,
|
|
43
|
+
WHITE: JimboColorOption.WHITE,
|
|
44
|
+
DARK_RED: JimboColorOption.DARK_RED,
|
|
45
|
+
DARK_BLUE: JimboColorOption.DARK_BLUE,
|
|
46
|
+
DARK_ORANGE: JimboColorOption.DARK_ORANGE,
|
|
47
|
+
DARK_GREEN: JimboColorOption.DARK_GREEN,
|
|
48
|
+
DARK_PURPLE: JimboColorOption.DARK_PURPLE,
|
|
49
|
+
};
|
|
50
|
+
export function withAlpha(hex, alpha) {
|
|
51
|
+
const clean = hex.replace('#', '');
|
|
52
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
53
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
54
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
55
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
56
|
+
}
|
|
57
|
+
export const JIMBO_ANIMATIONS = {
|
|
58
|
+
JUICE_UP_SCALE: 1.05,
|
|
59
|
+
JUICE_DOWN_SCALE: 1.0,
|
|
60
|
+
JUICE_DURATION: 150,
|
|
61
|
+
JUICE_EASING: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
62
|
+
SWAY_AMOUNT: 1.5,
|
|
63
|
+
SWAY_DURATION: 4000,
|
|
64
|
+
PRESS_TRANSLATE_Y: 2,
|
|
65
|
+
PRESS_DURATION: 50,
|
|
66
|
+
CARD_TILT_MAX: 6,
|
|
67
|
+
MENU_SINK_DURATION: 200,
|
|
68
|
+
MENU_RISE_DURATION: 300,
|
|
69
|
+
LETTER_POP_RATE: 3,
|
|
70
|
+
LETTER_BUMP_RATE: 2.666,
|
|
71
|
+
};
|
|
72
|
+
export const BUTTON_COLORS = {
|
|
73
|
+
primary: { bg: JimboColorOption.RED, hover: JimboColorOption.DARK_RED, text: JimboColorOption.WHITE },
|
|
74
|
+
secondary: { bg: JimboColorOption.BLUE, hover: JimboColorOption.DARK_BLUE, text: JimboColorOption.WHITE },
|
|
75
|
+
danger: { bg: JimboColorOption.RED, hover: JimboColorOption.DARK_RED, text: JimboColorOption.WHITE },
|
|
76
|
+
back: { bg: JimboColorOption.ORANGE, hover: JimboColorOption.DARK_ORANGE, text: JimboColorOption.WHITE },
|
|
77
|
+
ghost: { bg: 'transparent', hover: 'rgba(255,255,255,0.1)', text: JimboColorOption.WHITE },
|
|
78
|
+
};
|
package/dist/ui.d.ts
ADDED
package/dist/ui.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaml-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"types": "./dist/motely.d.ts",
|
|
19
19
|
"import": "./dist/motely.js"
|
|
20
20
|
},
|
|
21
|
+
"./ui": {
|
|
22
|
+
"types": "./dist/ui.d.ts",
|
|
23
|
+
"import": "./dist/ui.js"
|
|
24
|
+
},
|
|
25
|
+
"./r3f": {
|
|
26
|
+
"types": "./dist/r3f.d.ts",
|
|
27
|
+
"import": "./dist/r3f.js"
|
|
28
|
+
},
|
|
21
29
|
"./assets/*": "./assets/*",
|
|
22
30
|
"./package.json": "./package.json"
|
|
23
31
|
},
|
|
@@ -28,12 +36,6 @@
|
|
|
28
36
|
"README.md",
|
|
29
37
|
"LICENSE"
|
|
30
38
|
],
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "tsc --pretty false",
|
|
33
|
-
"dev": "tsc --watch",
|
|
34
|
-
"typecheck": "tsc --noEmit --pretty false",
|
|
35
|
-
"prepack": "npm run build"
|
|
36
|
-
},
|
|
37
39
|
"engines": {
|
|
38
40
|
"node": ">=18"
|
|
39
41
|
},
|
|
@@ -60,21 +62,47 @@
|
|
|
60
62
|
"author": "pifreak",
|
|
61
63
|
"license": "MIT",
|
|
62
64
|
"peerDependencies": {
|
|
63
|
-
"
|
|
65
|
+
"@react-spring/three": ">=9.0.0",
|
|
66
|
+
"@react-three/fiber": ">=8.0.0",
|
|
67
|
+
"motely-wasm": "^10.2.0 || ^11.0.0 || ^12.0.0",
|
|
64
68
|
"react": "^18.2.0 || ^19.0.0",
|
|
65
|
-
"react-dom": "^18.2.0 || ^19.0.0"
|
|
69
|
+
"react-dom": "^18.2.0 || ^19.0.0",
|
|
70
|
+
"react-icons": ">=5.0.0",
|
|
71
|
+
"three": ">=0.150.0"
|
|
66
72
|
},
|
|
67
73
|
"peerDependenciesMeta": {
|
|
74
|
+
"@react-spring/three": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@react-three/fiber": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
68
80
|
"motely-wasm": {
|
|
69
81
|
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"react-icons": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"three": {
|
|
87
|
+
"optional": true
|
|
70
88
|
}
|
|
71
89
|
},
|
|
72
90
|
"devDependencies": {
|
|
91
|
+
"@react-spring/three": "^10.0.3",
|
|
92
|
+
"@react-three/fiber": "^9.6.0",
|
|
73
93
|
"@types/react": "^19.2.14",
|
|
74
94
|
"@types/react-dom": "^19.2.3",
|
|
75
|
-
"
|
|
95
|
+
"@types/three": "^0.184.0",
|
|
96
|
+
"motely-wasm": "^12.0.0",
|
|
76
97
|
"react": "^19.2.4",
|
|
77
98
|
"react-dom": "^19.2.4",
|
|
99
|
+
"react-icons": "^5.6.0",
|
|
100
|
+
"three": "^0.184.0",
|
|
78
101
|
"typescript": "^5.9.3"
|
|
102
|
+
},
|
|
103
|
+
"scripts": {
|
|
104
|
+
"build": "tsc --pretty false",
|
|
105
|
+
"dev": "tsc --watch",
|
|
106
|
+
"typecheck": "tsc --noEmit --pretty false"
|
|
79
107
|
}
|
|
80
108
|
}
|