@nice2dev/ui-graphics 1.0.2 → 1.0.4

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.
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useState, useEffect } from 'react';
3
- import { NiceTextInput as ut, NiceSlider as Jr, NiceButton as Ee, NiceTextArea as bo, NiceSelect as tn, NiceColorPicker as _n } from '../ui/dist/index.js';
3
+ import { NiceTextInput as _t, NiceSlider as Ar, NiceButton as Oe, NiceTextArea as ms, NiceSelect as nn, NiceColorPicker as Nn } from '../ui/dist/index.js';
4
4
  import { useNiceTranslation } from '../core/i18n.js';
5
5
  import NiceAnimatedPerson from './AnimatedPerson.js';
6
6
  import { DEFAULT_CHARACTER, VARIANTS } from './characterTypes.js';
@@ -20,18 +20,18 @@ function ColorInputs({ value, onChange, }) {
20
20
  const base = clampPalette(value);
21
21
  const count = Math.min(3, Math.max(1, (_a = value === null || value === void 0 ? void 0 : value.length) !== null && _a !== void 0 ? _a : 1));
22
22
  const view = base.slice(0, count);
23
- return (jsxs("div", { className: "ntd-ae-color-inputs", children: [jsx("div", { className: "ntd-ae-colors", children: view.map((col, i) => (jsxs("label", { className: "ntd-ae-color-label", children: ["C", i + 1, jsx(_n, { value: col, onChange: (color) => {
23
+ return (jsxs("div", { className: "ntd-ae-color-inputs", children: [jsx("div", { className: "ntd-ae-colors", children: view.map((col, i) => (jsxs("label", { className: "ntd-ae-color-label", children: ["C", i + 1, jsx(Nn, { value: col, onChange: (color) => {
24
24
  const next = [...view];
25
25
  next[i] = color;
26
26
  onChange(next);
27
- } })] }, i))) }), jsxs("div", { className: "ntd-ae-color-btns", children: [jsx(Ee, { variant: "ghost", size: "sm", disabled: count <= 1, onClick: () => onChange(view.slice(0, Math.max(1, count - 1))), children: "-" }), jsx(Ee, { variant: "ghost", size: "sm", disabled: count >= 3, onClick: () => onChange([...view, view[view.length - 1] || "var(--anim-default-black, #000000)"]), children: "+" })] })] }));
27
+ } })] }, i))) }), jsxs("div", { className: "ntd-ae-color-btns", children: [jsx(Oe, { variant: "ghost", size: "sm", disabled: count <= 1, onClick: () => onChange(view.slice(0, Math.max(1, count - 1))), children: "-" }), jsx(Oe, { variant: "ghost", size: "sm", disabled: count >= 3, onClick: () => onChange([...view, view[view.length - 1] || "var(--anim-default-black, #000000)"]), children: "+" })] })] }));
28
28
  }
29
29
  function Row({ title, children }) {
30
30
  return (jsxs("div", { className: "ntd-ae-row", children: [jsx("div", { className: "ntd-ae-row-title", children: title }), jsx("div", { className: "ntd-ae-row-content", children: children })] }));
31
31
  }
32
32
  function FeatureEditor({ title, feature, variants, onChange, }) {
33
33
  const options = variants.map((v) => ({ value: v, label: v }));
34
- return (jsxs(Row, { title: title, children: [jsx(tn, { className: "ntd-ae-select", value: feature.variant, onChange: (val) => onChange({ ...feature, variant: val }), options: options }), jsx(ColorInputs, { value: feature.colors, onChange: (colors) => onChange({ ...feature, colors }) })] }));
34
+ return (jsxs(Row, { title: title, children: [jsx(nn, { className: "ntd-ae-select", value: feature.variant, onChange: (val) => onChange({ ...feature, variant: val }), options: options }), jsx(ColorInputs, { value: feature.colors, onChange: (colors) => onChange({ ...feature, colors }) })] }));
35
35
  }
36
36
  /* ---------- component ---------- */
37
37
  function NiceAnimationEditor({ initialConfig, onChange, storageKey = STORAGE_KEY, onNotify, showJsonPanel = true, showPreview = true, className, style, }) {
@@ -99,7 +99,7 @@ function NiceAnimationEditor({ initialConfig, onChange, storageKey = STORAGE_KEY
99
99
  const updateFeature = (key, val) => {
100
100
  setCfg((prev) => ({ ...prev, [key]: val }));
101
101
  };
102
- return (jsxs("div", { className: `ntd-animation-editor ${className !== null && className !== void 0 ? className : ""}`, style: style, children: [jsxs("div", { className: "ntd-ae-controls", children: [jsx(Row, { title: t("characterEditor.name", "Name"), children: jsx(ut, { className: "ntd-ae-input", value: cfg.name || "", onChange: (val) => setCfg({ ...cfg, name: val }), placeholder: t("characterEditor.namePlaceholder", "Character name") }) }), jsxs(Row, { title: t("characters.size", "Size"), children: [jsx(Jr, { min: 140, max: 300, value: (_a = cfg.size) !== null && _a !== void 0 ? _a : 180, onChange: (val) => setCfg({ ...cfg, size: val }) }), jsxs("span", { className: "ntd-ae-size-value", children: [(_b = cfg.size) !== null && _b !== void 0 ? _b : 180, "px"] })] }), jsx(FeatureEditor, { title: t("characters.faceShape", "Face"), feature: cfg.face, variants: VARIANTS.face, onChange: (v) => updateFeature("face", v) }), jsx(FeatureEditor, { title: t("characters.hairstyle", "Hair"), feature: cfg.hair, variants: VARIANTS.hair, onChange: (v) => updateFeature("hair", v) }), jsx(FeatureEditor, { title: t("characters.eyes", "Eyes"), feature: cfg.eyes, variants: VARIANTS.eyes, onChange: (v) => updateFeature("eyes", v) }), jsx(FeatureEditor, { title: t("characters.nose", "Nose"), feature: cfg.nose, variants: VARIANTS.nose, onChange: (v) => updateFeature("nose", v) }), jsx(FeatureEditor, { title: t("characters.mouth", "Mouth"), feature: cfg.mouth, variants: VARIANTS.mouth, onChange: (v) => updateFeature("mouth", v) }), jsx(FeatureEditor, { title: t("characters.clothing", "Outfit"), feature: cfg.outfit, variants: VARIANTS.outfit, onChange: (v) => updateFeature("outfit", v) }), jsx(FeatureEditor, { title: t("characters.headwear", "Headwear"), feature: cfg.headwear, variants: VARIANTS.headwear, onChange: (v) => updateFeature("headwear", v) }), jsx(FeatureEditor, { title: t("characters.prop", "Prop"), feature: cfg.prop, variants: VARIANTS.prop, onChange: (v) => updateFeature("prop", v) }), jsxs("div", { className: "ntd-ae-action-row", children: [jsxs(Ee, { className: "ntd-ae-btn", variant: "ghost", onClick: randomizeColors, children: ["\uD83C\uDFB2 ", t("characterEditor.randomizeColors", "Randomize colors")] }), jsxs(Ee, { className: "ntd-ae-btn", variant: "ghost", onClick: downloadJson, children: ["\u2B07 ", t("characters.downloadJson", "Download JSON")] })] }), showJsonPanel && (jsxs("div", { className: "ntd-ae-json-panel", children: [jsx("div", { className: "ntd-ae-json-label", children: t("characterEditor.jsonSection", "JSON") }), jsx(bo, { className: "ntd-ae-textarea", value: json, onChange: (val) => setJson(val) }), jsxs("div", { className: "ntd-ae-json-actions", children: [jsx(Ee, { className: "ntd-ae-btn", variant: "primary", onClick: applyJson, children: t("characterEditor.applyJson", "Apply JSON") }), jsx(Ee, { className: "ntd-ae-btn", variant: "ghost", onClick: () => navigator.clipboard.writeText(JSON.stringify(cfg, null, 2)), children: t("characterEditor.copyToClipboard", "Copy") })] })] }))] }), showPreview && (jsxs("div", { className: "ntd-ae-preview", children: [jsx("div", { className: "ntd-ae-preview-label", children: t("characterEditor.preview", "Preview") }), jsx(NiceAnimatedPerson, { character: cfg, score: 10, startPose: "idle" })] }))] }));
102
+ return (jsxs("div", { className: `ntd-animation-editor ${className !== null && className !== void 0 ? className : ""}`, style: style, children: [jsxs("div", { className: "ntd-ae-controls", children: [jsx(Row, { title: t("characterEditor.name", "Name"), children: jsx(_t, { className: "ntd-ae-input", value: cfg.name || "", onChange: (val) => setCfg({ ...cfg, name: val }), placeholder: t("characterEditor.namePlaceholder", "Character name") }) }), jsxs(Row, { title: t("characters.size", "Size"), children: [jsx(Ar, { min: 140, max: 300, value: (_a = cfg.size) !== null && _a !== void 0 ? _a : 180, onChange: (val) => setCfg({ ...cfg, size: val }) }), jsxs("span", { className: "ntd-ae-size-value", children: [(_b = cfg.size) !== null && _b !== void 0 ? _b : 180, "px"] })] }), jsx(FeatureEditor, { title: t("characters.faceShape", "Face"), feature: cfg.face, variants: VARIANTS.face, onChange: (v) => updateFeature("face", v) }), jsx(FeatureEditor, { title: t("characters.hairstyle", "Hair"), feature: cfg.hair, variants: VARIANTS.hair, onChange: (v) => updateFeature("hair", v) }), jsx(FeatureEditor, { title: t("characters.eyes", "Eyes"), feature: cfg.eyes, variants: VARIANTS.eyes, onChange: (v) => updateFeature("eyes", v) }), jsx(FeatureEditor, { title: t("characters.nose", "Nose"), feature: cfg.nose, variants: VARIANTS.nose, onChange: (v) => updateFeature("nose", v) }), jsx(FeatureEditor, { title: t("characters.mouth", "Mouth"), feature: cfg.mouth, variants: VARIANTS.mouth, onChange: (v) => updateFeature("mouth", v) }), jsx(FeatureEditor, { title: t("characters.clothing", "Outfit"), feature: cfg.outfit, variants: VARIANTS.outfit, onChange: (v) => updateFeature("outfit", v) }), jsx(FeatureEditor, { title: t("characters.headwear", "Headwear"), feature: cfg.headwear, variants: VARIANTS.headwear, onChange: (v) => updateFeature("headwear", v) }), jsx(FeatureEditor, { title: t("characters.prop", "Prop"), feature: cfg.prop, variants: VARIANTS.prop, onChange: (v) => updateFeature("prop", v) }), jsxs("div", { className: "ntd-ae-action-row", children: [jsxs(Oe, { className: "ntd-ae-btn", variant: "ghost", onClick: randomizeColors, children: ["\uD83C\uDFB2 ", t("characterEditor.randomizeColors", "Randomize colors")] }), jsxs(Oe, { className: "ntd-ae-btn", variant: "ghost", onClick: downloadJson, children: ["\u2B07 ", t("characters.downloadJson", "Download JSON")] })] }), showJsonPanel && (jsxs("div", { className: "ntd-ae-json-panel", children: [jsx("div", { className: "ntd-ae-json-label", children: t("characterEditor.jsonSection", "JSON") }), jsx(ms, { className: "ntd-ae-textarea", value: json, onChange: (val) => setJson(val) }), jsxs("div", { className: "ntd-ae-json-actions", children: [jsx(Oe, { className: "ntd-ae-btn", variant: "primary", onClick: applyJson, children: t("characterEditor.applyJson", "Apply JSON") }), jsx(Oe, { className: "ntd-ae-btn", variant: "ghost", onClick: () => navigator.clipboard.writeText(JSON.stringify(cfg, null, 2)), children: t("characterEditor.copyToClipboard", "Copy") })] })] }))] }), showPreview && (jsxs("div", { className: "ntd-ae-preview", children: [jsx("div", { className: "ntd-ae-preview-label", children: t("characterEditor.preview", "Preview") }), jsx(NiceAnimatedPerson, { character: cfg, score: 10, startPose: "idle" })] }))] }));
103
103
  }
104
104
  /** @deprecated Use NiceAnimationEditor */
105
105
  const AnimationEditor = NiceAnimationEditor;
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { NiceButton as Ee } from '../ui/dist/index.js';
3
+ import { NiceButton as Oe } from '../ui/dist/index.js';
4
4
  import NiceAnimatedPerson from './AnimatedPerson.js';
5
5
  import { DEFAULT_CHARACTER } from './characterTypes.js';
6
6
  import { runRounds, seq } from './choreoDSL.js';
@@ -51,7 +51,7 @@ const NiceAudience = ({ count = 36, columns = 12 }) => {
51
51
  display: "grid",
52
52
  gap: 8,
53
53
  gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
54
- }, children: people.map((c, i) => (jsx(NiceAnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxs("div", { style: { display: "flex", gap: 8 }, children: [jsx(Ee, { variant: "ghost", size: "sm", onClick: wave, children: "Wave" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: cheer, children: "Cheer" })] })] }));
54
+ }, children: people.map((c, i) => (jsx(NiceAnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxs("div", { style: { display: "flex", gap: 8 }, children: [jsx(Oe, { variant: "ghost", size: "sm", onClick: wave, children: "Wave" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: cheer, children: "Cheer" })] })] }));
55
55
  };
