jaml-ui 0.10.0 → 0.11.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/LICENSE +21 -21
- package/README.md +135 -135
- package/assets/8BitDeck.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/.design-canvas.state.json +1 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/BlindChips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/Boosters.json +303 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/boosters.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/BlindChips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/blinds_metadata.json +51 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/8BitDeck.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/Enhancers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/balatro-stake-chips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/enhancers_metadata.json +52 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/playing_cards_metadata.json +74 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/stakes.json +19 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Editions.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Enhancers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Editions.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Jokers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/jokers.json +1087 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers_metadata.json +25 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.json +191 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/Tarots.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/planets.json +15 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/spectrals.json +21 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/tarots.json +163 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/Vouchers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/vouchers.json +130 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/blinds.json +51 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/boosters.json +303 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/fonts/m6x11plusplus.otf +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/jokers.json +1087 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/planets.json +15 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/spectrals.json +21 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/stakes.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/stickers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.json +191 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/tarots.json +163 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/vouchers.json +130 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail v2.html +40 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail.html +34 -0
- package/assets/Balatro Seed Curator (DesignsV2)/public/fonts/m6x11plusplus.otf +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/AntePage.jsx +228 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/SeedDetail.jsx +222 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/app.jsx +35 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/mockData.js +185 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/sprites.jsx +259 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/tokens.js +49 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/AntePageV2.jsx +290 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/BalButton.jsx +107 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlBuilderV2.jsx +594 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlIde.jsx +302 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SearchResultsV2.jsx +286 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedDetailV2.jsx +336 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedOGCard.jsx +251 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/Showcase.jsx +131 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/app.jsx +55 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/data.js +296 -0
- package/assets/Balatro Seed Curator (DesignsV2)/starters/design-canvas.jsx +622 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/8BitDeck.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/BlindChips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Boosters.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Editions.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Enhancers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Jokers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Tarots.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749540653-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749644934-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749661871-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749674748-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749703076-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749882759-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750354200-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750733265-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776751928925-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776800975060-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/stickers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/tags.png +0 -0
- package/assets/BlindChips.png +0 -0
- package/assets/Boosters.png +0 -0
- package/assets/Editions.png +0 -0
- package/assets/Enhancers.png +0 -0
- package/assets/Jokers.png +0 -0
- package/assets/Tarots.png +0 -0
- package/assets/Vouchers.png +0 -0
- package/assets/fonts/m6x11plusplus.otf +0 -0
- package/assets/stickers.png +0 -0
- package/assets/tags.png +0 -0
- package/dist/assets.js +10 -13
- package/dist/components/GameCard.js +7 -5
- package/dist/components/JamlCodeEditor.js +28 -16
- package/dist/components/JamlIdeVisual.d.ts +5 -3
- package/dist/components/JamlIdeVisual.js +194 -36
- package/dist/components/JamlMapPreview.d.ts +1 -5
- package/dist/components/JamlMapPreview.js +67 -107
- package/dist/components/PlayingCard.js +15 -50
- package/dist/data/balatro-jokers.json +1241 -0
- package/dist/decode/motelyItemDecoder.js +1 -1
- package/dist/hooks/searchWorkerCode.js +59 -59
- package/dist/hooks/useAnalyzer.d.ts +1 -0
- package/dist/hooks/useAnalyzer.js +5 -1
- package/dist/hooks/useSearch.d.ts +1 -0
- package/dist/hooks/useSearch.js +4 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/motelyDisplay.js +2 -0
- package/dist/r3f/BalatroJokerMesh3D.d.ts +8 -0
- package/dist/r3f/BalatroJokerMesh3D.js +98 -0
- package/dist/r3f/BalatroJokerPreview3D.d.ts +14 -0
- package/dist/r3f/BalatroJokerPreview3D.js +30 -0
- package/dist/r3f/BalatroPlayingCard3D.d.ts +22 -0
- package/dist/r3f/BalatroPlayingCard3D.js +62 -0
- package/dist/r3f/cardConstants.d.ts +16 -0
- package/dist/r3f/cardConstants.js +14 -0
- package/dist/r3f/compositedAtlas.d.ts +5 -0
- package/dist/r3f/compositedAtlas.js +56 -0
- package/dist/r3f/gridUV.d.ts +22 -0
- package/dist/r3f/gridUV.js +30 -0
- package/dist/r3f/index.d.ts +12 -0
- package/dist/r3f/index.js +13 -0
- package/dist/r3f/jokerRegistry.d.ts +28 -0
- package/dist/r3f/jokerRegistry.js +40 -0
- package/dist/r3f/jokerTilt.d.ts +8 -0
- package/dist/r3f/jokerTilt.js +41 -0
- package/dist/r3f/magneticTilt.d.ts +18 -0
- package/dist/r3f/magneticTilt.js +34 -0
- package/dist/r3f/playingCardTypes.d.ts +24 -0
- package/dist/r3f/playingCardTypes.js +32 -0
- package/dist/r3f/playingCardVisuals.d.ts +7 -0
- package/dist/r3f/playingCardVisuals.js +45 -0
- package/dist/r3f/usePlayingCardTexture.d.ts +7 -0
- package/dist/r3f/usePlayingCardTexture.js +92 -0
- package/dist/sprites/spriteMapper.d.ts +1 -1
- package/dist/sprites/spriteMapper.js +14 -33
- package/dist/ui/footer.js +5 -5
- package/dist/ui/jimboBackground.js +55 -55
- package/dist/ui/jimboCopyRow.d.ts +5 -0
- package/dist/ui/jimboCopyRow.js +36 -0
- package/dist/ui/jimboTabs.js +5 -5
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/utils/gameCardUtils.d.ts +4 -12
- package/dist/utils/gameCardUtils.js +9 -43
- package/fonts.css +5 -5
- package/package.json +4 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import jokersPayload from "../data/balatro-jokers.json";
|
|
2
|
+
import { resolveJamlAssetUrl } from "../assets.js";
|
|
3
|
+
const payload = jokersPayload;
|
|
4
|
+
export const BALATRO_JOKER_ATLAS_META = payload.atlas;
|
|
5
|
+
export const BALATRO_JOKERS = payload.jokers;
|
|
6
|
+
/** Prefer bundled asset resolver so hosts can override base URL. */
|
|
7
|
+
export function getJokerAtlasImageUrl() {
|
|
8
|
+
return resolveJamlAssetUrl("jokers");
|
|
9
|
+
}
|
|
10
|
+
export function getJokerAtlasGridSize() {
|
|
11
|
+
let mx = 0;
|
|
12
|
+
let my = 0;
|
|
13
|
+
for (const j of BALATRO_JOKERS) {
|
|
14
|
+
mx = Math.max(mx, j.pos.x);
|
|
15
|
+
my = Math.max(my, j.pos.y);
|
|
16
|
+
if (j.soulPos) {
|
|
17
|
+
mx = Math.max(mx, j.soulPos.x);
|
|
18
|
+
my = Math.max(my, j.soulPos.y);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { cols: mx + 1, rows: my + 1 };
|
|
22
|
+
}
|
|
23
|
+
const NAME_ALIASES = {
|
|
24
|
+
canio: "Caino",
|
|
25
|
+
séance: "Seance",
|
|
26
|
+
seance: "Seance",
|
|
27
|
+
};
|
|
28
|
+
function normalizeLookup(name) {
|
|
29
|
+
const t = name.trim();
|
|
30
|
+
const alias = NAME_ALIASES[t.toLowerCase()];
|
|
31
|
+
return alias ?? t;
|
|
32
|
+
}
|
|
33
|
+
export function findJokerByDisplayName(displayName) {
|
|
34
|
+
const needle = normalizeLookup(displayName).toLowerCase();
|
|
35
|
+
return BALATRO_JOKERS.find((j) => j.name.toLowerCase() === needle);
|
|
36
|
+
}
|
|
37
|
+
export function findJokerByKey(key) {
|
|
38
|
+
const k = key.trim().toLowerCase();
|
|
39
|
+
return BALATRO_JOKERS.find((j) => j.key.toLowerCase() === k);
|
|
40
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import type { MagneticTarget } from "./magneticTilt.js";
|
|
3
|
+
export declare const JOKER_TILT_LERP_IN = 20;
|
|
4
|
+
export declare const JOKER_TILT_LERP_OUT = 11;
|
|
5
|
+
export declare function stableIdFraction(s: string): number;
|
|
6
|
+
export declare function applyJokerTiltFromNormalized(nx: number, ny: number, amtScale: number, target: MagneticTarget): void;
|
|
7
|
+
export declare function jokerPointerTiltFromUv(uv: THREE.Vector2, target: MagneticTarget): void;
|
|
8
|
+
export declare function jokerAmbientTiltAtTime(t: number, idFrac: number, target: MagneticTarget): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
const AMBIENT_TILT = 0.2;
|
|
3
|
+
const TILT_FACTOR = 0.3;
|
|
4
|
+
const MAGNET_MAX_RX = 0.34;
|
|
5
|
+
const MAGNET_MAX_RY = 0.4;
|
|
6
|
+
const MAGNET_MAX_SHIFT = 0.05;
|
|
7
|
+
const MAGNET_TWIST_Z = 0.1;
|
|
8
|
+
export const JOKER_TILT_LERP_IN = 20;
|
|
9
|
+
export const JOKER_TILT_LERP_OUT = 11;
|
|
10
|
+
export function stableIdFraction(s) {
|
|
11
|
+
let h = 2166136261;
|
|
12
|
+
for (let i = 0; i < s.length; i++) {
|
|
13
|
+
h ^= s.charCodeAt(i);
|
|
14
|
+
h = Math.imul(h, 16777619);
|
|
15
|
+
}
|
|
16
|
+
return ((h >>> 0) % 100000) / 100000;
|
|
17
|
+
}
|
|
18
|
+
export function applyJokerTiltFromNormalized(nx, ny, amtScale, target) {
|
|
19
|
+
const clampedX = THREE.MathUtils.clamp(nx, -1, 1);
|
|
20
|
+
const clampedY = THREE.MathUtils.clamp(ny, -1, 1);
|
|
21
|
+
target.ry = -clampedX * MAGNET_MAX_RY * amtScale;
|
|
22
|
+
target.rx = clampedY * MAGNET_MAX_RX * amtScale;
|
|
23
|
+
target.rz = -clampedX * clampedY * MAGNET_TWIST_Z * amtScale;
|
|
24
|
+
target.ox = clampedX * MAGNET_MAX_SHIFT * amtScale;
|
|
25
|
+
target.oy = -clampedY * MAGNET_MAX_SHIFT * 0.65 * amtScale;
|
|
26
|
+
}
|
|
27
|
+
export function jokerPointerTiltFromUv(uv, target) {
|
|
28
|
+
const nx = (uv.x - 0.5) * 2;
|
|
29
|
+
const ny = (uv.y - 0.5) * 2;
|
|
30
|
+
const amt = Math.abs(ny + nx - 1) * TILT_FACTOR;
|
|
31
|
+
applyJokerTiltFromNormalized(nx, ny, THREE.MathUtils.clamp(amt * 1.15, 0.35, 1.25), target);
|
|
32
|
+
}
|
|
33
|
+
export function jokerAmbientTiltAtTime(t, idFrac, target) {
|
|
34
|
+
const tiltAngle = t * (1.56 + ((idFrac / 1.14212) % 1)) + idFrac / 1.35122;
|
|
35
|
+
const nu = 0.5 + 0.5 * AMBIENT_TILT * Math.cos(tiltAngle);
|
|
36
|
+
const nv = 0.5 + 0.5 * AMBIENT_TILT * Math.sin(tiltAngle);
|
|
37
|
+
const nx = (nu - 0.5) * 2;
|
|
38
|
+
const ny = (nv - 0.5) * 2;
|
|
39
|
+
const amt = AMBIENT_TILT * (0.5 + Math.cos(tiltAngle)) * TILT_FACTOR;
|
|
40
|
+
applyJokerTiltFromNormalized(nx, ny, THREE.MathUtils.clamp(amt * 2.2, 0.2, 1), target);
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
export type MagneticTarget = {
|
|
3
|
+
rx: number;
|
|
4
|
+
ry: number;
|
|
5
|
+
rz: number;
|
|
6
|
+
ox: number;
|
|
7
|
+
oy: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function createZeroMagneticTarget(): MagneticTarget;
|
|
10
|
+
/** Map pointer UV on card face to rotation + in-plane shift (Balatro-style). */
|
|
11
|
+
export declare function magneticTargetFromUvPlayingCard(uv: THREE.Vector2, magnet: {
|
|
12
|
+
MAX_TILT_X: number;
|
|
13
|
+
MAX_TILT_Y: number;
|
|
14
|
+
MAX_SHIFT: number;
|
|
15
|
+
TWIST_Z: number;
|
|
16
|
+
}): MagneticTarget;
|
|
17
|
+
export declare function resetMagneticTarget(t: MagneticTarget): void;
|
|
18
|
+
export declare function lerpMagneticGroup(group: THREE.Group, target: MagneticTarget, dt: number, lerpIn: number, lerpOut: number, hovered: boolean, extraRz: number): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
export function createZeroMagneticTarget() {
|
|
3
|
+
return { rx: 0, ry: 0, rz: 0, ox: 0, oy: 0 };
|
|
4
|
+
}
|
|
5
|
+
/** Map pointer UV on card face to rotation + in-plane shift (Balatro-style). */
|
|
6
|
+
export function magneticTargetFromUvPlayingCard(uv, magnet) {
|
|
7
|
+
const nx = (uv.x - 0.5) * 2;
|
|
8
|
+
const ny = (uv.y - 0.5) * 2;
|
|
9
|
+
const clampedX = THREE.MathUtils.clamp(nx, -1, 1);
|
|
10
|
+
const clampedY = THREE.MathUtils.clamp(ny, -1, 1);
|
|
11
|
+
return {
|
|
12
|
+
ry: -clampedX * magnet.MAX_TILT_Y,
|
|
13
|
+
rx: clampedY * magnet.MAX_TILT_X,
|
|
14
|
+
rz: -clampedX * clampedY * magnet.TWIST_Z,
|
|
15
|
+
ox: clampedX * magnet.MAX_SHIFT,
|
|
16
|
+
oy: -clampedY * magnet.MAX_SHIFT * 0.65,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function resetMagneticTarget(t) {
|
|
20
|
+
t.rx = 0;
|
|
21
|
+
t.ry = 0;
|
|
22
|
+
t.rz = 0;
|
|
23
|
+
t.ox = 0;
|
|
24
|
+
t.oy = 0;
|
|
25
|
+
}
|
|
26
|
+
export function lerpMagneticGroup(group, target, dt, lerpIn, lerpOut, hovered, extraRz) {
|
|
27
|
+
const rate = hovered ? lerpIn : lerpOut;
|
|
28
|
+
const a = 1 - Math.exp(-rate * dt);
|
|
29
|
+
group.rotation.x = THREE.MathUtils.lerp(group.rotation.x, target.rx, a);
|
|
30
|
+
group.rotation.y = THREE.MathUtils.lerp(group.rotation.y, target.ry, a);
|
|
31
|
+
group.rotation.z = THREE.MathUtils.lerp(group.rotation.z, target.rz + extraRz, a);
|
|
32
|
+
group.position.x = THREE.MathUtils.lerp(group.position.x, target.ox, a);
|
|
33
|
+
group.position.y = THREE.MathUtils.lerp(group.position.y, target.oy, a);
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type PlayingCardSuit = "hearts" | "diamonds" | "clubs" | "spades";
|
|
2
|
+
export type PlayingCardRank = "A" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "J" | "Q" | "K";
|
|
3
|
+
export type PlayingCardEnhancement = "bonus" | "mult" | "wild" | "glass" | "steel" | "stone" | "gold" | "lucky";
|
|
4
|
+
export type PlayingCardSeal = "gold" | "red" | "blue" | "purple";
|
|
5
|
+
export type PlayingCardEdition = "foil" | "holographic" | "polychrome" | "negative";
|
|
6
|
+
/** Minimal card model for 3D mesh (game-specific id/chips optional). */
|
|
7
|
+
export interface PlayingCard3DModel {
|
|
8
|
+
suit: PlayingCardSuit;
|
|
9
|
+
rank: PlayingCardRank;
|
|
10
|
+
enhancement?: PlayingCardEnhancement;
|
|
11
|
+
seal?: PlayingCardSeal;
|
|
12
|
+
edition?: PlayingCardEdition;
|
|
13
|
+
}
|
|
14
|
+
export declare const PLAYING_CARD_ATLAS: {
|
|
15
|
+
readonly columns: 13;
|
|
16
|
+
readonly rows: 4;
|
|
17
|
+
readonly cellPx: {
|
|
18
|
+
readonly x: 71;
|
|
19
|
+
readonly y: 95;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export declare const SUIT_ROW: Record<PlayingCardSuit, number>;
|
|
23
|
+
export declare const RANK_COLUMN: Record<PlayingCardRank, number>;
|
|
24
|
+
export declare const SUIT_COLORS: Record<PlayingCardSuit, string>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const PLAYING_CARD_ATLAS = {
|
|
2
|
+
columns: 13,
|
|
3
|
+
rows: 4,
|
|
4
|
+
cellPx: { x: 71, y: 95 },
|
|
5
|
+
};
|
|
6
|
+
export const SUIT_ROW = {
|
|
7
|
+
hearts: 0,
|
|
8
|
+
clubs: 1,
|
|
9
|
+
diamonds: 2,
|
|
10
|
+
spades: 3,
|
|
11
|
+
};
|
|
12
|
+
export const RANK_COLUMN = {
|
|
13
|
+
"2": 0,
|
|
14
|
+
"3": 1,
|
|
15
|
+
"4": 2,
|
|
16
|
+
"5": 3,
|
|
17
|
+
"6": 4,
|
|
18
|
+
"7": 5,
|
|
19
|
+
"8": 6,
|
|
20
|
+
"9": 7,
|
|
21
|
+
"10": 8,
|
|
22
|
+
J: 9,
|
|
23
|
+
Q: 10,
|
|
24
|
+
K: 11,
|
|
25
|
+
A: 12,
|
|
26
|
+
};
|
|
27
|
+
export const SUIT_COLORS = {
|
|
28
|
+
hearts: "#e74c3c",
|
|
29
|
+
diamonds: "#3498db",
|
|
30
|
+
clubs: "#27ae60",
|
|
31
|
+
spades: "#2c3e50",
|
|
32
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PlayingCard3DModel } from "./playingCardTypes.js";
|
|
2
|
+
export declare function editionMaterialProps(edition: PlayingCard3DModel["edition"]): {
|
|
3
|
+
metalness: number;
|
|
4
|
+
roughness: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function enhancementGlowHex(card: PlayingCard3DModel, highlighted: boolean): string;
|
|
7
|
+
export declare function sealColorHex(seal: PlayingCard3DModel["seal"]): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { SUIT_COLORS } from "./playingCardTypes.js";
|
|
2
|
+
export function editionMaterialProps(edition) {
|
|
3
|
+
switch (edition) {
|
|
4
|
+
case "foil":
|
|
5
|
+
return { metalness: 0.9, roughness: 0.1 };
|
|
6
|
+
case "holographic":
|
|
7
|
+
return { metalness: 0.7, roughness: 0.2 };
|
|
8
|
+
case "polychrome":
|
|
9
|
+
return { metalness: 0.8, roughness: 0.15 };
|
|
10
|
+
default:
|
|
11
|
+
return { metalness: 0.1, roughness: 0.8 };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function enhancementGlowHex(card, highlighted) {
|
|
15
|
+
switch (card.enhancement) {
|
|
16
|
+
case "bonus":
|
|
17
|
+
return "#3498db";
|
|
18
|
+
case "mult":
|
|
19
|
+
return "#e74c3c";
|
|
20
|
+
case "wild":
|
|
21
|
+
return "#9b59b6";
|
|
22
|
+
case "glass":
|
|
23
|
+
return "#1abc9c";
|
|
24
|
+
case "steel":
|
|
25
|
+
return "#95a5a6";
|
|
26
|
+
case "gold":
|
|
27
|
+
return "#f1c40f";
|
|
28
|
+
case "lucky":
|
|
29
|
+
return "#2ecc71";
|
|
30
|
+
default:
|
|
31
|
+
return highlighted ? SUIT_COLORS[card.suit] : "#ffffff";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function sealColorHex(seal) {
|
|
35
|
+
switch (seal) {
|
|
36
|
+
case "gold":
|
|
37
|
+
return "#f1c40f";
|
|
38
|
+
case "red":
|
|
39
|
+
return "#e74c3c";
|
|
40
|
+
case "blue":
|
|
41
|
+
return "#3498db";
|
|
42
|
+
default:
|
|
43
|
+
return "#9b59b6";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { type PlayingCardRank, type PlayingCardSuit } from "./playingCardTypes.js";
|
|
3
|
+
export declare function usePlayingCardFaceTexture(suit: PlayingCardSuit, rank: PlayingCardRank, options?: {
|
|
4
|
+
deckUrl?: string;
|
|
5
|
+
enhancersUrl?: string;
|
|
6
|
+
}): THREE.Texture | null;
|
|
7
|
+
export declare function useCardBackTexture(): THREE.Texture;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
import { resolveJamlAssetUrl } from "../assets.js";
|
|
5
|
+
import { applyBalatroGridUV } from "./gridUV.js";
|
|
6
|
+
import { loadCompositedPlayingAtlas } from "./compositedAtlas.js";
|
|
7
|
+
import { PLAYING_CARD_ATLAS, RANK_COLUMN, SUIT_ROW, } from "./playingCardTypes.js";
|
|
8
|
+
const textureCache = new Map();
|
|
9
|
+
function magnetKey(suit, rank, deckUrl, enhancersUrl) {
|
|
10
|
+
return `${deckUrl}|${enhancersUrl}|${suit}|${rank}`;
|
|
11
|
+
}
|
|
12
|
+
export function usePlayingCardFaceTexture(suit, rank, options) {
|
|
13
|
+
const [texture, setTexture] = useState(null);
|
|
14
|
+
const loadSerial = useRef(0);
|
|
15
|
+
const deckUrl = options?.deckUrl ?? resolveJamlAssetUrl("deck");
|
|
16
|
+
const enhancersUrl = options?.enhancersUrl ?? resolveJamlAssetUrl("enhancers");
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const serial = ++loadSerial.current;
|
|
19
|
+
let cancelled = false;
|
|
20
|
+
const key = magnetKey(suit, rank, deckUrl, enhancersUrl);
|
|
21
|
+
const cached = textureCache.get(key);
|
|
22
|
+
if (cached) {
|
|
23
|
+
setTexture(cached);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
loadCompositedPlayingAtlas(deckUrl, PLAYING_CARD_ATLAS.columns, PLAYING_CARD_ATLAS.rows, enhancersUrl)
|
|
27
|
+
.then((canvas) => {
|
|
28
|
+
if (cancelled || serial !== loadSerial.current)
|
|
29
|
+
return;
|
|
30
|
+
const tex = new THREE.CanvasTexture(canvas);
|
|
31
|
+
tex.colorSpace = THREE.SRGBColorSpace;
|
|
32
|
+
tex.magFilter = THREE.NearestFilter;
|
|
33
|
+
tex.minFilter = THREE.NearestFilter;
|
|
34
|
+
const tw = canvas.width;
|
|
35
|
+
const th = canvas.height;
|
|
36
|
+
const cellW = tw / PLAYING_CARD_ATLAS.columns;
|
|
37
|
+
const cellH = th / PLAYING_CARD_ATLAS.rows;
|
|
38
|
+
applyBalatroGridUV(tex, { x: RANK_COLUMN[rank], y: SUIT_ROW[suit] }, {
|
|
39
|
+
cellW,
|
|
40
|
+
cellH,
|
|
41
|
+
textureWidth: tw,
|
|
42
|
+
textureHeight: th,
|
|
43
|
+
});
|
|
44
|
+
textureCache.set(key, tex);
|
|
45
|
+
setTexture(tex);
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
console.error("[jaml-ui/r3f] playing card atlas failed:", err);
|
|
49
|
+
});
|
|
50
|
+
return () => {
|
|
51
|
+
cancelled = true;
|
|
52
|
+
};
|
|
53
|
+
}, [suit, rank, deckUrl, enhancersUrl]);
|
|
54
|
+
return texture;
|
|
55
|
+
}
|
|
56
|
+
export function useCardBackTexture() {
|
|
57
|
+
const [tex] = useState(() => {
|
|
58
|
+
const canvas = document.createElement("canvas");
|
|
59
|
+
canvas.width = PLAYING_CARD_ATLAS.cellPx.x;
|
|
60
|
+
canvas.height = PLAYING_CARD_ATLAS.cellPx.y;
|
|
61
|
+
const ctx = canvas.getContext("2d");
|
|
62
|
+
if (!ctx)
|
|
63
|
+
throw new Error("2d context unavailable");
|
|
64
|
+
ctx.fillStyle = "#1a1a2e";
|
|
65
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
66
|
+
ctx.strokeStyle = "#c9a227";
|
|
67
|
+
ctx.lineWidth = 2;
|
|
68
|
+
ctx.strokeRect(2, 2, canvas.width - 4, canvas.height - 4);
|
|
69
|
+
ctx.fillStyle = "#16213e";
|
|
70
|
+
for (let y = 8; y < canvas.height - 8; y += 10) {
|
|
71
|
+
for (let x = 8; x < canvas.width - 8; x += 10) {
|
|
72
|
+
ctx.fillRect(x, y, 5, 5);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
ctx.fillStyle = "#c9a227";
|
|
76
|
+
const cx = canvas.width / 2;
|
|
77
|
+
const cy = canvas.height / 2;
|
|
78
|
+
ctx.beginPath();
|
|
79
|
+
ctx.moveTo(cx, cy - 17);
|
|
80
|
+
ctx.lineTo(cx + 14, cy);
|
|
81
|
+
ctx.lineTo(cx, cy + 17);
|
|
82
|
+
ctx.lineTo(cx - 14, cy);
|
|
83
|
+
ctx.closePath();
|
|
84
|
+
ctx.fill();
|
|
85
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
86
|
+
texture.magFilter = THREE.NearestFilter;
|
|
87
|
+
texture.minFilter = THREE.NearestFilter;
|
|
88
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
89
|
+
return texture;
|
|
90
|
+
});
|
|
91
|
+
return tex;
|
|
92
|
+
}
|
|
@@ -7,5 +7,5 @@ export interface SpriteData {
|
|
|
7
7
|
pos: SpritePos;
|
|
8
8
|
type: SpriteSheetType;
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/** Look up sprite data by name. Accepts display names ("Icy Joker"), enum keys ("IcyJoker"), and "Joker | Name" prefixed forms. */
|
|
11
11
|
export declare function getSpriteData(name: string): SpriteData | null;
|
|
@@ -1,42 +1,23 @@
|
|
|
1
1
|
import { JOKERS, JOKER_FACES, TAROTS_AND_PLANETS, CONSUMABLE_FACES, VOUCHERS, BOSSES, TAGS, BOOSTER_PACKS, } from "./spriteData.js";
|
|
2
|
+
const normalize = (name) => name.toLowerCase().replace(/\s+/g, "");
|
|
3
|
+
const stripPrefix = (name) => name.replace(/^(Joker|Tarot|Planet|Voucher|Pack|Edition|Tag) [|:] /i, "").trim();
|
|
2
4
|
const ITEM_MAP = new Map();
|
|
3
|
-
function
|
|
5
|
+
function registerAll(items, type) {
|
|
4
6
|
for (const item of items) {
|
|
5
7
|
if (!item.name || !item.pos)
|
|
6
8
|
continue;
|
|
7
|
-
|
|
8
|
-
ITEM_MAP.set(item.name, data);
|
|
9
|
-
ITEM_MAP.set(item.name.toLowerCase(), data);
|
|
10
|
-
ITEM_MAP.set(item.name.replace(/ /g, ""), data);
|
|
11
|
-
ITEM_MAP.set(item.name.replace(/ /g, "").toLowerCase(), data);
|
|
9
|
+
ITEM_MAP.set(normalize(item.name), { pos: item.pos, type });
|
|
12
10
|
}
|
|
13
11
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/**
|
|
12
|
+
registerAll(JOKERS, "Jokers");
|
|
13
|
+
registerAll(JOKER_FACES, "Jokers");
|
|
14
|
+
registerAll(TAROTS_AND_PLANETS, "Tarots");
|
|
15
|
+
registerAll(CONSUMABLE_FACES ?? [], "Tarots");
|
|
16
|
+
registerAll(VOUCHERS, "Vouchers");
|
|
17
|
+
registerAll(BOSSES, "BlindChips");
|
|
18
|
+
registerAll(TAGS, "tags");
|
|
19
|
+
registerAll(BOOSTER_PACKS ?? [], "Boosters");
|
|
20
|
+
/** Look up sprite data by name. Accepts display names ("Icy Joker"), enum keys ("IcyJoker"), and "Joker | Name" prefixed forms. */
|
|
23
21
|
export function getSpriteData(name) {
|
|
24
|
-
|
|
25
|
-
if (ITEM_MAP.has(cleaned))
|
|
26
|
-
return ITEM_MAP.get(cleaned);
|
|
27
|
-
if (ITEM_MAP.has(name))
|
|
28
|
-
return ITEM_MAP.get(name);
|
|
29
|
-
const variants = [
|
|
30
|
-
cleaned.toLowerCase(),
|
|
31
|
-
cleaned.replace(/ /g, ""),
|
|
32
|
-
cleaned.replace(/ /g, "").toLowerCase(),
|
|
33
|
-
name.toLowerCase(),
|
|
34
|
-
name.replace(/ /g, ""),
|
|
35
|
-
name.replace(/ /g, "").toLowerCase(),
|
|
36
|
-
];
|
|
37
|
-
for (const v of variants) {
|
|
38
|
-
if (ITEM_MAP.has(v))
|
|
39
|
-
return ITEM_MAP.get(v);
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
22
|
+
return ITEM_MAP.get(normalize(stripPrefix(name))) ?? ITEM_MAP.get(normalize(name)) ?? null;
|
|
42
23
|
}
|
package/dist/ui/footer.js
CHANGED
|
@@ -9,10 +9,10 @@ const SUITS = [
|
|
|
9
9
|
];
|
|
10
10
|
const CYCLE = '5s';
|
|
11
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} }
|
|
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
17
|
` })] }));
|
|
18
18
|
}
|
|
@@ -23,62 +23,62 @@ export function JimboBackground() {
|
|
|
23
23
|
const gl = canvas.getContext('webgl');
|
|
24
24
|
if (!gl)
|
|
25
25
|
return;
|
|
26
|
-
const vsSource = `
|
|
27
|
-
attribute vec2 position;
|
|
28
|
-
void main() {
|
|
29
|
-
gl_Position = vec4(position, 0.0, 1.0);
|
|
30
|
-
}
|
|
26
|
+
const vsSource = `
|
|
27
|
+
attribute vec2 position;
|
|
28
|
+
void main() {
|
|
29
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
30
|
+
}
|
|
31
31
|
`;
|
|
32
|
-
const fsSource = `
|
|
33
|
-
precision mediump float;
|
|
34
|
-
|
|
35
|
-
uniform float u_time;
|
|
36
|
-
uniform vec2 u_resolution;
|
|
37
|
-
|
|
38
|
-
const float SPIN_ROTATION = -2.0;
|
|
39
|
-
const float SPIN_SPEED = 4.5;
|
|
40
|
-
const vec4 COLOUR_1 = vec4(1.0, 0.2, 0.2, 1.0);
|
|
41
|
-
const vec4 COLOUR_2 = vec4(0.0, 0.5, 1.0, 1.0);
|
|
42
|
-
const vec4 COLOUR_3 = vec4(0.05, 0.08, 0.1, 1.0);
|
|
43
|
-
const float CONTRAST = 4.5;
|
|
44
|
-
const float LIGTHING = 0.5;
|
|
45
|
-
const float SPIN_AMOUNT = 0.35;
|
|
46
|
-
const float PIXEL_FILTER = 1024.0;
|
|
47
|
-
const float PI = 3.14159265359;
|
|
48
|
-
|
|
49
|
-
void main() {
|
|
50
|
-
vec2 screenSize = u_resolution;
|
|
51
|
-
float pixel_size = length(screenSize.xy) / PIXEL_FILTER;
|
|
52
|
-
vec2 uv = (floor(gl_FragCoord.xy*(1.0/pixel_size))*pixel_size - 0.5*screenSize.xy)/length(screenSize.xy);
|
|
53
|
-
float uv_len = length(uv);
|
|
54
|
-
|
|
55
|
-
float speed = (SPIN_ROTATION * 0.2) + 302.2;
|
|
56
|
-
float new_pixel_angle = atan(uv.y, uv.x) + speed - 20.0*(1.0*SPIN_AMOUNT*uv_len + (1.0 - 1.0*SPIN_AMOUNT));
|
|
57
|
-
|
|
58
|
-
vec2 mid = (screenSize.xy/length(screenSize.xy))/2.0;
|
|
59
|
-
uv = (vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid);
|
|
60
|
-
|
|
61
|
-
uv *= 30.0;
|
|
62
|
-
speed = u_time * SPIN_SPEED;
|
|
63
|
-
vec2 uv2 = vec2(uv.x, uv.y);
|
|
64
|
-
|
|
65
|
-
for(int i=0; i < 5; i++) {
|
|
66
|
-
uv2 += sin(max(uv.x, uv.y)) + uv;
|
|
67
|
-
uv += 0.5*vec2(cos(5.1123314 + 0.353*uv2.y + speed*0.131121), sin(uv2.x - 0.113*speed));
|
|
68
|
-
uv -= 1.0*cos(uv.x + uv.y) - 1.0*sin(uv.x*0.711 - uv.y);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
float contrast_mod = (0.25*CONTRAST + 0.5*SPIN_AMOUNT + 1.2);
|
|
72
|
-
float paint_res = min(2.0, max(0.0, length(uv)*(0.035)*contrast_mod));
|
|
73
|
-
float c1p = max(0.0, 1.0 - contrast_mod*abs(1.0 - paint_res));
|
|
74
|
-
float c2p = max(0.0, 1.0 - contrast_mod*abs(paint_res));
|
|
75
|
-
float c3p = 1.0 - min(1.0, c1p + c2p);
|
|
76
|
-
float light = (LIGTHING - 0.2)*max(c1p*5.0 - 4.0, 0.0) + LIGTHING*max(c2p*5.0 - 4.0, 0.0);
|
|
77
|
-
|
|
78
|
-
vec4 finalCol = (0.3/CONTRAST)*COLOUR_1 + (1.0 - 0.3/CONTRAST)*(COLOUR_1*c1p + COLOUR_2*c2p + vec4(c3p*COLOUR_3.rgb, c3p*COLOUR_1.a)) + light;
|
|
79
|
-
|
|
80
|
-
gl_FragColor = finalCol;
|
|
81
|
-
}
|
|
32
|
+
const fsSource = `
|
|
33
|
+
precision mediump float;
|
|
34
|
+
|
|
35
|
+
uniform float u_time;
|
|
36
|
+
uniform vec2 u_resolution;
|
|
37
|
+
|
|
38
|
+
const float SPIN_ROTATION = -2.0;
|
|
39
|
+
const float SPIN_SPEED = 4.5;
|
|
40
|
+
const vec4 COLOUR_1 = vec4(1.0, 0.2, 0.2, 1.0);
|
|
41
|
+
const vec4 COLOUR_2 = vec4(0.0, 0.5, 1.0, 1.0);
|
|
42
|
+
const vec4 COLOUR_3 = vec4(0.05, 0.08, 0.1, 1.0);
|
|
43
|
+
const float CONTRAST = 4.5;
|
|
44
|
+
const float LIGTHING = 0.5;
|
|
45
|
+
const float SPIN_AMOUNT = 0.35;
|
|
46
|
+
const float PIXEL_FILTER = 1024.0;
|
|
47
|
+
const float PI = 3.14159265359;
|
|
48
|
+
|
|
49
|
+
void main() {
|
|
50
|
+
vec2 screenSize = u_resolution;
|
|
51
|
+
float pixel_size = length(screenSize.xy) / PIXEL_FILTER;
|
|
52
|
+
vec2 uv = (floor(gl_FragCoord.xy*(1.0/pixel_size))*pixel_size - 0.5*screenSize.xy)/length(screenSize.xy);
|
|
53
|
+
float uv_len = length(uv);
|
|
54
|
+
|
|
55
|
+
float speed = (SPIN_ROTATION * 0.2) + 302.2;
|
|
56
|
+
float new_pixel_angle = atan(uv.y, uv.x) + speed - 20.0*(1.0*SPIN_AMOUNT*uv_len + (1.0 - 1.0*SPIN_AMOUNT));
|
|
57
|
+
|
|
58
|
+
vec2 mid = (screenSize.xy/length(screenSize.xy))/2.0;
|
|
59
|
+
uv = (vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid);
|
|
60
|
+
|
|
61
|
+
uv *= 30.0;
|
|
62
|
+
speed = u_time * SPIN_SPEED;
|
|
63
|
+
vec2 uv2 = vec2(uv.x, uv.y);
|
|
64
|
+
|
|
65
|
+
for(int i=0; i < 5; i++) {
|
|
66
|
+
uv2 += sin(max(uv.x, uv.y)) + uv;
|
|
67
|
+
uv += 0.5*vec2(cos(5.1123314 + 0.353*uv2.y + speed*0.131121), sin(uv2.x - 0.113*speed));
|
|
68
|
+
uv -= 1.0*cos(uv.x + uv.y) - 1.0*sin(uv.x*0.711 - uv.y);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
float contrast_mod = (0.25*CONTRAST + 0.5*SPIN_AMOUNT + 1.2);
|
|
72
|
+
float paint_res = min(2.0, max(0.0, length(uv)*(0.035)*contrast_mod));
|
|
73
|
+
float c1p = max(0.0, 1.0 - contrast_mod*abs(1.0 - paint_res));
|
|
74
|
+
float c2p = max(0.0, 1.0 - contrast_mod*abs(paint_res));
|
|
75
|
+
float c3p = 1.0 - min(1.0, c1p + c2p);
|
|
76
|
+
float light = (LIGTHING - 0.2)*max(c1p*5.0 - 4.0, 0.0) + LIGTHING*max(c2p*5.0 - 4.0, 0.0);
|
|
77
|
+
|
|
78
|
+
vec4 finalCol = (0.3/CONTRAST)*COLOUR_1 + (1.0 - 0.3/CONTRAST)*(COLOUR_1*c1p + COLOUR_2*c2p + vec4(c3p*COLOUR_3.rgb, c3p*COLOUR_1.a)) + light;
|
|
79
|
+
|
|
80
|
+
gl_FragColor = finalCol;
|
|
81
|
+
}
|
|
82
82
|
`;
|
|
83
83
|
const createShader = (type, source) => {
|
|
84
84
|
const shader = gl.createShader(type);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { JimboColorOption } from './tokens.js';
|
|
5
|
+
import { JimboText } from './jimboText.js';
|
|
6
|
+
export function JimboCopyRow({ value, label }) {
|
|
7
|
+
const [copied, setCopied] = useState(false);
|
|
8
|
+
const C = JimboColorOption;
|
|
9
|
+
function copy() {
|
|
10
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
11
|
+
setCopied(true);
|
|
12
|
+
setTimeout(() => setCopied(false), 1500);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [label && (_jsx(JimboText, { size: "xs", tone: "grey", uppercase: true, style: { letterSpacing: 2 }, children: label })), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("div", { style: {
|
|
16
|
+
flex: 1,
|
|
17
|
+
padding: '6px 10px',
|
|
18
|
+
background: C.DARKEST,
|
|
19
|
+
border: `2px solid ${C.PANEL_EDGE}`,
|
|
20
|
+
borderRadius: 4,
|
|
21
|
+
wordBreak: 'break-all',
|
|
22
|
+
}, children: _jsx(JimboText, { size: "sm", children: value }) }), _jsx("button", { type: "button", onClick: copy, style: {
|
|
23
|
+
fontFamily: "'m6x11plus', 'Courier New', monospace",
|
|
24
|
+
fontSize: 11,
|
|
25
|
+
letterSpacing: 2,
|
|
26
|
+
color: copied ? C.GREEN_TEXT : C.GOLD_TEXT,
|
|
27
|
+
background: copied ? 'rgba(53,189,134,0.12)' : 'rgba(228,182,67,0.12)',
|
|
28
|
+
border: `1px solid ${copied ? C.GREEN_TEXT : C.GOLD_TEXT}`,
|
|
29
|
+
borderRadius: 4,
|
|
30
|
+
padding: '4px 12px',
|
|
31
|
+
cursor: 'pointer',
|
|
32
|
+
flexShrink: 0,
|
|
33
|
+
transition: 'color 0.15s, background 0.15s, border-color 0.15s',
|
|
34
|
+
textTransform: 'uppercase',
|
|
35
|
+
}, children: copied ? 'Copied' : 'Copy' })] })] }));
|
|
36
|
+
}
|
package/dist/ui/jimboTabs.js
CHANGED
|
@@ -11,11 +11,11 @@ import { JimboText } from './jimboText.js';
|
|
|
11
11
|
export function JimboTabs({ tabs, activeTab, onTabChange, className = '', style }) {
|
|
12
12
|
return (_jsxs(_Fragment, { children: [_jsx("div", { className: className, style: { display: 'flex', gap: 8, alignItems: 'flex-end', ...style }, children: tabs.map((tab) => (_jsx(TabButton, { label: tab.label, active: activeTab === tab.id, onClick: () => onTabChange(tab.id) }, tab.id))) }), _jsx("style", { children: JIMBO_BOUNCE_KEYFRAMES })] }));
|
|
13
13
|
}
|
|
14
|
-
const JIMBO_BOUNCE_KEYFRAMES = `
|
|
15
|
-
@keyframes jimbo-bounce {
|
|
16
|
-
0%, 100% { transform: translateY(0); }
|
|
17
|
-
50% { transform: translateY(-3px); }
|
|
18
|
-
}
|
|
14
|
+
const JIMBO_BOUNCE_KEYFRAMES = `
|
|
15
|
+
@keyframes jimbo-bounce {
|
|
16
|
+
0%, 100% { transform: translateY(0); }
|
|
17
|
+
50% { transform: translateY(-3px); }
|
|
18
|
+
}
|
|
19
19
|
`;
|
|
20
20
|
function TabButton({ label, active, onClick }) {
|
|
21
21
|
const [pressed, setPressed] = useState(false);
|
package/dist/ui.d.ts
CHANGED
package/dist/ui.js
CHANGED