diff --git a/app/src/app.html b/app/src/app.html index dc71b5f..bd6529a 100644 --- a/app/src/app.html +++ b/app/src/app.html @@ -3,6 +3,7 @@ + %sveltekit.head% Nodes diff --git a/app/vite.config.ts b/app/vite.config.ts index 4685f32..bc99260 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -1,10 +1,10 @@ import { sveltekit } from '@sveltejs/kit/vite'; import tailwindcss from '@tailwindcss/vite'; import { playwright } from '@vitest/browser-playwright'; +import path from 'path'; import comlink from 'vite-plugin-comlink'; import glsl from 'vite-plugin-glsl'; import wasm from 'vite-plugin-wasm'; -import path from 'path'; import { defineConfig } from 'vitest/config'; export default defineConfig({ diff --git a/packages/planty/.prettierignore b/packages/planty/.prettierignore deleted file mode 100644 index 7d74fe2..0000000 --- a/packages/planty/.prettierignore +++ /dev/null @@ -1,9 +0,0 @@ -# Package Managers -package-lock.json -pnpm-lock.yaml -yarn.lock -bun.lock -bun.lockb - -# Miscellaneous -/static/ diff --git a/packages/planty/.prettierrc b/packages/planty/.prettierrc deleted file mode 100644 index 819fa57..0000000 --- a/packages/planty/.prettierrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], - "overrides": [ - { - "files": "*.svelte", - "options": { - "parser": "svelte" - } - } - ], - "tailwindStylesheet": "./src/routes/layout.css" -} diff --git a/packages/planty/eslint.config.js b/packages/planty/eslint.config.js index 0014edd..f0dc0ef 100644 --- a/packages/planty/eslint.config.js +++ b/packages/planty/eslint.config.js @@ -1,44 +1,44 @@ -import prettier from 'eslint-config-prettier'; -import path from 'node:path'; import { includeIgnoreFile } from '@eslint/compat'; import js from '@eslint/js'; +import prettier from 'eslint-config-prettier'; import svelte from 'eslint-plugin-svelte'; import { defineConfig } from 'eslint/config'; import globals from 'globals'; +import path from 'node:path'; import ts from 'typescript-eslint'; import svelteConfig from './svelte.config.js'; const gitignorePath = path.resolve(import.meta.dirname, '.gitignore'); export default defineConfig( - includeIgnoreFile(gitignorePath), - js.configs.recommended, - ts.configs.recommended, - svelte.configs.recommended, - prettier, - svelte.configs.prettier, - { - languageOptions: { globals: { ...globals.browser, ...globals.node } }, - rules: { - // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. - // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors - 'no-undef': 'off' - } - }, - { - files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], - languageOptions: { - parserOptions: { - projectService: true, - extraFileExtensions: ['.svelte'], - parser: ts.parser, - svelteConfig - } - } - }, - { - // Override or add rule settings here, such as: - // 'svelte/button-has-type': 'error' - rules: {} - } + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ts.configs.recommended, + svelte.configs.recommended, + prettier, + svelte.configs.prettier, + { + languageOptions: { globals: { ...globals.browser, ...globals.node } }, + rules: { + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off' + } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + }, + { + // Override or add rule settings here, such as: + // 'svelte/button-has-type': 'error' + rules: {} + } ); diff --git a/packages/planty/package.json b/packages/planty/package.json index 976b5d2..274f87d 100644 --- a/packages/planty/package.json +++ b/packages/planty/package.json @@ -9,8 +9,9 @@ "prepack": "svelte-kit sync && svelte-package && publint", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check . && eslint .", - "format": "prettier --write ." + "lint": "eslint .", + "format": "dprint fmt -c '../.dprint.jsonc' .", + "format:check": "dprint check -c '../.dprint.jsonc' ." }, "files": [ "dist", diff --git a/packages/planty/src/app.d.ts b/packages/planty/src/app.d.ts index da08e6d..520c421 100644 --- a/packages/planty/src/app.d.ts +++ b/packages/planty/src/app.d.ts @@ -1,13 +1,13 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/packages/planty/src/app.html b/packages/planty/src/app.html index 1bb93e3..9d8295f 100644 --- a/packages/planty/src/app.html +++ b/packages/planty/src/app.html @@ -1,13 +1,13 @@ - + - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/packages/planty/src/lib/components/Highlight.svelte b/packages/planty/src/lib/components/Highlight.svelte index 3d659d5..386909b 100644 --- a/packages/planty/src/lib/components/Highlight.svelte +++ b/packages/planty/src/lib/components/Highlight.svelte @@ -80,7 +80,7 @@ {#if rect}
@keyframes pulse { - 0%, 100% { + 0%, + 100% { box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45), 0 0 0 2px rgba(255, 255, 255, 0.9), diff --git a/packages/planty/src/lib/components/Planty.svelte b/packages/planty/src/lib/components/Planty.svelte index 3837fa2..9f10573 100644 --- a/packages/planty/src/lib/components/Planty.svelte +++ b/packages/planty/src/lib/components/Planty.svelte @@ -13,12 +13,7 @@ onComplete?: () => void; } - let { - config, - hooks = {}, - onStepChange, - onComplete - }: Props = $props(); + let { config, hooks = {}, onStepChange, onComplete }: Props = $props(); const AVATAR_SIZE = 80; const SCREEN_PADDING = 20; @@ -182,11 +177,7 @@ {#if isActive}
{#if highlight} - + {/if} diff --git a/packages/planty/src/lib/components/PlantyAvatar.svelte b/packages/planty/src/lib/components/PlantyAvatar.svelte index 7f5d257..a039790 100644 --- a/packages/planty/src/lib/components/PlantyAvatar.svelte +++ b/packages/planty/src/lib/components/PlantyAvatar.svelte @@ -82,14 +82,10 @@ } const left = $derived( - displayMood === 'talking' - ? { px: 0, py: 0 } - : pupilOffset(cursorX, cursorY, 9.5, 30.5) + displayMood === 'talking' ? { px: 0, py: 0 } : pupilOffset(cursorX, cursorY, 9.5, 30.5) ); const right = $derived( - displayMood === 'talking' - ? { px: 0, py: 0 } - : pupilOffset(cursorX, cursorY, 31.5, 35.5) + displayMood === 'talking' ? { px: 0, py: 0 } : pupilOffset(cursorX, cursorY, 31.5, 35.5) ); @@ -176,10 +172,10 @@ cursor: grab; user-select: none; pointer-events: auto; - filter: drop-shadow(0px 0px 10px black); + filter: drop-shadow(0px 0px 10px black); transition: left 0.85s cubic-bezier(0.33, 1, 0.68, 1), - top 0.85s cubic-bezier(0.33, 1, 0.68, 1), + top 0.85s cubic-bezier(0.33, 1, 0.68, 1); } .dragging { @@ -189,49 +185,83 @@ /* idle: steady vertical bob */ @keyframes bob { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-5px); } + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-5px); + } + } + .mood-idle { + animation: bob 2.6s ease-in-out infinite; + } + .mood-happy { + animation: bob 1.8s ease-in-out infinite; } - .mood-idle { animation: bob 2.6s ease-in-out infinite; } - .mood-happy { animation: bob 1.8s ease-in-out infinite; } /* thinking: head tilted to the side — clearly different from idle */ @keyframes think { - 0%, 100% { transform: rotate(-12deg) translateY(0); } - 50% { transform: rotate(-12deg) translateY(-3px); } + 0%, + 100% { + transform: rotate(-12deg) translateY(0); + } + 50% { + transform: rotate(-12deg) translateY(-3px); + } + } + .mood-thinking { + animation: think 2.8s ease-in-out infinite; } - .mood-thinking { animation: think 2.8s ease-in-out infinite; } /* talking: subtle head waggle */ @keyframes waggle { - 0%, 100% { transform: rotate(0deg); } - 25% { transform: rotate(-2deg) translateY(-1px); } - 75% { transform: rotate(2deg) translateY(1px); } + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(-2deg) translateY(-1px); + } + 75% { + transform: rotate(2deg) translateY(1px); + } + } + .mood-talking { + animation: waggle 0.3s ease-in-out infinite; } - .mood-talking { animation: waggle 0.3s ease-in-out infinite; } /* moving: forward-lean glide */ @keyframes glide { - 0%, 100% { transform: translateY(0) rotate(-6deg); } - 50% { transform: translateY(-8px) rotate(-4deg); } + 0%, + 100% { + transform: translateY(0) rotate(-6deg); + } + 50% { + transform: translateY(-8px) rotate(-4deg); + } + } + .mood-moving { + animation: glide 0.4s ease-in-out infinite; } - .mood-moving { animation: glide 0.4s ease-in-out infinite; } /* ── Drop shadows ────────────────────────────────────────────────── */ .body { filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.5)); transition: d 0.12s ease-in-out; } - .eye-left, .eye-right { + .eye-left, + .eye-right { filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.5)); } .mood-talking { - .eye-left, .eye-right { - > g { - transition: transform 0.5s ease-in-out; - } - } + .eye-left, + .eye-right { + > g { + transition: transform 0.5s ease-in-out; + } + } } /* ── Leaves ──────────────────────────────────────────────────────── */ @@ -246,74 +276,154 @@ /* idle: slow gentle breathing wave */ @keyframes idle-right { - 0%, 100% { transform: rotate(0deg); } - 50% { transform: rotate(-9deg); } + 0%, + 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(-9deg); + } } @keyframes idle-left { - 0%, 100% { transform: rotate(0deg); } - 50% { transform: rotate(7deg); } + 0%, + 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(7deg); + } + } + .mood-idle .leave-right { + animation: idle-right 3s ease-in-out infinite; + } + .mood-idle .leave-left { + animation: idle-left 3s ease-in-out infinite 0.15s; } - .mood-idle .leave-right { animation: idle-right 3s ease-in-out infinite; } - .mood-idle .leave-left { animation: idle-left 3s ease-in-out infinite 0.15s; } /* thinking: wings held raised, minimal drift */ @keyframes think-right { - 0%, 100% { transform: rotate(-14deg); } - 50% { transform: rotate(-10deg); } + 0%, + 100% { + transform: rotate(-14deg); + } + 50% { + transform: rotate(-10deg); + } } @keyframes think-left { - 0%, 100% { transform: rotate(10deg); } - 50% { transform: rotate(7deg); } + 0%, + 100% { + transform: rotate(10deg); + } + 50% { + transform: rotate(7deg); + } + } + .mood-thinking .leave-right { + animation: think-right 4s ease-in-out infinite; + } + .mood-thinking .leave-left { + animation: think-left 4s ease-in-out infinite 0.3s; } - .mood-thinking .leave-right { animation: think-right 4s ease-in-out infinite; } - .mood-thinking .leave-left { animation: think-left 4s ease-in-out infinite 0.3s; } /* talking: nearly still — tiny passive counter-sway */ @keyframes talk-right { - 0%, 100% { transform: rotate(-2deg); } - 50% { transform: rotate(2deg); } + 0%, + 100% { + transform: rotate(-2deg); + } + 50% { + transform: rotate(2deg); + } } @keyframes talk-left { - 0%, 100% { transform: rotate(2deg); } - 50% { transform: rotate(-2deg); } + 0%, + 100% { + transform: rotate(2deg); + } + 50% { + transform: rotate(-2deg); + } + } + .mood-talking .leave-right { + animation: talk-right 0.6s ease-in-out infinite; + } + .mood-talking .leave-left { + animation: talk-left 0.6s ease-in-out infinite 0.1s; } - .mood-talking .leave-right { animation: talk-right 0.6s ease-in-out infinite; } - .mood-talking .leave-left { animation: talk-left 0.6s ease-in-out infinite 0.1s; } /* happy: light casual flap */ @keyframes happy-right { - 0%, 100% { transform: rotate(0deg); } - 50% { transform: rotate(-18deg); } + 0%, + 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(-18deg); + } } @keyframes happy-left { - 0%, 100% { transform: rotate(0deg); } - 50% { transform: rotate(13deg); } + 0%, + 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(13deg); + } + } + .mood-happy .leave-right { + animation: happy-right 1.4s ease-in-out infinite; + } + .mood-happy .leave-left { + animation: happy-left 1.4s ease-in-out infinite 0.1s; } - .mood-happy .leave-right { animation: happy-right 1.4s ease-in-out infinite; } - .mood-happy .leave-left { animation: happy-left 1.4s ease-in-out infinite 0.1s; } /* moving: vigorous wing flap — full range, fast */ @keyframes flap-right { - 0%, 100% { transform: rotate(0deg); } - 40% { transform: rotate(-40deg); } + 0%, + 100% { + transform: rotate(0deg); + } + 40% { + transform: rotate(-40deg); + } } @keyframes flap-left { - 0%, 100% { transform: rotate(0deg); } - 40% { transform: rotate(26deg); } + 0%, + 100% { + transform: rotate(0deg); + } + 40% { + transform: rotate(26deg); + } + } + .mood-moving .leave-right { + animation: flap-right 0.34s ease-in-out infinite; + } + .mood-moving .leave-left { + animation: flap-left 0.34s ease-in-out infinite 0.04s; } - .mood-moving .leave-right { animation: flap-right 0.34s ease-in-out infinite; } - .mood-moving .leave-left { animation: flap-left 0.34s ease-in-out infinite 0.04s; } /* ── Eye blink (on pupil so it doesn't fight cursor translate) ───── */ @keyframes blink { - 0%, 93%, 100% { transform: scaleY(1); } - 96% { transform: scaleY(0.05); } + 0%, + 93%, + 100% { + transform: scaleY(1); + } + 96% { + transform: scaleY(0.05); + } } .pupil { transform-box: fill-box; transform-origin: center; animation: blink 4s ease-in-out infinite; } - .eye-left .pupil { animation-delay: 0s; } - .eye-right .pupil { animation-delay: 0.07s; } + .eye-left .pupil { + animation-delay: 0s; + } + .eye-right .pupil { + animation-delay: 0.07s; + } diff --git a/packages/planty/src/lib/components/SpeechBubble.svelte b/packages/planty/src/lib/components/SpeechBubble.svelte index 64f41b8..4a6b2ed 100644 --- a/packages/planty/src/lib/components/SpeechBubble.svelte +++ b/packages/planty/src/lib/components/SpeechBubble.svelte @@ -55,7 +55,7 @@ '$1' ) .replaceAll(/\*/, '') - .replaceAll(/\_/, '') + .replaceAll(/_/, '') .replaceAll(/\n+/g, '
'); } @@ -73,7 +73,6 @@ if (i < target.length) { displayed = target.slice(0, ++i); typeTimer = setTimeout(tick, 26); - } else { } } // Defer first tick so no reads happen during the synchronous effect body @@ -86,11 +85,11 @@
@@ -98,7 +97,10 @@
@@ -106,26 +108,29 @@ {:else}
{/if} -
+ {@html renderMarkdown(displayed)}
{#if choices.length > 0}
- {#each choices as choice, i} + {#each choices as choice, i (choice.label)} {#if finished}
- {#each moods as m} + {#each moods as m (m)} @@ -95,7 +101,7 @@ style="border-color: var(--color-outline); background-color: var(--color-layer-0);" > Parameters
Export
{/if} - diff --git a/packages/planty/src/routes/ThemeSelector.svelte b/packages/planty/src/routes/ThemeSelector.svelte index 3f330d9..948a4ba 100644 --- a/packages/planty/src/routes/ThemeSelector.svelte +++ b/packages/planty/src/routes/ThemeSelector.svelte @@ -12,11 +12,8 @@ 'custom' ]; - let { theme = $bindable() } = $props(); - let themeIndex = $state(0); $effect(() => { - theme = themes[themeIndex]; const classList = document.documentElement.classList; for (const c of classList) { if (c.startsWith('theme-')) document.documentElement.classList.remove(c); diff --git a/packages/planty/src/routes/layout.css b/packages/planty/src/routes/layout.css index c84b1e7..471283a 100644 --- a/packages/planty/src/routes/layout.css +++ b/packages/planty/src/routes/layout.css @@ -1,7 +1,7 @@ -@import "tailwindcss"; +@import 'tailwindcss'; body { - color: var(--color-text); - background-color: var(--color-layer-0); - margin: 0; + color: var(--color-text); + background-color: var(--color-layer-0); + margin: 0; } diff --git a/packages/planty/static/demo-tutorial.json b/packages/planty/static/demo-tutorial.json index 0452df3..74cf6ff 100644 --- a/packages/planty/static/demo-tutorial.json +++ b/packages/planty/static/demo-tutorial.json @@ -11,9 +11,9 @@ "position": "bottom-right", "text": "👋 Hey! I'm Planty — your guide to this app. How would you like me to explain things?", "choices": [ - { "label": "🤓 Technical — give me the details", "next": "intro_nerd" }, - { "label": "🌱 Simple — keep it friendly", "next": "intro_simple" }, - { "label": "No thanks, skip the tour", "next": null } + { "label": "🤓 Technical — give me the details", "next": "intro_nerd" }, + { "label": "🌱 Simple — keep it friendly", "next": "intro_simple" }, + { "label": "No thanks, skip the tour", "next": null } ] }, diff --git a/packages/planty/static/favicon.svg b/packages/planty/static/favicon.svg index cc5dc66..8449c0f 100644 --- a/packages/planty/static/favicon.svg +++ b/packages/planty/static/favicon.svg @@ -1 +1,9 @@ -svelte-logo \ No newline at end of file + + svelte-logo + diff --git a/packages/planty/svelte.config.js b/packages/planty/svelte.config.js index 0c3412e..9d2424e 100644 --- a/packages/planty/svelte.config.js +++ b/packages/planty/svelte.config.js @@ -2,16 +2,16 @@ import adapter from '@sveltejs/adapter-auto'; /** @type {import('@sveltejs/kit').Config} */ const config = { - compilerOptions: { - // Force runes mode for the project, except for libraries. Can be removed in svelte 6. - runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true) - }, - kit: { - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() - } + compilerOptions: { + // Force runes mode for the project, except for libraries. Can be removed in svelte 6. + runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true) + }, + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } }; export default config; diff --git a/packages/planty/tsconfig.json b/packages/planty/tsconfig.json index e7c4a9a..d6717f0 100644 --- a/packages/planty/tsconfig.json +++ b/packages/planty/tsconfig.json @@ -1,15 +1,15 @@ { - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "rewriteRelativeImportExtensions": true, - "allowJs": true, - "checkJs": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "module": "NodeNext", - "moduleResolution": "NodeNext" - } + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "module": "NodeNext", + "moduleResolution": "NodeNext" + } } diff --git a/packages/planty/vite.config.ts b/packages/planty/vite.config.ts index 56f40c7..5237e9e 100644 --- a/packages/planty/vite.config.ts +++ b/packages/planty/vite.config.ts @@ -1,5 +1,5 @@ -import tailwindcss from '@tailwindcss/vite'; import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [tailwindcss(), sveltekit()] });