create-pixi-vn 2.0.1 → 2.0.2

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.
Files changed (53) hide show
  1. package/package.json +1 -1
  2. package/template-react-vite-muijoy/package-lock.json +35 -0
  3. package/template-react-vite-muijoy/package.json +1 -0
  4. package/template-react-vite-muijoy/src/components/menus/save-menu/save-slots.tsx +2 -1
  5. package/template-react-vite-muijoy/src/components/menus/settings/menus/history.tsx +15 -6
  6. package/template-react-vite-muijoy/src/components/{menus → scrrens}/narration/index.tsx +1 -1
  7. package/template-react-vite-muijoy/src/components/{menus → scrrens}/narration/narration-cards.tsx +3 -2
  8. package/template-react-vite-muijoy/src/components/ui/image.tsx +50 -0
  9. package/template-react-vite-muijoy/src/lib/hooks/image-hooks.ts +11 -0
  10. package/template-react-vite-muijoy/src/routes/game/narration.tsx +2 -2
  11. package/template-react-vite-muijoy/src/routes/game.tsx +1 -1
  12. package/template-react-vite-muijoy-ink/package-lock.json +35 -0
  13. package/template-react-vite-muijoy-ink/package.json +1 -0
  14. package/template-react-vite-muijoy-ink/src/components/menus/save-menu/save-slots.tsx +2 -1
  15. package/template-react-vite-muijoy-ink/src/components/menus/settings/menus/history.tsx +15 -6
  16. package/{template-react-vite-muijoy-ink-tauri/src/components/menus → template-react-vite-muijoy-ink/src/components/scrrens}/narration/index.tsx +1 -1
  17. package/template-react-vite-muijoy-ink/src/components/{menus → scrrens}/narration/narration-cards.tsx +3 -2
  18. package/template-react-vite-muijoy-ink/src/components/ui/image.tsx +50 -0
  19. package/template-react-vite-muijoy-ink/src/lib/hooks/image-hooks.ts +11 -0
  20. package/template-react-vite-muijoy-ink/src/routes/game/narration.tsx +2 -2
  21. package/template-react-vite-muijoy-ink/src/routes/game.tsx +1 -1
  22. package/template-react-vite-muijoy-ink-tauri/package-lock.json +35 -0
  23. package/template-react-vite-muijoy-ink-tauri/package.json +1 -0
  24. package/template-react-vite-muijoy-ink-tauri/src/components/menus/save-menu/save-slots.tsx +2 -1
  25. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/menus/history.tsx +15 -6
  26. package/{template-react-vite-muijoy-ink/src/components/menus → template-react-vite-muijoy-ink-tauri/src/components/scrrens}/narration/index.tsx +1 -1
  27. package/{template-react-vite-muijoy-tauri/src/components/menus → template-react-vite-muijoy-ink-tauri/src/components/scrrens}/narration/narration-cards.tsx +3 -2
  28. package/template-react-vite-muijoy-ink-tauri/src/components/ui/image.tsx +50 -0
  29. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/image-hooks.ts +11 -0
  30. package/template-react-vite-muijoy-ink-tauri/src/routes/game/narration.tsx +2 -2
  31. package/template-react-vite-muijoy-ink-tauri/src/routes/game.tsx +1 -1
  32. package/template-react-vite-muijoy-tauri/package-lock.json +35 -0
  33. package/template-react-vite-muijoy-tauri/package.json +1 -0
  34. package/template-react-vite-muijoy-tauri/src/components/menus/save-menu/save-slots.tsx +2 -1
  35. package/template-react-vite-muijoy-tauri/src/components/menus/settings/menus/history.tsx +15 -6
  36. package/template-react-vite-muijoy-tauri/src/components/{menus → scrrens}/narration/index.tsx +1 -1
  37. package/{template-react-vite-muijoy-ink-tauri/src/components/menus → template-react-vite-muijoy-tauri/src/components/scrrens}/narration/narration-cards.tsx +3 -2
  38. package/template-react-vite-muijoy-tauri/src/components/ui/image.tsx +50 -0
  39. package/template-react-vite-muijoy-tauri/src/lib/hooks/image-hooks.ts +11 -0
  40. package/template-react-vite-muijoy-tauri/src/routes/game/narration.tsx +2 -2
  41. package/template-react-vite-muijoy-tauri/src/routes/game.tsx +1 -1
  42. /package/template-react-vite-muijoy/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  43. /package/template-react-vite-muijoy/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  44. /package/template-react-vite-muijoy/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
  45. /package/template-react-vite-muijoy-ink/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  46. /package/template-react-vite-muijoy-ink/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  47. /package/template-react-vite-muijoy-ink/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
  48. /package/template-react-vite-muijoy-ink-tauri/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  49. /package/template-react-vite-muijoy-ink-tauri/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  50. /package/template-react-vite-muijoy-ink-tauri/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
  51. /package/template-react-vite-muijoy-tauri/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  52. /package/template-react-vite-muijoy-tauri/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  53. /package/template-react-vite-muijoy-tauri/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-pixi-vn",