56
56
 
57
57
  export { NiceAudience as default };
@@ -1,6 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { useCallback } from 'react';
3
- import { NiceButton as Ee, NiceSlider as Jr, NiceTextArea as bo } from '../ui/dist/index.js';
3
+ import { NiceButton as Oe, NiceSlider as Ar, NiceTextArea as ms } from '../ui/dist/index.js';
4
4
 
5
5
  const variantMap = {
6
6
  "dark": "secondary",
@@ -15,7 +15,7 @@ const sizeMap = {
15
15
  "md": "md",
16
16
  };
17
17
  const Button = ({ variant = "dark", size = "md", className = "", children, disabled, onClick, title, style, }) => {
18
- return (jsx(Ee, { variant: variantMap[variant], size: sizeMap[size], className: className, disabled: disabled, onClick: onClick, "aria-label": title, style: style, children: children }));
18
+ return (jsx(Oe, { variant: variantMap[variant], size: sizeMap[size], className: className, disabled: disabled, onClick: onClick, "aria-label": title, style: style, children: children }));
19
19
  };
20
20
  const RangeInput = ({ accentColor, style, className = "", value, min = 0, max = 100, step = 1, onChange, onMouseUp, }) => {
21
21
  const handleChange = useCallback((val) => {
@@ -31,7 +31,7 @@ const RangeInput = ({ accentColor, style, className = "", value, min = 0, max =
31
31
  onMouseUp({});
32
32
  }
33
33
  }, [onChange, onMouseUp]);
34
- return (jsx(Jr, { value: typeof value === "number" ? value : Number(value) || 0, min: Number(min), max: Number(max), step: Number(step), className: className, style: { ...style, "--nice-accent": accentColor }, onChange: handleChange, showValue: false }));
34
+ return (jsx(Ar, { value: typeof value === "number" ? value : Number(value) || 0, min: Number(min), max: Number(max), step: Number(step), className: className, style: { ...style, "--nice-accent": accentColor }, onChange: handleChange, showValue: false }));
35
35
  };
36
36
  const TextArea = ({ className = "", value, onChange, rows, placeholder, disabled, style, }) => {
37
37
  const handleChange = useCallback((newValue) => {
@@ -43,7 +43,7 @@ const TextArea = ({ className = "", value, onChange, rows, placeholder, disabled
43
43
  onChange(syntheticEvent);
44
44
  }
45
45
  }, [onChange]);
46
- return (jsx(bo, { className: className, value: typeof value === "string" ? value : String(value !== null && value !== void 0 ? value : ""), onChange: handleChange, rows: rows, placeholder: placeholder, disabled: disabled, style: style }));
46
+ return (jsx(ms, { className: className, value: typeof value === "string" ? value : String(value !== null && value !== void 0 ? value : ""), onChange: handleChange, rows: rows, placeholder: placeholder, disabled: disabled, style: style }));
47
47
  };
48
48
 
49
49
  export { Button, RangeInput, TextArea };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * UI/UX Design Tools — Wireframes, mockups, prototyping,
3
+ * design tokens, component library, responsive preview,
4
+ * handoff, comments, versioning, Figma/Sketch/XD import, code export.
5
+ *
6
+ * PRO-4.6 — 13 items
7
+ *
8
+ * @module @nice2dev/ui-graphics/design/design-tools
9
+ */
10
+ const DEVICE_PRESETS = [
11
+ { name: 'iPhone 15', width: 393, height: 852, devicePixelRatio: 3, type: 'phone' },
12
+ { name: 'iPhone 15 Pro Max', width: 430, height: 932, devicePixelRatio: 3, type: 'phone' },
13
+ { name: 'Samsung Galaxy S24', width: 360, height: 780, devicePixelRatio: 3, type: 'phone' },
14
+ { name: 'Pixel 8', width: 412, height: 915, devicePixelRatio: 2.625, type: 'phone' },
15
+ { name: 'iPad Air', width: 820, height: 1180, devicePixelRatio: 2, type: 'tablet' },
16
+ { name: 'iPad Pro 12.9"', width: 1024, height: 1366, devicePixelRatio: 2, type: 'tablet' },
17
+ { name: 'MacBook Pro 14"', width: 1512, height: 982, devicePixelRatio: 2, type: 'desktop' },
18
+ { name: 'Desktop 1080p', width: 1920, height: 1080, devicePixelRatio: 1, type: 'desktop' },
19
+ { name: 'Desktop 4K', width: 3840, height: 2160, devicePixelRatio: 2, type: 'desktop' },
20
+ { name: 'Apple Watch Ultra', width: 205, height: 251, devicePixelRatio: 2, type: 'watch' },
21
+ ];
22
+
23
+ export { DEVICE_PRESETS };
24
+ //# sourceMappingURL=design-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design-tools.js","sources":["../../../src/design/design-tools.ts"],"sourcesContent":[null],"names":[],"mappings":"AAAA;;;;;;;;AAQG;AA2MI,MAAM,cAAc,GAAmB;AAC5C,IAAA,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;AAClF,IAAA,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;AAC1F,IAAA,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;AAC3F,IAAA,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE;AACpF,IAAA,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;AACnF,IAAA,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;AAC1F,IAAA,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE;AAC3F,IAAA,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE;AAC1F,IAAA,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE;AACvF,IAAA,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;;;;;"}
@@ -2,14 +2,14 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { useRef, useState, useMemo, useEffect, useCallback } from 'react';
3
3
  import styles from './GameAsset2dEditor.module.css.js';
4
4
  import { useGameAssetEditor } from './useGameAssetEditor.js';
5
- import { NiceButton as Ee, NiceTextInput as ut, NiceSelect as tn, NiceNumberInput as vi } from '../ui/dist/index.js';
5
+ import { NiceButton as Oe, NiceTextInput as _t, NiceSelect as nn, NiceNumberInput as Ri } from '../ui/dist/index.js';
6
6
 
7
7
  /* ────────────────────────────────────────────────────────────────
8
8
  MENU BAR
9
9
  ──────────────────────────────────────────────────────────────── */
10
10
  const MenuBar = ({ api }) => {
11
11
  const fileInputRef = useRef(null);
12
- return (jsxs("div", { className: styles.menuBar, children: [jsx("h3", { children: "\uD83C\uDFAE Game Asset Editor" }), jsxs("div", { className: styles.menuGroup, children: [jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.newProject('New Project'), children: "\uD83D\uDCC4 New" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "\uD83D\uDCC2 Open" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.saveProject, children: "\uD83D\uDCBE Save" })] }), jsx("div", { className: styles.spacer }), jsxs("div", { className: styles.menuGroup, children: [jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.undo, disabled: !api.canUndo, children: "\u21A9 Undo" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.redo, disabled: !api.canRedo, children: "\u21AA Redo" })] }), jsx("input", { ref: fileInputRef, type: "file", accept: ".json", style: { display: 'none' }, onChange: e => {
12
+ return (jsxs("div", { className: styles.menuBar, children: [jsx("h3", { children: "\uD83C\uDFAE Game Asset Editor" }), jsxs("div", { className: styles.menuGroup, children: [jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.newProject('New Project'), children: "\uD83D\uDCC4 New" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "\uD83D\uDCC2 Open" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.saveProject, children: "\uD83D\uDCBE Save" })] }), jsx("div", { className: styles.spacer }), jsxs("div", { className: styles.menuGroup, children: [jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.undo, disabled: !api.canUndo, children: "\u21A9 Undo" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.redo, disabled: !api.canRedo, children: "\u21AA Redo" })] }), jsx("input", { ref: fileInputRef, type: "file", accept: ".json", style: { display: 'none' }, onChange: e => {
13
13
  var _a;
14
14
  const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
15
15
  if (file)
@@ -27,7 +27,7 @@ const ModeTabs = ({ api }) => {
27
27
  { id: 'animation', label: 'Animation', icon: '🎬' },
28
28
  { id: 'effects', label: 'Effects', icon: '✨' },
29
29
  ];
30
- return (jsx("div", { className: styles.modeTabs, children: modes.map(mode => (jsxs(Ee, { className: `${styles.modeTab} ${api.editorState.mode === mode.id ? styles.active : ''}`, variant: api.editorState.mode === mode.id ? 'primary' : 'ghost', onClick: () => api.setMode(mode.id), children: [mode.icon, " ", mode.label] }, mode.id))) }));
30
+ return (jsx("div", { className: styles.modeTabs, children: modes.map(mode => (jsxs(Oe, { className: `${styles.modeTab} ${api.editorState.mode === mode.id ? styles.active : ''}`, variant: api.editorState.mode === mode.id ? 'primary' : 'ghost', onClick: () => api.setMode(mode.id), children: [mode.icon, " ", mode.label] }, mode.id))) }));
31
31
  };
32
32
  /* ────────────────────────────────────────────────────────────────
33
33
  TILE MAP TOOLBAR
@@ -42,7 +42,7 @@ const TileMapToolbar = ({ api }) => {
42
42
  { id: 'line', icon: '╱', label: 'Line' },
43
43
  { id: 'eyedropper', icon: '💧', label: 'Eyedropper' },
44
44
  ];
45
- return (jsxs("div", { className: styles.toolbar, children: [jsx("div", { className: styles.toolGroup, children: tools.map(tool => (jsx(Ee, { className: `${styles.toolBtn} ${api.editorState.tileMapTool === tool.id ? styles.active : ''}`, variant: api.editorState.tileMapTool === tool.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setTileMapTool(tool.id), title: tool.label, children: tool.icon }, tool.id))) }), jsx("div", { className: styles.spacer }), jsx(Ee, { className: `${styles.toolBtn} ${api.editorState.showGrid ? styles.active : ''}`, variant: api.editorState.showGrid ? 'primary' : 'ghost', size: "sm", onClick: api.toggleGrid, title: "Toggle Grid", children: "\u229E" }), jsx(Ee, { className: `${styles.toolBtn} ${api.editorState.showCollision ? styles.active : ''}`, variant: api.editorState.showCollision ? 'primary' : 'ghost', size: "sm", onClick: api.toggleCollision, title: "Show Collision", children: "\u25C7" }), jsx(Ee, { className: `${styles.toolBtn} ${api.editorState.snapToGrid ? styles.active : ''}`, variant: api.editorState.snapToGrid ? 'primary' : 'ghost', size: "sm", onClick: api.toggleSnap, title: "Snap to Grid", children: "\u22A1" })] }));
45
+ return (jsxs("div", { className: styles.toolbar, children: [jsx("div", { className: styles.toolGroup, children: tools.map(tool => (jsx(Oe, { className: `${styles.toolBtn} ${api.editorState.tileMapTool === tool.id ? styles.active : ''}`, variant: api.editorState.tileMapTool === tool.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setTileMapTool(tool.id), title: tool.label, children: tool.icon }, tool.id))) }), jsx("div", { className: styles.spacer }), jsx(Oe, { className: `${styles.toolBtn} ${api.editorState.showGrid ? styles.active : ''}`, variant: api.editorState.showGrid ? 'primary' : 'ghost', size: "sm", onClick: api.toggleGrid, title: "Toggle Grid", children: "\u229E" }), jsx(Oe, { className: `${styles.toolBtn} ${api.editorState.showCollision ? styles.active : ''}`, variant: api.editorState.showCollision ? 'primary' : 'ghost', size: "sm", onClick: api.toggleCollision, title: "Show Collision", children: "\u25C7" }), jsx(Oe, { className: `${styles.toolBtn} ${api.editorState.snapToGrid ? styles.active : ''}`, variant: api.editorState.snapToGrid ? 'primary' : 'ghost', size: "sm", onClick: api.toggleSnap, title: "Snap to Grid", children: "\u22A1" })] }));
46
46
  };
47
47
  /* ────────────────────────────────────────────────────────────────
48
48
  SPRITE SHEET TOOLBAR
@@ -50,14 +50,14 @@ const TileMapToolbar = ({ api }) => {
50
50
  const SpriteSheetToolbar = ({ api }) => {
51
51
  const [sliceWidth, setSliceWidth] = useState(16);
52
52
  const [sliceHeight, setSliceHeight] = useState(16);
53
- return (jsxs("div", { className: styles.toolbar, children: [jsxs("div", { className: styles.toolGroup, children: [jsx(Ee, { className: `${styles.toolBtn} ${api.editorState.spriteSheetTool === 'select' ? styles.active : ''}`, variant: api.editorState.spriteSheetTool === 'select' ? 'primary' : 'ghost', size: "sm", onClick: () => api.setSpriteSheetTool('select'), title: "Select", children: "\u2B1A" }), jsx(Ee, { className: `${styles.toolBtn} ${api.editorState.spriteSheetTool === 'slice' ? styles.active : ''}`, variant: api.editorState.spriteSheetTool === 'slice' ? 'primary' : 'ghost', size: "sm", onClick: () => api.setSpriteSheetTool('slice'), title: "Manual Slice", children: "\u2702" })] }), jsx("div", { className: styles.spacer }), jsxs("label", { style: { fontSize: 11, color: '#8892a8' }, children: ["Frame size:", jsx(vi, { value: sliceWidth, onChange: val => setSliceWidth(val !== null && val !== void 0 ? val : 16), style: { width: 40, margin: '0 4px' }, className: styles.panelInput }), "\u00D7", jsx(vi, { value: sliceHeight, onChange: val => setSliceHeight(val !== null && val !== void 0 ? val : 16), style: { width: 40, marginLeft: 4 }, className: styles.panelInput })] }), jsx(Ee, { className: styles.btnSecondary, variant: "ghost", onClick: () => api.autoSlice(sliceWidth, sliceHeight), style: { marginLeft: 8 }, children: "Auto Slice" }), jsx(Ee, { className: styles.btnSecondary, variant: "ghost", onClick: api.detectSprites, style: { marginLeft: 4 }, children: "Detect Sprites" })] }));
53
+ return (jsxs("div", { className: styles.toolbar, children: [jsxs("div", { className: styles.toolGroup, children: [jsx(Oe, { className: `${styles.toolBtn} ${api.editorState.spriteSheetTool === 'select' ? styles.active : ''}`, variant: api.editorState.spriteSheetTool === 'select' ? 'primary' : 'ghost', size: "sm", onClick: () => api.setSpriteSheetTool('select'), title: "Select", children: "\u2B1A" }), jsx(Oe, { className: `${styles.toolBtn} ${api.editorState.spriteSheetTool === 'slice' ? styles.active : ''}`, variant: api.editorState.spriteSheetTool === 'slice' ? 'primary' : 'ghost', size: "sm", onClick: () => api.setSpriteSheetTool('slice'), title: "Manual Slice", children: "\u2702" })] }), jsx("div", { className: styles.spacer }), jsxs("label", { style: { fontSize: 11, color: '#8892a8' }, children: ["Frame size:", jsx(Ri, { value: sliceWidth, onChange: val => setSliceWidth(val !== null && val !== void 0 ? val : 16), style: { width: 40, margin: '0 4px' }, className: styles.panelInput }), "\u00D7", jsx(Ri, { value: sliceHeight, onChange: val => setSliceHeight(val !== null && val !== void 0 ? val : 16), style: { width: 40, marginLeft: 4 }, className: styles.panelInput })] }), jsx(Oe, { className: styles.btnSecondary, variant: "ghost", onClick: () => api.autoSlice(sliceWidth, sliceHeight), style: { marginLeft: 8 }, children: "Auto Slice" }), jsx(Oe, { className: styles.btnSecondary, variant: "ghost", onClick: api.detectSprites, style: { marginLeft: 4 }, children: "Detect Sprites" })] }));
54
54
  };
55
55
  /* ────────────────────────────────────────────────────────────────
56
56
  TILE PALETTE (LEFT PANEL)
57
57
  ──────────────────────────────────────────────────────────────── */
58
58
  const TilePalette = ({ api }) => {
59
59
  const fileInputRef = useRef(null);
60
- return (jsxs("div", { className: styles.leftPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Tiles" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "+ Add" })] }), jsx("div", { className: styles.panelContent, children: api.project.tileSets.length === 0 ? (jsxs("div", { className: styles.emptyState, children: [jsx("div", { className: styles.emptyStateText, children: "No tiles yet" }), jsx(Ee, { className: styles.btnSecondary, variant: "ghost", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "Import Tiles" })] })) : (jsx("div", { className: styles.tilePalette, children: api.project.tileSets.map(tile => (jsx("div", { className: `${styles.tileItem} ${api.editorState.selectedTileId === tile.id ? styles.selected : ''}`, onClick: () => api.selectTile(tile.id), title: tile.name, children: jsx("img", { src: tile.thumbnail, alt: tile.name }) }, tile.id))) })) }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: e => {
60
+ return (jsxs("div", { className: styles.leftPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Tiles" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "+ Add" })] }), jsx("div", { className: styles.panelContent, children: api.project.tileSets.length === 0 ? (jsxs("div", { className: styles.emptyState, children: [jsx("div", { className: styles.emptyStateText, children: "No tiles yet" }), jsx(Oe, { className: styles.btnSecondary, variant: "ghost", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "Import Tiles" })] })) : (jsx("div", { className: styles.tilePalette, children: api.project.tileSets.map(tile => (jsx("div", { className: `${styles.tileItem} ${api.editorState.selectedTileId === tile.id ? styles.selected : ''}`, onClick: () => api.selectTile(tile.id), title: tile.name, children: jsx("img", { src: tile.thumbnail, alt: tile.name }) }, tile.id))) })) }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: e => {
61
61
  var _a;
62
62
  const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
63
63
  if (file)
@@ -70,10 +70,10 @@ const TilePalette = ({ api }) => {
70
70
  ──────────────────────────────────────────────────────────────── */
71
71
  const LayersPanel = ({ api }) => {
72
72
  const activeMap = api.project.tileMaps.find(m => m.id === api.editorState.activeTileMapId);
73
- return (jsxs("div", { className: styles.rightPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Layers" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.addLayer, disabled: !activeMap, children: "+ Add" })] }), jsx("div", { className: styles.panelContent, children: activeMap ? (jsx("div", { className: styles.layersList, children: [...activeMap.layers].reverse().map(layer => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeLayerId === layer.id ? styles.selected : ''}`, onClick: () => api.selectLayer(layer.id), children: [jsx(Ee, { className: `${styles.layerVisibility} ${!layer.visible ? styles.hidden : ''}`, variant: "ghost", size: "sm", onClick: e => {
73
+ return (jsxs("div", { className: styles.rightPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Layers" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.addLayer, disabled: !activeMap, children: "+ Add" })] }), jsx("div", { className: styles.panelContent, children: activeMap ? (jsx("div", { className: styles.layersList, children: [...activeMap.layers].reverse().map(layer => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeLayerId === layer.id ? styles.selected : ''}`, onClick: () => api.selectLayer(layer.id), children: [jsx(Oe, { className: `${styles.layerVisibility} ${!layer.visible ? styles.hidden : ''}`, variant: "ghost", size: "sm", onClick: e => {
74
74
  e.stopPropagation();
75
75
  api.toggleLayerVisibility(layer.id);
76
- }, children: layer.visible ? '👁' : '○' }), jsx("span", { className: styles.layerName, children: layer.name }), jsxs("span", { className: styles.layerOpacity, children: [Math.round(layer.opacity * 100), "%"] })] }, layer.id))) })) : (jsx("div", { className: styles.emptyState, children: jsx("div", { className: styles.emptyStateText, children: "No tile map selected" }) })) }), jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Maps" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.createTileMap('New Map', 32, 32), children: "+ New" })] }), jsx("div", { className: styles.panelContent, children: api.project.tileMaps.map(map => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeTileMapId === map.id ? styles.selected : ''}`, onClick: () => api.selectTileMap(map.id), children: [jsx("span", { className: styles.layerName, children: map.name }), jsxs("span", { className: styles.layerOpacity, children: [map.width, "\u00D7", map.height] })] }, map.id))) })] }));
76
+ }, children: layer.visible ? '👁' : '○' }), jsx("span", { className: styles.layerName, children: layer.name }), jsxs("span", { className: styles.layerOpacity, children: [Math.round(layer.opacity * 100), "%"] })] }, layer.id))) })) : (jsx("div", { className: styles.emptyState, children: jsx("div", { className: styles.emptyStateText, children: "No tile map selected" }) })) }), jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Maps" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.createTileMap('New Map', 32, 32), children: "+ New" })] }), jsx("div", { className: styles.panelContent, children: api.project.tileMaps.map(map => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeTileMapId === map.id ? styles.selected : ''}`, onClick: () => api.selectTileMap(map.id), children: [jsx("span", { className: styles.layerName, children: map.name }), jsxs("span", { className: styles.layerOpacity, children: [map.width, "\u00D7", map.height] })] }, map.id))) })] }));
77
77
  };
