Initial commit

Torsten Dittmann 3h ago 8176c2021494e25c6c275f76a140245263f0cbe4
root commit — shown against the empty tree
added .gitignore
added .npmrc
added .vscode/extensions.json
added README.md
added bun.lock
added package.json
added src/app.d.ts
added src/app.html
added src/lib/images/github.svg
added src/lib/images/svelte-logo.svg
added src/routes/+layout.svelte
added src/routes/+page.svelte
added src/routes/+page.ts
added src/routes/Counter.svelte
added src/routes/Header.svelte
added src/routes/about/+page.svelte
added src/routes/about/+page.ts
added src/routes/layout.css
added src/routes/sverdle/+page.server.ts
added src/routes/sverdle/+page.svelte
added src/routes/sverdle/game.ts
added src/routes/sverdle/how-to-play/+page.svelte
added src/routes/sverdle/how-to-play/+page.ts
added src/routes/sverdle/words.server.ts
added static/favicon.svg
added static/robots.txt
added tsconfig.json
added vite.config.ts
added src/lib/images/svelte-welcome.png
added src/lib/images/svelte-welcome.webp
+23 −0
1 + node_modules
2 +
3 + # Output
4 + .output
5 + .vercel
6 + .netlify
7 + .wrangler
8 + /.svelte-kit
9 + /build
10 +
11 + # OS
12 + .DS_Store
13 + Thumbs.db
14 +
15 + # Env
16 + .env
17 + .env.*
18 + !.env.example
19 + !.env.test
20 +
21 + # Vite
22 + vite.config.js.timestamp-*
23 + vite.config.ts.timestamp-*
+1 −0
1 + engine-strict=true
+3 −0
1 + {
2 + "recommendations": ["svelte.svelte-vscode"]
3 + }
+42 −0
1 + # sv
2 +
3 + Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
4 +
5 + ## Creating a project
6 +
7 + If you're seeing this, you've probably already done this step. Congrats!
8 +
9 + ```sh
10 + # create a new project
11 + npx sv create my-app
12 + ```
13 +
14 + To recreate this project with the same configuration:
15 +
16 + ```sh
17 + # recreate this project
18 + bun x sv@0.17.0 create --template demo --types ts --install bun better-hub-test-prods
19 + ```
20 +
21 + ## Developing
22 +
23 + Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
24 +
25 + ```sh
26 + npm run dev
27 +
28 + # or start the server and open the app in a new browser tab
29 + npm run dev -- --open
30 + ```
31 +
32 + ## Building
33 +
34 + To create a production version of your app:
35 +
36 + ```sh
37 + npm run build
38 + ```
39 +
40 + You can preview the production build with `npm run preview`.
41 +
42 + > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
+191 −0
1 + {
2 + "lockfileVersion": 2,
3 + "configVersion": 1,
4 + "workspaces": {
5 + "": {
6 + "name": "better-hub-test-prods",
7 + "devDependencies": {
8 + "@fontsource/fira-mono": "^5.2.7",
9 + "@neoconfetti/svelte": "^2.2.2",
10 + "@sveltejs/adapter-auto": "^7.0.1",
11 + "@sveltejs/kit": "^2.63.0",
12 + "@sveltejs/vite-plugin-svelte": "^7.1.2",
13 + "svelte": "^5.56.1",
14 + "svelte-check": "^4.6.0",
15 + "typescript": "^6.0.3",
16 + "vite": "^8.0.16",
17 + },
18 + },
19 + },
20 + "packages": {
21 + "@fontsource/fira-mono": ["@fontsource/fira-mono@5.3.0", "", {}, "sha512-FZzRwKhpoXQU1uhEGKUg5AHUUdfiaTXxGCdBBeGHUmcZaqEDEmndSVvpXHkSViH25f/UiWfhcWTib+6A8o4qeQ=="],
22 +
23 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
24 +
25 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
26 +
27 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
28 +
29 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
30 +
31 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
32 +
33 + "@neoconfetti/svelte": ["@neoconfetti/svelte@2.2.2", "", { "peerDependencies": { "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, "sha512-E7xCFVEEm5Ctnj2udTJy1b9oaTvjz1zi1mYdEtE8rB5BVwq6kHisosDS+zdWN5PMfEMjtbsOV9Cl6tsNSAD1sA=="],
34 +
35 + "@oxc-project/types": ["@oxc-project/types@0.146.0", "", {}, "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA=="],
36 +
37 + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
38 +
39 + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.5", "", { "os": "android", "cpu": "arm" }, "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA=="],
40 +
41 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.5", "", { "os": "android", "cpu": "arm64" }, "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig=="],
42 +
43 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww=="],
44 +
45 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A=="],
46 +
47 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw=="],
48 +
49 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.5", "", { "os": "linux", "cpu": "arm" }, "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg=="],
50 +
51 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA=="],
52 +
53 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA=="],
54 +
55 + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA=="],
56 +
57 + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ=="],
58 +
59 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ=="],
60 +
61 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA=="],
62 +
63 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.5", "", { "os": "none", "cpu": "arm64" }, "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw=="],
64 +
65 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw=="],
66 +
67 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.5", "", { "os": "win32", "cpu": "x64" }, "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw=="],
68 +
69 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
70 +
71 + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
72 +
73 + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.13", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ=="],
74 +
75 + "@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.1", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ=="],
76 +
77 + "@sveltejs/kit": ["@sveltejs/kit@2.70.3", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.9", "@types/cookie": "^0.6.0", "acorn": "^8.16.0", "cookie": "^0.6.0", "devalue": "^5.8.1", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3 || ^6.0.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-UDvEYuZqAMbfB/oXIoqKvbKcb7YczK5zYrzmsGV1zRJk03jntwp8dXiYoIJotxAndsKvcPFtx9H1GRSKFdSHgg=="],
78 +
79 + "@sveltejs/load-config": ["@sveltejs/load-config@0.2.3", "", {}, "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ=="],
80 +
81 + "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@7.3.0", "", { "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^1.0.0", "obug": "^2.1.0", "vitefu": "^1.1.2" }, "peerDependencies": { "svelte": "^5.46.4", "vite": "^8.0.0-beta.7 || ^8.0.0" } }, "sha512-QbRoJyD92e9R0ufeQIWRHrCC0ObcqSv/aBDdrQMoU+sypav3cDx5wytdQ6GLdXjEMO6xjrXGzfkUygng8JMv0A=="],
82 +
83 + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
84 +
85 + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
86 +
87 + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
88 +
89 + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
90 +
91 + "aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
92 +
93 + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
94 +
95 + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
96 +
97 + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
98 +
99 + "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
100 +
101 + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
102 +
103 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
104 +
105 + "devalue": ["devalue@5.9.1", "", {}, "sha512-+17vil3EVQRzvtDJSFuTWEb8XJRvXqAiV3qZyQWD398QeXUa6CxsUyMdD1fxzEhUrd4FojitFz7lhIHBTlV4fw=="],
106 +
107 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
108 +
109 + "esrap": ["esrap@2.3.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-yc0OC12UjPqLoc+fe+v5GNs4TOjAigUw3sTikfC+xeBPGUw7gDRz3DtYaqEhxyMVJojcSWJw7jT0QWR+CbuE/A=="],
110 +
111 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
112 +
113 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
114 +
115 + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
116 +
117 + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
118 +
119 + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
120 +
121 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
122 +
123 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
124 +
125 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
126 +
127 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
128 +
129 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
130 +
131 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
132 +
133 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
134 +
135 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
136 +
137 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
138 +
139 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
140 +
141 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
142 +
143 + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
144 +
145 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
146 +
147 + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
148 +
149 + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
150 +
151 + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
152 +
153 + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="],
154 +
155 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
156 +
157 + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
158 +
159 + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
160 +
161 + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
162 +
163 + "rolldown": ["rolldown@1.2.5", "", { "dependencies": { "@oxc-project/types": "=0.146.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.5", "@rolldown/binding-android-arm64": "1.2.5", "@rolldown/binding-darwin-arm64": "1.2.5", "@rolldown/binding-darwin-x64": "1.2.5", "@rolldown/binding-freebsd-x64": "1.2.5", "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", "@rolldown/binding-linux-arm64-gnu": "1.2.5", "@rolldown/binding-linux-arm64-musl": "1.2.5", "@rolldown/binding-linux-ppc64-gnu": "1.2.5", "@rolldown/binding-linux-s390x-gnu": "1.2.5", "@rolldown/binding-linux-x64-gnu": "1.2.5", "@rolldown/binding-linux-x64-musl": "1.2.5", "@rolldown/binding-openharmony-arm64": "1.2.5", "@rolldown/binding-win32-arm64-msvc": "1.2.5", "@rolldown/binding-win32-x64-msvc": "1.2.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA=="],
164 +
165 + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
166 +
167 + "set-cookie-parser": ["set-cookie-parser@3.1.2", "", {}, "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw=="],
168 +
169 + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
170 +
171 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
172 +
173 + "svelte": ["svelte@5.56.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.12", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-Lcxbj8I/KAbpY+VjtY4ENQBV0dDCipfGAhqb51XQZ67CIQqXgsv/8dPkbILaj4Fb6/b6JAEM/PIVbILXgDQy2g=="],
174 +
175 + "svelte-check": ["svelte-check@4.7.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "^0.2.3", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.0.0 || ^6.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw=="],
176 +
177 + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
178 +
179 + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
180 +
181 + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
182 +
183 + "vite": ["vite@8.2.2", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.26", "rolldown": "~1.2.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0 || ^0.5.0", "esbuild": "^0.27.0 || ^0.28.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" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q=="],
184 +
185 + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
186 +
187 + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
188 +
189 + "@sveltejs/vite-plugin-svelte/magic-string": ["magic-string@1.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-veT/+7iXrXzT39XnEN4lOxtNl72dMgJ8Lp+5Bd6YcMSWpb0n0MjBM8Uuooi6jgJr8dhUW2swQgBmoZVMni5SVg=="],
190 + }
191 + }
+25 −0
1 + {
2 + "name": "better-hub-test-prods",
3 + "private": true,
4 + "version": "0.0.1",
5 + "type": "module",
6 + "scripts": {
7 + "dev": "vite dev",
8 + "build": "vite build",
9 + "preview": "vite preview",
10 + "prepare": "svelte-kit sync || echo ''",
11 + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12 + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
13 + },
14 + "devDependencies": {
15 + "@fontsource/fira-mono": "^5.2.7",
16 + "@neoconfetti/svelte": "^2.2.2",
17 + "@sveltejs/adapter-auto": "^7.0.1",
18 + "@sveltejs/kit": "^2.63.0",
19 + "@sveltejs/vite-plugin-svelte": "^7.1.2",
20 + "svelte": "^5.56.1",
21 + "svelte-check": "^4.6.0",
22 + "typescript": "^6.0.3",
23 + "vite": "^8.0.16"
24 + }
25 + }
+13 −0
1 + // See https://svelte.dev/docs/kit/types#app.d.ts
2 + // for information about these interfaces
3 + declare global {
4 + namespace App {
5 + // interface Error {}
6 + // interface Locals {}
7 + // interface PageData {}
8 + // interface PageState {}
9 + // interface Platform {}
10 + }
11 + }
12 +
13 + export {};
+13 −0
1 + <!doctype html>
2 + <html lang="en">
3 + <head>
4 + <meta charset="utf-8" />
5 + <link rel="icon" href="%sveltekit.assets%/favicon.svg" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1" />
7 + <meta name="text-scale" content="scale" />
8 + %sveltekit.head%
9 + </head>
10 + <body data-sveltekit-preload-data="hover">
11 + <div style="display: contents">%sveltekit.body%</div>
12 + </body>
13 + </html>
+1 −0
1 + <svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>
+1 −0
1 + <svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>
+57 −0
1 + <script lang="ts">
2 + import Header from './Header.svelte';
3 + import './layout.css';
4 +
5 + let { children } = $props();
6 + </script>
7 +
8 + <div class="app">
9 + <Header />
10 +
11 + <main>
12 + {@render children()}
13 + </main>
14 +
15 + <footer>
16 + <p>
17 + visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to learn about SvelteKit
18 + </p>
19 + </footer>
20 + </div>
21 +
22 + <style>
23 + .app {
24 + display: flex;
25 + flex-direction: column;
26 + min-height: 100vh;
27 + }
28 +
29 + main {
30 + flex: 1;
31 + display: flex;
32 + flex-direction: column;
33 + padding: 1rem;
34 + width: 100%;
35 + max-width: 64rem;
36 + margin: 0 auto;
37 + box-sizing: border-box;
38 + }
39 +
40 + footer {
41 + display: flex;
42 + flex-direction: column;
43 + justify-content: center;
44 + align-items: center;
45 + padding: 12px;
46 + }
47 +
48 + footer a {
49 + font-weight: bold;
50 + }
51 +
52 + @media (min-width: 480px) {
53 + footer {
54 + padding: 12px 0;
55 + }
56 + }
57 + </style>
+60 −0
1 + <script lang="ts">
2 + import welcomeFallback from '$lib/images/svelte-welcome.png';
3 + import welcome from '$lib/images/svelte-welcome.webp';
4 +
5 + import Counter from './Counter.svelte';
6 + </script>
7 +
8 + <svelte:head>
9 + <title>Home</title>
10 + <meta name="description" content="Svelte demo app" />
11 + </svelte:head>
12 +
13 + <section>
14 + <h1>
15 + <span class="welcome">
16 + <picture>
17 + <source srcset={welcome} type="image/webp" />
18 + <img src={welcomeFallback} alt="Welcome" />
19 + </picture>
20 + </span>
21 +
22 + to your new<br />SvelteKit app
23 + </h1>
24 +
25 + <h2>
26 + try editing <strong>src/routes/+page.svelte</strong>
27 + </h2>
28 +
29 + <Counter />
30 + </section>
31 +
32 + <style>
33 + section {
34 + display: flex;
35 + flex-direction: column;
36 + justify-content: center;
37 + align-items: center;
38 + flex: 0.6;
39 + }
40 +
41 + h1 {
42 + width: 100%;
43 + }
44 +
45 + .welcome {
46 + display: block;
47 + position: relative;
48 + width: 100%;
49 + height: 0;
50 + padding: 0 0 calc(100% * 495 / 2048) 0;
51 + }
52 +
53 + .welcome img {
54 + position: absolute;
55 + width: 100%;
56 + height: 100%;
57 + top: 0;
58 + display: block;
59 + }
60 + </style>
+3 −0
1 + // since there's no dynamic data here, we can prerender
2 + // it so that it gets served as a static asset in production
3 + export const prerender = true;
+99 −0
1 + <script lang="ts">
2 + import { Spring } from 'svelte/motion';
3 +
4 + const count = new Spring(0);
5 + const offset = $derived(modulo(count.current, 1));
6 +
7 + function modulo(n: number, m: number) {
8 + // handle negative numbers
9 + return ((n % m) + m) % m;
10 + }
11 + </script>
12 +
13 + <div class="counter">
14 + <button onclick={() => (count.target -= 1)} aria-label="Decrease the counter by one">
15 + <svg aria-hidden="true" viewBox="0 0 1 1">
16 + <path d="M0,0.5 L1,0.5" />
17 + </svg>
18 + </button>
19 +
20 + <div class="counter-viewport">
21 + <div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
22 + <strong class="hidden" aria-hidden="true">{Math.floor(count.current + 1)}</strong>
23 + <strong>{Math.floor(count.current)}</strong>
24 + </div>
25 + </div>
26 +
27 + <button onclick={() => (count.target += 1)} aria-label="Increase the counter by one">
28 + <svg aria-hidden="true" viewBox="0 0 1 1">
29 + <path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
30 + </svg>
31 + </button>
32 + </div>
33 +
34 + <style>
35 + .counter {
36 + display: flex;
37 + border-top: 1px solid rgba(0, 0, 0, 0.1);
38 + border-bottom: 1px solid rgba(0, 0, 0, 0.1);
39 + margin: 1rem 0;
40 + }
41 +
42 + .counter button {
43 + width: 2em;
44 + padding: 0;
45 + display: flex;
46 + align-items: center;
47 + justify-content: center;
48 + border: 0;
49 + background-color: transparent;
50 + touch-action: manipulation;
51 + font-size: 2rem;
52 + }
53 +
54 + .counter button:hover {
55 + background-color: var(--color-bg-1);
56 + }
57 +
58 + svg {
59 + width: 25%;
60 + height: 25%;
61 + }
62 +
63 + path {
64 + vector-effect: non-scaling-stroke;
65 + stroke-width: 2px;
66 + stroke: #444;
67 + }
68 +
69 + .counter-viewport {
70 + width: 8em;
71 + height: 4em;
72 + overflow: hidden;
73 + text-align: center;
74 + position: relative;
75 + }
76 +
77 + .counter-viewport strong {
78 + position: absolute;
79 + display: flex;
80 + width: 100%;
81 + height: 100%;
82 + font-weight: 400;
83 + color: var(--color-theme-1);
84 + font-size: 4rem;
85 + align-items: center;
86 + justify-content: center;
87 + }
88 +
89 + .counter-digits {
90 + position: absolute;
91 + width: 100%;
92 + height: 100%;
93 + }
94 +
95 + .hidden {
96 + top: -100%;
97 + user-select: none;
98 + }
99 + </style>
+130 −0
1 + <script lang="ts">
2 + import { resolve } from '$app/paths';
3 + import { page } from '$app/state';
4 + import github from '$lib/images/github.svg';
5 + import logo from '$lib/images/svelte-logo.svg';
6 + </script>
7 +
8 + <header>
9 + <div class="corner">
10 + <a href="https://svelte.dev/docs/kit">
11 + <img src={logo} alt="SvelteKit" />
12 + </a>
13 + </div>
14 +
15 + <nav>
16 + <svg viewBox="0 0 2 3" aria-hidden="true">
17 + <path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
18 + </svg>
19 + <ul>
20 + <li aria-current={page.url.pathname === '/' ? 'page' : undefined}>
21 + <a href={resolve('/')}>Home</a>
22 + </li>
23 + <li aria-current={page.url.pathname === '/about' ? 'page' : undefined}>
24 + <a href={resolve('/about')}>About</a>
25 + </li>
26 + <li aria-current={page.url.pathname.startsWith('/sverdle') ? 'page' : undefined}>
27 + <a href={resolve('/sverdle')}>Sverdle</a>
28 + </li>
29 + </ul>
30 + <svg viewBox="0 0 2 3" aria-hidden="true">
31 + <path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
32 + </svg>
33 + </nav>
34 +
35 + <div class="corner">
36 + <a href="https://github.com/sveltejs/kit">
37 + <img src={github} alt="GitHub" />
38 + </a>
39 + </div>
40 + </header>
41 +
42 + <style>
43 + header {
44 + display: flex;
45 + justify-content: space-between;
46 + }
47 +
48 + .corner {
49 + width: 3em;
50 + height: 3em;
51 + }
52 +
53 + .corner a {
54 + display: flex;
55 + align-items: center;
56 + justify-content: center;
57 + width: 100%;
58 + height: 100%;
59 + }
60 +
61 + .corner img {
62 + width: 2em;
63 + height: 2em;
64 + object-fit: contain;
65 + }
66 +
67 + nav {
68 + display: flex;
69 + justify-content: center;
70 + --background: rgba(255, 255, 255, 0.7);
71 + }
72 +
73 + svg {
74 + width: 2em;
75 + height: 3em;
76 + display: block;
77 + }
78 +
79 + path {
80 + fill: var(--background);
81 + }
82 +
83 + ul {
84 + position: relative;
85 + padding: 0;
86 + margin: 0;
87 + height: 3em;
88 + display: flex;
89 + justify-content: center;
90 + align-items: center;
91 + list-style: none;
92 + background: var(--background);
93 + background-size: contain;
94 + }
95 +
96 + li {
97 + position: relative;
98 + height: 100%;
99 + }
100 +
101 + li[aria-current='page']::before {
102 + --size: 6px;
103 + content: '';
104 + width: 0;
105 + height: 0;
106 + position: absolute;
107 + top: 0;
108 + left: calc(50% - var(--size));
109 + border: var(--size) solid transparent;
110 + border-top: var(--size) solid var(--color-theme-1);
111 + }
112 +
113 + nav a {
114 + display: flex;
115 + height: 100%;
116 + align-items: center;
117 + padding: 0 0.5rem;
118 + color: var(--color-text);
119 + font-weight: 700;
120 + font-size: 0.8rem;
121 + text-transform: uppercase;
122 + letter-spacing: 0.1em;
123 + text-decoration: none;
124 + transition: color 0.2s linear;
125 + }
126 +
127 + a:hover {
128 + color: var(--color-theme-1);
129 + }
130 + </style>
+30 −0
1 + <script lang="ts">
2 + import { resolve } from '$app/paths';
3 + </script>
4 +
5 + <svelte:head>
6 + <title>About</title>
7 + <meta name="description" content="About this app" />
8 + </svelte:head>
9 +
10 + <div class="text-column">
11 + <h1>About this app</h1>
12 +
13 + <p>
14 + This is a <a href="https://svelte.dev/docs/kit">SvelteKit</a> app. You can make your own by typing
15 + the following into your command line and following the prompts:
16 + </p>
17 +
18 + <pre>npx sv create</pre>
19 +
20 + <p>
21 + The page you're looking at is purely static HTML, with no client-side interactivity needed.
22 + Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
23 + the devtools network panel and reloading.
24 + </p>
25 +
26 + <p>
27 + The <a href={resolve('/sverdle')}>Sverdle</a> page illustrates SvelteKit's data loading and form handling.
28 + Try using it with JavaScript disabled!
29 + </p>
30 + </div>
+9 −0
1 + import { dev } from '$app/environment';
2 +
3 + // we don't need any JS on this page, though we'll load
4 + // it in dev so that we get hot module replacement
5 + export const csr = dev;
6 +
7 + // since there's no dynamic data here, we can prerender
8 + // it so that it gets served as a static asset in production
9 + export const prerender = true;
+105 −0
1 + @import '@fontsource/fira-mono';
2 +
3 + :root {
4 + --font-body:
5 + Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
6 + 'Open Sans', 'Helvetica Neue', sans-serif;
7 + --font-mono: 'Fira Mono', monospace;
8 + --color-bg-0: rgb(202, 216, 228);
9 + --color-bg-1: hsl(209, 36%, 86%);
10 + --color-bg-2: hsl(224, 44%, 95%);
11 + --color-theme-1: #ff3e00;
12 + --color-theme-2: #4075a6;
13 + --color-text: rgba(0, 0, 0, 0.7);
14 + --column-width: 42rem;
15 + --column-margin-top: 4rem;
16 + font-family: var(--font-body);
17 + color: var(--color-text);
18 + }
19 +
20 + body {
21 + min-height: 100vh;
22 + margin: 0;
23 + background-attachment: fixed;
24 + background-color: var(--color-bg-1);
25 + background-size: 100vw 100vh;
26 + background-image:
27 + radial-gradient(50% 50% at 50% 50%, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0) 100%),
28 + linear-gradient(180deg, var(--color-bg-0) 0%, var(--color-bg-1) 15%, var(--color-bg-2) 50%);
29 + }
30 +
31 + h1,
32 + h2,
33 + p {
34 + font-weight: 400;
35 + }
36 +
37 + p {
38 + line-height: 1.5;
39 + }
40 +
41 + a {
42 + color: var(--color-theme-1);
43 + text-decoration: none;
44 + }
45 +
46 + a:hover {
47 + text-decoration: underline;
48 + }
49 +
50 + h1 {
51 + font-size: 2rem;
52 + text-align: center;
53 + }
54 +
55 + h2 {
56 + font-size: 1rem;
57 + }
58 +
59 + pre {
60 + font-size: 16px;
61 + font-family: var(--font-mono);
62 + background-color: rgba(255, 255, 255, 0.45);
63 + border-radius: 3px;
64 + box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
65 + padding: 0.5em;
66 + overflow-x: auto;
67 + color: var(--color-text);
68 + }
69 +
70 + .text-column {
71 + display: flex;
72 + max-width: 48rem;
73 + flex: 0.6;
74 + flex-direction: column;
75 + justify-content: center;
76 + margin: 0 auto;
77 + }
78 +
79 + input,
80 + button {
81 + font-size: inherit;
82 + font-family: inherit;
83 + }
84 +
85 + button:focus:not(:focus-visible) {
86 + outline: none;
87 + }
88 +
89 + @media (min-width: 720px) {
90 + h1 {
91 + font-size: 2.4rem;
92 + }
93 + }
94 +
95 + .visually-hidden {
96 + border: 0;
97 + clip: rect(0 0 0 0);
98 + height: auto;
99 + margin: 0;
100 + overflow: hidden;
101 + padding: 0;
102 + position: absolute;
103 + width: 1px;
104 + white-space: nowrap;
105 + }
+69 −0
1 + import { fail } from '@sveltejs/kit';
2 + import type { Actions, PageServerLoad } from './$types';
3 + import { Game } from './game.ts';
4 +
5 + export const load = (({ cookies }) => {
6 + const game = new Game(cookies.get('sverdle'));
7 +
8 + return {
9 + /**
10 + * The player's guessed words so far
11 + */
12 + guesses: game.guesses,
13 +
14 + /**
15 + * An array of strings like '__x_c' corresponding to the guesses, where 'x' means
16 + * an exact match, and 'c' means a close match (right letter, wrong place)
17 + */
18 + answers: game.answers,
19 +
20 + /**
21 + * The correct answer, revealed if the game is over
22 + */
23 + answer: game.answers.length >= 6 ? game.answer : null
24 + };
25 + }) satisfies PageServerLoad;
26 +
27 + export const actions = {
28 + /**
29 + * Modify game state in reaction to a keypress. If client-side JavaScript
30 + * is available, this will happen in the browser instead of here
31 + */
32 + update: async ({ request, cookies }) => {
33 + const game = new Game(cookies.get('sverdle'));
34 +
35 + const data = await request.formData();
36 + const key = data.get('key');
37 +
38 + const i = game.answers.length;
39 +
40 + if (key === 'backspace') {
41 + game.guesses[i] = game.guesses[i].slice(0, -1);
42 + } else {
43 + game.guesses[i] += key;
44 + }
45 +
46 + cookies.set('sverdle', game.toString(), { path: '/' });
47 + },
48 +
49 + /**
50 + * Modify game state in reaction to a guessed word. This logic always runs on
51 + * the server, so that people can't cheat by peeking at the JavaScript
52 + */
53 + enter: async ({ request, cookies }) => {
54 + const game = new Game(cookies.get('sverdle'));
55 +
56 + const data = await request.formData();
57 + const guess = data.getAll('guess') as string[];
58 +
59 + if (!game.enter(guess)) {
60 + return fail(400, { badGuess: true });
61 + }
62 +
63 + cookies.set('sverdle', game.toString(), { path: '/' });
64 + },
65 +
66 + restart: async ({ cookies }) => {
67 + cookies.delete('sverdle', { path: '/' });
68 + }
69 + } satisfies Actions;
+414 −0
1 + <script lang="ts">
2 + import { enhance } from '$app/forms';
3 + import { resolve } from '$app/paths';
4 + import { confetti } from '@neoconfetti/svelte';
5 + import { MediaQuery } from 'svelte/reactivity';
6 +
7 + import type { PageProps } from './$types';
8 +
9 + let { data }: PageProps = $props();
10 +
11 + /** Whether the user prefers reduced motion */
12 + const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)');
13 +
14 + let shake = $state(false);
15 +
16 + /** Whether or not the user has won */
17 + let won = $derived(data.answers.at(-1) === 'xxxxx');
18 +
19 + /** The index of the current guess */
20 + let i = $derived(won ? -1 : data.answers.length);
21 +
22 + /** The current guess */
23 + let currentGuess = $derived(data.guesses[i] || '');
24 +
25 + /** Whether the current guess can be submitted */
26 + let submittable = $derived(currentGuess.length === 5);
27 +
28 + const { classnames, description } = $derived.by(() => {
29 + /**
30 + * A map of classnames for all letters that have been guessed,
31 + * used for styling the keyboard
32 + */
33 + let classnames: Record<string, 'exact' | 'close' | 'missing'> = {};
34 + /**
35 + * A map of descriptions for all letters that have been guessed,
36 + * used for adding text for assistive technology (e.g. screen readers)
37 + */
38 + let description: Record<string, string> = {};
39 + data.answers.forEach((answer, i) => {
40 + const guess = data.guesses[i];
41 + for (let i = 0; i < 5; i += 1) {
42 + const letter = guess[i];
43 + if (answer[i] === 'x') {
44 + classnames[letter] = 'exact';
45 + description[letter] = 'correct';
46 + } else if (!classnames[letter]) {
47 + classnames[letter] = answer[i] === 'c' ? 'close' : 'missing';
48 + description[letter] = answer[i] === 'c' ? 'present' : 'absent';
49 + }
50 + }
51 + });
52 + return { classnames, description };
53 + });
54 +
55 + /**
56 + * Modify the game state without making a trip to the server,
57 + * if client-side JavaScript is enabled
58 + */
59 + function update(event: MouseEvent) {
60 + event.preventDefault();
61 + const key = (event.target as HTMLButtonElement).getAttribute(
62 + 'data-key'
63 + );
64 +
65 + if (key === 'backspace') {
66 + currentGuess = currentGuess.slice(0, -1);
67 + shake = false;
68 + } else if (currentGuess.length < 5) {
69 + currentGuess += key;
70 + }
71 + }
72 +
73 + /**
74 + * Trigger form logic in response to a keydown event, so that
75 + * desktop users can use the keyboard to play the game
76 + */
77 + function keydown(event: KeyboardEvent) {
78 + if (event.metaKey) return;
79 +
80 + if (event.key === 'Enter' && !submittable) return;
81 +
82 + document
83 + .querySelector(`[data-key="${event.key}" i]`)
84 + ?.dispatchEvent(new MouseEvent('click', { cancelable: true, bubbles: true }));
85 + }
86 + </script>
87 +
88 + <svelte:window onkeydown={keydown} />
89 +
90 + <svelte:head>
91 + <title>Sverdle</title>
92 + <meta name="description" content="A Wordle clone written in SvelteKit" />
93 + </svelte:head>
94 +
95 + <h1 class="visually-hidden">Sverdle</h1>
96 +
97 + <form
98 + method="post"
99 + action="?/enter"
100 + use:enhance={() => {
101 + // prevent default callback from resetting the form
102 + return ({ result, update }) => {
103 + shake = result.type === 'failure';
104 + update({ reset: false });
105 + };
106 + }}
107 + >
108 + <a class="how-to-play" href={resolve('/sverdle/how-to-play')}>How to play</a>
109 +
110 + <div class="grid" class:playing={!won} class:shake onanimationend={() => (shake = false)}>
111 + {#each Array.from(Array(6).keys()) as row (row)}
112 + {const current = $derived(row === i)}
113 +
114 + <h2 class="visually-hidden">Row {row + 1}</h2>
115 + <div class="row" class:current>
116 + {#each Array.from(Array(5).keys()) as column (column)}
117 + {const guess = $derived(current ? currentGuess : data.guesses[row])}
118 + {const answer = $derived(data.answers[row]?.[column])}
119 + {const value = $derived(guess?.[column] ?? '')}
120 + {const selected = $derived(current && column === guess.length)}
121 + {const exact = $derived(answer === 'x')}
122 + {const close = $derived(answer === 'c')}
123 + {const missing = $derived(answer === '_')}
124 +
125 + <div class="letter" class:exact class:close class:missing class:selected>
126 + {value}
127 + <span class="visually-hidden">
128 + {#if exact}
129 + (correct)
130 + {:else if close}
131 + (present)
132 + {:else if missing}
133 + (absent)
134 + {:else}
135 + empty
136 + {/if}
137 + </span>
138 + <input name="guess" disabled={!current} type="hidden" {value} />
139 + </div>
140 + {/each}
141 + </div>
142 + {/each}
143 + </div>
144 +
145 + <div class="controls">
146 + {#if won || data.answers.length >= 6}
147 + {#if !won && data.answer}
148 + <p>the answer was "{data.answer}"</p>
149 + {/if}
150 + <button data-key="enter" class="restart selected" formaction="?/restart">
151 + {won ? 'you won :)' : `game over :(`} play again?
152 + </button>
153 + {:else}
154 + <div class="keyboard">
155 + <button data-key="enter" class:selected={submittable} disabled={!submittable}>enter</button>
156 +
157 + <button
158 + onclick={update}
159 + data-key="backspace"
160 + formaction="?/update"
161 + name="key"
162 + value="backspace"
163 + >
164 + back
165 + </button>
166 +
167 + {#each ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'] as row (row)}
168 + <div class="row">
169 + {#each row as letter, index (index)}
170 + <button
171 + onclick={update}
172 + data-key={letter}
173 + class={classnames[letter]}
174 + disabled={submittable}
175 + formaction="?/update"
176 + name="key"
177 + value={letter}
178 + aria-label="{letter} {description[letter] || ''}"
179 + >
180 + {letter}
181 + </button>
182 + {/each}
183 + </div>
184 + {/each}
185 + </div>
186 + {/if}
187 + </div>
188 + </form>
189 +
190 + {#if won}
191 + <div
192 + style="position: absolute; left: 50%; top: 30%"
193 + use:confetti={{
194 + particleCount: reducedMotion.current ? 0 : undefined,
195 + force: 0.7,
196 + stageWidth: window.innerWidth,
197 + stageHeight: window.innerHeight,
198 + colors: ['#ff3e00', '#40b3ff', '#676778']
199 + }}
200 + ></div>
201 + {/if}
202 +
203 + <style>
204 + form {
205 + width: 100%;
206 + height: 100%;
207 + display: flex;
208 + flex-direction: column;
209 + align-items: center;
210 + justify-content: center;
211 + gap: 1rem;
212 + flex: 1;
213 + }
214 +
215 + .how-to-play {
216 + color: var(--color-text);
217 + }
218 +
219 + .how-to-play::before {
220 + content: 'i';
221 + display: inline-block;
222 + font-size: 0.8em;
223 + font-weight: 900;
224 + width: 1em;
225 + height: 1em;
226 + padding: 0.2em;
227 + line-height: 1;
228 + border: 1.5px solid var(--color-text);
229 + border-radius: 50%;
230 + text-align: center;
231 + margin: 0 0.5em 0 0;
232 + position: relative;
233 + top: -0.05em;
234 + }
235 +
236 + .grid {
237 + --width: min(100vw, 40vh, 380px);
238 + max-width: var(--width);
239 + align-self: center;
240 + justify-self: center;
241 + width: 100%;
242 + height: 100%;
243 + display: flex;
244 + flex-direction: column;
245 + justify-content: flex-start;
246 + }
247 +
248 + .grid .row {
249 + display: grid;
250 + grid-template-columns: repeat(5, 1fr);
251 + grid-gap: 0.2rem;
252 + margin: 0 0 0.2rem 0;
253 + }
254 +
255 + @media (prefers-reduced-motion: no-preference) {
256 + .grid.shake .row.current {
257 + animation: wiggle 0.5s;
258 + }
259 + }
260 +
261 + .grid.playing .row.current {
262 + filter: drop-shadow(3px 3px 10px var(--color-bg-0));
263 + }
264 +
265 + .letter {
266 + aspect-ratio: 1;
267 + width: 100%;
268 + display: flex;
269 + align-items: center;
270 + justify-content: center;
271 + text-align: center;
272 + box-sizing: border-box;
273 + text-transform: lowercase;
274 + border: none;
275 + font-size: calc(0.08 * var(--width));
276 + border-radius: 2px;
277 + background: white;
278 + margin: 0;
279 + color: rgba(0, 0, 0, 0.7);
280 + }
281 +
282 + .letter.missing {
283 + background: rgba(255, 255, 255, 0.5);
284 + color: rgba(0, 0, 0, 0.5);
285 + }
286 +
287 + .letter.exact {
288 + background: var(--color-theme-2);
289 + color: white;
290 + }
291 +
292 + .letter.close {
293 + border: 2px solid var(--color-theme-2);
294 + }
295 +
296 + .selected {
297 + outline: 2px solid var(--color-theme-1);
298 + }
299 +
300 + .controls {
301 + text-align: center;
302 + justify-content: center;
303 + height: min(18vh, 10rem);
304 + }
305 +
306 + .keyboard {
307 + --gap: 0.2rem;
308 + position: relative;
309 + display: flex;
310 + flex-direction: column;
311 + gap: var(--gap);
312 + height: 100%;
313 + }
314 +
315 + .keyboard .row {
316 + display: flex;
317 + justify-content: center;
318 + gap: 0.2rem;
319 + flex: 1;
320 + }
321 +
322 + .keyboard button,
323 + .keyboard button:disabled {
324 + --size: min(8vw, 4vh, 40px);
325 + background-color: white;
326 + color: black;
327 + width: var(--size);
328 + border: none;
329 + border-radius: 2px;
330 + font-size: calc(var(--size) * 0.5);
331 + margin: 0;
332 + }
333 +
334 + .keyboard button.exact {
335 + background: var(--color-theme-2);
336 + color: white;
337 + }
338 +
339 + .keyboard button.missing {
340 + opacity: 0.5;
341 + }
342 +
343 + .keyboard button.close {
344 + border: 2px solid var(--color-theme-2);
345 + }
346 +
347 + .keyboard button:focus {
348 + background: var(--color-theme-1);
349 + color: white;
350 + outline: none;
351 + }
352 +
353 + .keyboard button[data-key='enter'],
354 + .keyboard button[data-key='backspace'] {
355 + position: absolute;
356 + bottom: 0;
357 + width: calc(1.5 * var(--size));
358 + height: calc(1 / 3 * (100% - 2 * var(--gap)));
359 + text-transform: uppercase;
360 + font-size: calc(0.3 * var(--size));
361 + padding-top: calc(0.15 * var(--size));
362 + }
363 +
364 + .keyboard button[data-key='enter'] {
365 + right: calc(50% + 3.5 * var(--size) + 0.8rem);
366 + }
367 +
368 + .keyboard button[data-key='backspace'] {
369 + left: calc(50% + 3.5 * var(--size) + 0.8rem);
370 + }
371 +
372 + .keyboard button[data-key='enter']:disabled {
373 + opacity: 0.5;
374 + }
375 +
376 + .restart {
377 + width: 100%;
378 + padding: 1rem;
379 + background: rgba(255, 255, 255, 0.5);
380 + border-radius: 2px;
381 + border: none;
382 + }
383 +
384 + .restart:focus,
385 + .restart:hover {
386 + background: var(--color-theme-1);
387 + color: white;
388 + outline: none;
389 + }
390 +
391 + @keyframes wiggle {
392 + 0% {
393 + transform: translateX(0);
394 + }
395 + 10% {
396 + transform: translateX(-2px);
397 + }
398 + 30% {
399 + transform: translateX(4px);
400 + }
401 + 50% {
402 + transform: translateX(-6px);
403 + }
404 + 70% {
405 + transform: translateX(+4px);
406 + }
407 + 90% {
408 + transform: translateX(-2px);
409 + }
410 + 100% {
411 + transform: translateX(0);
412 + }
413 + }
414 + </style>
+75 −0
1 + import { allowed, words } from './words.server.ts';
2 +
3 + export class Game {
4 + index: number;
5 + guesses: string[];
6 + answers: string[];
7 + answer: string;
8 +
9 + /**
10 + * Create a game object from the player's cookie, or initialise a new game
11 + */
12 + constructor(serialized: string | undefined = undefined) {
13 + if (serialized) {
14 + const [index, guesses, answers] = serialized.split('-');
15 +
16 + this.index = +index;
17 + this.guesses = guesses ? guesses.split(' ') : [];
18 + this.answers = answers ? answers.split(' ') : [];
19 + } else {
20 + this.index = Math.floor(Math.random() * words.length);
21 + this.guesses = ['', '', '', '', '', ''];
22 + this.answers = [];
23 + }
24 +
25 + this.answer = words[this.index];
26 + }
27 +
28 + /**
29 + * Update game state based on a guess of a five-letter word. Returns
30 + * true if the guess was valid, false otherwise
31 + */
32 + enter(letters: string[]) {
33 + const word = letters.join('');
34 + const valid = allowed.has(word);
35 +
36 + if (!valid) return false;
37 +
38 + this.guesses[this.answers.length] = word;
39 +
40 + const available = Array.from(this.answer);
41 + const answer = Array(5).fill('_');
42 +
43 + // first, find exact matches
44 + for (let i = 0; i < 5; i += 1) {
45 + if (letters[i] === available[i]) {
46 + answer[i] = 'x';
47 + available[i] = ' ';
48 + }
49 + }
50 +
51 + // then find close matches (this has to happen
52 + // in a second step, otherwise an early close
53 + // match can prevent a later exact match)
54 + for (let i = 0; i < 5; i += 1) {
55 + if (answer[i] === '_') {
56 + const index = available.indexOf(letters[i]);
57 + if (index !== -1) {
58 + answer[i] = 'c';
59 + available[index] = ' ';
60 + }
61 + }
62 + }
63 +
64 + this.answers.push(answer.join(''));
65 +
66 + return true;
67 + }
68 +
69 + /**
70 + * Serialize game state so it can be set as a cookie
71 + */
72 + toString() {
73 + return `${this.index}-${this.guesses.join(' ')}-${this.answers.join(' ')}`;
74 + }
75 + }
+95 −0
1 + <svelte:head>
2 + <title>How to play Sverdle</title>
3 + <meta name="description" content="How to play Sverdle" />
4 + </svelte:head>
5 +
6 + <div class="text-column">
7 + <h1>How to play Sverdle</h1>
8 +
9 + <p>
10 + Sverdle is a clone of <a href="https://www.nytimes.com/games/wordle/index.html">Wordle</a>, the
11 + word guessing game. To play, enter a five-letter English word. For example:
12 + </p>
13 +
14 + <div class="example">
15 + <span class="close">r</span>
16 + <span class="missing">i</span>
17 + <span class="close">t</span>
18 + <span class="missing">z</span>
19 + <span class="exact">y</span>
20 + </div>
21 +
22 + <p>
23 + The <span class="exact">y</span> is in the right place. <span class="close">r</span> and
24 + <span class="close">t</span>
25 + are the right letters, but in the wrong place. The other letters are wrong, and can be discarded.
26 + Let's make another guess:
27 + </p>
28 +
29 + <div class="example">
30 + <span class="exact">p</span>
31 + <span class="exact">a</span>
32 + <span class="exact">r</span>
33 + <span class="exact">t</span>
34 + <span class="exact">y</span>
35 + </div>
36 +
37 + <p>This time we guessed right! You have <strong>six</strong> guesses to get the word.</p>
38 +
39 + <p>
40 + Unlike the original Wordle, Sverdle runs on the server instead of in the browser, making it
41 + impossible to cheat. It uses <code>&lt;form&gt;</code> and cookies to submit data, meaning you can
42 + even play with JavaScript disabled!
43 + </p>
44 + </div>
45 +
46 + <style>
47 + span {
48 + display: inline-flex;
49 + justify-content: center;
50 + align-items: center;
51 + font-size: 0.8em;
52 + width: 2.4em;
53 + height: 2.4em;
54 + background-color: white;
55 + box-sizing: border-box;
56 + border-radius: 2px;
57 + border-width: 2px;
58 + color: rgba(0, 0, 0, 0.7);
59 + }
60 +
61 + .missing {
62 + background: rgba(255, 255, 255, 0.5);
63 + color: rgba(0, 0, 0, 0.5);
64 + }
65 +
66 + .close {
67 + border-style: solid;
68 + border-color: var(--color-theme-2);
69 + }
70 +
71 + .exact {
72 + background: var(--color-theme-2);
73 + color: white;
74 + }
75 +
76 + .example {
77 + display: flex;
78 + justify-content: flex-start;
79 + margin: 1rem 0;
80 + gap: 0.2rem;
81 + }
82 +
83 + .example span {
84 + font-size: 1.4rem;
85 + }
86 +
87 + p span {
88 + position: relative;
89 + border-width: 1px;
90 + border-radius: 1px;
91 + font-size: 0.4em;
92 + transform: scale(2) translate(0, -10%);
93 + margin: 0 1em;
94 + }
95 + </style>
+9 −0
1 + import { dev } from '$app/environment';
2 +
3 + // we don't need any JS on this page, though we'll load
4 + // it in dev so that we get hot module replacement
5 + export const csr = dev;
6 +
7 + // since there's no dynamic data here, we can prerender
8 + // it so that it gets served as a static asset in production
9 + export const prerender = true;
+12980 −0
+1 −0
1 + <svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
+3 −0
1 + # allow crawling everything by default
2 + User-agent: *
3 + Disallow:
+20 −0
1 + {
2 + "extends": "./.svelte-kit/tsconfig.json",
3 + "compilerOptions": {
4 + "rewriteRelativeImportExtensions": true,
5 + "allowJs": true,
6 + "checkJs": true,
7 + "esModuleInterop": true,
8 + "forceConsistentCasingInFileNames": true,
9 + "resolveJsonModule": true,
10 + "skipLibCheck": true,
11 + "sourceMap": true,
12 + "strict": true,
13 + "moduleResolution": "bundler"
14 + }
15 + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
16 + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
17 + //
18 + // To make changes to top-level options such as include and exclude, we recommend extending
19 + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript
20 + }
+20 −0
1 + import adapter from '@sveltejs/adapter-auto';
2 + import { sveltekit } from '@sveltejs/kit/vite';
3 + import { defineConfig } from 'vite';
4 +
5 + export default defineConfig({
6 + plugins: [
7 + sveltekit({
8 + compilerOptions: {
9 + // Force runes mode for the project, except for libraries. Can be removed in svelte 6.
10 + runes: ({ filename }) =>
11 + filename.split(/[/\\]/).includes('node_modules') ? undefined : true
12 + },
13 +
14 + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
15 + // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
16 + // See https://svelte.dev/docs/kit/adapters for more information about adapters.
17 + adapter: adapter()
18 + })
19 + ]
20 + });
src/lib/images/svelte-welcome.png binary file not shown
src/lib/images/svelte-welcome.webp binary file not shown