Compare commits

..

4 Commits

Author SHA1 Message Date
e139eae06d zenno: start conversations tool 2026-03-30 21:53:03 -04:00
34e8c1f812 zenno: implement character picker 2026-03-30 12:13:20 -04:00
cc3128d65a zenno: better mobile nav 2026-03-30 10:13:17 -04:00
d04544030a zenno: create sveltekit website 2026-03-29 23:14:55 -04:00
32 changed files with 5223 additions and 0 deletions

23
zenno/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
zenno/.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

9
zenno/.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/

16
zenno/.prettierrc Normal file
View File

@@ -0,0 +1,16 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
],
"tailwindStylesheet": "./src/routes/layout.css"
}

42
zenno/README.md Normal file
View File

@@ -0,0 +1,42 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project
npx sv create my-app
```
To recreate this project with the same configuration:
```sh
# recreate this project
npx sv@0.13.0 create --template minimal --types ts --add prettier eslint vitest="usages:unit,component" tailwindcss="plugins:none" --install npm zenno
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

39
zenno/eslint.config.js Normal file
View File

@@ -0,0 +1,39 @@
import prettier from 'eslint-config-prettier';
import path from 'node:path';
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
export default defineConfig(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
ts.configs.recommended,
svelte.configs.recommended,
prettier,
svelte.configs.prettier,
{
languageOptions: { globals: { ...globals.browser, ...globals.node } },
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off'
}
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig
}
}
}
);

4336
zenno/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
zenno/package.json Normal file
View File

@@ -0,0 +1,44 @@
{
"name": "zenno",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"test:unit": "vitest",
"test": "npm run test:unit -- --run"
},
"devDependencies": {
"@eslint/compat": "^2.0.3",
"@eslint/js": "^10.0.1",
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^22",
"@vitest/browser-playwright": "^4.1.0",
"eslint": "^10.0.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.15.2",
"globals": "^17.4.0",
"playwright": "^1.58.2",
"prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.4.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.54.0",
"svelte-check": "^4.4.2",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.0",
"vite": "^7.3.1",
"vitest": "^4.1.0",
"vitest-browser-svelte": "^2.0.2"
}
}

