@retro-antlitz-kartei/react-editor 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useRef, useEffect, useState, useMemo, useCallback } from 'react';
2
- import { renderAvatar, normalizeConfig, DEFAULT_CONFIG, configFromSeed, PART_NAMES, BG, encodeConfig, SKIN, CLOTH, randomConfig, decodeConfig } from '@retro-antlitz-kartei/generator';
2
+ import { renderAvatar, normalizeConfig, DEFAULT_CONFIG, configFromSeed, PARTS, partLabel, BG, encodeConfig, SKIN, HAIRS, CLOTH, PANTS, randomConfig, decodeConfig } from '@retro-antlitz-kartei/generator';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { AvatarArena, POSES, POSE_NAMES } from '@retro-antlitz-kartei/animate';
5
5
 
@@ -176,7 +176,7 @@ function theme(layout) {
176
176
  mono: MONO,
177
177
  body: BODY,
178
178
  radius: "14px",
179
- titleText: "AVATAR EDITOR",
179
+ titleText: "@retro-antlitz-kartei AVATAR EDITOR",
180
180
  glow: false,
181
181
  paper: false
182
182
  };
@@ -231,20 +231,37 @@ var FONT_HREFS = [
231
231
  "https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap"
232
232
  ];
233
233
  var PART_CONTROLS = [
234
- { key: "gender", label: "BUILD" },
235
- { key: "hut", label: "HAT" },
236
- { key: "haare", label: "HAIR" },
237
- { key: "ohren", label: "EARS" },
238
- { key: "nase", label: "NOSE" },
239
- { key: "acc", label: "ACCESSORY" },
240
- { key: "torso", label: "TOP" },
241
- { key: "hose", label: "TROUSERS" }
234
+ { key: "build", label: "BUILD" },
235
+ { key: "hat", label: "HAT" },
236
+ { key: "hair", label: "HAIR" },
237
+ { key: "ears", label: "EARS" },
238
+ { key: "nose", label: "NOSE" },
239
+ { key: "accessory", label: "ACCESSORY" },
240
+ { key: "top", label: "TOP" },
241
+ { key: "trousers", label: "TROUSERS" },
242
+ { key: "shoes", label: "SHOES" }
242
243
  ];
243
244
  var VIEW_OPTIONS = [
244
245
  { key: "left", name: "LEFT" },
245
246
  { key: "front", name: "FRONT" },
246
247
  { key: "right", name: "RIGHT" }
247
248
  ];
249
+ function useNarrow(ref, breakpoint = 640) {
250
+ const [narrow, setNarrow] = useState(
251
+ () => typeof window !== "undefined" && window.innerWidth < breakpoint
252
+ );
253
+ useEffect(() => {
254
+ const el = ref.current;
255
+ if (!el || typeof ResizeObserver === "undefined") return;
256
+ const ro = new ResizeObserver((entries) => {
257
+ const w = entries[0]?.contentRect.width ?? 0;
258
+ if (w > 0) setNarrow(w < breakpoint);
259
+ });
260
+ ro.observe(el);
261
+ return () => ro.disconnect();
262
+ }, [ref, breakpoint]);
263
+ return narrow;
264
+ }
248
265
  function useControllable(controlled, initial, onChange) {
249
266
  const [internal, setInternal] = useState(initial);
250
267
  const value = controlled !== void 0 ? controlled : internal;
@@ -270,6 +287,7 @@ function AvatarEditor(props) {
270
287
  showCombat = true,
271
288
  showCode = true,
272
289
  showSeed = true,
290
+ onMagic,
273
291
  loadFonts = true,
274
292
  className,
275
293
  style
@@ -286,7 +304,10 @@ function AvatarEditor(props) {
286
304
  const [codeInput, setCodeInput] = useState("");
287
305
  const [codeMsg, setCodeMsg] = useState("");
288
306
  const [seedInput, setSeedInput] = useState(seed ?? "");
307
+ const stageRef = useRef(null);
308
+ const narrow = useNarrow(stageRef);
289
309
  const t = theme(layout);
310
+ const stacked = narrow || t.mainDir === "column";
290
311
  useEffect(() => {
291
312
  if (!loadFonts || typeof document === "undefined") return;
292
313
  for (const href of FONT_HREFS) {
@@ -296,18 +317,13 @@ function AvatarEditor(props) {
296
317
  link.href = href;
297
318
  document.head.appendChild(link);
298
319
  }
299
- if (!document.getElementById("rak-keyframes")) {
300
- const s = document.createElement("style");
301
- s.id = "rak-keyframes";
302
- s.textContent = "@keyframes rak-coinpulse{0%,100%{transform:translateY(0)}50%{transform:translateY(-2px)}}";
303
- document.head.appendChild(s);
304
- }
305
320
  }, [loadFonts]);
306
321
  const patch = useCallback((p) => setConfig({ ...config, ...p }), [config, setConfig]);
307
322
  const cycle = useCallback(
308
323
  (key, dir) => {
309
- const len = PART_NAMES[key].length;
310
- patch({ [key]: (config[key] + dir + len) % len });
324
+ const ids = PARTS[key];
325
+ const cur = ids.indexOf(config[key]);
326
+ patch({ [key]: ids[(cur + dir + ids.length) % ids.length] });
311
327
  },
312
328
  [config, patch]
313
329
  );
@@ -398,24 +414,25 @@ function AvatarEditor(props) {
398
414
  border: "2px solid " + (active ? t.accent : t.paper ? "#00000033" : "#ffffff44"),
399
415
  boxShadow: active ? "0 0 0 2px " + t.accent + "66" : "none"
400
416
  });
401
- const nameDisplay = PART_NAMES.torso[config.torso].toUpperCase();
417
+ const nameDisplay = partLabel("top", config.top).toUpperCase();
402
418
  const partRow = (key, label) => /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
403
419
  /* @__PURE__ */ jsx("button", { onClick: () => cycle(key, -1), style: arrowStyle, children: "\u25C0" }),
404
420
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0, textAlign: "center" }, children: [
405
421
  /* @__PURE__ */ jsx("div", { style: partLabelStyle, children: label }),
406
- /* @__PURE__ */ jsx("div", { style: partValueStyle, children: PART_NAMES[key][config[key]] })
422
+ /* @__PURE__ */ jsx("div", { style: partValueStyle, children: partLabel(key, config[key]) })
407
423
  ] }),