78
78
  /* ────────────────────────────────────────────────────────────────
79
79
  TILE MAP CANVAS
@@ -161,7 +161,7 @@ const TileMapCanvas = ({ api }) => {
161
161
  if (isDragging && (api.editorState.tileMapTool === 'brush' || api.editorState.tileMapTool === 'eraser')) {
162
162
  handleCanvasClick(e);
163
163
  }
164
- } })) : (jsxs("div", { className: styles.emptyState, children: [jsx("div", { className: styles.emptyStateText, children: "Create or select a tile map to start editing" }), jsx(Ee, { className: styles.btnPrimary, variant: "primary", onClick: () => api.createTileMap('New Map', 32, 32), children: "Create Tile Map" })] })) }));
164
+ } })) : (jsxs("div", { className: styles.emptyState, children: [jsx("div", { className: styles.emptyStateText, children: "Create or select a tile map to start editing" }), jsx(Oe, { className: styles.btnPrimary, variant: "primary", onClick: () => api.createTileMap('New Map', 32, 32), children: "Create Tile Map" })] })) }));
165
165
  };
166
166
  /* ────────────────────────────────────────────────────────────────
167
167
  SPRITE SHEET CANVAS
@@ -229,7 +229,7 @@ const SpriteSheetCanvas = ({ api }) => {
229
229
  }, [activeSheet, api]);
230
230
  return (jsx("div", { className: styles.viewport, children: activeSheet ? (jsx("canvas", { ref: canvasRef, className: styles.viewportCanvas, style: {
231
231
  transform: `translate(-50%, -50%) scale(${api.editorState.zoom})`,
232
- }, onClick: handleFrameClick })) : (jsxs("div", { className: styles.emptyState, children: [jsx("div", { className: styles.emptyStateText, children: "Import a sprite sheet to start" }), jsx(Ee, { className: styles.btnPrimary, variant: "primary", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "Import Sprite Sheet" }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: e => {
232
+ }, onClick: handleFrameClick })) : (jsxs("div", { className: styles.emptyState, children: [jsx("div", { className: styles.emptyStateText, children: "Import a sprite sheet to start" }), jsx(Oe, { className: styles.btnPrimary, variant: "primary", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "Import Sprite Sheet" }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: e => {
233
233
  var _a;
234
234
  const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
235
235
  if (file)
@@ -242,7 +242,7 @@ const SpriteSheetCanvas = ({ api }) => {
242
242
  ──────────────────────────────────────────────────────────────── */
