fix: make it work with new vite

This commit is contained in:
Max Richter
2026-01-10 19:28:09 +01:00
parent 694feb083d
commit 8d712322c0
66 changed files with 590 additions and 1544 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,3 @@
# dotenv environment variable files
.env
.env.development.local
.env.test.local
@@ -10,3 +9,4 @@ data-dev/
_fresh/
node_modules/
mise.toml

View File

@@ -15,7 +15,7 @@ COPY . .
ENV DATA_DIR=/app/data
RUN mkdir -p $DATA_DIR && \
deno install --allow-import --allow-ffi --allow-scripts=npm:sharp -e main.ts &&\
deno install --allow-import --allow-ffi -e main.ts &&\
sed -i -e 's/"deno"/"no-deno"/' node_modules/@libsql/client/package.json &&\
deno task build

View File

@@ -5,18 +5,13 @@ Started" guide here: https://fresh.deno.dev/docs/getting-started
### Usage
Make sure to install Deno: https://deno.land/manual/getting_started/installation
Make sure to install Deno:
https://docs.deno.com/runtime/getting_started/installation
Then start the project:
Then start the project in development mode:
```
deno task start
deno task dev
```
This will watch the project directory and restart as necessary.
## FIX
```
sed -i -e 's/"deno"/"no-deno"/' node_modules/@libsql/client/package.json
```

View File

@@ -1,3 +0,0 @@
@import "tailwindcss";
@source "../components/*"
@source "../components/**/*"

1
assets/styles.css Normal file
View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -1 +1,2 @@
import "./assets/style.css";
// Import CSS files here for hot module reloading to work.
import "./assets/styles.css";

View File

@@ -1,12 +1,10 @@
import { ButtonHTMLAttributes } from "preact";
import { IS_BROWSER } from "fresh/runtime";
export function Button(props: ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button
{...props}
disabled={!IS_BROWSER || props.disabled}
class={`px-2 py-1 ${props.class ? props.class : " "}`}
class={`cursor-pointer px-2 py-1 ${props.class ? props.class : ""}`}
/>
);
}

View File

@@ -1,7 +1,6 @@
import { isYoutubeLink } from "@lib/string.ts";
import { IconBrandYoutube } from "@components/icons.tsx";
import { SmallRating } from "@components/Rating.tsx";
import { Link } from "@islands/Link.tsx";
import { parseRating } from "@lib/helpers.ts";
import { GenericResource, getNameOfResource } from "@lib/marka/schema.ts";
@@ -24,7 +23,7 @@ export function Card(
rating?: number;
},
) {
const backgroundStyle: preact.JSX.CSSProperties = {
const backgroundStyle: preact.CSSProperties = {
backgroundSize: "cover",
backgroundColor: backgroundColor,
};
@@ -36,7 +35,7 @@ export function Card(
}
return (
<Link
<a
href={link}
style={backgroundStyle}
data-thumb={thumbhash}
@@ -88,7 +87,7 @@ export function Card(
)}
</div>
<div class="absolute inset-x-0 bottom-0 h-3/4" />
</Link>
</a>
);
}

View File