408
424
  /* @__PURE__ */ jsx("button", { onClick: () => cycle(key, 1), style: arrowStyle, children: "\u25B6" })
409
425
  ] }, key);
410
426
  return /* @__PURE__ */ jsxs(
411
427
  "div",
412
428
  {
429
+ ref: stageRef,
413
430
  className,
414
431
  style: {
415
432
  minHeight: "100%",
416
433
  width: "100%",
417
434
  background: t.stageBg,
418
- padding: "24px",
435
+ padding: narrow ? "10px" : "24px",
419
436
  display: "flex",
420
437
  flexDirection: "column",
421
438
  alignItems: "center",
@@ -452,11 +469,11 @@ function AvatarEditor(props) {
452
469
  {
453
470
  style: {
454
471
  width: "100%",
455
- maxWidth: t.mainDir === "row" ? "860px" : "540px",
472
+ maxWidth: narrow ? "100%" : t.mainDir === "row" ? "860px" : "540px",
456
473
  background: t.cabinet,
457
- border: "4px solid " + t.cabinetBorder,
458
- borderRadius: t.radius,
459
- boxShadow: "0 18px 50px rgba(0,0,0,.5)" + (t.glow ? ", 0 0 30px " + t.accent + "33" : ""),
474
+ border: narrow ? "none" : "4px solid " + t.cabinetBorder,
475
+ borderRadius: narrow ? "10px" : t.radius,
476
+ boxShadow: narrow ? "none" : "0 18px 50px rgba(0,0,0,.5)" + (t.glow ? ", 0 0 30px " + t.accent + "33" : ""),
460
477
  overflow: "hidden"
461
478
  },
462
479
  children: [
@@ -465,29 +482,32 @@ function AvatarEditor(props) {
465
482
  {
466
483
  style: {
467
484
  background: t.marqueeBg,
468
- padding: "14px",
485
+ padding: narrow ? "10px" : "14px",
469
486
  display: "flex",
470
- gap: "12px",
487
+ gap: narrow ? "8px" : "12px",
471
488
  alignItems: "center",
472
489
  justifyContent: "center",
473
- borderBottom: "3px solid " + t.cabinetBorder
490
+ flexWrap: "wrap",
491
+ borderBottom: narrow ? "none" : "3px solid " + t.cabinetBorder
474
492
  },
475
493
  children: [
476
- /* @__PURE__ */ jsx("span", { style: { width: "10px", height: "10px", borderRadius: "50%", background: t.accent, boxShadow: "0 0 8px " + t.accent } }),
494
+ /* @__PURE__ */ jsx("span", { style: { width: "10px", height: "10px", borderRadius: "50%", background: t.accent, boxShadow: "0 0 8px " + t.accent, flex: "0 0 auto" } }),
477
495
  /* @__PURE__ */ jsx(
478
496
  "span",
479
497
  {
480
498
  style: {
481
499
  fontFamily: t.mono,
482
- fontSize: t.titleText === "WANTED" ? "20px" : "15px",
500
+ fontSize: t.titleText === "WANTED" ? "20px" : narrow ? "10px" : "15px",
483
501
  color: t.title,
484
- letterSpacing: "2px",
485
- textShadow: t.glow ? "0 0 10px " + t.accent : "2px 2px 0 #00000044"
502
+ letterSpacing: narrow ? "1px" : "2px",
503
+ textShadow: t.glow ? "0 0 10px " + t.accent : "2px 2px 0 #00000044",
504
+ textAlign: "center",
505
+ lineHeight: 1.5
486
506
  },
487
507
  children: t.titleText
488
508
  }
489
509
  ),
490
- /* @__PURE__ */ jsx("span", { style: { width: "10px", height: "10px", borderRadius: "50%", background: t.accent, boxShadow: "0 0 8px " + t.accent } })
510
+ /* @__PURE__ */ jsx("span", { style: { width: "10px", height: "10px", borderRadius: "50%", background: t.accent, boxShadow: "0 0 8px " + t.accent, flex: "0 0 auto" } })
491
511
  ]
492
512
  }
493
513
  ),
@@ -496,21 +516,23 @@ function AvatarEditor(props) {
496
516
  {
497
517
  style: {
498
518
  display: "flex",
499
- flexDirection: t.mainDir,
519
+ flexDirection: stacked ? "column" : "row",
500
520
  gap: "16px",
501
- padding: "18px",
502
- alignItems: t.mainDir === "row" ? "stretch" : "center"
521
+ padding: narrow ? "10px" : "18px",
522
+ alignItems: stacked ? "center" : "stretch"
503
523
  },
504
524
  children: [
505
525
  /* @__PURE__ */ jsxs(
506
526
  "div",
507
527
  {
508
528
  style: {
509
- flex: t.mainDir === "row" ? "0 0 300px" : "0 0 auto",
529
+ flex: stacked ? "0 0 auto" : "0 0 300px",
510
530
  display: "flex",
511
531
  flexDirection: "column",
512
532
  gap: "8px",
513
- alignItems: "center"
533
+ alignItems: "center",
534
+ width: stacked ? "100%" : void 0,
535
+ maxWidth: "100%"
514
536
  },
515
537
  children: [
516
538
  /* @__PURE__ */ jsxs(
@@ -527,7 +549,15 @@ function AvatarEditor(props) {
527
549
  },
528
550
  onClick: showCombat ? () => setModalOpen(true) : void 0,
529
551
  children: [
530
- /* @__PURE__ */ jsx(AvatarPreview, { config, width: 320, height: 400, style: { width: "264px", height: "330px", borderRadius: "3px" } }),
552
+ /* @__PURE__ */ jsx(
553
+ AvatarPreview,
554
+ {
555
+ config,
556
+ width: 320,
557
+ height: 400,
558
+ style: { width: "264px", maxWidth: "100%", height: "auto", aspectRatio: "320 / 400", borderRadius: "3px" }
559
+ }
560
+ ),
531
561
  /* @__PURE__ */ jsx(
532
562
  "div",
533
563
  {
@@ -602,10 +632,10 @@ function AvatarEditor(props) {
602
632
  },
603
633
  o.key
604
634
  )) }),
605
- partRow("mund", "MOUTH"),
635
+ partRow("mouth", "MOUTH"),
606
636
  /* @__PURE__ */ jsxs("div", { style: swatchRowStyle, children: [
607
637
  /* @__PURE__ */ jsx("span", { style: swatchLabelStyle, children: "BACKGROUND" }),
608
- /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: BG.map((c, i) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ bg: i }), style: swatchBtn(c, config.bg === i) }, c)) })
638
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: BG.map((c) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ background: c }), style: swatchBtn(c, config.background === c) }, c)) })
609
639
  ] }),