243
243
  const SpriteSheetsList = ({ api }) => {
244
244
  const fileInputRef = useRef(null);
245
- return (jsxs("div", { className: styles.leftPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Sprite Sheets" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "+ Import" })] }), jsx("div", { className: styles.panelContent, children: api.project.spriteSheets.length === 0 ? (jsx("div", { className: styles.emptyState, children: jsx("div", { className: styles.emptyStateText, children: "No sprite sheets" }) })) : (jsx("div", { className: styles.layersList, children: api.project.spriteSheets.map(sheet => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeSpriteSheetId === sheet.id ? styles.selected : ''}`, onClick: () => api.selectSpriteSheet(sheet.id), children: [jsx("span", { className: styles.layerName, children: sheet.name }), jsxs("span", { className: styles.layerOpacity, children: [sheet.frames.length, " frames"] })] }, sheet.id))) })) }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: e => {
245
+ return (jsxs("div", { className: styles.leftPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Sprite Sheets" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "+ Import" })] }), jsx("div", { className: styles.panelContent, children: api.project.spriteSheets.length === 0 ? (jsx("div", { className: styles.emptyState, children: jsx("div", { className: styles.emptyStateText, children: "No sprite sheets" }) })) : (jsx("div", { className: styles.layersList, children: api.project.spriteSheets.map(sheet => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeSpriteSheetId === sheet.id ? styles.selected : ''}`, onClick: () => api.selectSpriteSheet(sheet.id), children: [jsx("span", { className: styles.layerName, children: sheet.name }), jsxs("span", { className: styles.layerOpacity, children: [sheet.frames.length, " frames"] })] }, sheet.id))) })) }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: e => {
246
246
  var _a;
247
247
  const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
248
248
  if (file)
@@ -263,7 +263,7 @@ const FramesPanel = ({ api }) => {
263
263
  setAnimName('');
264
264
  }
265
265
  }, [animName, api]);
266
- return (jsxs("div", { className: styles.rightPanel, children: [jsx("div", { className: styles.panelHeader, children: jsxs("span", { children: ["Frames (", (_a = activeSheet === null || activeSheet === void 0 ? void 0 : activeSheet.frames.length) !== null && _a !== void 0 ? _a : 0, ")"] }) }), jsx("div", { className: styles.panelContent, style: { maxHeight: 200 }, children: activeSheet === null || activeSheet === void 0 ? void 0 : activeSheet.frames.map(frame => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.selectedFrameIds.includes(frame.id) ? styles.selected : ''}`, onClick: () => api.selectFrames([frame.id]), children: [jsx("span", { className: styles.layerName, children: frame.name }), jsxs("span", { className: styles.layerOpacity, children: [frame.width, "\u00D7", frame.height] })] }, frame.id))) }), jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Animations" }) }), jsxs("div", { className: styles.panelContent, children: [api.editorState.selectedFrameIds.length > 0 && (jsxs("div", { className: styles.panelSection, children: [jsx(ut, { className: styles.panelInput, placeholder: "Animation name...", value: animName, onChange: val => setAnimName(val) }), jsxs(Ee, { className: styles.btnPrimary, variant: "primary", onClick: createAnimationFromSelection, style: { marginTop: 8, width: '100%' }, disabled: !animName, children: ["Create from ", api.editorState.selectedFrameIds.length, " frames"] })] })), activeSheet === null || activeSheet === void 0 ? void 0 : activeSheet.animations.map(anim => (jsxs("div", { className: styles.layerItem, children: [jsx("span", { className: styles.layerName, children: anim.name }), jsxs("span", { className: styles.layerOpacity, children: [anim.frameIds.length, "f"] }), jsx(Ee, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: () => api.removeAnimation(anim.id), children: "\u00D7" })] }, anim.id)))] })] }));
266
+ return (jsxs("div", { className: styles.rightPanel, children: [jsx("div", { className: styles.panelHeader, children: jsxs("span", { children: ["Frames (", (_a = activeSheet === null || activeSheet === void 0 ? void 0 : activeSheet.frames.length) !== null && _a !== void 0 ? _a : 0, ")"] }) }), jsx("div", { className: styles.panelContent, style: { maxHeight: 200 }, children: activeSheet === null || activeSheet === void 0 ? void 0 : activeSheet.frames.map(frame => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.selectedFrameIds.includes(frame.id) ? styles.selected : ''}`, onClick: () => api.selectFrames([frame.id]), children: [jsx("span", { className: styles.layerName, children: frame.name }), jsxs("span", { className: styles.layerOpacity, children: [frame.width, "\u00D7", frame.height] })] }, frame.id))) }), jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Animations" }) }), jsxs("div", { className: styles.panelContent, children: [api.editorState.selectedFrameIds.length > 0 && (jsxs("div", { className: styles.panelSection, children: [jsx(_t, { className: styles.panelInput, placeholder: "Animation name...", value: animName, onChange: val => setAnimName(val) }), jsxs(Oe, { className: styles.btnPrimary, variant: "primary", onClick: createAnimationFromSelection, style: { marginTop: 8, width: '100%' }, disabled: !animName, children: ["Create from ", api.editorState.selectedFrameIds.length, " frames"] })] })), activeSheet === null || activeSheet === void 0 ? void 0 : activeSheet.animations.map(anim => (jsxs("div", { className: styles.layerItem, children: [jsx("span", { className: styles.layerName, children: anim.name }), jsxs("span", { className: styles.layerOpacity, children: [anim.frameIds.length, "f"] }), jsx(Oe, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: () => api.removeAnimation(anim.id), children: "\u00D7" })] }, anim.id)))] })] }));
267
267
  };
268
268
  /* ────────────────────────────────────────────────────────────────
269
269
  ANIMATION STATE MACHINE
@@ -282,7 +282,7 @@ const AnimationStateMachineView = ({ api }) => {
282
282
  }
283
283
  return result;
284
284
  }, [api.project.spriteSheets]);
285
- return (jsxs("div", { style: { display: 'flex', flex: 1, overflow: 'hidden' }, children: [jsxs("div", { className: styles.leftPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "State Machines" }), jsx("button", { className: styles.menuBtn, onClick: () => api.createStateMachine('New State Machine'), children: "+ New" })] }), jsx("div", { className: styles.panelContent, style: { maxHeight: 150 }, children: api.project.stateMachines.map(sm => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeStateMachineId === sm.id ? styles.selected : ''}`, onClick: () => api.selectStateMachine(sm.id), children: [jsx("span", { className: styles.layerName, children: sm.name }), jsxs("span", { className: styles.layerOpacity, children: [sm.states.length, " states"] })] }, sm.id))) }), activeSM && (jsxs(Fragment, { children: [jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "States" }) }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.panelSection, children: [jsx(ut, { className: styles.panelInput, placeholder: "State name...", value: newStateName, onChange: val => setNewStateName(val) }), allAnimations.length > 0 && newStateName && (jsx(tn, { className: styles.panelInput, style: { marginTop: 4 }, onChange: val => {
285
+ return (jsxs("div", { style: { display: 'flex', flex: 1, overflow: 'hidden' }, children: [jsxs("div", { className: styles.leftPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "State Machines" }), jsx("button", { className: styles.menuBtn, onClick: () => api.createStateMachine('New State Machine'), children: "+ New" })] }), jsx("div", { className: styles.panelContent, style: { maxHeight: 150 }, children: api.project.stateMachines.map(sm => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.activeStateMachineId === sm.id ? styles.selected : ''}`, onClick: () => api.selectStateMachine(sm.id), children: [jsx("span", { className: styles.layerName, children: sm.name }), jsxs("span", { className: styles.layerOpacity, children: [sm.states.length, " states"] })] }, sm.id))) }), activeSM && (jsxs(Fragment, { children: [jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "States" }) }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.panelSection, children: [jsx(_t, { className: styles.panelInput, placeholder: "State name...", value: newStateName, onChange: val => setNewStateName(val) }), allAnimations.length > 0 && newStateName && (jsx(nn, { className: styles.panelInput, style: { marginTop: 4 }, onChange: val => {
286
286
  if (val) {
287
287
  api.addState(newStateName, val);
288
288
  setNewStateName('');
@@ -293,7 +293,7 @@ const AnimationStateMachineView = ({ api }) => {
293
293
  value: anim.id,
294
294
  label: `${anim.sheetName}: ${anim.name}`
295
295
  }))
296
- ] }))] }), activeSM.states.map(state => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.selectedStateId === state.id ? styles.selected : ''} ${activeSM.entryStateId === state.id ? styles.entry : ''}`, onClick: () => api.selectState(state.id), children: [jsxs("span", { className: styles.layerName, children: [activeSM.entryStateId === state.id && '▶ ', state.name] }), jsx(Ee, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: e => {
296
+ ] }))] }), activeSM.states.map(state => (jsxs("div", { className: `${styles.layerItem} ${api.editorState.selectedStateId === state.id ? styles.selected : ''} ${activeSM.entryStateId === state.id ? styles.entry : ''}`, onClick: () => api.selectState(state.id), children: [jsxs("span", { className: styles.layerName, children: [activeSM.entryStateId === state.id && '▶ ', state.name] }), jsx(Oe, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: e => {
297
297
  e.stopPropagation();
298
298
  api.removeState(state.id);
299
299
  }, children: "\u00D7" })] }, state.id)))] })] }))] }), jsx("div", { className: styles.stateGraph, children: activeSM ? (jsxs("svg", { width: "100%", height: "100%", children: [jsx("defs", { children: jsx("marker", { id: "arrowhead", markerWidth: "10", markerHeight: "7", refX: "9", refY: "3.5", orient: "auto", children: jsx("polygon", { points: "0 0, 10 3.5, 0 7", fill: "#4a5a7e" }) }) }), activeSM.transitions.map(trans => {
@@ -305,12 +305,12 @@ const AnimationStateMachineView = ({ api }) => {
305
305
  }), activeSM.states.map(state => {
306
306
  var _a, _b;
307
307
  return (jsx("foreignObject", { x: state.position.x, y: state.position.y, width: 120, height: 50, children: jsxs("div", { className: `${styles.stateNode} ${api.editorState.selectedStateId === state.id ? styles.selected : ''} ${activeSM.entryStateId === state.id ? styles.entry : ''}`, onClick: () => api.selectState(state.id), children: [jsx("div", { className: styles.stateNodeName, children: state.name }), jsx("div", { className: styles.stateNodeAnim, children: (_b = (_a = allAnimations.find(a => a.id === state.animationId)) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : 'No animation' })] }) }, state.id));
308
- })] })) : (jsx("div", { className: styles.emptyState, children: jsx("div", { className: styles.emptyStateText, children: "Create a state machine to design animation flows" }) })) }), jsxs("div", { className: styles.rightPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Parameters" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.addParameter('new_param', 'trigger'), disabled: !activeSM, children: "+ Add" })] }), jsx("div", { className: styles.panelContent, children: activeSM === null || activeSM === void 0 ? void 0 : activeSM.parameters.map(param => (jsxs("div", { className: styles.layerItem, children: [jsx("span", { className: styles.layerOpacity, children: param.type }), jsx("span", { className: styles.layerName, children: param.name }), jsx(Ee, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: () => api.removeParameter(param.id), children: "\u00D7" })] }, param.id))) }), jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Transitions" }) }), jsxs("div", { className: styles.panelContent, children: [activeSM === null || activeSM === void 0 ? void 0 : activeSM.transitions.map(trans => {
308
+ })] })) : (jsx("div", { className: styles.emptyState, children: jsx("div", { className: styles.emptyStateText, children: "Create a state machine to design animation flows" }) })) }), jsxs("div", { className: styles.rightPanel, children: [jsxs("div", { className: styles.panelHeader, children: [jsx("span", { children: "Parameters" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.addParameter('new_param', 'trigger'), disabled: !activeSM, children: "+ Add" })] }), jsx("div", { className: styles.panelContent, children: activeSM === null || activeSM === void 0 ? void 0 : activeSM.parameters.map(param => (jsxs("div", { className: styles.layerItem, children: [jsx("span", { className: styles.layerOpacity, children: param.type }), jsx("span", { className: styles.layerName, children: param.name }), jsx(Oe, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: () => api.removeParameter(param.id), children: "\u00D7" })] }, param.id))) }), jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Transitions" }) }), jsxs("div", { className: styles.panelContent, children: [activeSM === null || activeSM === void 0 ? void 0 : activeSM.transitions.map(trans => {
309
309
  var _a, _b;
310
310
  const from = activeSM.states.find(s => s.id === trans.fromStateId);
311
311
  const to = activeSM.states.find(s => s.id === trans.toStateId);
312
- return (jsxs("div", { className: styles.layerItem, children: [jsxs("span", { className: styles.layerName, children: [(_a = from === null || from === void 0 ? void 0 : from.name) !== null && _a !== void 0 ? _a : '?', " \u2192 ", (_b = to === null || to === void 0 ? void 0 : to.name) !== null && _b !== void 0 ? _b : '?'] }), jsx(Ee, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: () => api.removeTransition(trans.id), children: "\u00D7" })] }, trans.id));
313
- }), api.editorState.selectedStateId && (jsxs("div", { className: styles.panelSection, style: { marginTop: 12 }, children: [jsx("label", { className: styles.panelLabel, children: "Add transition to:" }), jsx(tn, { className: styles.panelInput, onChange: val => {
312
+ return (jsxs("div", { className: styles.layerItem, children: [jsxs("span", { className: styles.layerName, children: [(_a = from === null || from === void 0 ? void 0 : from.name) !== null && _a !== void 0 ? _a : '?', " \u2192 ", (_b = to === null || to === void 0 ? void 0 : to.name) !== null && _b !== void 0 ? _b : '?'] }), jsx(Oe, { className: styles.effectRemove, variant: "ghost", size: "sm", onClick: () => api.removeTransition(trans.id), children: "\u00D7" })] }, trans.id));
313
+ }), api.editorState.selectedStateId && (jsxs("div", { className: styles.panelSection, style: { marginTop: 12 }, children: [jsx("label", { className: styles.panelLabel, children: "Add transition to:" }), jsx(nn, { className: styles.panelInput, onChange: val => {
314
314
  if (val && api.editorState.selectedStateId) {
315
315
  api.addTransition(api.editorState.selectedStateId, val);
316
316
  }
@@ -339,7 +339,7 @@ const EffectsView = ({ api }) => {
339
339
  { type: 'shadow', label: 'Shadow' },
340
340
  { type: 'palette-swap', label: 'Palette Swap' },
341
341
  ];
342
- return (jsxs("div", { style: { display: 'flex', flex: 1, overflow: 'hidden' }, children: [jsxs("div", { className: styles.leftPanel, children: [jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Add Effect" }) }), jsx("div", { className: styles.panelContent, children: effectTypes.map(({ type, label }) => (jsx(Ee, { className: styles.layerItem, variant: "ghost", onClick: () => api.addEffect(type), style: { cursor: 'pointer', border: 'none', textAlign: 'left' }, children: jsx("span", { className: styles.layerName, children: label }) }, type))) }), jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Presets" }) }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.panelSection, children: [jsx(ut, { className: styles.panelInput, placeholder: "Preset name...", value: presetName, onChange: val => setPresetName(val) }), jsx(Ee, { className: styles.btnSecondary, variant: "ghost", style: { marginTop: 4, width: '100%' }, onClick: () => {
342
+ return (jsxs("div", { style: { display: 'flex', flex: 1, overflow: 'hidden' }, children: [jsxs("div", { className: styles.leftPanel, children: [jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Add Effect" }) }), jsx("div", { className: styles.panelContent, children: effectTypes.map(({ type, label }) => (jsx(Oe, { className: styles.layerItem, variant: "ghost", onClick: () => api.addEffect(type), style: { cursor: 'pointer', border: 'none', textAlign: 'left' }, children: jsx("span", { className: styles.layerName, children: label }) }, type))) }), jsx("div", { className: styles.panelHeader, children: jsx("span", { children: "Presets" }) }), jsxs("div", { className: styles.panelContent, children: [jsxs("div", { className: styles.panelSection, children: [jsx(_t, { className: styles.panelInput, placeholder: "Preset name...", value: presetName, onChange: val => setPresetName(val) }), jsx(Oe, { className: styles.btnSecondary, variant: "ghost", style: { marginTop: 4, width: '100%' }, onClick: () => {
343
343
  if (presetName) {
344
344
  api.saveEffectPreset(presetName);
345
345
  setPresetName('');
@@ -352,7 +352,7 @@ const EffectsView = ({ api }) => {
352
352
  const StatusBar = ({ api }) => {
353
353
  const activeMap = api.project.tileMaps.find(m => m.id === api.editorState.activeTileMapId);
354
354
  const activeSheet = api.project.spriteSheets.find(s => s.id === api.editorState.activeSpriteSheetId);
355
- return (jsxs("div", { className: styles.statusBar, children: [jsxs("span", { className: styles.statusItem, children: ["Mode: ", api.editorState.mode] }), api.editorState.mode === 'tilemap' && activeMap && (jsxs(Fragment, { children: [jsxs("span", { className: styles.statusItem, children: ["Map: ", activeMap.name, " (", activeMap.width, "\u00D7", activeMap.height, ")"] }), jsxs("span", { className: styles.statusItem, children: ["Tile: ", activeMap.tileWidth, "\u00D7", activeMap.tileHeight, "px"] })] })), api.editorState.mode === 'spritesheet' && activeSheet && (jsxs(Fragment, { children: [jsxs("span", { className: styles.statusItem, children: ["Sheet: ", activeSheet.name] }), jsxs("span", { className: styles.statusItem, children: ["Selected: ", api.editorState.selectedFrameIds.length, " frames"] })] })), jsxs("div", { className: styles.zoomControl, children: [jsx(Ee, { className: styles.zoomBtn, variant: "ghost", size: "sm", onClick: () => api.setZoom(api.editorState.zoom - 0.25), children: "\u2212" }), jsxs("span", { children: [Math.round(api.editorState.zoom * 100), "%"] }), jsx(Ee, { className: styles.zoomBtn, variant: "ghost", size: "sm", onClick: () => api.setZoom(api.editorState.zoom + 0.25), children: "+" }), jsx(Ee, { className: styles.zoomBtn, variant: "ghost", size: "sm", onClick: () => api.setZoom(1), children: "\u2299" })] })] }));
355
+ return (jsxs("div", { className: styles.statusBar, children: [jsxs("span", { className: styles.statusItem, children: ["Mode: ", api.editorState.mode] }), api.editorState.mode === 'tilemap' && activeMap && (jsxs(Fragment, { children: [jsxs("span", { className: styles.statusItem, children: ["Map: ", activeMap.name, " (", activeMap.width, "\u00D7", activeMap.height, ")"] }), jsxs("span", { className: styles.statusItem, children: ["Tile: ", activeMap.tileWidth, "\u00D7", activeMap.tileHeight, "px"] })] })), api.editorState.mode === 'spritesheet' && activeSheet && (jsxs(Fragment, { children: [jsxs("span", { className: styles.statusItem, children: ["Sheet: ", activeSheet.name] }), jsxs("span", { className: styles.statusItem, children: ["Selected: ", api.editorState.selectedFrameIds.length, " frames"] })] })), jsxs("div", { className: styles.zoomControl, children: [jsx(Oe, { className: styles.zoomBtn, variant: "ghost", size: "sm", onClick: () => api.setZoom(api.editorState.zoom - 0.25), children: "\u2212" }), jsxs("span", { children: [Math.round(api.editorState.zoom * 100), "%"] }), jsx(Oe, { className: styles.zoomBtn, variant: "ghost", size: "sm", onClick: () => api.setZoom(api.editorState.zoom + 0.25), children: "+" }), jsx(Oe, { className: styles.zoomBtn, variant: "ghost", size: "sm", onClick: () => api.setZoom(1), children: "\u2299" })] })] }));
356
356
  };
357
357
  /* ────────────────────────────────────────────────────────────────
358
358
  MAIN COMPONENT
package/dist/esm/index.js CHANGED
@@ -66,4 +66,6 @@ export { COMMON_KERNING_PAIRS, OPENTYPE_FEATURE_PRESETS, autoKern, buildTTF, dow
66
66
  export { applyObjectChange, addObject as crdtAddObject, batchUpdate as crdtBatchUpdate, removeObject as crdtRemoveObject, updateCursor as crdtUpdateCursor, updateSelection as crdtUpdateSelection, getRemotePresences, useYjsCollaboration } from './core/yjsCollaboration.js';
67
67
  export { BRUSH_PRESETS, NiceBrushEngine, useBrushEngine } from './core/NiceBrushEngine.js';
68
68
  export { NiceSvgToCode, convertSvgToCode, useSvgToCode } from './vector/NiceSvgToCode.js';
69
+ export { RETRO_PRESETS } from './pixel/pixel-pro.js';
70
+ export { DEVICE_PRESETS } from './design/design-tools.js';
69
71
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useState, useCallback } from 'react';
3
3
  import styles from './PixelEditor.module.css.js';
4
- import { NiceModal as ko, NiceNumberInput as vi, NiceButton as Ee } from '../ui/dist/index.js';
4
+ import { NiceModal as _s, NiceNumberInput as Ri, NiceButton as Oe } from '../ui/dist/index.js';
5
5
  import { usePixelEditor } from './usePixelEditor.js';
6
6
  import PixelEditorMenuBar from './PixelEditorMenuBar.js';
7
7
  import PixelEditorToolbar from './PixelEditorToolbar.js';
@@ -24,10 +24,10 @@ const NicePixelEditor = ({ initialWidth = 64, initialHeight = 64, className, onS
24
24
  if (f)
25
25
  api.openFile(f);
26
26
  e.target.value = "";
27
- } }), jsx(PixelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary, onOpenGenerator: () => setShowGenerator(true) }), jsxs("div", { className: styles.mainArea, children: [jsx(PixelEditorToolbar, { api: api }), jsx(PixelEditorCanvas, { api: api }), jsx(PixelEditorRightPanel, { api: api })] }), jsx(PixelEditorTimeline, { api: api }), jsx(PixelEditorStatusBar, { api: api }), showGenerator && (jsx(NiceSpriteGeneratorPanel, { onSendToCanvas: handleGeneratorSend, onClose: () => setShowGenerator(false) })), jsxs(ko, { open: api.dlgNew, onClose: () => api.setDlgNew(false), title: "New Document", children: [jsx("label", { children: "Width (px)" }), jsx(vi, { min: 1, max: 4096, value: api.nw, onChange: (val) => api.setNw(val !== null && val !== void 0 ? val : 64), "aria-label": "Document width" }), jsx("label", { children: "Height (px)" }), jsx(vi, { min: 1, max: 4096, value: api.nh, onChange: (val) => api.setNh(val !== null && val !== void 0 ? val : 64), "aria-label": "Document height" }), jsx("div", { style: { display: "flex", gap: 4, flexWrap: "wrap", marginTop: 8 }, children: [
27
+ } }), jsx(PixelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary, onOpenGenerator: () => setShowGenerator(true) }), jsxs("div", { className: styles.mainArea, children: [jsx(PixelEditorToolbar, { api: api }), jsx(PixelEditorCanvas, { api: api }), jsx(PixelEditorRightPanel, { api: api })] }), jsx(PixelEditorTimeline, { api: api }), jsx(PixelEditorStatusBar, { api: api }), showGenerator && (jsx(NiceSpriteGeneratorPanel, { onSendToCanvas: handleGeneratorSend, onClose: () => setShowGenerator(false) })), jsxs(_s, { open: api.dlgNew, onClose: () => api.setDlgNew(false), title: "New Document", children: [jsx("label", { children: "Width (px)" }), jsx(Ri, { min: 1, max: 4096, value: api.nw, onChange: (val) => api.setNw(val !== null && val !== void 0 ? val : 64), "aria-label": "Document width" }), jsx("label", { children: "Height (px)" }), jsx(Ri, { min: 1, max: 4096, value: api.nh, onChange: (val) => api.setNh(val !== null && val !== void 0 ? val : 64), "aria-label": "Document height" }), jsx("div", { style: { display: "flex", gap: 4, flexWrap: "wrap", marginTop: 8 }, children: [
28
28
  [16, 16], [32, 32], [48, 48], [64, 64], [128, 128],
29
29
  [256, 256], [16, 32], [32, 64], [48, 16], [64, 32],
30
- ].map(([w, h]) => (jsxs(Ee, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { fontSize: 10, padding: "2px 6px" }, onClick: () => { api.setNw(w); api.setNh(h); }, children: [w, "x", h] }, `${w}x${h}`))) }), jsxs("div", { className: styles.dialogActions, children: [jsx(Ee, { className: styles.btnSecondary, variant: "ghost", onClick: () => api.setDlgNew(false), children: "Cancel" }), jsx(Ee, { className: styles.btnPrimary, variant: "primary", onClick: () => api.newDoc(api.nw, api.nh), children: "Create" })] })] })] }));
30
+ ].map(([w, h]) => (jsxs(Oe, { className: styles.btnSecondary, variant: "ghost", size: "sm", style: { fontSize: 10, padding: "2px 6px" }, onClick: () => { api.setNw(w); api.setNh(h); }, children: [w, "x", h] }, `${w}x${h}`))) }), jsxs("div", { className: styles.dialogActions, children: [jsx(Oe, { className: styles.btnSecondary, variant: "ghost", onClick: () => api.setDlgNew(false), children: "Cancel" }), jsx(Oe, { className: styles.btnPrimary, variant: "primary", onClick: () => api.newDoc(api.nw, api.nh), children: "Create" })] })] })] }));
31
31
  };
32
32
  /** @deprecated Use NicePixelEditor */
33
33
  const PixelEditor = NicePixelEditor;
@@ -1,9 +1,9 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import React from 'react';
3
3
  import styles from './PixelEditor.module.css.js';
4
- import { NiceButton as Ee } from '../ui/dist/index.js';
4
+ import { NiceButton as Oe } from '../ui/dist/index.js';
5
5
 
6
- const PixelEditorMenuBar = React.memo(({ api, hasOnSaveToLibrary, onOpenGenerator }) => (jsxs("div", { className: styles.menuBar, children: [jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.setDlgNew(true), children: "New" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = api.fInp.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "Open" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportAse, children: "Save .ase" }), jsx("div", { className: styles.menuSep }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportPNG, children: "PNG" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportJPG, children: "JPG" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportWebP, children: "WebP" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportBMP, children: "BMP" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportSVG, children: "SVG" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportSheet, children: "Sheet" }), hasOnSaveToLibrary && (jsxs(Fragment, { children: [jsx("div", { className: styles.menuSep }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.saveToLibrary, children: "\uD83D\uDCE6 Save to Library" })] })), jsx("div", { className: styles.menuSep }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.undo, children: "Undo" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.redo, children: "Redo" }), jsx("div", { className: styles.menuSep }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.flipCv("h"), children: "Flip H" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.flipCv("v"), children: "Flip V" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.rotateCv, children: "Rot 90\u00B0" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.clearCv, children: "Clear" }), jsx("div", { className: styles.menuSep }), jsxs(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.setGrid((g) => !g), children: ["Grid: ", api.grid ? "ON" : "OFF"] }), jsx("div", { className: styles.menuSep }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doCopy, disabled: !api.sel, children: "Copy" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doCut, disabled: !api.sel, children: "Cut" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doPaste, disabled: !api.clipboard.current, children: "Paste" }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doSelectAll, children: "Sel All" }), jsx("div", { className: styles.menuSep }), jsx(Ee, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: onOpenGenerator, children: "\uD83C\uDFA8 Generator" })] })));
6
+ const PixelEditorMenuBar = React.memo(({ api, hasOnSaveToLibrary, onOpenGenerator }) => (jsxs("div", { className: styles.menuBar, children: [jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.setDlgNew(true), children: "New" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = api.fInp.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: "Open" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportAse, children: "Save .ase" }), jsx("div", { className: styles.menuSep }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportPNG, children: "PNG" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportJPG, children: "JPG" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportWebP, children: "WebP" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportBMP, children: "BMP" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportSVG, children: "SVG" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.exportSheet, children: "Sheet" }), hasOnSaveToLibrary && (jsxs(Fragment, { children: [jsx("div", { className: styles.menuSep }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.saveToLibrary, children: "\uD83D\uDCE6 Save to Library" })] })), jsx("div", { className: styles.menuSep }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.undo, children: "Undo" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.redo, children: "Redo" }), jsx("div", { className: styles.menuSep }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.flipCv("h"), children: "Flip H" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.flipCv("v"), children: "Flip V" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.rotateCv, children: "Rot 90\u00B0" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.clearCv, children: "Clear" }), jsx("div", { className: styles.menuSep }), jsxs(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => api.setGrid((g) => !g), children: ["Grid: ", api.grid ? "ON" : "OFF"] }), jsx("div", { className: styles.menuSep }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doCopy, disabled: !api.sel, children: "Copy" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doCut, disabled: !api.sel, children: "Cut" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doPaste, disabled: !api.clipboard.current, children: "Paste" }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: api.doSelectAll, children: "Sel All" }), jsx("div", { className: styles.menuSep }), jsx(Oe, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: onOpenGenerator, children: "\uD83C\uDFA8 Generator" })] })));
7
7
  PixelEditorMenuBar.displayName = "PixelEditorMenuBar";