13
zenno/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
zenno/src/app.html Normal file
View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { character } from '$lib/data/character'
interface Props {
id: string
value: number
label?: string
region?: keyof typeof character
required?: boolean
}
let { id, value = $bindable(), label, region = 'global', required = false }: Props = $props()
</script>
{#if label}
<label for={id}>{label}</label>
{/if}
<select id={id} bind:value={value} required={required}>
{#if !required}
<option value=0></option>
{/if}
{#each character[region] as c}
<option value={c.chara_id}>{c.name}</option>
{/each}
</select>

View File

@@ -0,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>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,23 @@
import type { RegionalName } from '$lib/regional-name'
import globalJSON from '../../../../global/character.json'
/**
* Character definitions.
*/
export interface Character {
/**
* Character ID.
*/
chara_id: number
/**
* Regional name of the character.
* E.g., Special Week for Global, or スペシャルウィーク for JP.
*/
name: string
}
export const character = {
global: globalJSON as Character[],
}
export const charaNames = globalJSON.reduce((m, c) => m.set(c.chara_id, {en: c.name}), new Map<Character['chara_id'], RegionalName>());

View File

@@ -0,0 +1,48 @@
import globalJSON from '../../../../global/conversation.json'
/**
* Lobby conversation data.
*/
export interface Conversation {
/**
* Character who owns the conversation as a gallery entry.
*/
chara_id: number;
/**
* Number of the conversation within the character's conversation gallery.
*/
number: number;
/**
* Location ID of the conversation.
*/
location: 110 | 120 | 130 | 210 | 220 | 310 | 410 | 420 | 430 | 510 | 520 | 530;
/**
* English name of the location, for convenience.
*/
location_name: string;
/**
* First character in the conversation.
* Not necessarily equal to chara_id.
*/
chara_1: number;
/**
* Second character, if present.
*/
chara_2?: number;
/**
* Third character, if present.
*/
chara_3?: number;
/**
* Some unknown number in the game's local database.
*/
condition_type: 0 | 1 | 2 | 3 | 4;
}
export const conversation = {
global: globalJSON as Conversation[],
}
export const byChara = {
global: globalJSON.reduce((m, c) => m.set(c.chara_id, (m.get(c.chara_id) ?? []).concat(c as Conversation)), new Map<Conversation['chara_id'], Conversation[]>()),
}

1
zenno/src/lib/index.ts Normal file
View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@@ -0,0 +1,7 @@
/**
* Names accounting for regions.
* Currently English is the only supported language.
*/
export interface RegionalName {
en: string
}

View File

@@ -0,0 +1,8 @@
<script>
import { greet } from './greet';
let { host = 'SvelteKit', guest = 'Vitest' } = $props();
</script>
<h1>{greet(host)}</h1>
<p>{greet(guest)}</p>

View File

@@ -0,0 +1,15 @@
import { page } from 'vitest/browser';
import { describe, expect, it } from 'vitest';
import { render } from 'vitest-browser-svelte';
import Welcome from './Welcome.svelte';
describe('Welcome.svelte', () => {
it('renders greetings for host and guest', async () => {
render(Welcome, { host: 'SvelteKit', guest: 'Vitest' });
await expect
.element(page.getByRole('heading', { level: 1 }))
.toHaveTextContent('Hello, SvelteKit!');
await expect.element(page.getByText('Hello, Vitest!')).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,8 @@
import { describe, it, expect } from 'vitest';
import { greet } from './greet';
describe('greet', () => {
it('returns a greeting', () => {
expect(greet('Svelte')).toBe('Hello, Svelte!');
});
});

View File

@@ -0,0 +1,3 @@
export function greet(name: string): string {
return 'Hello, ' + name + '!';
}

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
let { children } = $props();
</script>
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
<nav class="flex min-w-full p-4 mb-4 shadow-md">
<span class="hidden md:inline flex-1">
<a href="/" class="text-7xl">Zenno Rob Roy</a>
</span>
<span class="flex-1 text-center">
<a href="/" class="md:hidden block mx-8 my-1 font-semibold">Zenno Rob Roy</a>
<a href="/inherit" class="inline-block mx-8 my-1">Inheritance Chance</a>
<a href="/spark" class="inline-block mx-8 my-1">Spark Chance</a>
<a href="/vet" class="inline-block mx-8 my-1">My Veterans</a>
<a href="/convo" class="inline-block mx-8 my-1">Lobby Conversations</a>
</span>
</nav>
<div class="md:min-w-7xl md:max-w-7xl md:m-auto">
{@render children()}
</div>
<footer class="p-4 mt-32 inset-x-0 bottom-0 border-t text-center text-[14px]">
Umamusume: Pretty Derby tools by <a href="https://zephyrtronium.date/">zephyrtronium</a>.<br>
All data is generated from the game's local database.
</footer>

View File

@@ -0,0 +1,11 @@
<script lang="ts">
import CharaPick from "$lib/CharaPick.svelte";
let selChara = $state(0);
</script>
<h1>Welcome to SvelteKit</h1>
<CharaPick id="test-chara" bind:value={selChara} />
<p>selected character is id {selChara}</p>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
<p>Lorem ipsum (/ ˌ l ɔː. r ə m ˈ ɪ p. s ə m/ LOR-əm IP-səm) is a dummy or placeholder text commonly used in graphic design, publishing, and web development. It is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. The first two words are the truncation of dolorem ipsum ("pain itself"). Lorem ipsum's purpose is to permit a page layout to be designed, independently of the copy that will subsequently populate it, or to demonstrate various fonts of a typeface without meaningful text that could be distracting. Versions of the Lorem ipsum text have been used in typesetting since the 1960s, when advertisements for Letraset transfer sheets popularized it. Lorem ipsum was introduced to the digital world in the mid-1980s, when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors, including Pages and Microsoft Word, have since adopted Lorem ipsum, as have many LaTeX packages, web content</p>

View File

@@ -0,0 +1,11 @@
<script lang="ts">
import CharaPick from "$lib/CharaPick.svelte";
let charaID = $state(1001)
let convo = $state(1)
</script>
<h1>Lobby Conversations</h1>
<p>Find which horses are in a given lobby conversation, and get recommendations on which ones to assign to fixed locations to maximize the chance of getting it.</p>
<hr>
<CharaPick id="chara" label="Select a character" bind:value={charaID} required />

View File

@@ -0,0 +1,3 @@
<h1>Inheritance Chance</h1>
<p>Given a legacy, calculate the probability distribution of activation counts for each spark.</p>
<p>TODO</p>

View File

@@ -0,0 +1,6 @@
@import 'tailwindcss';
@import './sakura-vars.css';
@theme {
--text-sm: 1.25rem;
}

View File

@@ -0,0 +1,401 @@
/* Sakura.css v1.5.0
* ================
* Minimal css theme.
* Project: https://github.com/oxalorg/sakura/
*/
/* data-theme="taiyō" */
:root {
--blossom: #292722;
--fade: #7d7768;
--bg: #ffecec;
--bg-alt: #ffecec;
--text: #292222;
}
[data-theme="iron goddess"] {
--blossom: #424b51;
--fade: #64707a;
--bg: #fff2e2;
--bg-alt: #fffce2;
--text: #2c2923;
}
[data-theme="main sequence"] {
--blossom: #3a5425;
--fade: #698650;
--bg: #fffde5;
--bg-alt: #fff4e5;
--text: #5e592a;
}
[data-theme="sorcery"] {
--blossom: #5a5a69;
--fade: #868698;
--bg: #e5f4e5;
--bg-alt: #e6f4e6;
--text: #323932;
}
[data-theme="cirrus"] {
--blossom: #565a4b;
--fade: #9da587;
--bg: #e5f6fa;
--bg-alt: #e5f6fa;
--text: #31393b;
}
[data-theme="oxygen"] {
--blossom: #162011;
--fade: #343932;
--bg: #e1e2e4;
--bg-alt: #e3e0e3;
--text: #27282c;
}
[data-theme="dauphin"] {
--blossom: #171e1c;
--fade: #485b58;
--bg: #ebe5f8;
--bg-alt: #ebe5f8;
--text: #1c1a20;
}
[data-theme="diamond-burned"] {
--blossom: #0f0d0b;
--fade: #4d4743;
--bg: #f8ebf2;
--bg-alt: #ebe8f4;
--text: #3e363a;
}
[data-theme="chi"] {
--blossom: #908975;
--fade: #fff8e5;
--bg: #110c0c;
--bg-alt: #0a090c;
--text: #cfa9a9;
}
[data-theme="darjeeling"] {
--blossom: #ba949c;
--fade: #f8e1e6;
--bg: #1c160d;
--bg-alt: #1c160d;
--text: #c9b9a0;
}
[data-theme="subgiant"] {
--blossom: #9fad8a;
--fade: #e8f2d7;
--bg: #16130b;
--bg-alt: #16130b;
--text: #bbb396;
}
[data-theme="goblin"] {
--blossom: #7a808e;
--fade: #dae1ef;
--bg: #070905;
--bg-alt: #0a0906;
--text: #acbd9f;
}
[data-theme="altostratus"] {
--blossom: #a8a0b7;
--fade: #e5dbf7;
--bg: #0c0f0f;
--bg-alt: #1a1614;
--text: #8da4a4;
}
[data-theme="silicon"] {
--blossom: #717f63;
--fade: #c4d4b3;
--bg: #050a0f;
--bg-alt: #050a0f;
--text: #838e9a;
}
[data-theme="imperator"] {
--blossom: #93a0a3;
--fade: #f3fbfd;
--bg: #0e0c12;
--bg-alt: #0e0c12;
--text: #a8a1b1;
}
[data-theme="mædi"] {
--blossom: #ccd3b6;
--fade: #fdfbf3;
--bg: #10090f;
--bg-alt: #2f282e;
--text: #9e889a;
}
/* Body */
html {
font-size: 62.5%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
}
body {
font-size: 1.8rem;
line-height: 1.618;
/* max-width: 38em; */
margin: auto;
color: var(--text);
background-color: var(--bg);
padding: 13px;
}
@media (max-width: 684px) {
body {
font-size: 1.53rem;
}
}
@media (max-width: 382px) {
body {
font-size: 1.35rem;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
font-weight: 700;
margin-top: 3rem;
margin-bottom: 1.5rem;
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
word-break: break-word;
}
h1 {
font-size: 2.35em;
}
h2 {
font-size: 2em;
}
h3 {
font-size: 1.75em;
}
h4 {
font-size: 1.5em;
}
h5 {
font-size: 1.25em;
}
h6 {
font-size: 1em;
}
p {
margin-top: 0px;
margin-bottom: 2.5rem;
}
small,
sub,
sup {
font-size: 75%;
}
hr {
border-color: var(--blossom);
}
a {
text-decoration: none;
color: var(--blossom);
}
a:hover {
color: var(--fade);
border-bottom: 2px solid var(--text);
}
ul {
padding-left: 1.4em;
margin-top: 0px;
margin-bottom: 2.5rem;
}
li {
margin-bottom: 0.4em;
}
blockquote {
margin-left: 0px;
margin-right: 0px;
padding-left: 1em;
padding-top: 0.8em;
padding-bottom: 0.8em;
padding-right: 0.8em;
border-left: 5px solid var(--blossom);
margin-bottom: 2.5rem;
background-color: var(--bg-alt);
}
blockquote p {
margin-bottom: 0;
}
img,
video {
height: auto;
max-width: 100%;
margin-top: 0px;
margin-bottom: 2.5rem;
}
/* Pre and Code */
pre {
background-color: var(--bg-alt);
display: block;
padding: 1em;
overflow-x: auto;
margin-top: 0px;
margin-bottom: 2.5rem;
font-size: 0.9em;
}
code,
kbd,
samp {
font-size: 0.9em;
padding: 0 0.5em;
background-color: var(--bg-alt);
white-space: pre-wrap;
}
pre>code {
padding: 0;
background-color: transparent;
white-space: pre;
font-size: 1em;
}
/* Tables */
table {
text-align: justify;
width: 100%;
border-collapse: collapse;
margin-bottom: 2rem;
}
td,
th {
padding: 0.5em;
border-bottom: 1px solid var(--bg-alt);
}
/* Buttons, forms and input */
input,
textarea {
border: 1px solid var(--text);
}
input:focus,
textarea:focus {
border: 1px solid var(--blossom);
}
textarea {
width: 100%;
}
.button,
button,
input[type=submit],
input[type=reset],
input[type=button],
input[type=file]::file-selector-button {
display: inline-block;
padding: 5px 10px;
text-align: center;
text-decoration: none;
white-space: nowrap;
background-color: var(--blossom);
color: var(--bg);
border-radius: 1px;
border: 1px solid var(--blossom);
cursor: pointer;
box-sizing: border-box;
}
.button[disabled],
button[disabled],
input[type=submit][disabled],
input[type=reset][disabled],
input[type=button][disabled],
input[type=file]::file-selector-button[disabled] {
cursor: default;
opacity: 0.5;
}
.button:hover,
button:hover,
input[type=submit]:hover,
input[type=reset]:hover,
input[type=button]:hover,
input[type=file]::file-selector-button:hover {
background-color: var(--fade);
color: var(--bg);
outline: 0;
}
.button:focus-visible,
button:focus-visible,
input[type=submit]:focus-visible,
input[type=reset]:focus-visible,
input[type=button]:focus-visible,
input[type=file]::file-selector-button:focus-visible {
outline-style: solid;
outline-width: 2px;
}
textarea,
select,
input {
color: var(--text);
padding: 6px 10px;
/* The 6px vertically centers text on FF, ignored by Webkit */
margin-bottom: 10px;
background-color: var(--bg-alt);
border: 1px solid var(--bg-alt);
border-radius: 4px;
box-shadow: none;
box-sizing: border-box;
}
textarea:focus,
select:focus,
input:focus {
border: 1px solid var(--blossom);
outline: 0;
}
input[type=checkbox]:focus {
outline: 1px dotted var(--blossom);
}
label,
legend,
fieldset {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}

View File

@@ -0,0 +1,3 @@
<h1>Spark Generation Chance</h1>
<p>Given a legacy, calculate the chance of generating each spark if you fulfill the conditions to do so, and the distribution of total spark counts.</p>
<p>TODO</p>

View File

@@ -0,0 +1,4 @@
<h1>My Veterans</h1>
<p>Set up and track your veterans for Zenno Rob Roy's inspiration and spark calculators.</p>
<p>All data is saved on your own machine, not transmitted or shared unless you explicitly choose to do so.</p>
<p>TODO</p>

3
zenno/static/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:

24
zenno/svelte.config.js Normal file
View File

@@ -0,0 +1,24 @@
import adapter from '@sveltejs/adapter-auto';
import { relative, sep } from 'node:path';
/** @type {import('@sveltejs/kit').Config} */
const config = {
compilerOptions: {
// defaults to rune mode for the project, execept for `node_modules`. Can be removed in svelte 6.
runes: ({ filename }) => {
const relativePath = relative(import.meta.dirname, filename);
const pathSegments = relativePath.toLowerCase().split(sep);
const isExternalLibrary = pathSegments.includes('node_modules');
return isExternalLibrary ? undefined : true;
}
},
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

20
zenno/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}

36
zenno/vite.config.ts Normal file
View File

@@ -0,0 +1,36 @@
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
import { sveltekit } from '@sveltejs/kit/vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
test: {
expect: { requireAssertions: true },
projects: [
{
extends: './vite.config.ts',
test: {
name: 'client',
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium', headless: true }]
},
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
exclude: ['src/lib/server/**']
}
},
{
extends: './vite.config.ts',
test: {
name: 'server',
environment: 'node',
include: ['src/**/*.{test,spec}.{js,ts}'],
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
}
}
]
}
});