create-pixi-vn 2.0.12 → 2.0.13

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 (114) hide show
  1. package/package.json +1 -1
  2. package/template-react-vite-muijoy-ink/.vscode/extensions.json +2 -1
  3. package/template-react-vite-muijoy-ink/.vscode/settings.json +6 -1
  4. package/template-react-vite-muijoy-ink/_gitignore +1 -0
  5. package/template-react-vite-muijoy-ink/ink/second_part.ink +571 -0
  6. package/template-react-vite-muijoy-ink/ink/start.ink +214 -0
  7. package/template-react-vite-muijoy-ink/package-lock.json +230 -4
  8. package/template-react-vite-muijoy-ink/package.json +3 -1
  9. package/template-react-vite-muijoy-ink/src/App.tsx +25 -25
  10. package/template-react-vite-muijoy-ink/src/assets/index.ts +2 -4
  11. package/template-react-vite-muijoy-ink/src/assets/ink-manifest.gen.json +4 -1
  12. package/template-react-vite-muijoy-ink/src/content/ink/hashtag-commands.ts +38 -0
  13. package/template-react-vite-muijoy-ink/src/content/ink/text-replaces.ts +19 -0
  14. package/template-react-vite-muijoy-ink/src/lib/hooks/ink-hooks.tsx +12 -0
  15. package/template-react-vite-muijoy-ink/src/lib/i18n.ts +22 -1
  16. package/template-react-vite-muijoy-ink/src/pixi-vn-keys.gen.d.ts +2 -0
  17. package/template-react-vite-muijoy-ink/src/routes/__root.tsx +4 -0
  18. package/template-react-vite-muijoy-ink/vite.config.ts +6 -0
  19. package/template-react-vite-muijoy-ink-tauri/.assetpack.ts +9 -0
  20. package/template-react-vite-muijoy-ink-tauri/.vscode/extensions.json +5 -1
  21. package/template-react-vite-muijoy-ink-tauri/.vscode/launch.json +11 -0
  22. package/template-react-vite-muijoy-ink-tauri/.vscode/settings.json +6 -1
  23. package/template-react-vite-muijoy-ink-tauri/.vscode/tasks.json +47 -0
  24. package/template-react-vite-muijoy-ink-tauri/README.md +3 -3
  25. package/template-react-vite-muijoy-ink-tauri/_github/workflows/desktop.yml +188 -0
  26. package/template-react-vite-muijoy-ink-tauri/_github/workflows/mobile.yml +270 -0
  27. package/template-react-vite-muijoy-ink-tauri/_gitignore +11 -0
  28. package/template-react-vite-muijoy-ink-tauri/ink/second_part.ink +571 -0
  29. package/template-react-vite-muijoy-ink-tauri/ink/start.ink +214 -0
  30. package/template-react-vite-muijoy-ink-tauri/package-lock.json +480 -4
  31. package/template-react-vite-muijoy-ink-tauri/package.json +13 -2
  32. package/template-react-vite-muijoy-ink-tauri/src/App.tsx +25 -25
  33. package/template-react-vite-muijoy-ink-tauri/src/assets/index.ts +2 -4
  34. package/template-react-vite-muijoy-ink-tauri/src/assets/ink-manifest.gen.json +4 -1
  35. package/template-react-vite-muijoy-ink-tauri/src/components/menus/main-menu.tsx +16 -1
  36. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/quick-menus.tsx +17 -1
  37. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/system-controls.tsx +11 -1
  38. package/template-react-vite-muijoy-ink-tauri/src/content/ink/hashtag-commands.ts +55 -0
  39. package/template-react-vite-muijoy-ink-tauri/src/content/ink/text-replaces.ts +19 -0
  40. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/ink-hooks.tsx +12 -0
  41. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/quit-hooks.ts +24 -0
  42. package/template-react-vite-muijoy-ink-tauri/src/lib/i18n.ts +22 -1
  43. package/template-react-vite-muijoy-ink-tauri/src/lib/query/settings-query.ts +7 -1
  44. package/template-react-vite-muijoy-ink-tauri/src/lib/steam.ts +143 -0
  45. package/template-react-vite-muijoy-ink-tauri/src/pixi-vn-keys.gen.d.ts +2 -0
  46. package/template-react-vite-muijoy-ink-tauri/src/routes/__root.tsx +4 -0
  47. package/template-react-vite-muijoy-ink-tauri/src-tauri/Cargo.toml +50 -0
  48. package/template-react-vite-muijoy-ink-tauri/src-tauri/build.rs +46 -0
  49. package/template-react-vite-muijoy-ink-tauri/src-tauri/capabilities/default.json +21 -0
  50. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/128x128.png +0 -0
  51. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/128x128@2x.png +0 -0
  52. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/32x32.png +0 -0
  53. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square107x107Logo.png +0 -0
  54. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square142x142Logo.png +0 -0
  55. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square150x150Logo.png +0 -0
  56. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square284x284Logo.png +0 -0
  57. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square30x30Logo.png +0 -0
  58. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square310x310Logo.png +0 -0
  59. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square44x44Logo.png +0 -0
  60. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square71x71Logo.png +0 -0
  61. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/Square89x89Logo.png +0 -0
  62. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/StoreLogo.png +0 -0
  63. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/icon.icns +0 -0
  64. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/icon.ico +0 -0
  65. package/template-react-vite-muijoy-ink-tauri/src-tauri/icons/icon.png +0 -0
  66. package/template-react-vite-muijoy-ink-tauri/src-tauri/src/lib.rs +64 -0
  67. package/template-react-vite-muijoy-ink-tauri/src-tauri/src/main.rs +6 -0
  68. package/template-react-vite-muijoy-ink-tauri/src-tauri/src/steam.rs +238 -0
  69. package/template-react-vite-muijoy-ink-tauri/src-tauri/tauri.conf.json +50 -0
  70. package/template-react-vite-muijoy-ink-tauri/vite.config.ts +30 -0
  71. package/template-react-vite-muijoy-tauri/.assetpack.ts +9 -0
  72. package/template-react-vite-muijoy-tauri/.vscode/extensions.json +4 -1
  73. package/template-react-vite-muijoy-tauri/.vscode/launch.json +11 -0
  74. package/template-react-vite-muijoy-tauri/.vscode/tasks.json +47 -0
  75. package/template-react-vite-muijoy-tauri/README.md +3 -3
  76. package/template-react-vite-muijoy-tauri/_github/workflows/desktop.yml +188 -0
  77. package/template-react-vite-muijoy-tauri/_github/workflows/mobile.yml +270 -0
  78. package/template-react-vite-muijoy-tauri/_gitignore +10 -0
  79. package/template-react-vite-muijoy-tauri/package-lock.json +250 -0
  80. package/template-react-vite-muijoy-tauri/package.json +10 -1
  81. package/template-react-vite-muijoy-tauri/src/components/menus/main-menu.tsx +16 -1
  82. package/template-react-vite-muijoy-tauri/src/components/menus/settings/quick-menus.tsx +17 -1
  83. package/template-react-vite-muijoy-tauri/src/components/menus/settings/system-controls.tsx +11 -1
  84. package/template-react-vite-muijoy-tauri/src/lib/hooks/quit-hooks.ts +24 -0
  85. package/template-react-vite-muijoy-tauri/src/lib/query/settings-query.ts +7 -1
  86. package/template-react-vite-muijoy-tauri/src/lib/steam.ts +143 -0
  87. package/template-react-vite-muijoy-tauri/src-tauri/Cargo.toml +50 -0
  88. package/template-react-vite-muijoy-tauri/src-tauri/build.rs +46 -0
  89. package/template-react-vite-muijoy-tauri/src-tauri/capabilities/default.json +21 -0
  90. package/template-react-vite-muijoy-tauri/src-tauri/icons/128x128.png +0 -0
  91. package/template-react-vite-muijoy-tauri/src-tauri/icons/128x128@2x.png +0 -0
  92. package/template-react-vite-muijoy-tauri/src-tauri/icons/32x32.png +0 -0
  93. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square107x107Logo.png +0 -0
  94. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square142x142Logo.png +0 -0
  95. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square150x150Logo.png +0 -0
  96. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square284x284Logo.png +0 -0
  97. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square30x30Logo.png +0 -0
  98. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square310x310Logo.png +0 -0
  99. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square44x44Logo.png +0 -0
  100. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square71x71Logo.png +0 -0
  101. package/template-react-vite-muijoy-tauri/src-tauri/icons/Square89x89Logo.png +0 -0
  102. package/template-react-vite-muijoy-tauri/src-tauri/icons/StoreLogo.png +0 -0
  103. package/template-react-vite-muijoy-tauri/src-tauri/icons/icon.icns +0 -0
  104. package/template-react-vite-muijoy-tauri/src-tauri/icons/icon.ico +0 -0
  105. package/template-react-vite-muijoy-tauri/src-tauri/icons/icon.png +0 -0
  106. package/template-react-vite-muijoy-tauri/src-tauri/src/lib.rs +64 -0
  107. package/template-react-vite-muijoy-tauri/src-tauri/src/main.rs +6 -0
  108. package/template-react-vite-muijoy-tauri/src-tauri/src/steam.rs +238 -0
  109. package/template-react-vite-muijoy-tauri/src-tauri/tauri.conf.json +50 -0
  110. package/template-react-vite-muijoy-tauri/vite.config.ts +24 -0
  111. package/template-react-vite-muijoy-ink/src/content/labels/second.label.ts +0 -1207
  112. package/template-react-vite-muijoy-ink/src/content/labels/start.label.ts +0 -566
  113. package/template-react-vite-muijoy-ink-tauri/src/content/labels/second.label.ts +0 -1207
  114. package/template-react-vite-muijoy-ink-tauri/src/content/labels/start.label.ts +0 -566