8
8
 
9
9
  export { PixelEditorMenuBar as default };
@@ -1,12 +1,12 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import React from 'react';
3
3
  import styles from './PixelEditor.module.css.js';
4
- import { NiceSlider as Jr, NiceButton as Ee, NiceCheckbox as qi, NiceTextInput as ut, NiceSelect as tn } from '../ui/dist/index.js';
4
+ import { NiceSlider as Ar, NiceButton as Oe, NiceCheckbox as ma, NiceTextInput as _t, NiceSelect as nn } from '../ui/dist/index.js';
5
5
  import { BRUSH_SHAPES, BLEND_MODES } from './pixelEditorTypes.js';
6
6
  import { HSVPicker } from './HSVPicker.js';
7
7
 
8
- const PixelEditorRightPanel = React.memo(({ api }) => (jsxs("div", { className: styles.rightPanel, children: [jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsx("span", { children: "Brush" }) }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Size" }), jsx(Jr, { min: 1, max: 64, value: api.brush.size, onChange: (val) => api.setBrush((b) => ({ ...b, size: val })), "aria-label": "Brush size" }), jsx("span", { children: api.brush.size })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Opacity" }), jsx(Jr, { min: 1, max: 100, value: api.brush.opacity, onChange: (val) => api.setBrush((b) => ({ ...b, opacity: val })), "aria-label": "Brush opacity" }), jsxs("span", { children: [api.brush.opacity, "%"] })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Flow" }), jsx(Jr, { min: 1, max: 100, value: api.brush.flow, onChange: (val) => api.setBrush((b) => ({ ...b, flow: val })), "aria-label": "Brush flow" }), jsxs("span", { children: [api.brush.flow, "%"] })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Hard" }), jsx(Jr, { min: 0, max: 100, value: api.brush.hardness, onChange: (val) => api.setBrush((b) => ({ ...b, hardness: val })), "aria-label": "Brush hardness" }), jsxs("span", { children: [api.brush.hardness, "%"] })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Space" }), jsx(Jr, { min: 1, max: 100, value: api.brush.spacing, onChange: (val) => api.setBrush((b) => ({ ...b, spacing: val })), "aria-label": "Brush spacing" }), jsxs("span", { children: [api.brush.spacing, "%"] })] }), jsx("div", { className: styles.brushShapes, children: BRUSH_SHAPES.map((s) => (jsx(Ee, { className: `${styles.miniBtn} ${api.brush.shape === s.id ? styles.miniBtnActive : ""}`, variant: api.brush.shape === s.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setBrush((b) => ({ ...b, shape: s.id })), title: s.label, children: s.icon }, s.id))) }), jsxs("div", { className: styles.pressureToggles, children: [jsx(qi, { checked: api.brush.pressureSize, onChange: (checked) => api.setBrush((b) => ({ ...b, pressureSize: checked })), label: "Pressure\u2192Size", className: styles.checkLabel }), jsx(qi, { checked: api.brush.pressureOpacity, onChange: (checked) => api.setBrush((b) => ({ ...b, pressureOpacity: checked })), label: "Pressure\u2192Opacity", className: styles.checkLabel })] })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx("span", { children: "Colour" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: () => api.setShowHSV(!api.showHSV), title: "Toggle HSV Picker", children: api.showHSV ? "▾" : "▸" })] }), api.showHSV && jsx(HSVPicker, { color: api.col1, onChange: api.setCol1 })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx("span", { children: "Layers" }), jsxs("span", { children: [jsx(Ee, { variant: "ghost", size: "sm", onClick: api.addLayer, title: "Add Layer", "aria-label": "Add Layer", children: "+" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: api.dupLayer, title: "Duplicate Layer", "aria-label": "Duplicate Layer", children: "\u29C9" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: api.rmLayer, title: "Remove Layer", "aria-label": "Remove Layer", children: "\u2212" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: api.mergeDown, title: "Merge Down", "aria-label": "Merge Down", children: "\u2913" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: api.moveLayerUp, title: "Move Up", "aria-label": "Move Up", children: "\u2191" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: api.moveLayerDown, title: "Move Down", "aria-label": "Move Down", children: "\u2193" })] })] }), [...api.layers].map((l, i) => ({ l, i })).reverse().map(({ l, i }) => (jsxs("div", { className: `${styles.layerItem} ${i === api.aLi ? styles.layerItemActive : ""}`, onClick: () => api.setALi(i), children: [jsx("span", { className: styles.layerVis, onClick: (e) => { e.stopPropagation(); api.toggleVis(i); }, children: l.visible ? "👁" : "─" }), jsx("span", { className: styles.layerLock, onClick: (e) => { e.stopPropagation(); api.toggleLock(i); }, title: l.locked ? "Locked" : "Unlocked", children: l.locked ? "🔒" : "🔓" }), api.editingLayerName === l.id ? (jsx(ut, { className: styles.layerNameInput, value: l.name, onChange: (val) => api.renameLayer(i, val), onBlur: () => api.setEditingLayerName(null), onKeyDown: (e) => { if (e.key === "Enter")
9
- api.setEditingLayerName(null); }, autoFocus: true, "aria-label": "Layer name" })) : (jsx("span", { className: styles.layerName, onDoubleClick: (e) => { e.stopPropagation(); api.setEditingLayerName(l.id); }, children: l.name })), jsxs("span", { className: styles.layerOpacity, children: [Math.round((l.opacity / 255) * 100), "%"] })] }, l.id))), api.layers[api.aLi] && (jsxs("div", { className: styles.layerControls, children: [jsx(tn, { value: api.layers[api.aLi].blendMode, onChange: (val) => api.setLayerBlendMode(api.aLi, val), className: styles.blendSelect, "aria-label": "Blend mode", options: BLEND_MODES.map((m) => ({ value: m, label: m })) }), jsx(Jr, { min: 0, max: 255, value: api.layers[api.aLi].opacity, onChange: (val) => api.setLayerOpacity(api.aLi, val), className: styles.layerOpSlider, title: `Opacity: ${Math.round((api.layers[api.aLi].opacity / 255) * 100)}%` })] }))] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx("span", { children: "Palette" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: () => { if (!api.pal.includes(api.col1))
8
+ const PixelEditorRightPanel = React.memo(({ api }) => (jsxs("div", { className: styles.rightPanel, children: [jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsx("span", { children: "Brush" }) }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Size" }), jsx(Ar, { min: 1, max: 64, value: api.brush.size, onChange: (val) => api.setBrush((b) => ({ ...b, size: val })), "aria-label": "Brush size" }), jsx("span", { children: api.brush.size })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Opacity" }), jsx(Ar, { min: 1, max: 100, value: api.brush.opacity, onChange: (val) => api.setBrush((b) => ({ ...b, opacity: val })), "aria-label": "Brush opacity" }), jsxs("span", { children: [api.brush.opacity, "%"] })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Flow" }), jsx(Ar, { min: 1, max: 100, value: api.brush.flow, onChange: (val) => api.setBrush((b) => ({ ...b, flow: val })), "aria-label": "Brush flow" }), jsxs("span", { children: [api.brush.flow, "%"] })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Hard" }), jsx(Ar, { min: 0, max: 100, value: api.brush.hardness, onChange: (val) => api.setBrush((b) => ({ ...b, hardness: val })), "aria-label": "Brush hardness" }), jsxs("span", { children: [api.brush.hardness, "%"] })] }), jsxs("div", { className: styles.brushSlider, children: [jsx("label", { children: "Space" }), jsx(Ar, { min: 1, max: 100, value: api.brush.spacing, onChange: (val) => api.setBrush((b) => ({ ...b, spacing: val })), "aria-label": "Brush spacing" }), jsxs("span", { children: [api.brush.spacing, "%"] })] }), jsx("div", { className: styles.brushShapes, children: BRUSH_SHAPES.map((s) => (jsx(Oe, { className: `${styles.miniBtn} ${api.brush.shape === s.id ? styles.miniBtnActive : ""}`, variant: api.brush.shape === s.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setBrush((b) => ({ ...b, shape: s.id })), title: s.label, children: s.icon }, s.id))) }), jsxs("div", { className: styles.pressureToggles, children: [jsx(ma, { checked: api.brush.pressureSize, onChange: (checked) => api.setBrush((b) => ({ ...b, pressureSize: checked })), label: "Pressure\u2192Size", className: styles.checkLabel }), jsx(ma, { checked: api.brush.pressureOpacity, onChange: (checked) => api.setBrush((b) => ({ ...b, pressureOpacity: checked })), label: "Pressure\u2192Opacity", className: styles.checkLabel })] })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx("span", { children: "Colour" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: () => api.setShowHSV(!api.showHSV), title: "Toggle HSV Picker", children: api.showHSV ? "▾" : "▸" })] }), api.showHSV && jsx(HSVPicker, { color: api.col1, onChange: api.setCol1 })] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx("span", { children: "Layers" }), jsxs("span", { children: [jsx(Oe, { variant: "ghost", size: "sm", onClick: api.addLayer, title: "Add Layer", "aria-label": "Add Layer", children: "+" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: api.dupLayer, title: "Duplicate Layer", "aria-label": "Duplicate Layer", children: "\u29C9" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: api.rmLayer, title: "Remove Layer", "aria-label": "Remove Layer", children: "\u2212" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: api.mergeDown, title: "Merge Down", "aria-label": "Merge Down", children: "\u2913" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: api.moveLayerUp, title: "Move Up", "aria-label": "Move Up", children: "\u2191" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: api.moveLayerDown, title: "Move Down", "aria-label": "Move Down", children: "\u2193" })] })] }), [...api.layers].map((l, i) => ({ l, i })).reverse().map(({ l, i }) => (jsxs("div", { className: `${styles.layerItem} ${i === api.aLi ? styles.layerItemActive : ""}`, onClick: () => api.setALi(i), children: [jsx("span", { className: styles.layerVis, onClick: (e) => { e.stopPropagation(); api.toggleVis(i); }, children: l.visible ? "👁" : "─" }), jsx("span", { className: styles.layerLock, onClick: (e) => { e.stopPropagation(); api.toggleLock(i); }, title: l.locked ? "Locked" : "Unlocked", children: l.locked ? "🔒" : "🔓" }), api.editingLayerName === l.id ? (jsx(_t, { className: styles.layerNameInput, value: l.name, onChange: (val) => api.renameLayer(i, val), onBlur: () => api.setEditingLayerName(null), onKeyDown: (e) => { if (e.key === "Enter")
9
+ api.setEditingLayerName(null); }, autoFocus: true, "aria-label": "Layer name" })) : (jsx("span", { className: styles.layerName, onDoubleClick: (e) => { e.stopPropagation(); api.setEditingLayerName(l.id); }, children: l.name })), jsxs("span", { className: styles.layerOpacity, children: [Math.round((l.opacity / 255) * 100), "%"] })] }, l.id))), api.layers[api.aLi] && (jsxs("div", { className: styles.layerControls, children: [jsx(nn, { value: api.layers[api.aLi].blendMode, onChange: (val) => api.setLayerBlendMode(api.aLi, val), className: styles.blendSelect, "aria-label": "Blend mode", options: BLEND_MODES.map((m) => ({ value: m, label: m })) }), jsx(Ar, { min: 0, max: 255, value: api.layers[api.aLi].opacity, onChange: (val) => api.setLayerOpacity(api.aLi, val), className: styles.layerOpSlider, title: `Opacity: ${Math.round((api.layers[api.aLi].opacity / 255) * 100)}%` })] }))] }), jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx("span", { children: "Palette" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: () => { if (!api.pal.includes(api.col1))
10
10
  api.setPal((p) => [...p, api.col1]); }, title: "Add current colour", children: "+" })] }), jsx("div", { className: styles.paletteGrid, children: api.pal.map((c, i) => (jsx("div", { className: `${styles.paletteSwatch} ${c === api.col1 ? styles.paletteSwatchActive : ""}`, style: { backgroundColor: c }, onClick: () => api.setCol1(c), onContextMenu: (e) => { e.preventDefault(); api.setCol2(c); }, title: c }, i))) })] })] })));
