commit e9b0e9d85623f04e58b8c626ef914e1a30a5ba37 Author: Raphaƫl Forment Date: Mon Dec 1 17:42:42 2025 +0100 OK diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dda74cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +dist-ssr/ +*.local + +# Tauri +src-tauri/target/ +src-tauri/WixTools/ +src-tauri/Cargo.lock + +# Rust +**/*.rs.bk +*.pdb + +# Editor +.vscode/* +!.vscode/extensions.json +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +Desktop.ini + +# Env +.env +.env.* +!.env.example + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Cache +.cache/ +*.tsbuildinfo +.eslintcache +.prettiercache + +# Test +coverage/ + +# Temp +*.tmp +*.temp diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..bdef820 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e6cd94f --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Svelte + TS + Vite + +This template should help get you started developing with Svelte and TypeScript in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). + +## Need an official Svelte framework? + +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. + +## Technical considerations + +**Why use this over SvelteKit?** + +- It brings its own routing solution which might not be preferable for some users. +- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. + +This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. + +Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. + +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** + +Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. + +**Why include `.vscode/extensions.json`?** + +Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. + +**Why enable `allowJs` in the TS template?** + +While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. + +**Why is HMR not preserving my local component state?** + +HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). + +If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. + +```ts +// store.ts +// An extremely simple external store +import { writable } from 'svelte/store' +export default writable(0) +``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..4e4c72b --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + buboard + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..b7a11bd --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "buboard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tsconfig/svelte": "^5.0.6", + "@types/node": "^24.10.1", + "svelte": "^5.43.8", + "svelte-check": "^4.3.4", + "typescript": "~5.9.3", + "vite": "npm:rolldown-vite@7.2.5" + }, + "pnpm": { + "overrides": { + "vite": "npm:rolldown-vite@7.2.5" + } + }, + "dependencies": { + "@codemirror/commands": "^6.10.0", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.11", + "@codemirror/language": "^6.11.3", + "@codemirror/state": "^6.5.2", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.38.8", + "@lezer/highlight": "^1.2.3", + "@replit/codemirror-emacs": "^6.1.0", + "@replit/codemirror-vim": "^6.3.0", + "codemirror": "^6.0.2", + "jszip": "^3.10.1", + "lucide-svelte": "^0.555.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..32ae746 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1138 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + vite: npm:rolldown-vite@7.2.5 + +importers: + + .: + dependencies: + '@codemirror/commands': + specifier: ^6.10.0 + version: 6.10.0 + '@codemirror/lang-css': + specifier: ^6.3.1 + version: 6.3.1 + '@codemirror/lang-html': + specifier: ^6.4.11 + version: 6.4.11 + '@codemirror/language': + specifier: ^6.11.3 + version: 6.11.3 + '@codemirror/state': + specifier: ^6.5.2 + version: 6.5.2 + '@codemirror/theme-one-dark': + specifier: ^6.1.3 + version: 6.1.3 + '@codemirror/view': + specifier: ^6.38.8 + version: 6.38.8 + '@lezer/highlight': + specifier: ^1.2.3 + version: 1.2.3 + '@replit/codemirror-emacs': + specifier: ^6.1.0 + version: 6.1.0(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8) + '@replit/codemirror-vim': + specifier: ^6.3.0 + version: 6.3.0(@codemirror/commands@6.10.0)(@codemirror/language@6.11.3)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8) + codemirror: + specifier: ^6.0.2 + version: 6.0.2 + jszip: + specifier: ^3.10.1 + version: 3.10.1 + lucide-svelte: + specifier: ^0.555.0 + version: 0.555.0(svelte@5.45.2) + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.1 + version: 6.2.1(rolldown-vite@7.2.5(@types/node@24.10.1))(svelte@5.45.2) + '@tsconfig/svelte': + specifier: ^5.0.6 + version: 5.0.6 + '@types/node': + specifier: ^24.10.1 + version: 24.10.1 + svelte: + specifier: ^5.43.8 + version: 5.45.2 + svelte-check: + specifier: ^4.3.4 + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: npm:rolldown-vite@7.2.5 + version: rolldown-vite@7.2.5(@types/node@24.10.1) + +packages: + + '@codemirror/autocomplete@6.20.0': + resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} + + '@codemirror/commands@6.10.0': + resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-html@6.4.11': + resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} + + '@codemirror/lang-javascript@6.2.4': + resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} + + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/lint@6.9.2': + resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} + + '@codemirror/search@6.5.11': + resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@6.38.8': + resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} + + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lezer/common@1.4.0': + resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} + + '@lezer/css@1.3.0': + resolution: {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/html@1.3.12': + resolution: {integrity: sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/lr@1.4.4': + resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + + '@oxc-project/runtime@0.97.0': + resolution: {integrity: sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.97.0': + resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==} + + '@replit/codemirror-emacs@6.1.0': + resolution: {integrity: sha512-74DITnht6Cs6sHg02PQ169IKb1XgtyhI9sLD0JeOFco6Ds18PT+dkD8+DgXBDokne9UIFKsBbKPnpFRAz60/Lw==} + peerDependencies: + '@codemirror/autocomplete': ^6.0.2 + '@codemirror/commands': ^6.0.0 + '@codemirror/search': ^6.0.0 + '@codemirror/state': ^6.0.1 + '@codemirror/view': ^6.3.0 + + '@replit/codemirror-vim@6.3.0': + resolution: {integrity: sha512-aTx931ULAMuJx6xLf7KQDOL7CxD+Sa05FktTDrtLaSy53uj01ll3Zf17JdKsriER248oS55GBzg0CfCTjEneAQ==} + peerDependencies: + '@codemirror/commands': 6.x.x + '@codemirror/language': 6.x.x + '@codemirror/search': 6.x.x + '@codemirror/state': 6.x.x + '@codemirror/view': 6.x.x + + '@rolldown/binding-android-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + resolution: {integrity: sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + resolution: {integrity: sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + resolution: {integrity: sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + resolution: {integrity: sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.50': + resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} + + '@sveltejs/acorn-typescript@1.0.8': + resolution: {integrity: sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1': + resolution: {integrity: sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: npm:rolldown-vite@7.2.5 + + '@sveltejs/vite-plugin-svelte@6.2.1': + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: npm:rolldown-vite@7.2.5 + + '@tsconfig/svelte@5.0.6': + resolution: {integrity: sha512-yGxYL0I9eETH1/DR9qVJey4DAsCdeau4a9wYPKuXfEhm8lFO8wg+LLYJjIpAm6Fw7HSlhepPhYPDop75485yWQ==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devalue@5.5.0: + resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + esrap@2.2.0: + resolution: {integrity: sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + lucide-svelte@0.555.0: + resolution: {integrity: sha512-2kWIcstKGgQObLGpYXmcYwm/Y83Am+AQcP7MnybrmaRU1+QUJ/WSEpv/wAbwaSkrWUiN1TX/9FuWQKLeEeWCrQ==} + peerDependencies: + svelte: ^3 || ^4 || ^5.0.0-next.42 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + rolldown-vite@7.2.5: + resolution: {integrity: sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + esbuild: ^0.25.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + rolldown@1.0.0-beta.50: + resolution: {integrity: sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + + svelte-check@4.3.4: + resolution: {integrity: sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte@5.45.2: + resolution: {integrity: sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + peerDependencies: + vite: npm:rolldown-vite@7.2.5 + peerDependenciesMeta: + vite: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + +snapshots: + + '@codemirror/autocomplete@6.20.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + + '@codemirror/commands@6.10.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.4.0 + '@lezer/css': 1.3.0 + + '@codemirror/lang-html@6.4.11': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + '@lezer/css': 1.3.0 + '@lezer/html': 1.3.12 + + '@codemirror/lang-javascript@6.2.4': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.9.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + '@lezer/javascript': 1.5.4 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.4.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.4 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.2': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + crelt: 1.0.6 + + '@codemirror/search@6.5.11': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/highlight': 1.2.3 + + '@codemirror/view@6.38.8': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + + '@emnapi/core@1.7.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lezer/common@1.4.0': {} + + '@lezer/css@1.3.0': + dependencies: + '@lezer/common': 1.4.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.4 + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.4.0 + + '@lezer/html@1.3.12': + dependencies: + '@lezer/common': 1.4.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.4 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.4.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.4 + + '@lezer/lr@1.4.4': + dependencies: + '@lezer/common': 1.4.0 + + '@marijn/find-cluster-break@1.0.2': {} + + '@napi-rs/wasm-runtime@1.0.7': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@oxc-project/runtime@0.97.0': {} + + '@oxc-project/types@0.97.0': {} + + '@replit/codemirror-emacs@6.1.0(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.0 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + + '@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.0)(@codemirror/language@6.11.3)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)': + dependencies: + '@codemirror/commands': 6.10.0 + '@codemirror/language': 6.11.3 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + + '@rolldown/binding-android-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.50': {} + + '@sveltejs/acorn-typescript@1.0.8(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(rolldown-vite@7.2.5(@types/node@24.10.1))(svelte@5.45.2))(rolldown-vite@7.2.5(@types/node@24.10.1))(svelte@5.45.2)': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.1(rolldown-vite@7.2.5(@types/node@24.10.1))(svelte@5.45.2) + debug: 4.4.3 + svelte: 5.45.2 + vite: rolldown-vite@7.2.5(@types/node@24.10.1) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@6.2.1(rolldown-vite@7.2.5(@types/node@24.10.1))(svelte@5.45.2)': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(rolldown-vite@7.2.5(@types/node@24.10.1))(svelte@5.45.2))(rolldown-vite@7.2.5(@types/node@24.10.1))(svelte@5.45.2) + debug: 4.4.3 + deepmerge: 4.3.1 + magic-string: 0.30.21 + svelte: 5.45.2 + vite: rolldown-vite@7.2.5(@types/node@24.10.1) + vitefu: 1.1.1(rolldown-vite@7.2.5(@types/node@24.10.1)) + transitivePeerDependencies: + - supports-color + + '@tsconfig/svelte@5.0.6': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/estree@1.0.8': {} + + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + acorn@8.15.0: {} + + aria-query@5.3.2: {} + + axobject-query@4.1.0: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + clsx@2.1.1: {} + + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.0 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.9.2 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + + core-util-is@1.0.3: {} + + crelt@1.0.6: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deepmerge@4.3.1: {} + + detect-libc@2.1.2: {} + + devalue@5.5.0: {} + + esm-env@1.2.2: {} + + esrap@2.2.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fsevents@2.3.3: + optional: true + + immediate@3.0.6: {} + + inherits@2.0.4: {} + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + isarray@1.0.0: {} + + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + locate-character@3.0.0: {} + + lucide-svelte@0.555.0(svelte@5.45.2): + dependencies: + svelte: 5.45.2 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mri@1.2.0: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + pako@1.0.11: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + process-nextick-args@2.0.1: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readdirp@4.1.2: {} + + rolldown-vite@7.2.5(@types/node@24.10.1): + dependencies: + '@oxc-project/runtime': 0.97.0 + fdir: 6.5.0(picomatch@4.0.3) + lightningcss: 1.30.2 + picomatch: 4.0.3 + postcss: 8.5.6 + rolldown: 1.0.0-beta.50 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + + rolldown@1.0.0-beta.50: + dependencies: + '@oxc-project/types': 0.97.0 + '@rolldown/pluginutils': 1.0.0-beta.50 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-x64': 1.0.0-beta.50 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.50 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.50 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.50 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.50 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.50 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safe-buffer@5.1.2: {} + + setimmediate@1.0.5: {} + + source-map-js@1.2.1: {} + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + style-mod@4.1.3: {} + + svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.45.2 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch + + svelte@5.45.2: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.5.0 + esm-env: 1.2.2 + esrap: 2.2.0 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tslib@2.8.1: + optional: true + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + util-deprecate@1.0.2: {} + + vitefu@1.1.1(rolldown-vite@7.2.5(@types/node@24.10.1)): + optionalDependencies: + vite: rolldown-vite@7.2.5(@types/node@24.10.1) + + w3c-keyname@2.2.8: {} + + zimmerframe@1.1.4: {} diff --git a/public/fonts/DepartureMono-Regular.woff b/public/fonts/DepartureMono-Regular.woff new file mode 100644 index 0000000..b7bb672 Binary files /dev/null and b/public/fonts/DepartureMono-Regular.woff differ diff --git a/public/fonts/DepartureMono-Regular.woff2 b/public/fonts/DepartureMono-Regular.woff2 new file mode 100644 index 0000000..b4a23dc Binary files /dev/null and b/public/fonts/DepartureMono-Regular.woff2 differ diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..b74fa26 --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,163 @@ + + + + {@html ``} + + + + +
+ {#if !interfaceHidden} + (interfaceHidden = true)} /> + {/if} +
+
+ +
+ {#if showEditor && !interfaceHidden} + +
+
+ {#if editingItem} + appState.edit(null)} /> + {:else} + appState.editGlobal(false)} /> + {/if} +
+ {/if} +
+ {#if interfaceHidden} +
+ + {#each ['1', '2', '3', '4', '5', '6', '7', '8', '9'] as key} + + {/each} +
+ {/if} +
+ + diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..6484edc --- /dev/null +++ b/src/app.css @@ -0,0 +1,28 @@ +@font-face { + font-family: 'Departure Mono'; + src: url('/fonts/DepartureMono-Regular.woff2') format('woff2'), + url('/fonts/DepartureMono-Regular.woff') format('woff'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + font-family: 'Departure Mono', monospace; + color: var(--text, #fff); + background: var(--bg, #1a1a1a); +} + +html, +body, +#app { + width: 100%; + height: 100%; + overflow: hidden; +} diff --git a/src/lib/Canvas.svelte b/src/lib/Canvas.svelte new file mode 100644 index 0000000..224e91b --- /dev/null +++ b/src/lib/Canvas.svelte @@ -0,0 +1,174 @@ + + + + + +
+
+ {#each state.manifest.items as item (item.id)} + + {/each} +
+
+ + diff --git a/src/lib/Editor.svelte b/src/lib/Editor.svelte new file mode 100644 index 0000000..e0e2941 --- /dev/null +++ b/src/lib/Editor.svelte @@ -0,0 +1,258 @@ + + +
+
+
+ {#if mode === 'item'} + + + {/if} + + +
+ + +
+
+ {#if mode === 'item'} +
+
+ {/if} +
+
+
+
+ + diff --git a/src/lib/Item.svelte b/src/lib/Item.svelte new file mode 100644 index 0000000..5fcc7dd --- /dev/null +++ b/src/lib/Item.svelte @@ -0,0 +1,298 @@ + + + + +
+ + + {#if !isFocused} +
+ {/if} + + {#if isSelected} +
+ +
handleResizeStart(e, 'nw')}>
+ +
handleResizeStart(e, 'ne')}>
+ +
handleResizeStart(e, 'sw')}>
+ +
handleResizeStart(e, 'se')}>
+ +
+
+
+ {/if} +
+ + diff --git a/src/lib/Palette.svelte b/src/lib/Palette.svelte new file mode 100644 index 0000000..be5f16e --- /dev/null +++ b/src/lib/Palette.svelte @@ -0,0 +1,224 @@ + + +
+ + + + + + + + + + +
+ + diff --git a/src/lib/Toolbar.svelte b/src/lib/Toolbar.svelte new file mode 100644 index 0000000..9e8b84b --- /dev/null +++ b/src/lib/Toolbar.svelte @@ -0,0 +1,207 @@ + + +
+ Buboard + +
+ {#each flagKeys as key} + + {/each} +
+
+
+ + {zoomPercent}% +
+ + + + + {#if onHide} + + {/if} + +
+ + diff --git a/src/lib/geometry.ts b/src/lib/geometry.ts new file mode 100644 index 0000000..2306595 --- /dev/null +++ b/src/lib/geometry.ts @@ -0,0 +1,81 @@ +export interface Point { + x: number; + y: number; +} + +export function calculateCenterOffset( + corner: string, + deltaWidth: number, + deltaHeight: number, + rotation: number +): Point { + const rad = (rotation * Math.PI) / 180; + const cos = Math.cos(rad); + const sin = Math.sin(rad); + + let localDx = 0; + let localDy = 0; + + if (corner.includes('w')) localDx = -deltaWidth / 2; + else if (corner.includes('e')) localDx = deltaWidth / 2; + + if (corner.includes('n')) localDy = -deltaHeight / 2; + else if (corner.includes('s')) localDy = deltaHeight / 2; + + return { + x: localDx * cos - localDy * sin, + y: localDx * sin + localDy * cos + }; +} + +export function constrainToAspectRatio( + newWidth: number, + newHeight: number, + aspectRatio: number +): { width: number; height: number } { + const newRatio = newWidth / newHeight; + + if (newRatio > aspectRatio) { + return { width: newHeight * aspectRatio, height: newHeight }; + } else { + return { width: newWidth, height: newWidth / aspectRatio }; + } +} + +export function detectRotationCorner( + localX: number, + localY: number, + halfWidth: number, + halfHeight: number, + zoneRadius: number +): string | null { + const corners: Record = { + nw: { x: -halfWidth, y: -halfHeight }, + ne: { x: halfWidth, y: -halfHeight }, + sw: { x: -halfWidth, y: halfHeight }, + se: { x: halfWidth, y: halfHeight } + }; + + const isInsideBounds = + localX >= -halfWidth && + localX <= halfWidth && + localY >= -halfHeight && + localY <= halfHeight; + + if (isInsideBounds) return null; + + for (const [name, corner] of Object.entries(corners)) { + const dx = localX - corner.x; + const dy = localY - corner.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist > zoneRadius || dist < 3) continue; + + const isOutwardX = (name.includes('w') && dx < 0) || (name.includes('e') && dx > 0); + const isOutwardY = (name.includes('n') && dy < 0) || (name.includes('s') && dy > 0); + + if (isOutwardX || isOutwardY) return name; + } + + return null; +} diff --git a/src/lib/io.ts b/src/lib/io.ts new file mode 100644 index 0000000..83b5975 --- /dev/null +++ b/src/lib/io.ts @@ -0,0 +1,93 @@ +import JSZip from 'jszip'; +import type { Manifest, AssetStore } from './types'; +import { state } from './state.svelte'; + +export async function exportBoard(): Promise<{ success: boolean; error?: string }> { + try { + const zip = new JSZip(); + const assetsFolder = zip.folder('assets'); + if (!assetsFolder) throw new Error('Failed to create assets folder'); + + const exportManifest: Manifest = { + version: 1, + items: state.manifest.items.map((item) => ({ ...item })), + sharedCss: state.manifest.sharedCss, + appCss: state.manifest.appCss + }; + + for (const item of exportManifest.items) { + if (item.assetId) { + const asset = state.assets[item.assetId]; + if (asset) { + const ext = asset.filename.split('.').pop() || 'bin'; + const filename = `${item.assetId}.${ext}`; + assetsFolder.file(filename, asset.blob); + } + } + } + + zip.file('manifest.json', JSON.stringify(exportManifest, null, 2)); + + const blob = await zip.generateAsync({ type: 'blob' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'board.bub'; + a.click(); + URL.revokeObjectURL(url); + return { success: true }; + } catch (e) { + return { success: false, error: e instanceof Error ? e.message : 'Export failed' }; + } +} + +export async function importBoard(file: File): Promise<{ success: boolean; error?: string }> { + try { + const zip = await JSZip.loadAsync(file); + + const manifestFile = zip.file('manifest.json'); + if (!manifestFile) throw new Error('Invalid .bub file: missing manifest.json'); + + const manifestJson = await manifestFile.async('string'); + const raw = JSON.parse(manifestJson); + + if (raw.version !== 1) throw new Error(`Unsupported manifest version: ${raw.version}`); + + const manifest: Manifest = { + version: 1, + items: raw.items, + sharedCss: raw.sharedCss ?? '', + appCss: raw.appCss ?? '', + flags: raw.flags ?? {} + }; + + const assets: AssetStore = {}; + const urlReplacements: Map = new Map(); + + for (const item of manifest.items) { + if (item.assetId) { + const assetFiles = zip.folder('assets')?.file(new RegExp(`^${item.assetId}\\.`)); + if (assetFiles && assetFiles.length > 0) { + const assetFile = assetFiles[0]; + const blob = await assetFile.async('blob'); + const url = URL.createObjectURL(blob); + const filename = assetFile.name.split('/').pop() || 'asset'; + assets[item.assetId] = { blob, url, filename }; + urlReplacements.set(item.assetId, url); + } + } + } + + for (const item of manifest.items) { + if (item.assetId && urlReplacements.has(item.assetId)) { + const newUrl = urlReplacements.get(item.assetId)!; + item.html = item.html.replace(/src="[^"]*"/g, `src="${newUrl}"`); + } + } + + state.load(manifest, assets); + return { success: true }; + } catch (e) { + return { success: false, error: e instanceof Error ? e.message : 'Import failed' }; + } +} diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts new file mode 100644 index 0000000..392ab50 --- /dev/null +++ b/src/lib/state.svelte.ts @@ -0,0 +1,358 @@ +import type { Item, Manifest, Asset, AssetStore, Viewport, PositionFlag } from './types'; + +const STORAGE_KEY = 'buboard'; + +const DEFAULT_SHARED_CSS = `* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, body { + width: 100%; + height: 100%; +} + +body { + font-family: 'Departure Mono', monospace; + color: #fff; + overflow: hidden; +}`; + +const DEFAULT_APP_CSS = `:root { + /* Theme */ + --bg: #1a1a1a; + --surface: #282c34; + --border: #333; + --accent: #4a9eff; + --text: #fff; + --text-dim: #666; + + /* Syntax */ + --cm-keyword: #c678dd; + --cm-variable: #e06c75; + --cm-function: #61afef; + --cm-string: #98c379; + --cm-comment: #7d8799; + --cm-number: #d19a66; + --cm-operator: #56b6c2; +}`; + +interface StoredAsset { + dataUrl: string; + filename: string; +} + +interface StoredState { + manifest: Manifest; + assets: Record; +} + +async function blobToDataUrl(blob: Blob): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = reject; + reader.readAsDataURL(blob); + }); +} + +async function dataUrlToBlob(dataUrl: string): Promise { + const res = await fetch(dataUrl); + return res.blob(); +} + +function createState() { + let manifest = $state({ + version: 1, + items: [], + sharedCss: DEFAULT_SHARED_CSS, + appCss: DEFAULT_APP_CSS, + flags: {} + }); + let assets = $state({}); + let viewport = $state({ x: 0, y: 0, zoom: 1 }); + let selectedId = $state(null); + let editingId = $state(null); + let editingGlobal = $state(false); + let focusedId = $state(null); + + let saveTimeout: ReturnType | null = null; + let animationId: number | null = null; + + let maxZIndex = $derived( + manifest.items.length > 0 ? Math.max(...manifest.items.map((i) => i.zIndex)) : 0 + ); + + async function save() { + if (saveTimeout) clearTimeout(saveTimeout); + saveTimeout = setTimeout(async () => { + const storedAssets: Record = {}; + for (const [id, asset] of Object.entries(assets)) { + storedAssets[id] = { + dataUrl: await blobToDataUrl(asset.blob), + filename: asset.filename + }; + } + const stored: StoredState = { manifest, assets: storedAssets }; + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(stored)); + } catch { + // localStorage full or unavailable + } + }, 500); + } + + async function restore() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return; + const stored: StoredState = JSON.parse(raw); + if (stored.manifest.version !== 1) return; + + const restoredAssets: AssetStore = {}; + for (const [id, storedAsset] of Object.entries(stored.assets)) { + const blob = await dataUrlToBlob(storedAsset.dataUrl); + const url = URL.createObjectURL(blob); + restoredAssets[id] = { blob, url, filename: storedAsset.filename }; + } + + for (const item of stored.manifest.items) { + if (item.assetId && restoredAssets[item.assetId]) { + const newUrl = restoredAssets[item.assetId].url; + item.html = item.html.replace(/src="[^"]*"/g, `src="${newUrl}"`); + } + } + + manifest = { ...stored.manifest, flags: stored.manifest.flags ?? {} }; + assets = restoredAssets; + } catch { + // corrupted or missing data + } + } + + function addItem(item: Item) { + manifest.items.push(item); + save(); + } + + function updateItem(id: string, updates: Partial) { + const item = manifest.items.find((i) => i.id === id); + if (!item) return; + Object.assign(item, updates); + save(); + } + + function removeItem(id: string) { + const idx = manifest.items.findIndex((i) => i.id === id); + if (idx === -1) return; + const item = manifest.items[idx]; + if (item.assetId && assets[item.assetId]) { + URL.revokeObjectURL(assets[item.assetId].url); + delete assets[item.assetId]; + } + manifest.items.splice(idx, 1); + if (selectedId === id) selectedId = null; + if (editingId === id) editingId = null; + save(); + } + + function addAsset(id: string, asset: Asset) { + assets[id] = asset; + save(); + } + + function getItem(id: string): Item | undefined { + return manifest.items.find((i) => i.id === id); + } + + function select(id: string | null) { + selectedId = id; + } + + function edit(id: string | null) { + editingId = id; + if (id) editingGlobal = false; + } + + function editGlobal(editing: boolean) { + editingGlobal = editing; + if (editing) editingId = null; + } + + function focus(id: string | null) { + focusedId = id; + } + + function updateSharedCss(css: string) { + manifest.sharedCss = css; + save(); + } + + function updateAppCss(css: string) { + manifest.appCss = css; + save(); + } + + function bringToFront(id: string) { + const item = manifest.items.find((i) => i.id === id); + if (!item) return; + item.zIndex = maxZIndex + 1; + save(); + } + + function pan(dx: number, dy: number) { + viewport.x += dx; + viewport.y += dy; + } + + function zoomAt(factor: number, cx: number, cy: number) { + const newZoom = Math.max(0.1, Math.min(10, viewport.zoom * factor)); + const scale = newZoom / viewport.zoom; + viewport.x = cx - (cx - viewport.x) * scale; + viewport.y = cy - (cy - viewport.y) * scale; + viewport.zoom = newZoom; + } + + function setZoom(zoom: number) { + viewport.zoom = Math.max(0.1, Math.min(10, zoom)); + } + + function hasFlag(key: string): boolean { + return !!manifest.flags?.[key]; + } + + function setFlag(key: string) { + if (!manifest.flags) manifest.flags = {}; + manifest.flags[key] = { x: viewport.x, y: viewport.y, zoom: viewport.zoom }; + save(); + } + + function clearFlag(key: string) { + if (manifest.flags?.[key]) { + delete manifest.flags[key]; + save(); + } + } + + function easeInOutCubic(t: number): number { + return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; + } + + function animateViewport(targetX: number, targetY: number, targetZoom: number) { + if (animationId) cancelAnimationFrame(animationId); + + const startX = viewport.x; + const startY = viewport.y; + const startZoom = viewport.zoom; + const startTime = performance.now(); + const duration = 600; + + function tick(now: number) { + const elapsed = now - startTime; + const t = Math.min(elapsed / duration, 1); + const eased = easeInOutCubic(t); + + viewport.x = startX + (targetX - startX) * eased; + viewport.y = startY + (targetY - startY) * eased; + viewport.zoom = startZoom + (targetZoom - startZoom) * eased; + + if (t < 1) { + animationId = requestAnimationFrame(tick); + } else { + animationId = null; + } + } + + animationId = requestAnimationFrame(tick); + } + + function gotoFlag(key: string) { + const flag = manifest.flags?.[key]; + if (flag) { + animateViewport(flag.x, flag.y, flag.zoom); + } + } + + function reset() { + Object.values(assets).forEach((a) => URL.revokeObjectURL(a.url)); + manifest = { + version: 1, + items: [], + sharedCss: DEFAULT_SHARED_CSS, + appCss: DEFAULT_APP_CSS, + flags: {} + }; + assets = {}; + viewport = { x: 0, y: 0, zoom: 1 }; + selectedId = null; + editingId = null; + editingGlobal = false; + focusedId = null; + localStorage.removeItem(STORAGE_KEY); + } + + function load(newManifest: Manifest, newAssets: AssetStore) { + Object.values(assets).forEach((a) => URL.revokeObjectURL(a.url)); + manifest = newManifest; + assets = newAssets; + viewport = { x: 0, y: 0, zoom: 1 }; + selectedId = null; + editingId = null; + editingGlobal = false; + focusedId = null; + save(); + } + + restore(); + + return { + get manifest() { + return manifest; + }, + get assets() { + return assets; + }, + get viewport() { + return viewport; + }, + get selectedId() { + return selectedId; + }, + get editingId() { + return editingId; + }, + get editingGlobal() { + return editingGlobal; + }, + get focusedId() { + return focusedId; + }, + get maxZIndex() { + return maxZIndex; + }, + addItem, + updateItem, + removeItem, + addAsset, + getItem, + select, + edit, + editGlobal, + focus, + updateSharedCss, + updateAppCss, + bringToFront, + pan, + zoomAt, + setZoom, + hasFlag, + setFlag, + clearFlag, + gotoFlag, + reset, + load + }; +} + +export const state = createState(); diff --git a/src/lib/theme.ts b/src/lib/theme.ts new file mode 100644 index 0000000..056a4b2 --- /dev/null +++ b/src/lib/theme.ts @@ -0,0 +1,131 @@ +import { EditorView } from '@codemirror/view'; +import { syntaxHighlighting, HighlightStyle } from '@codemirror/language'; +import { tags } from '@lezer/highlight'; + +function getVar(name: string, fallback: string): string { + const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); + return value || fallback; +} + +export function createTheme() { + const surface = getVar('--surface', '#282c34'); + const border = getVar('--border', '#333'); + const accent = getVar('--accent', '#4a9eff'); + const text = getVar('--text', '#fff'); + const textDim = getVar('--text-dim', '#666'); + + const keyword = getVar('--cm-keyword', '#c678dd'); + const variable = getVar('--cm-variable', '#e06c75'); + const func = getVar('--cm-function', '#61afef'); + const string = getVar('--cm-string', '#98c379'); + const comment = getVar('--cm-comment', '#7d8799'); + const number = getVar('--cm-number', '#d19a66'); + const operator = getVar('--cm-operator', '#56b6c2'); + + const theme = EditorView.theme( + { + '&': { + backgroundColor: surface, + color: '#abb2bf', + height: '100%' + }, + '.cm-scroller': { + overflow: 'auto', + fontFamily: "'Departure Mono', monospace" + }, + '.cm-content': { + caretColor: accent + }, + '.cm-cursor, .cm-dropCursor': { + borderLeftColor: accent + }, + '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': + { + backgroundColor: '#3E4451' + }, + '.cm-panels': { + backgroundColor: surface, + color: '#abb2bf' + }, + '.cm-panels.cm-panels-top': { + borderBottom: `1px solid ${border}` + }, + '.cm-panels.cm-panels-bottom': { + borderTop: `1px solid ${border}` + }, + '.cm-searchMatch': { + backgroundColor: '#72a1ff59', + outline: `1px solid ${border}` + }, + '.cm-searchMatch.cm-searchMatch-selected': { + backgroundColor: '#6199ff2f' + }, + '.cm-activeLine': { + backgroundColor: '#2c313c50' + }, + '.cm-selectionMatch': { + backgroundColor: '#aafe661a' + }, + '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { + backgroundColor: '#bad0f847' + }, + '.cm-gutters': { + backgroundColor: surface, + color: textDim, + border: 'none' + }, + '.cm-activeLineGutter': { + backgroundColor: '#2c313c50' + }, + '.cm-foldPlaceholder': { + backgroundColor: 'transparent', + border: 'none', + color: textDim + }, + '.cm-tooltip': { + border: 'none', + backgroundColor: surface + }, + '.cm-tooltip .cm-tooltip-arrow:before': { + borderTopColor: 'transparent', + borderBottomColor: 'transparent' + }, + '.cm-tooltip .cm-tooltip-arrow:after': { + borderTopColor: surface, + borderBottomColor: surface + }, + '.cm-tooltip-autocomplete': { + '& > ul > li[aria-selected]': { + backgroundColor: accent, + color: text + } + } + }, + { dark: true } + ); + + const highlighting = HighlightStyle.define([ + { tag: tags.keyword, color: keyword }, + { tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: variable }, + { tag: [tags.function(tags.variableName), tags.labelName], color: func }, + { tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: number }, + { tag: [tags.definition(tags.name), tags.separator], color: '#abb2bf' }, + { tag: [tags.typeName, tags.className, tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: number }, + { tag: [tags.operator, tags.operatorKeyword, tags.url, tags.escape, tags.regexp, tags.link, tags.special(tags.string)], color: operator }, + { tag: [tags.meta, tags.comment], color: comment, fontStyle: 'italic' }, + { tag: tags.strong, fontWeight: 'bold' }, + { tag: tags.emphasis, fontStyle: 'italic' }, + { tag: tags.strikethrough, textDecoration: 'line-through' }, + { tag: tags.link, color: comment, textDecoration: 'underline' }, + { tag: tags.heading, fontWeight: 'bold', color: variable }, + { tag: [tags.atom, tags.bool, tags.special(tags.variableName)], color: number }, + { tag: [tags.processingInstruction, tags.string, tags.inserted], color: string }, + { tag: tags.invalid, color: '#ff0000' }, + { tag: tags.tagName, color: variable }, + { tag: tags.attributeName, color: number }, + { tag: tags.attributeValue, color: string }, + { tag: tags.propertyName, color: func } + ]); + + return [theme, syntaxHighlighting(highlighting)]; +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..025bee5 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,42 @@ +export interface Item { + id: string; + x: number; + y: number; + width: number; + height: number; + rotation: number; + zIndex: number; + html: string; + css: string; + assetId?: string; +} + +export interface Manifest { + version: 1; + items: Item[]; + sharedCss: string; + appCss: string; + flags?: Record; +} + +export interface Asset { + blob: Blob; + url: string; + filename: string; +} + +export interface AssetStore { + [assetId: string]: Asset; +} + +export interface Viewport { + x: number; + y: number; + zoom: number; +} + +export interface PositionFlag { + x: number; + y: number; + zoom: number; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..664a057 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,9 @@ +import { mount } from 'svelte' +import './app.css' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..96b3455 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,8 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..31c18cf --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "types": ["svelte", "vite/client"], + "noEmit": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d32eba1 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [svelte()], +})