@@ -11,7 +11,12 @@
11
11
  "build": "tsc -b && vite build",
12
12
  "build:fast": "vite build",
13
13
  "prebuild": "npm run icon",
14
- "icon": "pwa-assets-generator",
14
+ "icon": "pwa-assets-generator && tauri icon public/icon.png",
15
+ "tauri": "tauri",
16
+ "tauri:dev": "tauri dev",
17
+ "tauri:build": "tauri build",
18
+ "tauri:android:dev": "tauri android dev",
19
+ "tauri:ios:dev": "tauri ios dev",
15
20
  "lint": "biome lint",
16
21
  "check": "biome check",
17
22
  "format": "biome format --write",
@@ -20,6 +25,7 @@
20
25
  "dependencies": {
21
26
  "@base-ui/react": "^1.5.0",
22
27
  "@drincs/pixi-vn": "^1.8.13",
28
+ "@drincs/pixi-vn-ink": "^1.0.5",
23
29
  "@drincs/pixi-vn-spine": "^0.2.1",
24
30
  "@tailwindcss/vite": "^4.3.0",
25
31
  "@tanstack/hotkeys": "^0.8.0",
@@ -35,6 +41,8 @@
35
41
  "@tanstack/react-store": "latest",
36
42
  "@tanstack/router-plugin": "latest",
37
43
  "@tanstack/store": "latest",
44
+ "@tauri-apps/api": "^2",
45
+ "@tauri-apps/plugin-opener": "^2",
38
46
  "@unpic/react": "^1.0.2",
39
47
  "class-variance-authority": "^0.7.1",
40
48
  "clsx": "^2.1.1",
@@ -61,13 +69,16 @@
61
69
  "tailwind-merge": "^3.6.0",
62
70
  "tailwindcss": "^4.3.0",
63
71
  "tw-animate-css": "^1.4.0",
64
- "vaul": "^1.1.2"
72
+ "vaul": "^1.1.2",
73
+ "zod": "^4.4.3"
65
74
  },