610
640
  showCode && /* @__PURE__ */ jsxs(Fragment, { children: [
611
641
  /* @__PURE__ */ jsx("div", { style: dividerStyle }),
@@ -664,7 +694,7 @@ function AvatarEditor(props) {
664
694
  style: {
665
695
  flex: 1,
666
696
  minWidth: 0,
667
- width: t.mainDir === "column" ? "100%" : "auto",
697
+ width: stacked ? "100%" : "auto",
668
698
  display: "flex",
669
699
  flexDirection: "column",
670
700
  gap: "9px",
@@ -678,56 +708,66 @@ function AvatarEditor(props) {
678
708
  /* @__PURE__ */ jsx("div", { style: dividerStyle }),
679
709
  /* @__PURE__ */ jsxs("div", { style: swatchRowStyle, children: [
680
710
  /* @__PURE__ */ jsx("span", { style: swatchLabelStyle, children: "SKIN" }),
681
- /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: SKIN.map((c, i) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ skin: i }), style: swatchBtn(c, config.skin === i) }, c)) })
711
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: SKIN.map((c) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ skin: c }), style: swatchBtn(c, config.skin === c) }, c)) })
712
+ ] }),
713
+ /* @__PURE__ */ jsxs("div", { style: swatchRowStyle, children: [
714
+ /* @__PURE__ */ jsx("span", { style: swatchLabelStyle, children: "HAIR" }),
715
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: HAIRS.map((c) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ hairColor: c }), style: swatchBtn(c, config.hairColor === c) }, c)) })
682
716
  ] }),