11
11
  PixelEditorRightPanel.displayName = "PixelEditorRightPanel";
12
12
 
@@ -1,9 +1,9 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import React from 'react';
3
3
  import styles from './PixelEditor.module.css.js';
4
- import { NiceButton as Ee, NiceNumberInput as vi, NiceCheckbox as qi } from '../ui/dist/index.js';
4
+ import { NiceButton as Oe, NiceNumberInput as Ri, NiceCheckbox as ma } from '../ui/dist/index.js';
5
5
 
6
- const PixelEditorTimeline = React.memo(({ api }) => (jsxs("div", { className: styles.bottomPanel, children: [jsxs("div", { className: styles.timelineControls, children: [jsx(Ee, { variant: "ghost", size: "sm", onClick: api.addFrame, children: "+ Frame" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: api.dupFrame, "aria-label": "Duplicate frame", children: "Dup" }), jsx(Ee, { variant: "ghost", size: "sm", onClick: api.rmFrame, "aria-label": "Remove frame", children: "\u2212 Frame" }), jsx("div", { className: styles.menuSep }), jsx(Ee, { variant: "ghost", size: "sm", onClick: () => api.setPlaying((p) => !p), children: api.playing ? "⏸" : "▶" }), jsx("label", { children: "FPS:" }), jsx(vi, { min: 1, max: 60, value: api.fps, onChange: (val) => api.setFps(val !== null && val !== void 0 ? val : 8), "aria-label": "Frames per second" }), jsx("div", { className: styles.menuSep }), jsx(qi, { checked: api.onion, onChange: (checked) => api.setOnion(checked), label: "Onion" }), jsx("div", { style: { flex: 1 } }), jsxs("span", { children: [api.fc, " frame", api.fc !== 1 ? "s" : ""] })] }), jsx("div", { className: styles.timelineFrames, children: Array.from({ length: api.fc }, (_, fi) => (jsxs("div", { className: `${styles.frameThumb} ${fi === api.afi ? styles.frameThumbActive : ""}`, onClick: () => api.setAfi(fi), children: [api.thumbs[fi] && (jsx("img", { src: api.thumbs[fi], alt: `Frame ${fi + 1}`, style: { width: 36, height: 36, imageRendering: "pixelated", objectFit: "contain" } })), jsx("span", { className: styles.frameNum, children: fi + 1 })] }, fi))) })] })));
6
+ const PixelEditorTimeline = React.memo(({ api }) => (jsxs("div", { className: styles.bottomPanel, children: [jsxs("div", { className: styles.timelineControls, children: [jsx(Oe, { variant: "ghost", size: "sm", onClick: api.addFrame, children: "+ Frame" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: api.dupFrame, "aria-label": "Duplicate frame", children: "Dup" }), jsx(Oe, { variant: "ghost", size: "sm", onClick: api.rmFrame, "aria-label": "Remove frame", children: "\u2212 Frame" }), jsx("div", { className: styles.menuSep }), jsx(Oe, { variant: "ghost", size: "sm", onClick: () => api.setPlaying((p) => !p), children: api.playing ? "⏸" : "▶" }), jsx("label", { children: "FPS:" }), jsx(Ri, { min: 1, max: 60, value: api.fps, onChange: (val) => api.setFps(val !== null && val !== void 0 ? val : 8), "aria-label": "Frames per second" }), jsx("div", { className: styles.menuSep }), jsx(ma, { checked: api.onion, onChange: (checked) => api.setOnion(checked), label: "Onion" }), jsx("div", { style: { flex: 1 } }), jsxs("span", { children: [api.fc, " frame", api.fc !== 1 ? "s" : ""] })] }), jsx("div", { className: styles.timelineFrames, children: Array.from({ length: api.fc }, (_, fi) => (jsxs("div", { className: `${styles.frameThumb} ${fi === api.afi ? styles.frameThumbActive : ""}`, onClick: () => api.setAfi(fi), children: [api.thumbs[fi] && (jsx("img", { src: api.thumbs[fi], alt: `Frame ${fi + 1}`, style: { width: 36, height: 36, imageRendering: "pixelated", objectFit: "contain" } })), jsx("span", { className: styles.frameNum, children: fi + 1 })] }, fi))) })] })));
7
7
  PixelEditorTimeline.displayName = "PixelEditorTimeline";