66
75
  "devDependencies": {
67
76
  "@assetpack/core": "^1.7.0",
68
77
  "@biomejs/biome": "latest",
69
78
  "@tailwindcss/typography": "^0.5.19",
70
79
  "@tanstack/devtools-vite": "latest",
80
+ "@tauri-apps/cli": "^2",
81
+ "@tauri-apps/plugin-process": "^2.3.1",
71
82
  "@types/node": "^25.9.1",
72
83
  "@types/react": "^19.2.15",
73
84
  "@types/react-dom": "^19.2.3",
@@ -1,25 +1,25 @@
1
- import { routeTree } from "@/routeTree.gen";
2
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
- import { createRouter, RouterProvider } from "@tanstack/react-router";
4
-
5
- const queryClient = new QueryClient();
6
- const router = createRouter({
7
- routeTree,
8
- defaultPreload: "intent",
9
- scrollRestoration: true,
10
- context: { queryClient },
11
- });
12
-
13
- declare module "@tanstack/react-router" {
14
- interface Register {
15
- router: typeof router;
16
- }
17
- }
18
-
19
- export default function App() {
20
- return (
21
- <QueryClientProvider client={queryClient}>
22
- <RouterProvider router={router} />
23
- </QueryClientProvider>
24
- );
25
- }
1
+ import { routeTree } from "@/routeTree.gen";
2
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
+ import { createRouter, RouterProvider } from "@tanstack/react-router";
4
+
5
+ const queryClient = new QueryClient();
6
+ const router = createRouter({
7
+ routeTree,
8
+ defaultPreload: "intent",
9
+ scrollRestoration: true,
10
+ context: { queryClient },
11
+ });
12
+
13
+ declare module "@tanstack/react-router" {
14
+ interface Register {
15
+ router: typeof router;
16
+ }
17
+ }
18
+
19
+ export default function App() {
20
+ return (
21
+ <QueryClientProvider client={queryClient}>
22
+ <RouterProvider router={router} />
23
+ </QueryClientProvider>
24
+ );
25
+ }
@@ -1,7 +1,5 @@
1
1
  import generatedManifestJson from "@/assets/manifest.gen.json";
2
2
  import { AUDIO_BUNDLE_NAME } from "@/constants";
3
- import { secondPart } from "@/content/labels/second.label";
4
- import { startLabel } from "@/content/labels/start.label";
5
3
  import type { FileRouteTypes } from "@/routeTree.gen";
6
4
  import type { AssetsManifest } from "@drincs/pixi-vn";
7
5
 
@@ -37,7 +35,7 @@ export const manifest: AssetsManifest = {
37
35
  },
38
36
  // labels
39
37
  {
40
- name: startLabel.id,
38
+ name: "start",
41
39
  assets: [
42
40
  {
43
41
  alias: "bg01-hallway",
@@ -46,7 +44,7 @@ export const manifest: AssetsManifest = {
46
44
  ],
47
45
  },
48
46
  {
49
- name: secondPart.id,
47
+ name: "second_part",
50
48
  assets: [
51
49
  {
52
50
  alias: "bg02-dorm",
@@ -1 +1,4 @@
1
- []
1
+ [
2
+ "/ink-json/second_part.gen.json",
3
+ "/ink-json/start.gen.json"
4
+ ]
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/constants";
11
11
  import { useSetSearchParamState } from "@/lib/hooks/navigation-hooks";
12
12
  import { useGameProps } from "@/lib/hooks/props-hooks";
13
+ import { useQuit } from "@/lib/hooks/quit-hooks";
13
14
  import { useQueryLastSave } from "@/lib/query/save-query";
14
15
  import { InterfaceSettings } from "@/lib/stores/interface-settings-store";
15
16
  import { cn } from "@/lib/utils";
@@ -17,7 +18,7 @@ import { loadSave } from "@/lib/utils/save-utility";
17
18
  import { canvas, Game, ImageSprite } from "@drincs/pixi-vn";
18
19
  import { useHotkeys } from "@tanstack/react-hotkeys";
19
20
  import { useQueryClient } from "@tanstack/react-query";
20
- import { AlertCircle, CirclePlay, Play, Save, Settings } from "lucide-react";
21
+ import { AlertCircle, CirclePlay, LogOut, Play, Save, Settings } from "lucide-react";
21
22
  import { useCallback, useEffect, useRef, useState } from "react";
22
23
  import { useTranslation } from "react-i18next";
23
24
  import { toast } from "sonner";
@@ -33,6 +34,7 @@ export function MainMenu() {
33
34
  const setSettingsTab = useSetSearchParamState<string>("settings_tab");
34
35
  const [loading, setLoading] = useState(false);
35
36
  const menuRef = useRef<HTMLDivElement>(null);
37
+ const { quit, canQuit } = useQuit();
36
38
 
37
39
  /** Returns all enabled menuitem buttons inside the menu container. */
38
40
  function getMenuItems(): HTMLButtonElement[] {
@@ -186,6 +188,19 @@ export function MainMenu() {
186
188
  {t("settings")}
187
189
  </Button>
188
190
 
191
+ {canQuit && (
192
+ <Button
193
+ role="menuitem"
194
+ onClick={quit}
195
+ disabled={loading}
196
+ variant="outline"
197
+ className={menuButtonClass}
198
+ >
199
+ <LogOut className="size-4" />
200
+ {t("quit")}
201
+ </Button>
202
+ )}
203
+
189
204
  {loading ? (
190
205
  <div
191
206
  className="flex items-center justify-end pt-1 text-muted-foreground"
@@ -3,9 +3,10 @@ import { Button } from "@/components/ui/button";
3
3
  import { Kbd, KbdGroup } from "@/components/ui/kbd";
4
4
  import { Separator } from "@/components/ui/separator";
5
5
  import { useSetSearchParamState } from "@/lib/hooks/navigation-hooks";
6
+ import { useQuit } from "@/lib/hooks/quit-hooks";
6
7
  import { Game } from "@drincs/pixi-vn";
7
8
  import { useLocation, useNavigate } from "@tanstack/react-router";
8
- import { Gamepad2Icon, HistoryIcon, LogOutIcon, SaveIcon } from "lucide-react";
9
+ import { Gamepad2Icon, HistoryIcon, LogOutIcon, PowerIcon, SaveIcon } from "lucide-react";
9
10
  import { useTranslation } from "react-i18next";
10
11
 
11
12
  export function QuickMenus() {
@@ -21,6 +22,7 @@ export function QuickMenus() {
21
22
  </div>
22
23
  {isInGame && <Separator />}
23
24
  {isInGame && <ReturnMainMenuButton />}
25
+ <QuitButton />
24
26
  </div>
25
27
  );
26
28
  }
@@ -101,6 +103,20 @@ export function OpenHistorySettingButton() {
101
103
  );
102
104
  }
103
105
 
106
+ export function QuitButton() {
107
+ const { t } = useTranslation(["ui"]);
108
+ const { quit, canQuit } = useQuit();
109
+
110
+ if (!canQuit) return null;
111
+
112
+ return (
113
+ <Button variant="destructive" onClick={quit}>
114
+ <PowerIcon />
115
+ {t("quit")}
116
+ </Button>
117
+ );
118
+ }
119
+
104
120
  export function SaveLoadMenuButton() {
105
121
  const { t } = useTranslation(["ui"]);
106
122
  const setSettingsOpen = useSetSearchParamState<boolean>("settings");
@@ -20,6 +20,8 @@ import {
20
20
  import { TextDisplaySettings } from "@/lib/stores/text-display-settings-store";
21
21
  import { useQueryClient } from "@tanstack/react-query";
22
22
  import { useSelector } from "@tanstack/react-store";
23
+ import type * as app from "@tauri-apps/api";
24
+ import { getCurrentWindow } from "@tauri-apps/api/window";
23
25
  import {
24
26
  DownloadIcon,
25
27
  FullscreenIcon,
@@ -31,6 +33,12 @@ import {
31
33
  import { useState } from "react";
32
34
  import { useTranslation } from "react-i18next";
33
35
 
36
+ declare global {
37
+ interface Window {
38
+ __TAURI__: typeof app;
39
+ }
40
+ }
41
+
34
42
  export function SystemControls() {
35
43
  return (
36
44
  <div className="flex flex-col gap-4">
@@ -154,7 +162,9 @@ export function FullScreenSettings() {
154
162
  onClick={() => {
155
163
  setLoading(true);
156
164
  let promise: Promise<void>;
157
- if (isFullScreenMode) {
165
+ if (window.__TAURI__) {
166
+ promise = getCurrentWindow().setFullscreen(!isFullScreenMode);
167
+ } else if (isFullScreenMode) {
158
168
  promise = document.exitFullscreen();
159
169
  } else {
160
170
  promise = document.documentElement.requestFullscreen();
@@ -0,0 +1,55 @@
1
+ import { steam } from "@/lib/steam";
2
+ import { RegisteredCharacters } from "@drincs/pixi-vn";
3
+ import { HashtagCommands } from "@drincs/pixi-vn-ink";
4
+ import zod from "zod";
5
+
6
+ HashtagCommands.add(
7
+ async (script, { navigate }) => {
8
+ await navigate({ to: script[1] });
9
+ return true;
10
+ },
11
+ {
12
+ name: "navigate",
13
+ description: `Navigates to a specified route within the game.
14
+
15
+ \`\`\`ink
16
+ # navigate <route>
17
+ \`\`\``,
18
+ validation: zod.tuple([zod.literal("navigate"), zod.string()]),
19
+ },
20
+ );
21
+
22
+ HashtagCommands.add(
23
+ async (script) => {
24
+ const character = RegisteredCharacters.get(script[1]);
25
+ if (character) {
26
+ character.name = script[2];
27
+ }
28
+ return true;
29
+ },
30
+ {
31
+ name: "character rename",
32
+ description: `Renames a character in the game.
33
+
34
+ \`\`\`ink
35
+ # rename <characterId> <newName>
36
+ \`\`\``,
37
+ validation: zod.tuple([zod.literal("rename"), zod.string(), zod.string()]),
38
+ },
39
+ );
40
+
41
+ HashtagCommands.add(
42
+ async (script) => {
43
+ await steam.unlockAchievement(script[2]);
44
+ return true;
45
+ },
46
+ {
47
+ name: "achievement",
48
+ description: `Unlocks a Steam achievement.
49
+
50
+ \`\`\`ink
51
+ # unlock achievement <achievementId>
52
+ \`\`\``,
53
+ validation: zod.tuple([zod.literal("unlock"), zod.literal("achievement"), zod.string()]),
54
+ },
55
+ );
@@ -0,0 +1,19 @@
1
+ import { RegisteredCharacters } from "@drincs/pixi-vn";
2
+ import { TextReplaces } from "@drincs/pixi-vn-ink";
3
+
4
+ TextReplaces.add((key) => RegisteredCharacters.get(key)?.name, {
5
+ name: "character name",
6
+ validation: "characterId",
7
+ type: "after-translation",
8
+ i18nInterpolation: true,
9
+ description: "Replaces a character ID with the character's name in the game.",
10
+ });
11
+
12
+ TextReplaces.add(() => "Stephanie", {
13
+ name: "steph_fullname",
14
+ validation: /steph_fullname/,
15
+ type: "after-translation",
16
+ i18nInterpolation: true,
17
+ description:
18
+ "Replaces the placeholder 'steph_fullname' with the full name of the character Stephanie.",
19
+ });
@@ -0,0 +1,12 @@
1
+ import { onInkTranslate } from "@drincs/pixi-vn-ink";
2
+ import { useEffect } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+
5
+ export default function useInkInitialization() {
6
+ const { t } = useTranslation(["narration"]);
7
+ useEffect(() => {
8
+ onInkTranslate(t);
9
+ }, [t]);
10
+
11
+ return null;
12
+ }
@@ -0,0 +1,24 @@
1
+ import { useAlertDialog } from "@/components/providers/alert-dialog-provider";
2
+ import { exit } from "@tauri-apps/plugin-process";
3
+ import { useCallback } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ export function useQuit() {
7
+ const { openAlertDialog } = useAlertDialog();
8
+ const { t } = useTranslation(["ui"]);
9
+
10
+ const canQuit = typeof window !== "undefined" && !!window.__TAURI__;
11
+
12
+ const quit = useCallback(() => {
13
+ openAlertDialog({
14
+ head: t("quit"),
15
+ content: t("quit_confirm"),
16
+ onConfirm: async () => {
17
+ await exit();
18
+ return true;
19
+ },
20
+ });
21
+ }, [openAlertDialog, t]);
22
+
23
+ return { quit, canQuit };
24
+ }
@@ -1,3 +1,4 @@
1
+ import { generateJsonInkTranslation } from "@drincs/pixi-vn-ink";
1
2
  import i18n, { type ReadCallback } from "i18next";
2
3
  import LanguageDetector from "i18next-browser-languagedetector";
3
4
  import ChainedBackend from "i18next-chained-backend";
@@ -44,9 +45,29 @@ function getLocalesResource(lng: string): Promise<Record<string, Record<string,
44
45
  return import(`./../locales/${lng}.json`);
45
46
  }
46
47
 
48
+ async function generateResourceToTranslate(lng: string): Promise<any> {
49
+ let res = await getLocalesResource(lng);
50
+ res = { ...res };
51
+ if (!res) {
52
+ res = {};
53
+ }
54
+ if (!res.narration) {
55
+ res.narration = {};
56
+ }
57
+ if (res.default) {
58
+ delete res.default;
59
+ }
60
+ const manifest = await import("@/assets/ink-manifest.gen.json");
61
+ for (const path of manifest.default) {
62
+ const element = await fetch(path).then((r) => r.json());
63
+ element && (await generateJsonInkTranslation(element, res.narration));
64
+ }
65
+ return res;
66
+ }
67
+
47
68
  export async function downloadResourceToTranslate() {
48
69
  const lng = i18n.options.fallbackLng?.toString() || "en";
49
- const data = await getLocalesResource(lng);
70
+ const data = await generateResourceToTranslate(lng);
50
71
  const jsonString = JSON.stringify(data);
51
72
  // download the save data as a JSON file
52
73
  const blob = new Blob([jsonString], { type: "application/json" });
@@ -1,10 +1,16 @@
1
1
  import { useQuery } from "@tanstack/react-query";
2
+ import { getCurrentWindow } from "@tauri-apps/api/window";
2
3
 
3
4
  export const IS_FULL_SCREEN_MODE_USE_QUERY_KEY = "is_full_screen_mode_use_query_key";
4
5
 
5
6
  export function useQueryIsFullModeScreen() {
6
7
  return useQuery({
7
8
  queryKey: [IS_FULL_SCREEN_MODE_USE_QUERY_KEY],
8
- queryFn: async () => document.fullscreenElement !== null,
9
+ queryFn: async () => {
10
+ if (window.__TAURI__) {
11
+ return getCurrentWindow().isFullscreen();
12
+ }
13
+ return document.fullscreenElement !== null;
14
+ },
9
15
  });
10
16
  }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Steam utility — thin wrappers around Tauri commands exposed by the
3
+ * `steam` Cargo feature. All functions are safe to call even when:
4
+ * - the app is running outside Tauri (web mode)
5
+ * - Steam is not running
6
+ * - the `steam` feature was not compiled in
7
+ * In all those cases the functions return sensible defaults (null / false / 0)
8
+ * without throwing.
9
+ *
10
+ * Enable Steam in Rust:
11
+ * src-tauri/Cargo.toml → default = ["steam"] (or pass --features steam)
12
+ *
13
+ * Typical usage:
14
+ * import { steam } from "@/lib/steam";
15
+ *
16
+ * const name = await steam.getPlayerName(); // "Alice" | null
17
+ * await steam.unlockAchievement("ACH_COMPLETE_CH1"); // fire & forget
18
+ * await steam.openOverlay("achievements");
19
+ */
20
+
21
+ import { invoke } from "@tauri-apps/api/core";
22
+
23
+ const isTauri = typeof window !== "undefined" && "__TAURI__" in window;
24
+
25
+ /** Dialogs supported by the Steam overlay. */
26
+ export type SteamOverlayDialog =
27
+ | "achievements"
28
+ | "community"
29
+ | "friends"
30
+ | "players"
31
+ | "settings"
32
+ | "officialgamegroup"
33
+ | "stats";
34
+
35
+ async function call<T>(cmd: string, args?: Record<string, unknown>): Promise<T | null> {
36
+ if (!isTauri) return null;
37
+ try {
38
+ return await invoke<T>(cmd, args);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ // ── API ───────────────────────────────────────────────────────────────────────
45
+
46
+ export namespace steam {
47
+ /** `true` when Steam was initialised successfully (Steam client running). */
48
+ export async function isAvailable(): Promise<boolean> {
49
+ return (await call<boolean>("steam_is_available")) ?? false;
50
+ }
51
+
52
+ /** Steam display name of the logged-in user. */
53
+ export async function getPlayerName(): Promise<string | null> {
54
+ return call<string>("steam_get_player_name");
55
+ }
56
+
57
+ /** Numeric App ID of the running application. */
58
+ export async function getAppId(): Promise<number | null> {
59
+ return call<number>("steam_get_app_id");
60
+ }
61
+
62
+ // ── Achievements ──────────────────────────────────────────────────────────
63
+
64
+ /**
65
+ * Unlock an achievement and immediately persist it.
66
+ * `id` must match the API Name in Steamworks Partner.
67
+ */
68
+ export async function unlockAchievement(id: string): Promise<void> {
69
+ await call("steam_unlock_achievement", { achievementId: id });
70
+ }
71
+
72
+ /**
73
+ * Returns `true` if the user has already unlocked the achievement.
74
+ * Reliable only after the first few seconds of launch (stats are fetched
75
+ * automatically at startup).
76
+ */
77
+ export async function isAchievementUnlocked(id: string): Promise<boolean> {
78
+ return (await call<boolean>("steam_is_achievement_unlocked", { achievementId: id })) ?? false;
79
+ }
80
+
81
+ /** Reset an achievement — intended for development / testing only. */
82
+ export async function clearAchievement(id: string): Promise<void> {
83
+ await call("steam_clear_achievement", { achievementId: id });
84
+ }
85
+
86
+ // ── Stats ─────────────────────────────────────────────────────────────────
87
+
88
+ /** Set an integer stat. Remember to call `storeStats()` afterwards. */
89
+ export async function setStatInt(name: string, value: number): Promise<void> {
90
+ await call("steam_set_stat_int", { name, value: Math.trunc(value) });
91
+ }
92
+
93
+ /** Read an integer stat (returns `0` on error). */
94
+ export async function getStatInt(name: string): Promise<number> {
95
+ return (await call<number>("steam_get_stat_int", { name })) ?? 0;
96
+ }
97
+
98
+ /** Set a float stat. Remember to call `storeStats()` afterwards. */
99
+ export async function setStatFloat(name: string, value: number): Promise<void> {
100
+ await call("steam_set_stat_float", { name, value });
101
+ }
102
+
103
+ /** Read a float stat (returns `0` on error). */
104
+ export async function getStatFloat(name: string): Promise<number> {
105
+ return (await call<number>("steam_get_stat_float", { name })) ?? 0;
106
+ }
107
+
108
+ /**
109
+ * Commit pending stat changes to Steam servers.
110
+ * `unlockAchievement` / `clearAchievement` already call this automatically;
111
+ * you only need this when using `setStatInt` / `setStatFloat` directly.
112
+ */
113
+ export async function storeStats(): Promise<void> {
114
+ await call("steam_store_stats");
115
+ }
116
+
117
+ // ── DLC ───────────────────────────────────────────────────────────────────
118
+
119
+ /** `true` if the user owns and has installed the DLC with the given App ID. */
120
+ export async function isDlcInstalled(appId: number): Promise<boolean> {
121
+ return (await call<boolean>("steam_is_dlc_installed", { appId })) ?? false;
122
+ }
123
+
124
+ // ── Overlay ───────────────────────────────────────────────────────────────
125
+
126
+ /**
127
+ * Open the Steam overlay to a specific dialog.
128
+ *
129
+ * Common values: "achievements", "friends", "community", "stats",
130
+ * "settings", "officialgamegroup", "players"
131
+ */
132
+ export async function openOverlay(dialog: SteamOverlayDialog): Promise<void> {
133
+ await call("steam_open_overlay", { dialog });
134
+ }
135
+
136
+ /**
137
+ * Open the Steam store page for this game.
138
+ * Pass a different `appId` to open another game's page.
139
+ */
140
+ export async function openStore(appId?: number): Promise<void> {
141
+ await call("steam_open_store", { appId: appId ?? null });
142
+ }
143
+ }
@@ -15,6 +15,8 @@ declare module "@drincs/pixi-vn/narration" {
15
15
  "animation_01": never;
16
16
  "second_part": never;
17
17
  "start": never;
18
+ "start_|_c-0": never;
19
+ "start_|_c-1": never;
18
20
  }
19
21
  }
20
22
  export {};
@@ -3,6 +3,7 @@ import { SettingsDialogue } from "@/components/menus/settings";
3
3
  import { OfflineAllert } from "@/components/modals/error-allerts";
4
4
  import { RootProvider } from "@/components/providers/root-provider";
5
5
  import { INTERFACE_DATA_USE_QUERY_KEY } from "@/constants";
6
+ import useInkInitialization from "@/lib/hooks/ink-hooks";
6
7
  import { useConfirmBackNavigation } from "@/lib/hooks/navigation-hooks";
7
8
  import { useAutoSaveOnPageClose } from "@/lib/hooks/save-hooks";
8
9
  import { useI18n } from "@/lib/i18n";
@@ -11,6 +12,7 @@ import { defineAssets } from "@/lib/utils/assets-utility";
11
12
  import { initializeIndexedDB } from "@/lib/utils/db-utility";
12
13
  import { loadRefreshSave } from "@/lib/utils/save-utility";
13
14
  import type { RouterContext } from "@/router";
15
+ import { setupInkHmrListener } from "@drincs/pixi-vn-ink/vite-listener";
14
16
  import { setupPixivnViteData } from "@drincs/pixi-vn/vite-listener";
15
17
  import { TanStackDevtools } from "@tanstack/react-devtools";
16
18
  import { hotkeysDevtoolsPlugin } from "@tanstack/react-hotkeys-devtools";
@@ -26,6 +28,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
26
28
  // Game.onNavigate(async (to) => redirect({ to }));
27
29
  await Promise.all([import("@/content"), initializeIndexedDB(), defineAssets(), useI18n()]);
28
30
  await setupPixivnViteData();
31
+ await setupInkHmrListener();
29
32
  if (location.pathname !== "/") {
30
33
  const isRefreshSaveExist = await loadRefreshSave();
31
34
  if (isRefreshSaveExist) {
@@ -43,6 +46,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
43
46
  });
44
47
 
45
48
  function RootComponent() {
49
+ useInkInitialization();
46
50
  useAutoSaveOnPageClose();
47
51
  useConfirmBackNavigation();
48
52
 
@@ -0,0 +1,50 @@
1
+ [package]
2
+ name = "my-app-package-name"
3
+ version = "0.1.0"
4
+ description = "my-app-description"
5
+ authors = ["you"]
6
+ edition = "2021"
7
+
8
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9
+
10
+ # ── Steam (optional) ─────────────────────────────────────────────────────────
11
+ # To enable Steam support:
12
+ # 1. Set your App ID in steam_appid.txt (root of the repo, use 480 for testing)
13
+ # 2. Change `default = []` to `default = ["steam"]` below, or pass
14
+ # `--features steam` to cargo / tauri build.
15
+ # 3. On Windows, ship steam_api64.dll next to the executable.
16
+ # 4. On Linux, ship libsteam_api.so next to the executable.
17
+ # 5. On macOS, ship libsteam_api.dylib next to the executable.
18
+ # (The steamworks-sys crate copies the library to the build output
19
+ # automatically; you only need to include it in the Tauri bundle.)
20
+ # ─────────────────────────────────────────────────────────────────────────────
21
+ [features]
22
+ default = []
23
+ steam = ["dep:steamworks"]
24
+
25
+ [lib]
26
+ # The `_lib` suffix may seem redundant but it is necessary
27
+ # to make the lib name unique and wouldn't conflict with the bin name.
28
+ # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
29
+ name = "my_app_lib"
30
+ crate-type = ["staticlib", "cdylib", "rlib"]
31
+
32
+ [build-dependencies]
33
+ tauri-build = { version = "2", features = [] }
34
+
35
+ [dependencies]
36
+ tauri = { version = "2", features = [] }
37
+ tauri-plugin-opener = "2"
38
+ tauri-plugin-process = "2"
39
+ tauri-plugin-window-state = "2"
40
+ serde = { version = "1", features = ["derive"] }
41
+ serde_json = "1"
42
+ steamworks = { version = "0.13", optional = true }
43
+
44
+ [profile.release]
45
+ opt-level = "z" # max runtime performance
46
+ lto = true # link-time optimization — removes dead code across crates
47
+ codegen-units = 1 # slower compile, smaller output
48
+ panic = "abort" # no stack unwinding machinery
49
+ strip = true # strip symbols from the binary
50
+