3
3
  "description": "Create a new Pixi’VN project",
4
- "version": "2.0.1",
4
+ "version": "2.0.2",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0",
7
7
  "author": "DRincs-Productions",
@@ -25,6 +25,7 @@
25
25
  "@tanstack/react-store": "latest",
26
26
  "@tanstack/router-plugin": "latest",
27
27
  "@tanstack/store": "latest",
28
+ "@unpic/react": "^1.0.2",
28
29
  "class-variance-authority": "^0.7.1",
29
30
  "clsx": "^2.1.1",
30
31
  "embla-carousel-react": "^8.6.0",
@@ -6902,6 +6903,34 @@
6902
6903
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
6903
6904
  "license": "ISC"
6904
6905
  },
6906
+ "node_modules/@unpic/core": {
6907
+ "version": "1.0.3",
6908
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
6909
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
6910
+ "license": "MIT",
6911
+ "dependencies": {
6912
+ "unpic": "^4.2.2"
6913
+ }
6914
+ },
6915
+ "node_modules/@unpic/react": {
6916
+ "version": "1.0.2",
6917
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
6918
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
6919
+ "license": "MIT",
6920
+ "dependencies": {
6921
+ "@unpic/core": "^1.0.3"
6922
+ },
6923
+ "peerDependencies": {
6924
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
6925
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
6926
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
6927
+ },
6928
+ "peerDependenciesMeta": {
6929
+ "next": {
6930
+ "optional": true
6931
+ }
6932
+ }
6933
+ },
6905
6934
  "node_modules/@vite-pwa/assets-generator": {
6906
6935
  "version": "1.0.2",
6907
6936
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -16982,6 +17011,12 @@
16982
17011
  "node": ">= 10.0.0"
16983
17012
  }
16984
17013
  },
17014
+ "node_modules/unpic": {
17015
+ "version": "4.2.2",
17016
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17017
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17018
+ "license": "MIT"
17019
+ },
16985
17020
  "node_modules/unpipe": {
16986
17021
  "version": "1.0.0",
16987
17022
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -34,6 +34,7 @@
34
34
  "@tanstack/react-store": "latest",
35
35
  "@tanstack/router-plugin": "latest",
36
36
  "@tanstack/store": "latest",
37
+ "@unpic/react": "^1.0.2",
37
38
  "class-variance-authority": "^0.7.1",
38
39
  "clsx": "^2.1.1",
39
40
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";
@@ -26,6 +26,7 @@
26
26
  "@tanstack/react-store": "latest",
27
27
  "@tanstack/router-plugin": "latest",
28
28
  "@tanstack/store": "latest",
29
+ "@unpic/react": "^1.0.2",
29
30
  "class-variance-authority": "^0.7.1",
30
31
  "clsx": "^2.1.1",
31
32
  "embla-carousel-react": "^8.6.0",
@@ -7119,6 +7120,34 @@
7119
7120
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
7120
7121
  "license": "ISC"
7121
7122
  },
7123
+ "node_modules/@unpic/core": {
7124
+ "version": "1.0.3",
7125
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
7126
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
7127
+ "license": "MIT",
7128
+ "dependencies": {
7129
+ "unpic": "^4.2.2"
7130
+ }
7131
+ },
7132
+ "node_modules/@unpic/react": {
7133
+ "version": "1.0.2",
7134
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
7135
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
7136
+ "license": "MIT",
7137
+ "dependencies": {
7138
+ "@unpic/core": "^1.0.3"
7139
+ },
7140
+ "peerDependencies": {
7141
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
7142
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
7143
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
7144
+ },
7145
+ "peerDependenciesMeta": {
7146
+ "next": {
7147
+ "optional": true
7148
+ }
7149
+ }
7150
+ },
7122
7151
  "node_modules/@vite-pwa/assets-generator": {
7123
7152
  "version": "1.0.2",
7124
7153
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -17208,6 +17237,12 @@
17208
17237
  "node": ">= 10.0.0"
17209
17238
  }
17210
17239
  },