@@ -1,5 +1,4 @@
import { asset } from "fresh/runtime";
import * as CSS from "csstype";
interface ResponsiveAttributes {
srcset: string;
@@ -38,7 +37,7 @@ const Image = (
fill?: boolean;
width?: number | string;
height?: number | string;
style?: CSS.HtmlAttributes;
style?: preact.CSSProperties;
},
) => {
const responsiveAttributes = generateResponsiveAttributes(

View File

@@ -21,7 +21,7 @@ function Wrapper(
return (
<div
class={`flex justify-between flex-col relative w-full ${
image ? "min-h-[400px]" : "min-h-[200px]"
image ? "min-h-100" : "min-h-50"
} rounded-3xl overflow-hidden`}
>
<HeroContext.Provider value={{ image }}>
@@ -62,7 +62,7 @@ function Title(
>
{children}
{link &&
<IconExternalLink />}
<IconExternalLink class="h-6 w-6" />}
</h2>
</OuterTag>
);
@@ -110,7 +110,7 @@ function Subline(
const ctx = useContext(HeroContext);
return (
<div
class={`relative flex items-center z-10 flex gap-5 font-sm text-light mt-3`}
class={`relative items-center z-10 flex gap-5 font-sm text-light mt-3`}
style={{ color: ctx.image ? "#1F1F1F" : "white" }}
>
{children}

View File

@@ -4,10 +4,10 @@ services:
context: .
dockerfile: Dockerfile
volumes:
- .:/app # Mount the local directory to /app in the container
working_dir: /app # Set the working directory inside the container to /app
command: run --env-file -A --watch=static/,routes/ dev.ts # Custom start command
- .:/app
working_dir: /app
command: deno task dev --host 0.0.0.0
ports:
- "8000:8000" # Expose the container port
- "8000:8000"
environment:
- DATA_DIR=/app/data # Set the environment variable inside the container
- DATA_DIR=/app/data

View File

@@ -1,66 +1,70 @@
{
"nodeModulesDir": "manual",
"tasks": {
"check": "deno fmt --check && deno lint && deno check",
"start": "deno run --env-file -A main.ts",
"db": "deno run --env-file -A npm:drizzle-kit",
"update": "deno run -A -r jsr:@fresh/update .",
"check": "deno fmt --check . && deno lint . && deno check",
"dev": "vite",
"db": "deno run --env-file -A npm:drizzle-kit",
"build": "vite build",
"preview": "deno serve -A _fresh/server.js"
"start": "deno serve -A _fresh/server.js",
"update": "deno run -A -r jsr:@fresh/update ."
},
"lint": { "rules": { "tags": ["fresh", "recommended"] } },
"exclude": ["**/_fresh/*"],
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
},
"exclude": [
"**/_fresh/*"
],
"imports": {
"@cmd-johnson/oauth2-client": "jsr:@cmd-johnson/oauth2-client@^2.0.0",
"@components": "./components",
"@components/": "./components/",
"@denosaurs/emoji": "jsr:@denosaurs/emoji@^0.3.1",
"@deno/gfm": "jsr:@deno/gfm@^0.11.0",
"@islands": "./islands",
"@islands/": "./islands/",
"@lib": "./lib",
"@lib/": "./lib/",
"@libsql/client": "npm:@libsql/client@^0.14.0",
"@/": "./",
"@libsql/client": "npm:@libsql/client@^0.17.0",
"@openai/openai": "jsr:@openai/openai@^6.16.0",
"@preact-icons/tb": "jsr:@preact-icons/tb@^1.0.14",
"@std/fs": "jsr:@std/fs@^1.0.21",
"@std/http": "jsr:@std/http@^1.0.23",
"@std/media-types": "jsr:@std/media-types@^1.1.0",
"@std/yaml": "jsr:@std/yaml@^1.0.10",
"@types/jsdom": "npm:@types/jsdom@^27.0.0",
"@types/turndown": "npm:@types/turndown@^5.0.6",
"@zaubrik/djwt": "jsr:@zaubrik/djwt@^3.0.2",
"csstype": "npm:csstype@^3.2.3",
"defuddle": "npm:defuddle@^0.6.6",
"drizzle-kit": "npm:drizzle-kit@^0.30.6",
"drizzle-orm": "npm:drizzle-orm@^0.38.4",
"drizzle-kit": "npm:drizzle-kit@^0.31.8",
"drizzle-orm": "npm:drizzle-orm@^0.45.1",
"fresh": "jsr:@fresh/core@^2.2.0",
"fuzzysort": "npm:fuzzysort@^3.1.0",
"grammy": "npm:grammy@^1.39.2",
"gfm": "jsr:@deno/gfm@0.11.0",
"jsdom": "npm:jsdom@^27.4.0",
"moviedb-promise": "npm:moviedb-promise@^4.0.7",
"parse-ingredient": "npm:parse-ingredient@^1.3.1",
"playwright": "npm:playwright@^1.57.0",
"playwright-extra": "npm:playwright-extra@^4.3.6",
"gfm": "jsr:@deno/gfm@0.11.0",
"prismjs": "npm:prismjs@^1.30.0",
"puppeteer-extra-plugin-stealth": "npm:puppeteer-extra-plugin-stealth@^2.11.2",
"sharp": "npm:sharp@^0.34.5",
"@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.12",
"camelcase-css": "npm:camelcase-css@2.0.1",
"thumbhash": "npm:thumbhash@^0.1.1",
"tsx": "npm:tsx@^4.21.0",
"turndown": "npm:turndown@^7.2.2",
"zod": "npm:zod@^3.25.76",
"fresh": "jsr:@fresh/core@^2.2.0",
"preact": "npm:preact@^10.27.2",
"@preact/signals": "npm:@preact/signals@^2.5.0",
"@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.8",
"vite": "npm:vite@^7.3.1",
"tailwindcss": "npm:tailwindcss@^4.1.18"
"puppeteer-extra-plugin-stealth": "npm:puppeteer-extra-plugin-stealth@^2.11.2",
"sharp": "npm:sharp@^0.34.5",
"thumbhash": "npm:thumbhash@^0.1.1",
"turndown": "npm:turndown@^7.2.2",
"vite": "npm:vite@^7.1.3",
"tailwindcss": "npm:tailwindcss@^4.1.10",
"@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.12",
"zod": "npm:zod@^4.3.5"
},
"compilerOptions": {
"lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns"],
"jsx": "react-jsx",
"lib": [
"dom",
"dom.asynciterable",
"dom.iterable",
"deno.ns"
],
"jsx": "precompile",
"jsxImportSource": "preact",
"jsxPrecompileSkipElements": [
"a",
@@ -78,7 +82,19 @@
"noscript",
"template"
],
"types": ["vite/client"]
"types": [
"vite/client"
]
},
"allowScripts": { "allow": ["npm:esbuild@0.27.2", "npm:sharp@0.34.5"] }
"allowScripts": {
"allow": [
"npm:sharp@0.34.5"
],
"deny": [
"npm:esbuild@0.18.20",
"npm:esbuild@0.25.12",
"npm:esbuild@0.25.7",
"npm:esbuild@0.27.2"
]
}
}

451
deno.lock generated
View File

@@ -3,6 +3,7 @@
"specifiers": {
"jsr:@cmd-johnson/oauth2-client@2": "2.0.0",
"jsr:@deno/esbuild-plugin@^1.2.0": "1.2.1",
"jsr:@deno/gfm@0.11": "0.11.0",
"jsr:@deno/gfm@0.11.0": "0.11.0",
"jsr:@deno/loader@~0.3.10": "0.3.11",
"jsr:@deno/loader@~0.3.2": "0.3.11",
@@ -39,30 +40,24 @@
"jsr:@std/semver@^1.0.6": "1.0.7",
"jsr:@std/streams@^1.0.16": "1.0.16",
"jsr:@std/uuid@^1.0.9": "1.1.0",
"jsr:@std/yaml@^1.0.10": "1.0.10",
"jsr:@zaubrik/djwt@^3.0.2": "3.0.2",
"npm:@babel/core@^7.28.0": "7.28.5",
"npm:@babel/preset-react@^7.27.1": "7.28.5_@babel+core@7.28.5",
"npm:@libsql/client@0.14": "0.14.0",
"npm:@libsql/client@0.17": "0.17.0",
"npm:@mjackson/node-fetch-server@0.7": "0.7.0",
"npm:@opentelemetry/api@^1.9.0": "1.9.0",
"npm:@preact/signals@^2.2.1": "2.5.1_preact@10.28.2",
"npm:@preact/signals@^2.5.0": "2.5.1_preact@10.28.2",
"npm:@prefresh/vite@^2.4.8": "2.4.11_preact@10.28.2_vite@7.3.1__tsx@4.21.0__picomatch@4.0.3_tsx@4.21.0",
"npm:@tailwindcss/vite@^4.1.12": "4.1.18_vite@7.3.1__tsx@4.21.0__picomatch@4.0.3_tsx@4.21.0",
"npm:@types/jsdom@27": "27.0.0",
"npm:@types/turndown@^5.0.6": "5.0.6",
"npm:camelcase-css@2.0.1": "2.0.1",
"npm:csstype@^3.2.3": "3.2.3",
"npm:@prefresh/vite@^2.4.8": "2.4.11_preact@10.28.2_vite@7.3.1__picomatch@4.0.3",
"npm:@tailwindcss/vite@^4.1.12": "4.1.18_vite@7.3.1__picomatch@4.0.3",
"npm:defuddle@~0.6.6": "0.6.6_jsdom@27.4.0",
"npm:drizzle-kit@~0.30.6": "0.30.6_esbuild@0.19.12",
"npm:drizzle-orm@~0.38.4": "0.38.4_@libsql+client@0.14.0_@opentelemetry+api@1.9.0",
"npm:drizzle-kit@~0.31.8": "0.31.8_esbuild@0.25.12",
"npm:drizzle-orm@~0.45.1": "0.45.1_@libsql+client@0.17.0_@opentelemetry+api@1.9.0",
"npm:esbuild-wasm@~0.25.11": "0.25.12",
"npm:esbuild@0.25.7": "0.25.7",
"npm:esbuild@~0.25.5": "0.25.12",
"npm:fuzzysort@^3.1.0": "3.1.0",
"npm:github-slugger@2": "2.0.0",
"npm:grammy@^1.39.2": "1.39.2",
"npm:he@^1.2.0": "1.2.0",
"npm:jsdom@^27.4.0": "27.4.0",
"npm:katex@0.16": "0.16.27",
@@ -72,27 +67,23 @@
"npm:marked@12": "12.0.2",
"npm:moviedb-promise@^4.0.7": "4.0.7",
"npm:parse-ingredient@^1.3.1": "1.3.1",
"npm:playwright-extra@^4.3.6": "4.3.6_playwright@1.57.0",
"npm:playwright@^1.57.0": "1.57.0",
"npm:playwright-extra@^4.3.6": "4.3.6",
"npm:preact-render-to-string@^6.6.3": "6.6.5_preact@10.28.2",
"npm:preact@^10.22.1": "10.28.2",
"npm:preact@^10.27.0": "10.28.2",
"npm:preact@^10.27.2": "10.28.2",
"npm:prismjs@^1.29.0": "1.30.0",
"npm:prismjs@^1.30.0": "1.30.0",
"npm:puppeteer-extra-plugin-stealth@^2.11.2": "2.11.2_playwright-extra@4.3.6__playwright@1.57.0_playwright@1.57.0",
"npm:puppeteer-extra-plugin-stealth@^2.11.2": "2.11.2_playwright-extra@4.3.6",
"npm:rollup@^4.50.0": "4.55.1",
"npm:sanitize-html@^2.13.0": "2.17.0",
"npm:sharp@~0.34.5": "0.34.5",
"npm:tailwindcss@^4.1.18": "4.1.18",
"npm:tailwindcss@^4.1.10": "4.1.18",
"npm:thumbhash@~0.1.1": "0.1.1",
"npm:tsx@^4.21.0": "4.21.0",
"npm:turndown@^7.2.2": "7.2.2",
"npm:vite@7.3.1": "7.3.1_tsx@4.21.0_picomatch@4.0.3",
"npm:vite@^7.1.4": "7.3.1_tsx@4.21.0_picomatch@4.0.3",
"npm:vite@^7.3.1": "7.3.1_tsx@4.21.0_picomatch@4.0.3",
"npm:vite@^7.1.3": "7.3.1_picomatch@4.0.3",
"npm:vite@^7.1.4": "7.3.1_picomatch@4.0.3",
"npm:zod@3": "3.25.76",
"npm:zod@^3.25.76": "3.25.76"
"npm:zod@^4.3.5": "4.3.5"
},
"jsr": {
"@cmd-johnson/oauth2-client@2.0.0": {
@@ -120,7 +111,7 @@
"npm:marked-alert",
"npm:marked-footnote",
"npm:marked-gfm-heading-id",
"npm:prismjs@^1.29.0",
"npm:prismjs",
"npm:sanitize-html"
]
},
@@ -217,7 +208,6 @@
"@std/fs@1.0.21": {
"integrity": "d720fe1056d78d43065a4d6e0eeb2b19f34adb8a0bc7caf3a4dbf1d4178252cd",
"dependencies": [
"jsr:@std/internal",
"jsr:@std/path@^1.1.4"
]
},
@@ -274,9 +264,6 @@
"jsr:@std/bytes"
]
},
"@std/yaml@1.0.10": {
"integrity": "245706ea3511cc50c8c6d00339c23ea2ffa27bd2c7ea5445338f8feff31fa58e"
},
"@zaubrik/djwt@3.0.2": {
"integrity": "8070adaa49cd9e5d2b8ae82fd461132c966ef2f8ff8378db4a4da8df4f17c664",
"dependencies": [
@@ -517,8 +504,8 @@
"@csstools/css-tokenizer"
]
},
"@csstools/css-syntax-patches-for-csstree@1.0.23": {
"integrity": "sha512-YEmgyklR6l/oKUltidNVYdjSmLSW88vMsKx0pmiS3r71s8ZZRpd8A0Yf0U+6p/RzElmMnPBv27hNWjDQMSZRtQ=="
"@csstools/css-syntax-patches-for-csstree@1.0.24": {
"integrity": "sha512-T0pSTcd9eYEHV+llVPSkZU7URdVGu87BpSvozMwRoLJYXmLXvEHgYfv0yDsQH9+DIdLzkJCOJBABqWWnwTGPvg=="
},
"@csstools/css-tokenizer@3.0.4": {
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="
@@ -548,11 +535,6 @@
],
"deprecated": true
},
"@esbuild/aix-ppc64@0.19.12": {
"integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
"os": ["aix"],
"cpu": ["ppc64"]
},
"@esbuild/aix-ppc64@0.25.12": {
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"os": ["aix"],
@@ -573,11 +555,6 @@
"os": ["android"],
"cpu": ["arm64"]
},
"@esbuild/android-arm64@0.19.12": {
"integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
"os": ["android"],
"cpu": ["arm64"]
},
"@esbuild/android-arm64@0.25.12": {
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"os": ["android"],
@@ -598,11 +575,6 @@
"os": ["android"],
"cpu": ["arm"]
},
"@esbuild/android-arm@0.19.12": {
"integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
"os": ["android"],
"cpu": ["arm"]
},
"@esbuild/android-arm@0.25.12": {
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"os": ["android"],
@@ -623,11 +595,6 @@
"os": ["android"],
"cpu": ["x64"]
},
"@esbuild/android-x64@0.19.12": {
"integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
"os": ["android"],
"cpu": ["x64"]
},
"@esbuild/android-x64@0.25.12": {
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"os": ["android"],
@@ -648,11 +615,6 @@
"os": ["darwin"],
"cpu": ["arm64"]
},
"@esbuild/darwin-arm64@0.19.12": {
"integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@esbuild/darwin-arm64@0.25.12": {
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"os": ["darwin"],
@@ -673,11 +635,6 @@
"os": ["darwin"],
"cpu": ["x64"]
},
"@esbuild/darwin-x64@0.19.12": {
"integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@esbuild/darwin-x64@0.25.12": {
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"os": ["darwin"],
@@ -698,11 +655,6 @@
"os": ["freebsd"],
"cpu": ["arm64"]
},
"@esbuild/freebsd-arm64@0.19.12": {
"integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
"os": ["freebsd"],
"cpu": ["arm64"]
},
"@esbuild/freebsd-arm64@0.25.12": {
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"os": ["freebsd"],
@@ -723,11 +675,6 @@
"os": ["freebsd"],
"cpu": ["x64"]
},
"@esbuild/freebsd-x64@0.19.12": {
"integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@esbuild/freebsd-x64@0.25.12": {
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"os": ["freebsd"],
@@ -748,11 +695,6 @@
"os": ["linux"],
"cpu": ["arm64"]
},
"@esbuild/linux-arm64@0.19.12": {
"integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@esbuild/linux-arm64@0.25.12": {
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"os": ["linux"],
@@ -773,11 +715,6 @@
"os": ["linux"],
"cpu": ["arm"]
},
"@esbuild/linux-arm@0.19.12": {
"integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
"os": ["linux"],
"cpu": ["arm"]
},
"@esbuild/linux-arm@0.25.12": {
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"os": ["linux"],
@@ -798,11 +735,6 @@
"os": ["linux"],
"cpu": ["ia32"]
},
"@esbuild/linux-ia32@0.19.12": {
"integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
"os": ["linux"],
"cpu": ["ia32"]
},
"@esbuild/linux-ia32@0.25.12": {
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"os": ["linux"],
@@ -823,11 +755,6 @@
"os": ["linux"],
"cpu": ["loong64"]
},
"@esbuild/linux-loong64@0.19.12": {
"integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
"os": ["linux"],
"cpu": ["loong64"]
},
"@esbuild/linux-loong64@0.25.12": {
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"os": ["linux"],
@@ -848,11 +775,6 @@
"os": ["linux"],
"cpu": ["mips64el"]
},
"@esbuild/linux-mips64el@0.19.12": {
"integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
"os": ["linux"],
"cpu": ["mips64el"]
},
"@esbuild/linux-mips64el@0.25.12": {
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"os": ["linux"],
@@ -873,11 +795,6 @@
"os": ["linux"],
"cpu": ["ppc64"]
},
"@esbuild/linux-ppc64@0.19.12": {
"integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
"os": ["linux"],
"cpu": ["ppc64"]
},
"@esbuild/linux-ppc64@0.25.12": {
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"os": ["linux"],
@@ -898,11 +815,6 @@
"os": ["linux"],
"cpu": ["riscv64"]
},
"@esbuild/linux-riscv64@0.19.12": {
"integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
"os": ["linux"],
"cpu": ["riscv64"]
},
"@esbuild/linux-riscv64@0.25.12": {
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"os": ["linux"],
@@ -923,11 +835,6 @@
"os": ["linux"],
"cpu": ["s390x"]
},
"@esbuild/linux-s390x@0.19.12": {
"integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
"os": ["linux"],
"cpu": ["s390x"]
},
"@esbuild/linux-s390x@0.25.12": {
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"os": ["linux"],
@@ -948,11 +855,6 @@
"os": ["linux"],
"cpu": ["x64"]
},
"@esbuild/linux-x64@0.19.12": {
"integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
"os": ["linux"],
"cpu": ["x64"]
},
"@esbuild/linux-x64@0.25.12": {
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"os": ["linux"],
@@ -988,11 +890,6 @@
"os": ["netbsd"],
"cpu": ["x64"]
},
"@esbuild/netbsd-x64@0.19.12": {
"integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
"os": ["netbsd"],
"cpu": ["x64"]
},
"@esbuild/netbsd-x64@0.25.12": {
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"os": ["netbsd"],
@@ -1028,11 +925,6 @@
"os": ["openbsd"],
"cpu": ["x64"]
},
"@esbuild/openbsd-x64@0.19.12": {
"integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
"os": ["openbsd"],
"cpu": ["x64"]
},
"@esbuild/openbsd-x64@0.25.12": {
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"os": ["openbsd"],
@@ -1068,11 +960,6 @@
"os": ["sunos"],
"cpu": ["x64"]
},
"@esbuild/sunos-x64@0.19.12": {
"integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
"os": ["sunos"],
"cpu": ["x64"]
},
"@esbuild/sunos-x64@0.25.12": {
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"os": ["sunos"],
@@ -1093,11 +980,6 @@
"os": ["win32"],
"cpu": ["arm64"]
},
"@esbuild/win32-arm64@0.19.12": {
"integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@esbuild/win32-arm64@0.25.12": {
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"os": ["win32"],
@@ -1118,11 +1000,6 @@
"os": ["win32"],
"cpu": ["ia32"]
},
"@esbuild/win32-ia32@0.19.12": {
"integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
"os": ["win32"],
"cpu": ["ia32"]
},
"@esbuild/win32-ia32@0.25.12": {
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"os": ["win32"],
@@ -1143,11 +1020,6 @@
"os": ["win32"],
"cpu": ["x64"]
},
"@esbuild/win32-x64@0.19.12": {
"integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
"os": ["win32"],
"cpu": ["x64"]
},
"@esbuild/win32-x64@0.25.12": {
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"os": ["win32"],
@@ -1166,9 +1038,6 @@
"@exodus/bytes@1.8.0": {
"integrity": "sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ=="
},
"@grammyjs/types@3.23.0": {
"integrity": "sha512-D3jQ4UWERPsyR3op/YFudMMIPNTU47vy7L51uO9/73tMELmjO/+LX5N36/Y0CG5IQfIsz43MxiHI5rgsK0/k+g=="
},
"@img/colour@1.0.0": {
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="
},
@@ -1351,8 +1220,8 @@
"@jridgewell/sourcemap-codec"
]
},
"@libsql/client@0.14.0": {
"integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==",
"@libsql/client@0.17.0": {
"integrity": "sha512-TLjSU9Otdpq0SpKHl1tD1Nc9MKhrsZbCFGot3EbCxRa8m1E5R1mMwoOjKMMM31IyF7fr+hPNHLpYfwbMKNusmg==",
"dependencies": [
"@libsql/core",
"@libsql/hrana-client",
@@ -1361,34 +1230,31 @@
"promise-limit"
]
},
"@libsql/core@0.14.0": {
"integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==",
"@libsql/core@0.17.0": {
"integrity": "sha512-hnZRnJHiS+nrhHKLGYPoJbc78FE903MSDrFJTbftxo+e52X+E0Y0fHOCVYsKWcg6XgB7BbJYUrz/xEkVTSaipw==",
"dependencies": [
"js-base64"
]
},
"@libsql/darwin-arm64@0.4.7": {
"integrity": "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg==",
"@libsql/darwin-arm64@0.5.22": {
"integrity": "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@libsql/darwin-x64@0.4.7": {
"integrity": "sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA==",
"@libsql/darwin-x64@0.5.22": {
"integrity": "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@libsql/hrana-client@0.7.0": {
"integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==",
"@libsql/hrana-client@0.9.0": {
"integrity": "sha512-pxQ1986AuWfPX4oXzBvLwBnfgKDE5OMhAdR/5cZmRaB4Ygz5MecQybvwZupnRz341r2CtFmbk/BhSu7k2Lm+Jw==",
"dependencies": [
"@libsql/isomorphic-fetch",
"@libsql/isomorphic-ws",
"cross-fetch",
"js-base64",
"node-fetch@3.3.2"
]
},
"@libsql/isomorphic-fetch@0.3.1": {
"integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw=="
},
"@libsql/isomorphic-ws@0.1.5": {
"integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==",
"dependencies": [
@@ -1396,28 +1262,38 @@
"ws"
]
},
"@libsql/linux-arm64-gnu@0.4.7": {
"integrity": "sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA==",
"@libsql/linux-arm-gnueabihf@0.5.22": {
"integrity": "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==",
"os": ["linux"],
"cpu": ["arm"]
},
"@libsql/linux-arm-musleabihf@0.5.22": {
"integrity": "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==",
"os": ["linux"],
"cpu": ["arm"]
},
"@libsql/linux-arm64-gnu@0.5.22": {
"integrity": "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@libsql/linux-arm64-musl@0.4.7": {
"integrity": "sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw==",
"@libsql/linux-arm64-musl@0.5.22": {
"integrity": "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@libsql/linux-x64-gnu@0.4.7": {
"integrity": "sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ==",
"@libsql/linux-x64-gnu@0.5.22": {
"integrity": "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==",
"os": ["linux"],
"cpu": ["x64"]
},
"@libsql/linux-x64-musl@0.4.7": {
"integrity": "sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA==",
"@libsql/linux-x64-musl@0.5.22": {
"integrity": "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==",
"os": ["linux"],
"cpu": ["x64"]
},
"@libsql/win32-x64-msvc@0.4.7": {
"integrity": "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw==",
"@libsql/win32-x64-msvc@0.5.22": {
"integrity": "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==",
"os": ["win32"],
"cpu": ["x64"]
},
@@ -1433,9 +1309,6 @@
"@opentelemetry/api@1.9.0": {
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="
},
"@petamoriken/float16@3.9.3": {
"integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="
},
"@preact/signals-core@1.12.1": {
"integrity": "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA=="
},
@@ -1458,7 +1331,7 @@
"@prefresh/utils@1.2.1": {
"integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw=="
},
"@prefresh/vite@2.4.11_preact@10.28.2_vite@7.3.1__tsx@4.21.0__picomatch@4.0.3_tsx@4.21.0": {
"@prefresh/vite@2.4.11_preact@10.28.2_vite@7.3.1__picomatch@4.0.3": {
"integrity": "sha512-/XjURQqdRiCG3NpMmWqE9kJwrg9IchIOWHzulCfqg2sRe/8oQ1g5De7xrk9lbqPIQLn7ntBkKdqWXIj4E9YXyg==",
"dependencies": [
"@babel/core",
@@ -1690,7 +1563,7 @@
"@tailwindcss/oxide-win32-x64-msvc"
]
},
"@tailwindcss/vite@4.1.18_vite@7.3.1__tsx@4.21.0__picomatch@4.0.3_tsx@4.21.0": {
"@tailwindcss/vite@4.1.18_vite@7.3.1__picomatch@4.0.3": {
"integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
"dependencies": [
"@tailwindcss/node",
@@ -1708,29 +1581,15 @@
"@types/estree@1.0.8": {
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
},
"@types/jsdom@27.0.0": {
"integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==",
"dependencies": [
"@types/node",
"@types/tough-cookie",
"parse5@7.3.0"
]
},
"@types/ms@2.1.0": {
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
},
"@types/node@22.5.4": {
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"@types/node@25.0.5": {
"integrity": "sha512-FuLxeLuSVOqHPxSN1fkcD8DLU21gAP7nCKqGRJ/FglbCUBs0NYN6TpHcdmyLeh8C0KwGIaZQJSv+OYG+KZz+Gw==",
"dependencies": [
"undici-types"
]
},
"@types/tough-cookie@4.0.5": {
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="
},
"@types/turndown@5.0.6": {
"integrity": "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg=="
},
"@types/ws@8.18.1": {
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dependencies": [
@@ -1740,12 +1599,6 @@
"@xmldom/xmldom@0.8.11": {
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="
},
"abort-controller@3.0.0": {
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": [
"event-target-shim"
]
},
"agent-base@7.1.4": {
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="
},
@@ -1804,9 +1657,6 @@
"function-bind"
]
},
"camelcase-css@2.0.1": {
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
},
"caniuse-lite@1.0.30001763": {
"integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ=="
},
@@ -1835,6 +1685,12 @@
"convert-source-map@2.0.0": {
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"cross-fetch@4.1.0": {
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"dependencies": [
"node-fetch@2.7.0"
]
},
"css-tree@3.1.0": {
"integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
"dependencies": [
@@ -1851,9 +1707,6 @@
"lru-cache@11.2.4"
]
},
"csstype@3.2.3": {
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
},
"data-uri-to-buffer@4.0.1": {
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
},
@@ -1921,19 +1774,18 @@
"domhandler"
]
},
"drizzle-kit@0.30.6_esbuild@0.19.12": {
"integrity": "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==",
"drizzle-kit@0.31.8_esbuild@0.25.12": {
"integrity": "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==",
"dependencies": [
"@drizzle-team/brocli",
"@esbuild-kit/esm-loader",
"esbuild@0.19.12",
"esbuild-register",
"gel"
"esbuild@0.25.12",
"esbuild-register"
],
"bin": true
},
"drizzle-orm@0.38.4_@libsql+client@0.14.0_@opentelemetry+api@1.9.0": {
"integrity": "sha512-s7/5BpLKO+WJRHspvpqTydxFob8i1vo2rEx4pY6TGY7QSMuUfWUuzaY0DIpXCkgHOo37BaFC+SJQb99dDUXT3Q==",
"drizzle-orm@0.45.1_@libsql+client@0.17.0_@opentelemetry+api@1.9.0": {
"integrity": "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==",
"dependencies": [
"@libsql/client",
"@opentelemetry/api"
@@ -1967,9 +1819,6 @@
"entities@6.0.1": {
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="
},
"env-paths@3.0.0": {
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="
},
"es-define-property@1.0.1": {
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
},
@@ -1991,11 +1840,11 @@
"hasown"
]
},
"esbuild-register@3.6.0_esbuild@0.19.12": {
"esbuild-register@3.6.0_esbuild@0.25.12": {
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
"dependencies": [
"debug",
"esbuild@0.19.12"
"esbuild@0.25.12"
]
},
"esbuild-wasm@0.25.12": {
@@ -2031,36 +1880,6 @@
"scripts": true,
"bin": true
},
"esbuild@0.19.12": {
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
"optionalDependencies": [
"@esbuild/aix-ppc64@0.19.12",
"@esbuild/android-arm@0.19.12",
"@esbuild/android-arm64@0.19.12",
"@esbuild/android-x64@0.19.12",
"@esbuild/darwin-arm64@0.19.12",
"@esbuild/darwin-x64@0.19.12",
"@esbuild/freebsd-arm64@0.19.12",
"@esbuild/freebsd-x64@0.19.12",
"@esbuild/linux-arm@0.19.12",
"@esbuild/linux-arm64@0.19.12",
"@esbuild/linux-ia32@0.19.12",
"@esbuild/linux-loong64@0.19.12",
"@esbuild/linux-mips64el@0.19.12",
"@esbuild/linux-ppc64@0.19.12",
"@esbuild/linux-riscv64@0.19.12",
"@esbuild/linux-s390x@0.19.12",
"@esbuild/linux-x64@0.19.12",
"@esbuild/netbsd-x64@0.19.12",
"@esbuild/openbsd-x64@0.19.12",
"@esbuild/sunos-x64@0.19.12",
"@esbuild/win32-arm64@0.19.12",
"@esbuild/win32-ia32@0.19.12",
"@esbuild/win32-x64@0.19.12"
],
"scripts": true,
"bin": true
},
"esbuild@0.25.12": {
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"optionalDependencies": [
@@ -2169,9 +1988,6 @@
"estree-walker@2.0.2": {
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"event-target-shim@5.0.1": {
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"fdir@6.5.0_picomatch@4.0.3": {
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dependencies": [
@@ -2230,11 +2046,6 @@
"fs.realpath@1.0.0": {
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"fsevents@2.3.2": {
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"os": ["darwin"],
"scripts": true
},
"fsevents@2.3.3": {
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"os": ["darwin"],
@@ -2246,18 +2057,6 @@
"fuzzysort@3.1.0": {
"integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="
},
"gel@2.2.0": {
"integrity": "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ==",
"dependencies": [
"@petamoriken/float16",
"debug",
"env-paths",
"semver@7.7.3",
"shell-quote",
"which"
],
"bin": true
},
"gensync@1.0.0-beta.2": {
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
},
@@ -2310,15 +2109,6 @@
"graceful-fs@4.2.11": {
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"grammy@1.39.2": {
"integrity": "sha512-7mLdNsNI2HBN1Gjombx7p3/Cjfgw/kErR/Qv3pDRw2qO/dYEdK8B9G9rpdZayNID+3qFQVTXNqxcGpG38mC2TQ==",
"dependencies": [
"@grammyjs/types",
"abort-controller",
"debug",
"node-fetch@2.7.0"
]
},
"has-symbols@1.1.0": {
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
},
@@ -2396,9 +2186,6 @@
"is-potential-custom-element-name@1.0.1": {
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
},
"isexe@3.1.1": {
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="
},
"isobject@3.0.1": {
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
},
@@ -2425,7 +2212,7 @@
"http-proxy-agent",
"https-proxy-agent",
"is-potential-custom-element-name",
"parse5@8.0.0",
"parse5",
"saxes",
"symbol-tree",
"tough-cookie",
@@ -2479,8 +2266,8 @@
"lazy-cache@1.0.4": {
"integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ=="
},
"libsql@0.4.7": {
"integrity": "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==",
"libsql@0.5.22": {
"integrity": "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==",
"dependencies": [
"@neon-rs/load",
"detect-libc@2.0.2"
@@ -2488,6 +2275,8 @@
"optionalDependencies": [
"@libsql/darwin-arm64",
"@libsql/darwin-x64",
"@libsql/linux-arm-gnueabihf",
"@libsql/linux-arm-musleabihf",
"@libsql/linux-arm64-gnu",
"@libsql/linux-arm64-musl",
"@libsql/linux-x64-gnu",
@@ -2495,7 +2284,7 @@
"@libsql/win32-x64-msvc"
],
"os": ["darwin", "linux", "win32"],
"cpu": ["x64", "arm64", "wasm32"]
"cpu": ["x64", "arm64", "wasm32", "arm"]
},
"lightningcss-android-arm64@1.30.2": {
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
@@ -2708,12 +2497,6 @@
"parse-srcset@1.0.2": {
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
},
"parse5@7.3.0": {
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"dependencies": [
"entities@6.0.1"
]
},
"parse5@8.0.0": {
"integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
"dependencies": [
@@ -2732,30 +2515,12 @@
"picomatch@4.0.3": {
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
},
"playwright-core@1.57.0": {
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"bin": true
},
"playwright-extra@4.3.6_playwright@1.57.0": {
"playwright-extra@4.3.6": {
"integrity": "sha512-q2rVtcE8V8K3vPVF1zny4pvwZveHLH8KBuVU2MoE3Jw4OKVoBWsHI9CH9zPydovHHOCDxjGN2Vg+2m644q3ijA==",
"dependencies": [
"debug",
"playwright"
],
"optionalPeers": [
"playwright"
"debug"
]
},
"playwright@1.57.0": {
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"dependencies": [
"playwright-core"
],
"optionalDependencies": [
"fsevents@2.3.2"
],
"bin": true
},
"postcss@8.5.6": {
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dependencies": [
@@ -2788,7 +2553,7 @@
"punycode@2.3.1": {
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
"puppeteer-extra-plugin-stealth@2.11.2_playwright-extra@4.3.6__playwright@1.57.0_playwright@1.57.0": {
"puppeteer-extra-plugin-stealth@2.11.2_playwright-extra@4.3.6": {
"integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==",
"dependencies": [
"debug",
@@ -2800,7 +2565,7 @@
"playwright-extra"
]
},
"puppeteer-extra-plugin-user-data-dir@2.4.1_playwright-extra@4.3.6__playwright@1.57.0_playwright@1.57.0": {
"puppeteer-extra-plugin-user-data-dir@2.4.1_playwright-extra@4.3.6": {
"integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==",
"dependencies": [
"debug",
@@ -2813,7 +2578,7 @@
"playwright-extra"
]
},
"puppeteer-extra-plugin-user-preferences@2.4.1_playwright-extra@4.3.6__playwright@1.57.0_playwright@1.57.0": {
"puppeteer-extra-plugin-user-preferences@2.4.1_playwright-extra@4.3.6": {
"integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==",
"dependencies": [
"debug",
@@ -2826,7 +2591,7 @@
"playwright-extra"
]
},
"puppeteer-extra-plugin@3.2.3_playwright-extra@4.3.6__playwright@1.57.0_playwright@1.57.0": {
"puppeteer-extra-plugin@3.2.3_playwright-extra@4.3.6": {
"integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==",
"dependencies": [
"@types/debug",
@@ -2883,7 +2648,7 @@
"@rollup/rollup-win32-ia32-msvc",
"@rollup/rollup-win32-x64-gnu",
"@rollup/rollup-win32-x64-msvc",
"fsevents@2.3.3"
"fsevents"
],
"bin": true
},
@@ -2956,9 +2721,6 @@
],
"scripts": true
},
"shell-quote@1.8.3": {
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="
},
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
@@ -3022,25 +2784,14 @@
"tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"tsx@4.21.0": {
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dependencies": [
"esbuild@0.27.2",
"get-tsconfig"
],
"optionalDependencies": [
"fsevents@2.3.3"
],
"bin": true
},
"turndown@7.2.2": {
"integrity": "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==",
"dependencies": [
"@mixmark-io/domino"
]
},
"undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
"undici-types@7.16.0": {
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
},
"universalify@2.0.1": {
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="
@@ -3054,7 +2805,7 @@
],
"bin": true
},
"vite@7.3.1_tsx@4.21.0_picomatch@4.0.3": {
"vite@7.3.1_picomatch@4.0.3": {
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dependencies": [
"esbuild@0.27.2",
@@ -3062,14 +2813,10 @@
"picomatch@4.0.3",
"postcss",
"rollup",
"tinyglobby",
"tsx"
"tinyglobby"
],
"optionalDependencies": [
"fsevents@2.3.3"
],
"optionalPeers": [
"tsx"
"fsevents"
],
"bin": true
},
@@ -3105,13 +2852,6 @@
"webidl-conversions@3.0.1"
]
},
"which@4.0.0": {
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
"dependencies": [
"isexe"
],
"bin": true
},
"wrappy@1.0.2": {
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
@@ -3129,49 +2869,42 @@
},
"zod@3.25.76": {
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
},
"zod@4.3.5": {
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="
}
},
"workspace": {
"dependencies": [
"jsr:@cmd-johnson/oauth2-client@2",
"jsr:@deno/gfm@0.11",
"jsr:@deno/gfm@0.11.0",
"jsr:@denosaurs/emoji@~0.3.1",
"jsr:@fresh/core@^2.2.0",
"jsr:@fresh/plugin-vite@^1.0.8",
"jsr:@openai/openai@^6.16.0",
"jsr:@preact-icons/tb@^1.0.14",
"jsr:@std/fs@^1.0.21",
"jsr:@std/http@^1.0.23",
"jsr:@std/media-types@^1.1.0",
"jsr:@std/yaml@^1.0.10",
"jsr:@zaubrik/djwt@^3.0.2",
"npm:@libsql/client@0.14",
"npm:@libsql/client@0.17",
"npm:@preact/signals@^2.5.0",
"npm:@tailwindcss/vite@^4.1.12",
"npm:@types/jsdom@27",
"npm:@types/turndown@^5.0.6",
"npm:camelcase-css@2.0.1",
"npm:csstype@^3.2.3",
"npm:defuddle@~0.6.6",
"npm:drizzle-kit@~0.30.6",
"npm:drizzle-orm@~0.38.4",
"npm:drizzle-kit@~0.31.8",
"npm:drizzle-orm@~0.45.1",
"npm:fuzzysort@^3.1.0",
"npm:grammy@^1.39.2",
"npm:jsdom@^27.4.0",
"npm:moviedb-promise@^4.0.7",
"npm:parse-ingredient@^1.3.1",
"npm:playwright-extra@^4.3.6",
"npm:playwright@^1.57.0",
"npm:preact@^10.27.2",
"npm:prismjs@^1.30.0",
"npm:puppeteer-extra-plugin-stealth@^2.11.2",
"npm:sharp@~0.34.5",
"npm:tailwindcss@^4.1.18",
"npm:tailwindcss@^4.1.10",
"npm:thumbhash@~0.1.1",
"npm:tsx@^4.21.0",
"npm:turndown@^7.2.2",
"npm:vite@^7.3.1",
"npm:zod@^3.25.76"
"npm:vite@^7.1.3",
"npm:zod@^4.3.5"
]
}
}

View File

@@ -0,0 +1,7 @@
DROP INDEX "key_idx";--> statement-breakpoint
DROP INDEX "scope_idx";--> statement-breakpoint
DROP INDEX "name_idx";--> statement-breakpoint
ALTER TABLE `image` ALTER COLUMN "created_at" TO "created_at" integer DEFAULT (unixepoch());--> statement-breakpoint
CREATE INDEX `key_idx` ON `cache` (`key`);--> statement-breakpoint
CREATE INDEX `scope_idx` ON `cache` (`scope`);--> statement-breakpoint
CREATE INDEX `name_idx` ON `document` (`name`);

View File

@@ -0,0 +1,309 @@
{
"version": "6",
"dialect": "sqlite",
"id": "11bbbc9d-3c0c-4fb9-893f-c87d5b8660d0",
"prevId": "685b57ca-45e0-4373-baee-fc3abb4f2d74",
"tables": {
"cache": {
"name": "cache",
"columns": {
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"json": {
"name": "json",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"binary": {
"name": "binary",
"type": "blob",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(current_timestamp)"
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"key_idx": {
"name": "key_idx",
"columns": [
"key"
],
"isUnique": false
},
"scope_idx": {
"name": "scope_idx",
"columns": [
"scope"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"document": {
"name": "document",
"columns": {
"name": {
"name": "name",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"last_modified": {
"name": "last_modified",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"content_type": {
"name": "content_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"size": {
"name": "size",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"perm": {
"name": "perm",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"name_idx": {
"name": "name_idx",
"columns": [
"name"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"image": {
"name": "image",
"columns": {
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(unixepoch())"
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"average": {
"name": "average",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"thumbhash": {
"name": "thumbhash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"mime": {
"name": "mime",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"performance": {
"name": "performance",
"columns": {
"path": {
"name": "path",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"search": {
"name": "search",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time": {
"name": "time",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(STRFTIME('%s', 'now') * 1000)"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"session": {
"name": "session",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(current_timestamp)"
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(current_timestamp)"
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@@ -78,6 +78,13 @@
"when": 1762099260474,
"tag": "0010_youthful_tyrannus",
"breakpoints": true
},
{
"idx": 11,
"version": "6",
"when": 1768058169808,
"tag": "0011_reflective_frank_castle",
"breakpoints": true
}
]
}

View File

@@ -10,11 +10,8 @@ export default function Counter(props: CounterProps) {
props.count.value = Math.max(1, props.count.value);
return (
<div class="flex items-center px-1 py-2 rounded-xl">
<Button
class=""
onClick={() => props.count.value -= 1}
>
<TbCircleMinus />
<Button onClick={() => props.count.value -= 1}>
<TbCircleMinus class="h-6 w-6" />
</Button>
<input
class="text-3xl bg-transparent inline text-center -mx-4"
@@ -27,7 +24,7 @@ export default function Counter(props: CounterProps) {
}}
/>
<Button onClick={() => props.count.value += 1}>
<TbCirclePlus />
<TbCirclePlus class="h-6 w-6" />
</Button>
</div>
);

View File

@@ -4,7 +4,6 @@ import { useEventListener } from "@lib/hooks/useEventListener.ts";
import { menus } from "@islands/KMenu/commands.ts";
import { MenuEntry } from "@islands/KMenu/types.ts";
import * as icons from "@components/icons.tsx";
import { IS_BROWSER } from "fresh/runtime";
import { isKMenuOpen } from "@lib/kmenu.ts";
const KMenuEntry = (
{ entry, activeIndex, index }: {
@@ -155,17 +154,17 @@ export const KMenu = (
} else {
input.current?.blur();
}
}, IS_BROWSER ? document?.body : undefined);
}, typeof document !== "undefined" ? document?.body : undefined);
return (
<div
class={`${visible.value ? "opacity-100" : "opacity-0"} pointer-events-${
visible.value ? "auto" : "none"
class={`${visible.value ? "opacity-100" : "opacity-0"} ${
visible.value ? "pointer-events-auto" : "pointer-events-none"
} transition grid place-items-center w-full h-full fixed top-0 left-0 z-50`}
style={{ background: "#141217ee" }}
>
<div
class={`relative w-1/2 max-h-64 max-w-[400px] rounded-2xl shadow-2xl nnoisy-gradient overflow-hidden after:opacity-10 bg-gray-700`}
class={`relative w-1/2 max-h-64 max-w-100 rounded-2xl shadow-2xl nnoisy-gradient overflow-hidden after:opacity-10 bg-gray-700`}
style={{ background: "#2B2930", color: "#818181" }}
>
<div

View File

@@ -8,7 +8,7 @@ export function Link(
props: {
href?: string;
class?: string;
style?: preact.JSX.CSSProperties;
style?: preact.CSSProperties;
children: preact.ComponentChildren;
"data-thumb"?: string;
},

View File

@@ -43,7 +43,7 @@ export const performanceTable = sqliteTable("performance", {
export const imageTable = sqliteTable("image", {
createdAt: integer("created_at", { mode: "timestamp" }).default(
sql`(current_timestamp)`,
sql`(unixepoch())`,
),
url: text().notNull(),
average: text().notNull(),

View File

@@ -4,7 +4,6 @@ import path from "node:path";
const DB_FILE = "file:" + path.resolve(DATA_DIR, "db.sqlite");
// You can specify any property from the libsql connection options
export const db = drizzle({
connection: {
url: DB_FILE,

View File

@@ -36,9 +36,10 @@ export async function getLogs() {
.map((line) => {
const [date, ...rest] = line.split(" | ");
const parsed = JSON.parse(rest.join(" | ")) as Log;
const dateObj = new Date(date);
return {
...parsed,
date: new Date(date),
date: isNaN(dateObj.getTime()) ? new Date() : dateObj,
} as Log;
});

View File

@@ -1,105 +0,0 @@
/**
* Interface zur Beschreibung eines eingereihten Promises in der `PromiseQueue`.
*/
interface QueuedPromise<T = unknown> {
promise: () => Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: unknown) => void;
}
/**
* Eine einfache Promise Queue, die es ermöglicht mehrere Aufgaben in kontrollierter
* Reihenfolge abzuarbeiten.
*
* Lizenz: CC BY-NC-SA 4.0
* (c) Peter Müller <peter@crycode.de> (https://crycode.de/promise-queue-in-typescript)
*/
export class PromiseQueue {
/**
* Eingereihte Promises.
*/
private queue: QueuedPromise<unknown>[] = [];
/**
* Indikator, dass aktuell ein Promise abgearbeitet wird.
*/
private working = false;
/**
* Ein Promise einreihen.
* Dies fügt das Promise der Warteschlange hinzu. Wenn die Warteschlange leer
* ist, dann wird das Promise sofort gestartet.
* @param promise Funktion, die das Promise zurückgibt.
* @returns Ein Promise, welches eingelöst (oder zurückgewiesen) wird sobald das eingereihte Promise abgearbeitet ist.
*/
public enqueue<T = void>(promise: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push({
promise,
resolve: resolve as (value: unknown) => void,
reject,
});
this.dequeue();
});
}
/**
* Das erste Promise aus der Warteschlange holen und starten, sofern nicht
* bereits ein Promise aktiv ist.
* @returns `true` wenn ein Promise aus der Warteschlange gestartet wurde oder `false` wenn bereits ein Promise aktiv oder die Warteschlange leer ist.
*/
private dequeue(): boolean {
if (this.working) {
return false;
}
const item = this.queue.shift();
if (!item) {
return false;
}
try {
this.working = true;
item.promise()
.then((value) => {
item.resolve(value);
})
.catch((err) => {
item.reject(err);
})
.finally(() => {
this.working = false;
this.dequeue();
});
} catch (err) {
item.reject(err);
this.working = false;
this.dequeue();
}
return true;
}
}
export class ConcurrentPromiseQueue {
/**
* Eingereihte Promises.
*/
private queues: PromiseQueue[] = [];
constructor(concurrency: number = 1) {
this.queues = Array.from({ length: concurrency }).map(() => {
return new PromiseQueue();
});
}
private queueIndex = 0;
private getQueue() {
this.queueIndex = (this.queueIndex + 1) % this.queues.length;
return this.queues[this.queueIndex];
}
public enqueue<T = void>(promise: () => Promise<T>): Promise<T> {
return this.getQueue().enqueue(promise);
}
}

View File

@@ -61,7 +61,9 @@ export async function createRecommendationResource(
const d = typeof datePublished === "string"
? new Date(datePublished)
: datePublished;
resource.year = d.getFullYear();
if (!isNaN(d.getTime())) {
resource.year = d.getFullYear();
}
}
cache.set(cacheId, JSON.stringify(resource));

View File

@@ -1,110 +0,0 @@
import { transcribe } from "@lib/openai.ts";
import { createResource } from "@lib/marka/index.ts";
import { createLogger } from "./log/index.ts";
import { convertOggToMp3 } from "./helpers.ts";
const log = createLogger("taskManager");
// In-memory task state
const activeTasks: Record<
string,
{
noteName: string;
entries: Array<
{ type: string; content: string | ArrayBufferLike; fileName?: string }
>;
}
> = {};
export function startTask(chatId: string, noteName: string) {
activeTasks[chatId] = { noteName, entries: [] };
log.info(`Started note: ${noteName}`);
}
export async function endTask(chatId: string): Promise<string | null> {
const task = activeTasks[chatId];
if (!task) return null;
log.info("Ending note", task.noteName);
let finalNote = `# ${task.noteName}\n\n`;
const photoTasks: { content: ArrayBuffer; path: string }[] = [];
let photoIndex = 0;
for (const entry of task.entries) {
if (entry.type === "text") {
finalNote += entry.content + "\n\n";
} else if (entry.type === "voice") {
try {
log.info("Converting OGG to MP3");
const mp3Data = await convertOggToMp3(entry.content as ArrayBuffer);
log.info("Finished converting OGG to MP3, transcribing...");
const transcript = await transcribe(mp3Data);
finalNote += `**Voice Transcript:**\n${transcript}\n\n`;
log.info("Finished transcribing");
} catch (error) {
log.error(error);
finalNote += "**[Voice message could not be transcribed]**\n\n";
}
} else if (entry.type === "photo") {
const photoUrl = `${
task.noteName.replace(/\.md$/, "")
}/photo-${photoIndex++}.jpg`;
finalNote += `**Photo**:\n ${photoUrl}\n\n`;
photoTasks.push({
content: entry.content as ArrayBuffer,
path: photoUrl,
});
}
}
try {
for (const entry of photoTasks) {
await createResource(entry.path, entry.content);
}
} catch (err) {
log.error("Error creating photo document:", err);
}
try {
await createResource(task.noteName, finalNote);
} catch (error) {
log.error("Error creating document:", error);
return error instanceof Error
? error.toString()
: "Error creating document";
}
delete activeTasks[chatId];
log.debug({ finalNote });
return finalNote;
}
export function addTextEntry(chatId: string, text: string) {
const task = activeTasks[chatId];
if (!task) return;
const entry = { type: "text", content: text };
log.debug("New Entry", entry);
task.entries.push(entry);
}
export function addVoiceEntry(chatId: string, buffer: ArrayBufferLike) {
const task = activeTasks[chatId];
if (!task) return;
const entry = { type: "voice", content: buffer };
log.debug("New Entry", entry);
task.entries.push(entry);
}
export function addPhotoEntry(
chatId: string,
buffer: ArrayBufferLike,
fileName: string,
) {
const task = activeTasks[chatId];
if (!task) return;
const entry = { type: "photo", content: buffer, fileName };
log.debug("New Entry", entry);
task.entries.push(entry);
}

View File

@@ -1,65 +0,0 @@
import { Bot } from "grammy";
import { TELEGRAM_API_KEY } from "@lib/env.ts";
import { createLogger } from "./log/index.ts";
const bot = new Bot(TELEGRAM_API_KEY);
const log = createLogger("telegram");
import * as manager from "./taskManager.ts";
async function downloadFile(filePath: string): Promise<Uint8Array> {
log.info(`Downloading file from path: ${filePath}`);
const url = `https://api.telegram.org/file/bot${bot.token}/${filePath}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to download file: " + response.statusText);
}
log.info("File downloaded successfully");
const buffer = await response.arrayBuffer();
return new Uint8Array(buffer);
}
bot.command("start", async (ctx) => {
log.info("Received /start command");
const [_, noteName] = ctx.message?.text?.split(" ") || [];
if (!noteName) {
return ctx.reply("Please provide a note name. Usage: /start NoteName");
}
manager.startTask(ctx.chat.id.toString(), noteName);
await ctx.reply(`Started note: ${noteName}`);
});
bot.command("end", async (ctx) => {
log.info("Received /end command");
const finalNote = await manager.endTask(ctx.chat.id.toString());
if (!finalNote) return ctx.reply("No active note found.");
try {
await ctx.reply("Note complete. Here is your markdown:");
await ctx.reply(finalNote, { parse_mode: "MarkdownV2" });
} catch (err) {
console.error("Error sending final note:", err);
}
});
bot.on("message:text", (ctx) => {
log.info("Received text message");
manager.addTextEntry(ctx.chat.id.toString(), ctx.message.text);
});
bot.on("message:voice", async (ctx) => {
log.info("Received photo message");
log.info("Received voice message");
const file = await ctx.getFile();
const buffer = await downloadFile(file.file_path!);
manager.addVoiceEntry(ctx.chat.id.toString(), buffer.buffer);
});
bot.on("message:photo", async (ctx) => {
const file = await ctx.getFile();
const buffer = await downloadFile(file.file_path!);
const fileName = file.file_path!.split("/").pop()!;
manager.addPhotoEntry(ctx.chat.id.toString(), buffer.buffer, fileName);
});
bot.start();

11
main.ts
View File

@@ -1,7 +1,8 @@
import { App, staticFiles } from "fresh";
import { type State } from "./utils.ts";
export const app = new App()
// Add static file serving middleware
.use(staticFiles())
// Enable file-system based routing
.fsRoutes();
export const app = new App<State>();
app.use(staticFiles());
app.fsRoutes();

View File

@@ -1,7 +1,6 @@
import { PageProps } from "fresh";
import { Partial } from "fresh/runtime";
import { define } from "../utils.ts";
export default function App({ Component }: PageProps) {
export default define.page(function ({ Component }) {
return (
<html>
<head>
@@ -12,9 +11,6 @@ export default function App({ Component }: PageProps) {
href="/favicon.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#141218" />
<link
rel="preload"
href="/fonts/work-sans-v18-latin-regular.woff2"
@@ -28,14 +24,24 @@ export default function App({ Component }: PageProps) {
type="font/woff2"
/>
<link rel="stylesheet" href="/global.css" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#141218" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style
dangerouslySetInnerHTML={{
__html: Deno.readTextFileSync("./static/global.css"),
}}
>
</style>
<title>Memorium</title>
</head>
<body f-client-nav>
<Partial name="body">
<Component />
</Partial>
<body>
<Component />
</body>
<script src="/thumbhash.js" type="module" async defer />
</html>
);
}
});

View File

@@ -20,7 +20,7 @@ export default function ErrorPage(props: PageProps) {
</Head>
<MainLayout url="">
<div class="px-8 text-white mt-10">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<div class="max-w-3xl mx-auto flex flex-col items-center justify-center">
<h1 class="text-4xl font-bold">404 - Page not found</h1>
<p class="my-4">
The page you were looking for doesn't exist.

View File

@@ -1,10 +1,10 @@
import { resources } from "@lib/resources.ts";
import { Link } from "@islands/Link.tsx";
import { Emoji } from "@components/Emoji.tsx";
import KMenuButton from "@islands/KMenuButton.tsx";
import { PageProps } from "fresh";
import { define } from "../utils.ts";
export default function MyLayout({ Component }: PageProps) {
export default define.layout(function ({ Component }: PageProps) {
return (
<div
class="md:grid mx-auto"
@@ -14,12 +14,12 @@ export default function MyLayout({ Component }: PageProps) {
<nav class="min-h-fit rounded-3xl p-3 grid gap-3 fixed t-0">
{Object.values(resources).map((m) => {
return (
<Link
<a
href={m.link}
class="flex items-center gap-2 text-white data-[current]:bg-white data-[current]:text-black p-3 text-xl w-full rounded-2xl"
class="flex items-center gap-2 text-white data-current:bg-white data-current:text-black p-3 text-xl w-full rounded-2xl"
>
<Emoji class="w-6 h-6" name={m.emoji} /> {m.name}
</Link>
</a>
);
})}
</nav>
@@ -30,4 +30,4 @@ export default function MyLayout({ Component }: PageProps) {
<KMenuButton />
</div>
);
}
});

View File

@@ -58,4 +58,4 @@ const authMiddleware = define.middleware(async function (
}
});
export default [];
export default [authMiddleware];

View File

@@ -1,31 +0,0 @@
import { MainLayout } from "@components/layouts/main.tsx";
import { PageProps } from "fresh";
import { getCacheInfo } from "@lib/cache.ts";
import { define } from "../../../utils.ts";
export const handler = define.handlers({
GET() {
return { data: { cacheInfo: getCacheInfo() } };
},
});
export default function Greet(
props: PageProps<
{ cacheInfo: ReturnType<typeof getCacheInfo> }
>,
) {
const { cacheInfo } = props.data;
return (
<MainLayout
url={props.url}
title="Recipes"
context={{ type: "recipes" }}
>
<code>
<pre class="text-white">
{JSON.stringify(cacheInfo, null, 2)}
</pre>
</code>
</MainLayout>
);
}

View File

@@ -1,81 +0,0 @@
import { MainLayout } from "@components/layouts/main.tsx";
import { PageProps } from "fresh";
import { AccessDeniedError } from "@lib/errors.ts";
import { getLogs, Log } from "@lib/log/index.ts";
import { formatDate } from "@lib/string.ts";
import { renderMarkdown } from "@lib/markdown.ts";
import { define } from "../../../utils.ts";
const renderLog = (t: unknown) =>
renderMarkdown(`\`\`\`js
${typeof t === "string" ? t : JSON.stringify(t, null, 2).trim()}
\`\`\``);
export const handler = define.handlers({
async GET(ctx) {
const logs = await getLogs();
if (!("session" in ctx.state)) {
throw new AccessDeniedError();
}
return {
data: {
logs: logs.map((l) => {
return {
...l,
html: l.args.map(renderLog).join("<br/>"),
};
}),
},
};
},
});
function LogLine(
{ log }: {
log: Log & { html?: string };
},
) {
return (
<div
class="mt-4 flex flex-col gap-2 bg-gray-900 px-4 py-2 rounded-2xl max-w-3xl"
style={{ background: "var(--light)" }}
>
<div class="flex gap-2">
<span class="bg-gray-600 py-1 px-2 text-xs rounded-xl text-white">
{log.date.getHours().toString().padStart(2, "0")}:{log.date
.getMinutes().toString().padStart(2, "0")}:{log.date.getSeconds()
.toString().padStart(2, "0")} {formatDate(log.date)}
</span>
<span class="bg-gray-600 py-1 px-2 text-xs rounded-xl text-white">
{log.scope}
</span>
<span class="bg-gray-600 py-1 px-2 text-xs rounded-xl text-white">
{log.level}
</span>
</div>
<div
class="text-white"
// deno-lint-ignore react-no-danger
dangerouslySetInnerHTML={{ __html: log.html ?? "" }}
/>
</div>
);
}
export default function Greet(
{ data: { logs }, url }: PageProps<{ logs: Log[] }>,
) {
return (
<MainLayout url={url}>
<h1 class="text-white text-4xl">Logs</h1>
{logs.map((r) => {
return (
<LogLine
log={r}
/>
);
})}
</MainLayout>
);
}

View File

@@ -1,90 +0,0 @@
import { MainLayout } from "@components/layouts/main.tsx";
import { PageProps } from "fresh";
import { getPerformances, PerformanceRes } from "@lib/performance.ts";
import { AccessDeniedError } from "@lib/errors.ts";
import { define } from "../../../utils.ts";
export const handler = define.handlers({
async GET(ctx) {
const performances = await getPerformances();
if (!("session" in ctx.state)) {
throw new AccessDeniedError();
}
return { data: { performances } };
},
});
function PerformanceLine(
{ maximum, data: [amount, min, average, max], url }: {
maximum: number;
url: string;
data: readonly [number, number, number, number];
},
) {
return (
<div
class="mt-10 flex flex-col gap-2 bg-gray-900 px-4 py-2 rounded-2xl"
style={{ background: "var(--light)" }}
>
<div style={{ color: "var(--foreground)" }}>
{url}
</div>
<div class="flex gap-2">
<span class="bg-gray-600 p-1 text-xs rounded-xl text-white">
{Math.floor(average / 1000)}ms
</span>
<span class="bg-gray-600 p-1 text-xs rounded-xl text-white">
{amount}x
</span>
</div>
<div class="" style={{ maxWidth: "75vw" }}>
<div
class="h-2 bg-red-200 rounded"
style={{
width: `${Math.floor((max / maximum) * 100)}%`,
borderRadius: "1rem 1rem 1rem 0px",
minWidth: "5px",
}}
/>
<div
class="h-2 bg-green-200 rounded"
style={{
width: `${Math.floor((average / maximum) * 100)}%`,
borderStartEndRadius: "0px",
borderRadius: "0px 0px 1rem 0px",
minWidth: "5px",
}}
/>
<div
class="h-2 bg-yellow-200 rounded"
style={{
width: `${Math.floor((min / maximum) * 100)}%`,
borderRadius: "0px 0px 1rem 1rem",
minWidth: "5px",
}}
/>
</div>
</div>
);
}
export default function Greet(
{ data: { performances }, url }: PageProps<{ performances: PerformanceRes }>,
) {
return (
<MainLayout url={url}>
<h1 class="text-white text-4xl ">Performance</h1>
{performances.res.map((r) => {
return (
<PerformanceLine
maximum={performances.max}
url={r.url}
data={r.data}
/>
);
})}
</MainLayout>
);
}

View File

@@ -16,7 +16,6 @@ export const handler = define.handlers({
throw new BadRequestError();
}
console.log(s);
const resources = await searchResource(s);
return json(resources);

View File

@@ -38,7 +38,10 @@ export const handler = define.handlers({
const releaseDate = seriesDetails.first_air_date;
if (releaseDate && series.content?.datePublished) {
series.content.datePublished = new Date(releaseDate).toISOString();
const d = new Date(releaseDate);
if (!isNaN(d.getTime())) {
series.content.datePublished = d.toISOString();
}
}
const posterPath = seriesDetails.poster_path;
const director = seriesCredits &&

View File

@@ -27,7 +27,7 @@ export const handler = define.handlers({
},
});
export default function Greet(
export default define.page(function (
props: PageProps<
{ article: ArticleResource; session: Record<string, string> }
>,
@@ -104,4 +104,4 @@ export default function Greet(
</div>
</MainLayout>
);
}
});

View File

@@ -6,7 +6,6 @@ import { Grid } from "@components/Grid.tsx";
import { RedirectSearchHandler } from "@islands/Search.tsx";
import { parseResourceUrl, searchResource } from "@lib/search.ts";
import { ResourceCard } from "@components/Card.tsx";
import { Link } from "@islands/Link.tsx";
import { listResources } from "@lib/marka/index.ts";
import { define } from "../../utils.ts";
import { TbArrowLeft } from "@preact-icons/tb";
@@ -22,7 +21,7 @@ export const handler = define.handlers({
},
});
export default define.page(function Greet(
export default define.page(function (
props: PageProps<
{ articles: ArticleResource[] | null; searchResults: GenericResource[] }
>,
@@ -36,13 +35,13 @@ export default define.page(function Greet(
searchResults={searchResults}
>
<header class="flex gap-4 items-center mb-5 md:hidden">
<Link
<a
class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1"
href="/"
>
<TbArrowLeft class="w-5 h-5" />
Back
</Link>
</a>
<h3 class="text-2xl text-white font-light">📝 Articles</h3>
</header>

View File

@@ -4,9 +4,10 @@ import { PageProps } from "fresh";
import { resources } from "@lib/resources.ts";
import { RedirectSearchHandler } from "@islands/Search.tsx";
import { KMenu } from "@islands/KMenu.tsx";
import { define } from "../utils.ts";
// import "@lib/telegram.ts";
export default function Home(props: PageProps) {
export default define.page(function (props: PageProps) {
return (
<>
<RedirectSearchHandler />
@@ -32,4 +33,4 @@ export default function Home(props: PageProps) {
</MainLayout>
</>
);
}
});

View File

@@ -77,7 +77,7 @@ function ValidRecipe({
);
}
export default function Page(
export default define.page(function (
props: PageProps<{ recipe: RecipeResource; session: Record<string, string> }>,
) {
const { recipe, session } = props.data;
@@ -143,4 +143,4 @@ export default function Page(
</div>
</MainLayout>
);
}
});

View File

@@ -1,4 +1,4 @@
import { Context, PageProps } from "fresh";
import { PageProps } from "fresh";
import { MainLayout } from "@components/layouts/main.tsx";
import { Grid } from "@components/Grid.tsx";
import { TbArrowLeft } from "@preact-icons/tb";
@@ -8,9 +8,10 @@ import { ResourceCard } from "@components/Card.tsx";
import { listResources } from "@lib/marka/index.ts";
import { parseResourceUrl, searchResource } from "@lib/search.ts";
import { GenericResource, RecipeResource } from "@lib/marka/schema.ts";
import { define } from "../../utils.ts";
export const handler = {
async GET(ctx: Context<{ test: number }>) {
export const handler = define.handlers({
async GET(ctx) {
const req = ctx.req;
const recipes = await listResources<RecipeResource>("recipes");
const searchParams = parseResourceUrl(req.url);
@@ -18,9 +19,9 @@ export const handler = {
await searchResource({ ...searchParams, types: ["recipes"] });
return { data: { recipes, searchResults } };
},
};
});
export default function Page(
export default define.page(function (
{ data, url }: PageProps<{
recipes: RecipeResource[] | null;
searchResults: GenericResource[];
@@ -54,4 +55,4 @@ export default function Page(
</Grid>
</MainLayout>
);
}
});

View File

@@ -26,7 +26,7 @@ export const handler = define.handlers({
},
});
export default function Greet(
export default define.page(function (
props: PageProps<{ serie: ReviewResource; session: Record<string, string> }>,
) {
const { serie, session } = props.data;
@@ -96,4 +96,4 @@ export default function Greet(
</div>
</MainLayout>
);
}
});

View File

@@ -10,9 +10,6 @@ import { GenericResource, ReviewResource } from "@lib/marka/schema.ts";
import { define } from "../../utils.ts";
import { TbArrowLeft } from "@preact-icons/tb";
// : <
// { series: ReviewResource[] | null; searchResults?: GenericResource[] }
// >
export const handler = define.handlers({
async GET(ctx) {
const req = ctx.req;
@@ -24,7 +21,7 @@ export const handler = define.handlers({
},
});
export default function Greet(
export default define.page(function (
props: PageProps<
{ series: ReviewResource[] | null; searchResults: GenericResource[] }
>,
@@ -60,4 +57,4 @@ export default function Greet(
</Grid>
</MainLayout>
);
}
});

12
static/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
data/
data-dev/
_fresh/
node_modules/
mise.toml

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png" />
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -39,12 +39,7 @@ body {
font-family: "Work Sans", monospace;
}
pre {
font-family: "Work Sans", monospace;
}
a {
color: cadetblue;
a, pre {
font-family: "Work Sans", monospace;
}
@@ -53,7 +48,6 @@ a {
}
@keyframes hover {
0%,
100% {
transform: translateY(-15%);
@@ -98,11 +92,11 @@ a {
fill: #dcdbdc;
}
.items-52>* {
.items-52 > * {
height: 52px;
}
.highlight>pre {
.highlight > pre {
text-wrap: wrap;
}
@@ -118,24 +112,24 @@ main.loading {
opacity: 0;
}
.markdown-body>h1 {
.markdown-body > h1 {
font-size: 2.25rem;
font-weight: 600;
}
.markdown-body>h2 {
.markdown-body > h2 {
font-size: 1.75rem;
font-weight: 600;
}
.markdown-body>h3 {
.markdown-body > h3 {
font-size: 1.5rem;
font-weight: 600;
}
.markdown-body>h1>.anchor,
.markdown-body>h2>.anchor,
.markdown-body>h3>.anchor {
.markdown-body > h1 > .anchor,
.markdown-body > h2 > .anchor,
.markdown-body > h3 > .anchor {
display: inline-flex;
margin-left: -26px;
margin-right: 12px;
@@ -143,9 +137,9 @@ main.loading {
transition: opacity 0.2s;
}
.markdown-body>h1:hover .anchor,
.markdown-body>h2:hover .anchor,
.markdown-body>h3:hover .anchor {
.markdown-body > h1:hover .anchor,
.markdown-body > h2:hover .anchor,
.markdown-body > h3:hover .anchor {
opacity: 1;
}

View File

@@ -1,19 +0,0 @@
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M34.092 8.845C38.929 20.652 34.092 27 30 30.5c1 3.5-2.986 4.222-4.5 2.5-4.457 1.537-13.512 1.487-20-5C2 24.5 4.73 16.714 14 11.5c8-4.5 16-7 20.092-2.655Z"
fill="#FFDB1E"
/>
<path
d="M14 11.5c6.848-4.497 15.025-6.38 18.368-3.47C37.5 12.5 21.5 22.612 15.5 25c-6.5 2.587-3 8.5-6.5 8.5-3 0-2.5-4-5.183-7.75C2.232 23.535 6.16 16.648 14 11.5Z"
fill="#fff"
stroke="#FFDB1E"
/>
<path
d="M28.535 8.772c4.645 1.25-.365 5.695-4.303 8.536-3.732 2.692-6.606 4.21-7.923 4.83-.366.173-1.617-2.252-1.617-1 0 .417-.7 2.238-.934 2.326-1.365.512-4.223 1.29-5.835 1.29-3.491 0-1.923-4.754 3.014-9.122.892-.789 1.478-.645 2.283-.645-.537-.773-.534-.917.403-1.546C17.79 10.64 23 8.77 25.212 8.42c.366.014.82.35.82.629.41-.14 2.095-.388 2.503-.278Z"
fill="#FFE600"
/>
<path
d="M14.297 16.49c.985-.747 1.644-1.01 2.099-2.526.566.121.841-.08 1.29-.701.324.466 1.657.608 2.453.701-.715.451-1.057.852-1.452 2.106-1.464-.611-3.167-.302-4.39.42Z"
fill="#fff"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

View File

@@ -1,177 +0,0 @@
/**
* prism.js Twilight theme
* Based (more or less) on the Twilight theme originally of Textmate fame.
* @author Remy Bach
*/
code[class*="language-"],
pre[class*="language-"] {
color: white;
background: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
font-size: 1em;
text-align: left;
text-shadow: 0 -0.1em 0.2em black;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"],
:not(pre) > code[class*="language-"] {
background: hsl(0, 0%, 8%); /* #141414 */
}
/* Code blocks */
pre[class*="language-"] {
border-radius: 0.5em;
border: 0.3em solid hsl(0, 0%, 33%); /* #282A2B */
box-shadow: 1px 1px 0.5em black inset;
margin: 0.5em 0;
overflow: auto;
padding: 1em;
}
pre[class*="language-"]::-moz-selection {
/* Firefox */
background: hsl(200, 4%, 16%); /* #282A2B */
}
pre[class*="language-"]::selection {
/* Safari */
background: hsl(200, 4%, 16%); /* #282A2B */
}
/* Text Selection colour */
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */
}
/* Inline code */
:not(pre) > code[class*="language-"] {
border-radius: 0.3em;
border: 0.13em solid hsl(0, 0%, 33%); /* #545454 */
box-shadow: 1px 1px 0.3em -0.1em black inset;
padding: 0.15em 0.2em 0.05em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: hsl(0, 0%, 47%); /* #777777 */
}
.token.punctuation {
opacity: 0.7;
}
.token.namespace {
opacity: 0.7;
}
.token.tag,
.token.boolean,
.token.number,
.token.deleted {
color: hsl(14, 58%, 55%); /* #CF6A4C */
}
.token.keyword,
.token.property,
.token.selector,
.token.constant,
.token.symbol,
.token.builtin {
color: hsl(53, 89%, 79%); /* #F9EE98 */
}
.token.attr-name,
.token.attr-value,
.token.string,
.token.char,
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable,
.token.inserted {
color: hsl(76, 21%, 52%); /* #8F9D6A */
}
.token.atrule {
color: hsl(218, 22%, 55%); /* #7587A6 */
}
.token.regex,
.token.important {
color: hsl(42, 75%, 65%); /* #E9C062 */
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
/* Markup */
.language-markup .token.tag,
.language-markup .token.attr-name,
.language-markup .token.punctuation {
color: hsl(33, 33%, 52%); /* #AC885B */
}
/* Make the tokens sit above the line highlight so the colours don't look faded. */
.token {
position: relative;
z-index: 1;
}
.line-highlight.line-highlight {
background: hsla(0, 0%, 33%, 0.25); /* #545454 */
background: linear-gradient(
to right,
hsla(0, 0%, 33%, 0.1) 70%,
hsla(0, 0%, 33%, 0)
); /* #545454 */
border-bottom: 1px dashed hsl(0, 0%, 33%); /* #545454 */
border-top: 1px dashed hsl(0, 0%, 33%); /* #545454 */
margin-top: 0.75em; /* Same as .prisms padding-top */
z-index: 0;
}
.line-highlight.line-highlight:before,
.line-highlight.line-highlight[data-end]:after {
background-color: hsl(215, 15%, 59%); /* #8794A6 */
color: hsl(24, 20%, 95%); /* #F5F2F0 */
}

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
width="256.000000pt"
height="256.000000pt"
viewBox="0 0 256.000000 256.000000"
preserveAspectRatio="xMidYMid meet"
>
<metadata
>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g
transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"
fill="#000000"
stroke="none"
>
<path
d="M1404 2075 c-16 -7 -75 -60 -131 -116 -55 -57 -130 -133 -166 -167
l-64 -64 0 122 c0 91 -3 124 -13 130 -19 12 -192 12 -210 0 -11 -7 -15 -34
-16 -117 -1 -59 -3 -110 -3 -113 -1 -3 -20 -11 -43 -17 -67 -18 -126 -76 -164
-160 -33 -72 -34 -73 -108 -108 -41 -19 -87 -50 -102 -68 -64 -76 -82 -169
-50 -264 20 -61 16 -81 -27 -130 -108 -123 -68 -323 81 -393 41 -20 65 -23
182 -25 l135 -1 2 -127 3 -127 -58 0 c-140 -2 -358 -8 -373 -12 -51 -12 -109
-80 -109 -128 l0 -25 1110 0 1110 0 0 24 c0 46 -40 99 -89 118 -25 10 -52 16
-59 15 -9 -2 -12 10 -9 45 6 72 -17 145 -62 196 -13 16 -16 60 -16 318 0 164
3 299 6 299 3 0 18 -13 34 -30 39 -41 75 -52 113 -33 36 18 53 57 40 93 -5 14
-82 96 -171 183 -89 87 -282 277 -429 423 -146 145 -270 264 -275 265 -4 0
-15 2 -24 4 -8 1 -29 -3 -45 -10z"
/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,19 +0,0 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#141218",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

View File

@@ -1,271 +0,0 @@
// deno-lint-ignore-file
function thumbHashToApproximateAspectRatio(hash) {
let header = hash[3];
let hasAlpha = hash[2] & 0x80;
let isLandscape = hash[4] & 0x80;
let lx = isLandscape ? hasAlpha ? 5 : 7 : header & 7;
let ly = isLandscape ? header & 7 : hasAlpha ? 5 : 7;
return lx / ly;
}
function thumbHashToRGBA(hash) {
let { PI, min, max, cos, round } = Math;
// Read the constants
let header24 = hash[0] | (hash[1] << 8) | (hash[2] << 16);
let header16 = hash[3] | (hash[4] << 8);
let l_dc = (header24 & 63) / 63;
let p_dc = ((header24 >> 6) & 63) / 31.5 - 1;
let q_dc = ((header24 >> 12) & 63) / 31.5 - 1;
let l_scale = ((header24 >> 18) & 31) / 31;
let hasAlpha = header24 >> 23;
let p_scale = ((header16 >> 3) & 63) / 63;
let q_scale = ((header16 >> 9) & 63) / 63;
let isLandscape = header16 >> 15;
let lx = max(3, isLandscape ? hasAlpha ? 5 : 7 : header16 & 7);
let ly = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7);
let a_dc = hasAlpha ? (hash[5] & 15) / 15 : 1;
let a_scale = (hash[5] >> 4) / 15;
// Read the varying factors (boost saturation by 1.25x to compensate for quantization)
let ac_start = hasAlpha ? 6 : 5;
let ac_index = 0;
let decodeChannel = (nx, ny, scale) => {
let ac = [];
for (let cy = 0; cy < ny; cy++) {
for (let cx = cy ? 0 : 1; cx * ny < nx * (ny - cy); cx++) {
ac.push(
(((hash[ac_start + (ac_index >> 1)] >> ((ac_index++ & 1) << 2)) &
15) / 7.5 - 1) * scale,
);
}
}
return ac;
};
const l_ac = decodeChannel(lx, ly, l_scale);
const p_ac = decodeChannel(3, 3, p_scale * 1.25);
const q_ac = decodeChannel(3, 3, q_scale * 1.25);
const a_ac = hasAlpha && decodeChannel(5, 5, a_scale);
// Decode using the DCT into RGB
const ratio = thumbHashToApproximateAspectRatio(hash);
const w = round(ratio > 1 ? 32 : 32 * ratio);
const h = round(ratio > 1 ? 32 / ratio : 32);
const rgba = new Uint8Array(w * h * 4), fx = [], fy = [];
for (let y = 0, i = 0; y < h; y++) {
for (let x = 0; x < w; x++, i += 4) {
let l = l_dc, p = p_dc, q = q_dc, a = a_dc;
// Precompute the coefficients
for (let cx = 0, n = max(lx, hasAlpha ? 5 : 3); cx < n; cx++) {
fx[cx] = cos(PI / w * (x + 0.5) * cx);
}
for (let cy = 0, n = max(ly, hasAlpha ? 5 : 3); cy < n; cy++) {
fy[cy] = cos(PI / h * (y + 0.5) * cy);
}
// Decode L
for (let cy = 0, j = 0; cy < ly; cy++) {
for (
let cx = cy ? 0 : 1, fy2 = fy[cy] * 2;
cx * ly < lx * (ly - cy);
cx++, j++
) {
l += l_ac[j] * fx[cx] * fy2;
}
}
// Decode P and Q
for (let cy = 0, j = 0; cy < 3; cy++) {
for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 3 - cy; cx++, j++) {
const f = fx[cx] * fy2;
p += p_ac[j] * f;
q += q_ac[j] * f;
}
}
// Decode A
if (hasAlpha) {
for (let cy = 0, j = 0; cy < 5; cy++) {
for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 5 - cy; cx++, j++) {
a += a_ac[j] * fx[cx] * fy2;
}
}
}
// Convert to RGB
const b = l - 2 / 3 * p;
const r = (3 * l - b + q) / 2;
const g = r - q;
rgba[i] = max(0, 255 * min(1, r));
rgba[i + 1] = max(0, 255 * min(1, g));
rgba[i + 2] = max(0, 255 * min(1, b));
rgba[i + 3] = max(0, 255 * min(1, a));
}
}
return { w, h, rgba };
}
function rgbaToDataURL(w, h, rgba) {
const row = w * 4 + 1;
const idat = 6 + h * (5 + row);
const bytes = [
137,
80,
78,
71,
13,
10,
26,
10,
0,
0,
0,
13,
73,
72,
68,
82,
0,
0,
w >> 8,
w & 255,
0,
0,
h >> 8,
h & 255,
8,
6,
0,
0,
0,
0,
0,
0,
0,
idat >>> 24,
(idat >> 16) & 255,
(idat >> 8) & 255,
idat & 255,
73,
68,
65,
84,
120,
1,
];
const table = [
0,
498536548,
997073096,
651767980,
1994146192,
1802195444,
1303535960,
1342533948,
-306674912,
-267414716,
-690576408,
-882789492,
-1687895376,
-2032938284,
-1609899400,
-1111625188,
];
let a = 1, b = 0;
for (let y = 0, i = 0, end = row - 1; y < h; y++, end += row - 1) {
bytes.push(
y + 1 < h ? 0 : 1,
row & 255,
row >> 8,
~row & 255,
(row >> 8) ^ 255,
0,
);
for (b = (b + a) % 65521; i < end; i++) {
let u = rgba[i] & 255;
bytes.push(u);
a = (a + u) % 65521;
b = (b + a) % 65521;
}
}
bytes.push(
b >> 8,
b & 255,
a >> 8,
a & 255,
0,
0,
0,
0,
0,
0,
0,
0,
73,
69,
78,
68,
174,
66,
96,
130,
);
for (let [start, end] of [[12, 29], [37, 41 + idat]]) {
let c = ~0;
for (let i = start; i < end; i++) {
c ^= bytes[i];
c = (c >>> 4) ^ table[c & 15];
c = (c >>> 4) ^ table[c & 15];
}
c = ~c;
bytes[end++] = c >>> 24;
bytes[end++] = (c >> 16) & 255;
bytes[end++] = (c >> 8) & 255;
bytes[end++] = c & 255;
}
return "data:image/png;base64," + btoa(String.fromCharCode(...bytes));
}
function updateThumbhashImages() {
document.querySelectorAll("[data-thumb]").forEach((entry) => {
const hash = entry.getAttribute("data-thumb");
if (entry.getAttribute("data-thumb-decoded")) return;
if (!hash) return;
entry.setAttribute("data-thumb-decoded", true);
const decodedString = atob(hash);
// Create Uint8Array from decoded string
const buffer = new Uint8Array(decodedString.length);
for (let i = 0; i < decodedString.length; i++) {
buffer[i] = decodedString.charCodeAt(i);
}
const image = thumbHashToRGBA(buffer);
const dataURL = rgbaToDataURL(image.w, image.h, image.rgba);
entry.style.background = `url(${dataURL})`;
entry.style.backgroundSize = "cover";
const child = entry.querySelector("img[data-thumb-img]");
setTimeout(() => {
const isLoaded = child && child.complete && child.naturalHeight !== 0;
if (child && !isLoaded) {
child.style.opacity = 0;
child.style.filter = "blur(5px)";
child.addEventListener("load", () => {
child.style.transition = "opacity 0.3s ease, filter 0.6s ease";
child.style.opacity = 1;
child.style.filter = "blur(0px)";
setTimeout(() => {
//entry.style.background = "";
}, 400);
});
}
}, 50);
});
}
globalThis.addEventListener("load", updateThumbhashImages);
globalThis.addEventListener("loading-finished", updateThumbhashImages);

View File

@@ -1,7 +0,0 @@
import { type Config } from "tailwindcss";
export default {
content: [
"{routes,islands,components}/**/*.{ts,tsx}",
],
} satisfies Config;

View File

@@ -7,4 +7,10 @@ export default defineConfig({
fresh(),
tailwindcss(),
],
server: {
port: 8000,
watch: {
ignored: ["**/data/**"],
},
},
});

View File

@@ -1,14 +0,0 @@
// vite.config.ts
import { defineConfig } from "file:///home/max/Projects/memorium/node_modules/.deno/vite@7.3.1/node_modules/vite/dist/node/index.js";
import { fresh } from "@fresh/plugin-vite";
import tailwindcss from "file:///home/max/Projects/memorium/node_modules/.deno/@tailwindcss+vite@4.1.18/node_modules/@tailwindcss/vite/dist/index.mjs";
var vite_config_default = defineConfig({
plugins: [
fresh(),
tailwindcss()
]
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlUm9vdCI6ICJmaWxlOi8vL2hvbWUvbWF4L1Byb2plY3RzL21lbW9yaXVtLyIsCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL2hvbWUvbWF4L1Byb2plY3RzL21lbW9yaXVtXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCIvaG9tZS9tYXgvUHJvamVjdHMvbWVtb3JpdW0vdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2hvbWUvbWF4L1Byb2plY3RzL21lbW9yaXVtL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcbmltcG9ydCB7IGZyZXNoIH0gZnJvbSBcIkBmcmVzaC9wbHVnaW4tdml0ZVwiO1xuaW1wb3J0IHRhaWx3aW5kY3NzIGZyb20gXCJAdGFpbHdpbmRjc3Mvdml0ZVwiO1xuXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBwbHVnaW5zOiBbXG4gICAgZnJlc2goKSxcbiAgICB0YWlsd2luZGNzcygpLFxuICBdLFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQW1RLFNBQVMsb0JBQW9CO0FBQ2hTLFNBQVMsYUFBYTtBQUN0QixPQUFPLGlCQUFpQjtBQUV4QixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxNQUFNO0FBQUEsSUFDTixZQUFZO0FBQUEsRUFDZDtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==