683
717
  /* @__PURE__ */ jsxs("div", { style: swatchRowStyle, children: [
684
718
  /* @__PURE__ */ jsx("span", { style: swatchLabelStyle, children: "CLOTHING" }),
685
- /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: CLOTH.map((c, i) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ cloth: i }), style: swatchBtn(c, config.cloth === i) }, c)) })
719
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: CLOTH.map((c) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ topColor: c }), style: swatchBtn(c, config.topColor === c) }, c)) })
720
+ ] }),
721
+ /* @__PURE__ */ jsxs("div", { style: swatchRowStyle, children: [
722
+ /* @__PURE__ */ jsx("span", { style: swatchLabelStyle, children: "TROUSERS" }),
723
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" }, children: PANTS.map((c) => /* @__PURE__ */ jsx("button", { onClick: () => patch({ trousersColor: c }), style: swatchBtn(c, config.trousersColor === c) }, c)) })
686
724
  ] }),
687
725
  /* @__PURE__ */ jsx("div", { style: dividerStyle }),
688
- /* @__PURE__ */ jsx(
689
- "button",
690
- {
691
- onClick: onRandom,
692
- style: {
693
- fontFamily: t.mono,
694
- fontSize: "10px",
695
- letterSpacing: "1px",
696
- cursor: "pointer",
697
- padding: "12px",
698
- borderRadius: t.paper ? "3px" : "9px",
699
- marginTop: "2px",
700
- border: "3px solid " + t.title,
701
- background: t.accent,
702
- color: "#10131c"
703
- },
704
- children: "\u{1F3B2} RANDOM"
705
- }
706
- )
726
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", marginTop: "2px" }, children: [
727
+ onMagic && /* @__PURE__ */ jsx(
728
+ "button",
729
+ {
730
+ onClick: onMagic,
731
+ style: {
732
+ fontFamily: t.mono,
733
+ fontSize: "10px",
734
+ letterSpacing: "1px",
735
+ cursor: "pointer",
736
+ padding: "12px",
737
+ borderRadius: t.paper ? "3px" : "9px",
738
+ border: "3px solid " + t.title,
739
+ background: "#9b5de5",
740
+ color: "#fff",
741
+ flex: "0 0 auto"
742
+ },
743
+ children: "\u2728 MAGIC"
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsx(
747
+ "button",
748
+ {
749
+ onClick: onRandom,
750
+ style: {
751
+ fontFamily: t.mono,
752
+ fontSize: "10px",
753
+ letterSpacing: "1px",
754
+ cursor: "pointer",
755
+ padding: "12px",
756
+ borderRadius: t.paper ? "3px" : "9px",
757
+ border: "3px solid " + t.title,
758
+ background: t.accent,
759
+ color: "#10131c",
760
+ flex: 1
761
+ },
762
+ children: "\u{1F3B2} RANDOM"
763
+ }
764
+ )
765
+ ] })
707
766
  ]
708
767
  }
709
768
  )
710
769
  ]
711
770
  }
712
- ),
713
- /* @__PURE__ */ jsxs(
714
- "div",
715
- {
716
- style: {
717
- background: t.marqueeBg,
718
- padding: "10px",
719
- display: "flex",
720
- gap: "10px",
721
- alignItems: "center",
722
- justifyContent: "center",
723
- color: t.title,
724
- borderTop: "3px solid " + t.cabinetBorder
725
- },
726
- children: [
727
- /* @__PURE__ */ jsx("span", { style: { color: t.accent, fontSize: "14px", animation: "rak-coinpulse 1.2s ease-in-out infinite" }, children: "\u25CF" }),
728
- /* @__PURE__ */ jsx("span", { style: { fontFamily: t.mono, fontSize: "8px", letterSpacing: "1px" }, children: "INSERT COIN \xB7 SATIRE EDITION" })
729
- ]
730
- }
731
771
  )
732
772
  ]
733
773
  }