8
8
 
9
9
  export { PixelEditorTimeline as default };
@@ -1,10 +1,10 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import React from 'react';
3
3
  import styles from './PixelEditor.module.css.js';
4
- import { NiceButton as Ee, NiceColorPicker as _n } from '../ui/dist/index.js';
4
+ import { NiceButton as Oe, NiceColorPicker as Nn } from '../ui/dist/index.js';
5
5
  import { TOOLS, SYMMETRY_MODES } from './pixelEditorTypes.js';
6
6
 
7
- const PixelEditorToolbar = React.memo(({ api }) => (jsxs("div", { className: styles.toolbar, children: [TOOLS.map((t) => (jsx(Ee, { className: `${styles.toolBtn} ${api.tool === t.id ? styles.toolBtnActive : ""}`, variant: api.tool === t.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setTool(t.id), title: `${t.label} (${t.key})`, children: t.icon }, t.id))), jsx("div", { className: styles.toolSep }), jsx(Ee, { className: `${styles.toolBtn} ${api.shapeFilled ? styles.toolBtnActive : ""}`, variant: api.shapeFilled ? 'primary' : 'ghost', size: "sm", onClick: () => api.setShapeFilled((f) => !f), title: `${api.shapeFilled ? "Filled" : "Outline"} (F)`, children: api.shapeFilled ? "■" : "□" }), jsx("div", { className: styles.toolGroup, children: SYMMETRY_MODES.map((m) => (jsx(Ee, { className: `${styles.miniBtn} ${api.symmetry === m.id ? styles.miniBtnActive : ""}`, variant: api.symmetry === m.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setSymmetry(m.id), title: `Symmetry: ${m.label}`, children: m.label.slice(0, 2) }, m.id))) }), jsxs("div", { className: styles.colorSwatches, children: [jsx("label", { style: { position: "relative", width: 28, height: 28, cursor: "pointer" }, title: "Primary Colour (click for HSV)", children: jsx(_n, { value: api.col1, onChange: (c) => api.setCol1(c) }) }), jsx("label", { style: { position: "relative", width: 22, height: 22, cursor: "pointer" }, title: "Secondary Colour", children: jsx(_n, { value: api.col2, onChange: (c) => api.setCol2(c) }) }), jsx(Ee, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => {
7
+ const PixelEditorToolbar = React.memo(({ api }) => (jsxs("div", { className: styles.toolbar, children: [TOOLS.map((t) => (jsx(Oe, { className: `${styles.toolBtn} ${api.tool === t.id ? styles.toolBtnActive : ""}`, variant: api.tool === t.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setTool(t.id), title: `${t.label} (${t.key})`, children: t.icon }, t.id))), jsx("div", { className: styles.toolSep }), jsx(Oe, { className: `${styles.toolBtn} ${api.shapeFilled ? styles.toolBtnActive : ""}`, variant: api.shapeFilled ? 'primary' : 'ghost', size: "sm", onClick: () => api.setShapeFilled((f) => !f), title: `${api.shapeFilled ? "Filled" : "Outline"} (F)`, children: api.shapeFilled ? "■" : "□" }), jsx("div", { className: styles.toolGroup, children: SYMMETRY_MODES.map((m) => (jsx(Oe, { className: `${styles.miniBtn} ${api.symmetry === m.id ? styles.miniBtnActive : ""}`, variant: api.symmetry === m.id ? 'primary' : 'ghost', size: "sm", onClick: () => api.setSymmetry(m.id), title: `Symmetry: ${m.label}`, children: m.label.slice(0, 2) }, m.id))) }), jsxs("div", { className: styles.colorSwatches, children: [jsx("label", { style: { position: "relative", width: 28, height: 28, cursor: "pointer" }, title: "Primary Colour (click for HSV)", children: jsx(Nn, { value: api.col1, onChange: (c) => api.setCol1(c) }) }), jsx("label", { style: { position: "relative", width: 22, height: 22, cursor: "pointer" }, title: "Secondary Colour", children: jsx(Nn, { value: api.col2, onChange: (c) => api.setCol2(c) }) }), jsx(Oe, { className: styles.toolBtn, variant: "ghost", size: "sm", onClick: () => {
8
8
  const tmp = api.col1;
9
9
  api.setCol1(api.col2);
10
10
  api.setCol2(tmp);