17240
+ "node_modules/unpic": {
17241
+ "version": "4.2.2",
17242
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17243
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17244
+ "license": "MIT"
17245
+ },
17211
17246
  "node_modules/unpipe": {
17212
17247
  "version": "1.0.0",
17213
17248
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -35,6 +35,7 @@
35
35
  "@tanstack/react-store": "latest",
36
36
  "@tanstack/router-plugin": "latest",
37
37
  "@tanstack/store": "latest",
38
+ "@unpic/react": "^1.0.2",
38
39
  "class-variance-authority": "^0.7.1",
39
40
  "clsx": "^2.1.1",
40
41
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";
@@ -28,6 +28,7 @@
28
28
  "@tanstack/store": "latest",
29
29
  "@tauri-apps/api": "^2",
30
30
  "@tauri-apps/plugin-opener": "^2",
31
+ "@unpic/react": "^1.0.2",
31
32
  "class-variance-authority": "^0.7.1",
32
33
  "clsx": "^2.1.1",
33
34
  "embla-carousel-react": "^8.6.0",
@@ -7369,6 +7370,34 @@
7369
7370
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
7370
7371
  "license": "ISC"
7371
7372
  },
7373
+ "node_modules/@unpic/core": {
7374
+ "version": "1.0.3",
7375
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
7376
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
7377
+ "license": "MIT",
7378
+ "dependencies": {
7379
+ "unpic": "^4.2.2"
7380
+ }
7381
+ },
7382
+ "node_modules/@unpic/react": {
7383
+ "version": "1.0.2",
7384
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
7385
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
7386
+ "license": "MIT",
7387
+ "dependencies": {
7388
+ "@unpic/core": "^1.0.3"
7389
+ },
7390
+ "peerDependencies": {
7391
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
7392
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
7393
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
7394
+ },
7395
+ "peerDependenciesMeta": {
7396
+ "next": {
7397
+ "optional": true
7398
+ }
7399
+ }
7400
+ },
7372
7401
  "node_modules/@vite-pwa/assets-generator": {
7373
7402
  "version": "1.0.2",
7374
7403
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -17458,6 +17487,12 @@
17458
17487
  "node": ">= 10.0.0"
17459
17488
  }
17460
17489
  },
17490
+ "node_modules/unpic": {
17491
+ "version": "4.2.2",
17492
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17493
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17494
+ "license": "MIT"
17495
+ },
17461
17496
  "node_modules/unpipe": {
17462
17497
  "version": "1.0.0",
17463
17498
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -42,6 +42,7 @@
42
42
  "@tanstack/store": "latest",
43
43
  "@tauri-apps/api": "^2",
44
44
  "@tauri-apps/plugin-opener": "^2",
45
+ "@unpic/react": "^1.0.2",
45
46
  "class-variance-authority": "^0.7.1",
46
47
  "clsx": "^2.1.1",
47
48
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";
@@ -27,6 +27,7 @@
27
27
  "@tanstack/store": "latest",
28
28
  "@tauri-apps/api": "^2",
29
29
  "@tauri-apps/plugin-opener": "^2",
30
+ "@unpic/react": "^1.0.2",
30
31
  "class-variance-authority": "^0.7.1",
31
32
  "clsx": "^2.1.1",
32
33
  "embla-carousel-react": "^8.6.0",
@@ -7152,6 +7153,34 @@
7152
7153
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
7153
7154
  "license": "ISC"
7154
7155
  },
7156
+ "node_modules/@unpic/core": {
7157
+ "version": "1.0.3",
7158
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
7159
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
7160
+ "license": "MIT",
7161
+ "dependencies": {
7162
+ "unpic": "^4.2.2"
7163
+ }
7164
+ },
7165
+ "node_modules/@unpic/react": {
7166
+ "version": "1.0.2",
7167
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
7168
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
7169
+ "license": "MIT",
7170
+ "dependencies": {
7171
+ "@unpic/core": "^1.0.3"
7172
+ },
7173
+ "peerDependencies": {
7174
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
7175
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
7176
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
7177
+ },
7178
+ "peerDependenciesMeta": {
7179
+ "next": {
7180
+ "optional": true
7181
+ }
7182
+ }
7183
+ },
7155
7184
  "node_modules/@vite-pwa/assets-generator": {
7156
7185
  "version": "1.0.2",
7157
7186
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -17232,6 +17261,12 @@
17232
17261
  "node": ">= 10.0.0"
17233
17262
  }
17234
17263
  },
17264
+ "node_modules/unpic": {
17265
+ "version": "4.2.2",
17266
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17267
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17268
+ "license": "MIT"
17269
+ },
17235
17270
  "node_modules/unpipe": {
17236
17271
  "version": "1.0.0",
17237
17272
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -41,6 +41,7 @@
41
41
  "@tanstack/store": "latest",
42
42
  "@tauri-apps/api": "^2",
43
43
  "@tauri-apps/plugin-opener": "^2",
44
+ "@unpic/react": "^1.0.2",
44
45
  "class-variance-authority": "^0.7.1",
45
46
  "clsx": "^2.1.1",
46
47
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";