Compare commits

...

3 Commits

Author SHA1 Message Date
6713c1619a
initial dashboard content 2025-05-04 04:16:30 -04:00
2c81978ea2 schema: initialize database (#12)
Define the initial schemata for emotes, users, and lists.

Fixes #9.

Co-authored-by: Branden J Brown <zephyrtronium@gmail.com>
Reviewed-on: #12
Reviewed-by: hamza <hamza@noreply.localhost>
2025-05-03 19:10:26 -04:00
0d25fe733f meta: add .vscode configuring elixirls (#4)
Co-authored-by: Branden J Brown <zephyrtronium@gmail.com>
Reviewed-on: #4
Reviewed-by: hamza <hamza@noreply.localhost>
Reviewed-by: jolheiser <jolheiser@noreply.localhost>
2025-04-30 21:57:05 -04:00
16 changed files with 1161 additions and 55 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"elixirLS.projectDir": "backend"
}

View File

@ -16,9 +16,13 @@
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@lucide/svelte": "^0.507.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.5",
"clsx": "^2.1.1",
"daisyui": "^5.0.35",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
@ -27,6 +31,7 @@
"prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.1.5",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^6.2.6"

View File

@ -0,0 +1,79 @@
@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&display=swap');
@import "tailwindcss";
@theme {
--font-logo: "Caveat Brush", system-ui;
}
@plugin "daisyui" {
themes: sunturtle --default, moonturtle --prefersdark;
}
@plugin "daisyui/theme" {
name: "sunturtle";
default: true;
prefersdark: false;
color-scheme: "light";
--color-base-100: oklch(98% 0 0);
--color-base-200: oklch(97% 0 0);
--color-base-300: oklch(92% 0 0);
--color-base-content: oklch(20% 0 0);
--color-primary: oklch(79% 0.184 86.047);
--color-primary-content: oklch(98% 0.018 155.826);
--color-secondary: oklch(68% 0.162 75.834);
--color-secondary-content: oklch(97% 0.013 236.62);
--color-accent: oklch(62% 0.265 303.9);
--color-accent-content: oklch(97% 0.014 308.299);
--color-neutral: oklch(43% 0 0);
--color-neutral-content: oklch(98% 0 0);
--color-info: oklch(71% 0.143 215.221);
--color-info-content: oklch(98% 0.019 200.873);
--color-success: oklch(70% 0.14 182.503);
--color-success-content: oklch(98% 0.014 180.72);
--color-warning: oklch(70% 0.213 47.604);
--color-warning-content: oklch(98% 0.016 73.684);
--color-error: oklch(63% 0.237 25.331);
--color-error-content: oklch(97% 0.013 17.38);
--radius-selector: 0.25rem;
--radius-field: 0.5rem;
--radius-box: 1rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}
@plugin "daisyui/theme" {
name: "moonturtle";
prefersdark: true;
color-scheme: "dark";
--color-base-100: oklch(14% 0.004 49.25);
--color-base-200: oklch(27% 0 0);
--color-base-300: oklch(21% 0.006 285.885);
--color-base-content: oklch(94% 0.129 101.54);
--color-primary: oklch(100% 0 0);
--color-primary-content: oklch(20% 0 0);
--color-secondary: oklch(27.581% 0.064 261.069);
--color-secondary-content: oklch(85.516% 0.012 261.069);
--color-accent: oklch(58% 0.176 304.987);
--color-accent-content: oklch(87.334% 0.01 338.825);
--color-neutral: oklch(37% 0.01 67.558);
--color-neutral-content: oklch(97% 0.071 103.193);
--color-info: oklch(79.061% 0.121 237.133);
--color-info-content: oklch(15.812% 0.024 237.133);
--color-success: oklch(78.119% 0.192 132.154);
--color-success-content: oklch(55.623% 0.038 132.154);
--color-warning: oklch(86.127% 0.136 102.891);
--color-warning-content: oklch(17.225% 0.027 102.891);
--color-error: oklch(71.753% 0.176 22.568);
--color-error-content: oklch(14.35% 0.035 22.568);
--radius-selector: 0.25rem;
--radius-field: 0.5rem;
--radius-box: 1rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 0;
--noise: 0;
}

View File

@ -0,0 +1,23 @@
<script lang="ts">
import { ArrowLeft, Home } from "@lucide/svelte";
import { page } from "$app/state";
const back = () => {
history.back();
};
const home = () => {
location.href = "/dashboard";
};
</script>
<div class="h-[calc(100dvh-(var(--spacing)*16))] grid place-items-center">
<div class="flex flex-col items-center gap-2">
<h1 class="text-5xl">Error</h1>
<h3 class="text-lg">{page.status}: {page.error?.message}</h3>
<div class="flex gap-4">
<button on:click={back} class="btn btn-xl"><ArrowLeft /> Go Back</button>
<button on:click={home} class="btn btn-xl"><Home /> Home</button>
</div>
</div>
</div>

View File

@ -0,0 +1,81 @@
<script lang="ts">
import { Sun, Moon } from "@lucide/svelte";
import { MediaQuery } from "svelte/reactivity";
import "../global.css";
const { children } = $props();
const preferTheme = new MediaQuery("(prefers-color-scheme: dark)");
let dark = $state(
(() => {
if (localStorage["theme"]) return localStorage["theme"] === "dark";
return preferTheme.current;
})(),
);
$effect(() => {
localStorage["theme"] = dark ? "dark" : "light";
});
</script>
<div class="flex flex-col">
<header class="z-10 w-full sticky top-0 h-16">
<div class="navbar border-b-2 bg-base-100 font-semibold border-secondary px-4 shadow-xl">
<div class="flex-1">
<a class="btn btn-ghost font-logo inline-flex items-center text-2xl -mt-1.5" href="/">
<img class="w-8 drop-shadow-sm" src="/sunturtle.png" alt="" />
Ligmotes
</a>
<ul class="menu menu-horizontal px-1">
<li><a href="/dashboard">Dashboard</a></li>
<li><a href="/emotes">Emotes</a></li>
</ul>
</div>
<div class="flex-none">
<ul class="menu menu-horizontal px-1">
<li><a href="/support">Support</a></li>
</ul>
</div>
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-ghost">
<div class="w-8 rounded-full"><img alt="" class="rounded-full" src="https://picsum.photos/200" /></div>
LigmaUser123
</div>
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<ul
tabindex="0"
class="menu dropdown-content bg-base-200 inset-shadow-sm rounded-selector z-1 mt-3 w-full p-2 shadow"
>
<li>
<a class="text-right" href="/"> Profile </a>
</li>
<li><a href="/">Settings</a></li>
<li><a href="/">Logout</a></li>
</ul>
</div>
{#if preferTheme.current}
<label class="swap swap-rotate">
<input type="checkbox" class="theme-controller" bind:checked={dark} value={"sunturtle"} />
<Moon class="swap-off w-7 fill-current" />
<Sun class="swap-on w-7 fill-current" />
</label>
{:else}
<label class="swap swap-rotate">
<input type="checkbox" class="theme-controller" bind:checked={dark} value="moonturtle" />
<Sun class="swap-off w-7 fill-current" />
<Moon class="swap-on w-7 fill-current" />
</label>
{/if}
</div>
</header>
<main class="flex-1">
{@render children()}
</main>
</div>
<style>
:global(body) {
margin: 0;
padding: 0;
}
</style>

View File

@ -0,0 +1 @@
export const ssr = false;

View File

@ -1,2 +1,2 @@
<h1>Welcome to SvelteKit</h1>
<h1 class="py-5 bg-red-200">Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>

View File

@ -0,0 +1,126 @@
<script lang="ts">
import {
SquarePlus,
Folder,
Smile,
Twitch,
Wrench,
User,
MessageSquare,
CircleHelp,
LogOut,
Hammer,
} from "@lucide/svelte";
import type { Component } from "svelte";
const { children } = $props();
interface Item {
icon: Component;
text: string;
href: string;
divider?: undefined;
}
interface Divider {
divider: true;
}
const sidebar: (Item | Divider)[] = [
{
icon: Folder,
text: "Emote Sets",
href: "/dashboard/emote-sets",
},
{
icon: Smile,
text: "Your Emotes",
href: "/dashboard/emotes",
},
{
icon: Twitch,
text: "Channel Connections",
href: "/dashboard/connections",
},
{
icon: Wrench,
text: "Editors",
href: "/dashboard/editors",
},
{
icon: Hammer,
text: "Test",
href: "/dashboard/test",
},
{
divider: true,
},
{
icon: User,
text: "Account",
href: "/dashboard/account",
},
{
icon: MessageSquare,
text: "Community",
href: "/community",
},
{
icon: CircleHelp,
text: "Help",
href: "/dashboard/help",
},
{
divider: true,
},
{
icon: LogOut,
text: "Log Out",
href: "/logout",
},
];
</script>
<div
class="grid min-h-[calc(100dvh-(var(--spacing)*16))] grid-cols-1 grid-rows-[1fr_1px_auto_1px_auto] lg:grid-cols-[var(--container-2xs)_2.5rem_minmax(0,1fr)_2.5rem] xl:grid-cols-[var(--container-2xs)_2.5rem_minmax(0,1fr)_2.5rem]"
>
<div class="relative flex flex-1">
<aside class="absolute inset-0">
<div class="sticky bg-base-200 top-16 bottom-0 h-full max-h-[calc(100dvh-(var(--spacing)*16))] overflow-y-auto">
<ul class="menu space-y-2 rounded-none min-w-72 max-w-xs py-8">
<li>
<button class="btn btn-neutral w-1/2 mx-auto">
<SquarePlus />
Upload
</button>
</li>
<div class="divider"></div>
{#each sidebar as item}
{#if item.divider}
<div class="divider"></div>
{:else}
{@const Icon = item.icon}
<li>
<a href={item.href}>
<Icon />
{item.text}
</a>
</li>
{/if}
{/each}
</ul>
</div>
</aside>
</div>
<div
class="col-start-2 row-span-5 row-start-1 border-x border-x-(--pattern-fg) bg-[image:repeating-linear-gradient(315deg,_var(--pattern-fg)_0,_var(--pattern-fg)_1px,_transparent_0,_transparent_50%)] bg-[size:10px_10px] bg-fixed [--pattern-fg:var(--color-gray-950)]/5 max-lg:hidden dark:[--pattern-fg:var(--color-white)]/10"
></div>
<div class="py-8 mx-auto w-full max-w-5xl">
{@render children()}
</div>
<div
class="col-start-4 row-span-5 row-start-1 border-x border-x-(--pattern-fg) bg-[image:repeating-linear-gradient(315deg,_var(--pattern-fg)_0,_var(--pattern-fg)_1px,_transparent_0,_transparent_50%)] bg-[size:10px_10px] bg-fixed [--pattern-fg:var(--color-gray-950)]/5 max-lg:hidden dark:[--pattern-fg:var(--color-white)]/10"
></div>
</div>

View File

@ -0,0 +1,5 @@
import { redirect } from "@sveltejs/kit";
export function load() {
redirect(307, "/dashboard/emote-sets");
}

View File

@ -0,0 +1,196 @@
<script lang="ts">
import { Plus, Twitch, Youtube } from "@lucide/svelte";
import type { Component } from "svelte";
const user = {
name: "ligmalord",
};
const createTop = (n: number) => {
const motes = [];
for (let i = 0; i < n; i++) {
motes.push(`https://picsum.photos/200?r=${i}${n}`);
}
return motes;
};
interface EmoteSet {
id: string;
active?: {
type: "twitch" | "youtube";
name: string;
};
name: string;
owner: string;
emotes: number;
top: string[];
}
const sets: EmoteSet[] = [
{
id: crypto.randomUUID(),
active: {
type: "twitch",
name: "caedrel",
},
name: "Main Set",
owner: "ligmalord",
emotes: 481,
top: createTop(12),
},
{
id: crypto.randomUUID(),
active: {
type: "twitch",
name: "amouranth",
},
name: "Goon Set",
owner: "simpmaster9009",
emotes: 690,
top: createTop(12),
},
{
id: crypto.randomUUID(),
active: {
type: "youtube",
name: "DrLupo",
},
name: "CHESS Vibes",
owner: "legitplayer",
emotes: 8012,
top: createTop(12),
},
{
id: crypto.randomUUID(),
name: "random set",
owner: "ligmalord",
emotes: Math.floor(Math.random() * 1500),
top: createTop(12),
},
{
id: crypto.randomUUID(),
name: "idk bocchi or something",
owner: "ligmalord",
emotes: Math.floor(Math.random() * 1500),
top: createTop(12),
},
{
id: crypto.randomUUID(),
name: "Lorem Ipsum",
owner: "ligmalord",
emotes: Math.floor(Math.random() * 1500),
top: createTop(12),
},
{
id: crypto.randomUUID(),
name: "Dolor Sit Amet",
owner: "ligmalord",
emotes: Math.floor(Math.random() * 1500),
top: createTop(12),
},
];
</script>
{#snippet active(type: string, channel: string)}
{#if type === "twitch"}
<div class="px-3 bg-[#A970FF] rounded-box text-white inline-flex items-center gap-2">
<Twitch class="w-4" />
<p>{channel}</p>
</div>
{:else if type === "youtube"}
<div class="px-3 bg-[#FF0033] rounded-box text-white inline-flex items-center gap-2">
<Youtube class="w-4" />
<p>{channel}</p>
</div>
{/if}
{/snippet}
{#snippet emoteSet(set: EmoteSet)}
<a
class="card card-sm card-border bg-base-100 hover:brightness-95 cursor-pointer"
href="/dashboard/emote-sets/{set.id}"
>
<div class="card-body">
<div class="card-title flex items-center">
<h2>
{set.name}
</h2>
{#if set.active}
<div class="badge badge-sm badge-accent">Active</div>
{/if}
</div>
<div class="grid grid-cols-6 gap-2">
{#each Array(12), i}
{@const src = set.top[i]}
{#if src}
<img class="w-full rounded-field aspect-square" alt="" {src} />
{:else}
<div class="w-full aspect-square rounded-field bg-neutral"></div>
{/if}
{/each}
</div>
<div class="flex-1 flex items-center gap-2">
<span class="font-semibold opacity-80">By {set.owner}</span>
{#if set.owner === user.name}
<div class="badge badge-outline badge-sm badge-success">Owner</div>
{:else}
<div class="badge badge-outline badge-sm badge-info">Editor</div>
{/if}
</div>
{#if set.active}
<div>
{@render active(set.active.type, set.active.name)}
</div>
{/if}
<div class="text-right">
{set.emotes} emotes
</div>
</div>
</a>
{/snippet}
<div class="flex justify-between">
<h2 class="text-3xl font-bold">Emote Sets</h2>
<button class="btn btn-neutral">
<Plus />
Create Set
</button>
</div>
<div class="divider divider-neutral"></div>
<div class="card card-border bg-base-300">
<div class="card-body">
<h2 class="card-title">Active Emote Sets</h2>
<div class="grid grid-cols-3 gap-4">
{#each sets as set}
{#if set.active}
{@render emoteSet(set)}
{/if}
{/each}
</div>
</div>
</div>
<div class="divider divider-neutral"></div>
<div class="card card-border bg-base-300">
<div class="card-body">
<h2 class="card-title">Inactive Emote Sets</h2>
<div class="grid grid-cols-3 gap-4">
{#each sets as set}
{#if !set.active}
{@render emoteSet(set)}
{/if}
{/each}
</div>
</div>
</div>

View File

@ -0,0 +1,72 @@
<script lang="ts">
import { Grid3x3, LayoutGrid, Plus } from "@lucide/svelte";
const [compact, cozy] = ["compact", "cozy"];
let viewMode = $state(localStorage["viewMode"] || compact);
$effect(() => {
localStorage["viewMode"] = viewMode;
});
</script>
{#snippet emote()}
{#if viewMode === compact}
<div class="w-full aspect-square rounded-field bg-neutral"></div>
{:else}
<div class="w-full aspect-square rounded-field bg-neutral"></div>
{/if}
{/snippet}
{#snippet emotes(emotes: Array<any>)}
{#if viewMode === compact}
<div class="grid grid-cols-12 gap-1">
{#each Array(97)}
{@render emote()}
{/each}
</div>
{:else}
<div class="grid grid-cols-8 gap-4">
{#each Array(97)}
{@render emote()}
{/each}
</div>
{/if}
{/snippet}
<div class="flex justify-between">
<h2 class="text-3xl font-bold">User Emotes</h2>
<button class="btn btn-neutral">
<Plus />
Upload
</button>
</div>
<div class="divider divider-neutral"></div>
<div class="flex justify-end gap-2 mb-4">
<button class:btn-active={viewMode === compact} onclick={() => (viewMode = compact)} class="btn btn-ghost btn-square">
<Grid3x3 aria-label="enabled" />
</button>
<button class:btn-active={viewMode === cozy} onclick={() => (viewMode = cozy)} class="btn btn-ghost btn-square">
<LayoutGrid aria-label="enabled" />
</button>
</div>
<div class="card card-border bg-base-300">
<div class="card-body">
<h2 class="card-title">Live Emotes</h2>
{@render emotes(Array(97))}
</div>
</div>
<div class="divider divider-neutral"></div>
<div class="card card-border bg-base-300">
<div class="card-body">
<h2 class="card-title">Private Emotes</h2>
{@render emotes(Array(12))}
</div>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,8 +1,9 @@
import tailwindcss from "@tailwindcss/vite";
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()],
plugins: [tailwindcss(), sveltekit()],
server: {
port: 3000,
},

180
backend/schema/init.sql Normal file
View File

@ -0,0 +1,180 @@
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
BEGIN TRANSACTION;
-- User data.
CREATE TABLE user (
-- User ID.
-- UUIDv7 in binary format.
"id" BLOB PRIMARY KEY NOT NULL,
-- Display name.
"name" TEXT NOT NULL,
-- Canonical name for deduplication.
-- It is used only for uniqueness, never displayed to users.
-- Canonicalization is handled in the application layer.
"canonical_name" TEXT UNIQUE NOT NULL,
-- URL of the user's profile picture, if any.
"picture" TEXT,
-- Timestamp at which this user was soft-deleted, if ever.
"deleted_at" INTEGER,
-- Timestamp at which this user will be unbanned, if they are banned.
-- Banning this way is weak: it disables all lists for anything other than
-- export and prevents uploading and modifying emotes.
-- Nanoseconds since UNIX epoch.
"unban_at" INTEGER,
-- Reason for ban, e.g. abusive/illegal uploads or manipulating emote usage.
"ban" TEXT
) STRICT;
-- Metadata about emotes.
CREATE TABLE emote (
-- Emote ID.
-- UUIDv7 in binary format.
"id" BLOB PRIMARY KEY NOT NULL,
-- Suggested or default name for the emote when added to a list.
"name" TEXT NOT NULL,
-- ID of the user who uploaded the emote.
"uploader" BLOB NOT NULL REFERENCES user("id"),
-- Timestamp of creation.
-- Nanoseconds since UNIX epoch.
"created_at" INTEGER NOT NULL,
-- Search tags. The emote name is always one.
-- This is the user-facing list; the actual search index is defined elsewhere.
-- JSONB array of strings.
"tags" BLOB NOT NULL,
-- Credits for the work.
-- JSONB array.
--
-- We require at least one attribution.
-- For standard uploads, we typically expect the uploader to list themselves,
-- at the very least because they represent they have the right to grant
-- license to Ligmotes, which suggests they own some attribution.
-- For things like 7TV imports, we would default to attributing their
-- emote URL.
--
-- Each attribution may be just text, a social media link, an arbitrary
-- hyperlink, a Ligmotes user ID, &c.
"attributions" BLOB NOT NULL,
-- User IDs authorized to list the emote, or NULL if anyone is allowed.
-- JSONB array of user IDs.
"restrictions" BLOB,
-- User ID of the moderator who reviewed the emote, or NULL if not reviewed.
-- ON DELETE RESTRICT because we don't want reviews to disappear if a
-- moderator is deleted; rather, the reviews should be re-attributed to an
-- existing moderator first.
"reviewer" BLOB REFERENCES user("id") ON DELETE RESTRICT,
-- Timestamp of public listing approval, or NULL if not approved.
-- An emote with non-NULL reviewer but NULL approved_at is private.
-- Nanoseconds since UNIX epoch.
"approved_at" INTEGER,
-- ID of a newer version, if one exists.
-- ON DELETE SET NULL because we don't want deleting a new version to affect
-- an old version.
-- TODO(zephyr): Ideally we would use the deleted version's "replaced" instead.
"replaced" BLOB REFERENCES emote("id") ON DELETE SET NULL,
-- ID of an older version, if one exists.
-- ON DELETE SET NULL for the same reason as "replaced".
"replaces" BLOB REFERENCES emote("id") ON DELETE SET NULL
) STRICT;
-- Emote list information.
CREATE TABLE list (
-- Emote list ID.
-- UUIDv7 in binary format.
"id" BLOB PRIMARY KEY NOT NULL,
-- List owner's user ID.
-- ON DELETE CASCADE because we only hard-delete users if all their data is
-- being obliterated.
"owner" BLOB NOT NULL REFERENCES user("id") ON DELETE CASCADE,
-- Display name.
"name" TEXT NOT NULL,
-- Timestamp of creation.
-- Nanoseconds since UNIX epoch.
"created_at" INTEGER NOT NULL
) STRICT;
-- User connections and active emote lists.
CREATE TABLE user_connection (
-- User ID.
-- ON DELETE CASCADE because we don't need connections for a user that's deleted.
"id" BLOB NOT NULL REFERENCES user("id") ON DELETE CASCADE,
-- Name of the connected platform, e.g. 'Twitch'.
"platform" TEXT NOT NULL,
-- User ID on the connected platform.
"platform_id" TEXT UNIQUE NOT NULL,
-- ID of the active emote list for this connection.
-- NULL if there is no active list.
"list" BLOB REFERENCES list("id") ON DELETE SET NULL,
UNIQUE("id", "platform")
) STRICT;
CREATE INDEX "user_connection_active_list" ON user_connection ("platform", "platform_id", "list");
-- Media links for emotes.
CREATE TABLE emote_media (
-- Emote for which this is a media link.
-- NOTE(zephyr): ON DELETE RESTRICT because we want to be sure we remove
-- media from the blob store before we remove the emote from the database.
-- In other words, we should always delete from here before there.
"id" BLOB NOT NULL REFERENCES emote("id") ON DELETE RESTRICT,
-- Media format: 'AVIF', 'WEBP', 'PNG', &c.
"format" TEXT NOT NULL,
-- Image scale.
-- We use Twitch rather than 7TV conventions for image sizes.
-- Scale 1 is the default size;
-- it always has a width of at least 26 px and a height of at most 26 px,
-- unless we manually insert funny things.
-- The other scale values are 2 and 4.
"scale" INTEGER NOT NULL,
-- Content ID, a hash of the image file contents.
-- This is also used as the filename for the blob store and CDN.
"hash" BLOB NOT NULL,
-- Other metadata: image dimensions and file size.
"height" INTEGER NOT NULL,
"width" INTEGER NOT NULL,
"filesize" INTEGER NOT NULL,
UNIQUE("id", "format", "scale"),
CONSTRAINT "emote_media_valid_scale" CHECK ("scale" IN (1, 2, 4))
) STRICT;
-- Emote list contents.
CREATE TABLE list_emote (
-- Emote list ID.
-- ON DELETE CASCADE because if a list is deleted then we never care about
-- what emotes were in it.
"list" BLOB NOT NULL REFERENCES list("id") ON DELETE CASCADE,
-- Emote ID.
-- ON DELETE CASCADE because all the media links will already be gone anyway,
-- since media links have to be deleted before emotes.
"emote" BLOB NOT NULL REFERENCES emote("id") ON DELETE CASCADE,
-- Name of the emote in the list.
-- This can be different from or the same as the emote's listed name.
"name" TEXT NOT NULL,
-- Timestamp at which this emote falls off the list.
-- The CDN updates this whenever an emote image is requested for a chat.
-- Nanoseconds since the UNIX epoch.
"expires_at" INTEGER NOT NULL,
-- Timestamp at which this emote was manually removed from the list.
-- If it's added again, this should be set to NULL, and expires_at should
-- only be updated if it's in the past.
-- This prevents removing and re-adding an emote from circumventing the
-- usage requirement.
-- Nanoseconds since the UNIX epoch.
"removed_at" INTEGER,
UNIQUE("list", "emote")
) STRICT;
-- List editors.
-- A (list, user) pair must exist in this table for the user to make any edit
-- to the list. Being the list owner is not sufficient.
CREATE TABLE list_editor (
"list" BLOB NOT NULL REFERENCES list("id"),
"user" BLOB NOT NULL REFERENCES user("id"),
PRIMARY KEY ("list", "user")
) STRICT;
COMMIT;

440
pnpm-lock.yaml generated
View File

@ -12,28 +12,40 @@ importers:
devDependencies:
'@eslint/compat':
specifier: ^1.2.5
version: 1.2.8(eslint@9.25.1)
version: 1.2.8(eslint@9.25.1(jiti@2.4.2))
'@eslint/js':
specifier: ^9.18.0
version: 9.25.1
'@lucide/svelte':
specifier: ^0.507.0
version: 0.507.0(svelte@5.28.2)
'@sveltejs/adapter-static':
specifier: ^3.0.8
version: 3.0.8(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3))(svelte@5.28.2)(vite@6.3.3))
version: 3.0.8(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))
'@sveltejs/kit':
specifier: ^2.16.0
version: 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3))(svelte@5.28.2)(vite@6.3.3)
version: 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
'@sveltejs/vite-plugin-svelte':
specifier: ^5.0.0
version: 5.0.3(svelte@5.28.2)(vite@6.3.3)
version: 5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
'@tailwindcss/vite':
specifier: ^4.1.5
version: 4.1.5(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
clsx:
specifier: ^2.1.1
version: 2.1.1
daisyui:
specifier: ^5.0.35
version: 5.0.35
eslint:
specifier: ^9.18.0
version: 9.25.1
version: 9.25.1(jiti@2.4.2)
eslint-config-prettier:
specifier: ^10.0.1
version: 10.1.2(eslint@9.25.1)
version: 10.1.2(eslint@9.25.1(jiti@2.4.2))
eslint-plugin-svelte:
specifier: ^3.0.0
version: 3.5.1(eslint@9.25.1)(svelte@5.28.2)
version: 3.5.1(eslint@9.25.1(jiti@2.4.2))(svelte@5.28.2)
globals:
specifier: ^16.0.0
version: 16.0.0
@ -49,15 +61,18 @@ importers:
svelte-check:
specifier: ^4.0.0
version: 4.1.6(picomatch@4.0.2)(svelte@5.28.2)(typescript@5.8.3)
tailwindcss:
specifier: ^4.1.5
version: 4.1.5
typescript:
specifier: ^5.0.0
version: 5.8.3
typescript-eslint:
specifier: ^8.20.0
version: 8.31.1(eslint@9.25.1)(typescript@5.8.3)
version: 8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
vite:
specifier: ^6.2.6
version: 6.3.3
version: 6.3.3(jiti@2.4.2)(lightningcss@1.29.2)
packages:
@ -300,6 +315,11 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@lucide/svelte@0.507.0':
resolution: {integrity: sha512-kXIg/2IHEo1bAwZG94fjA9O6+E3Sv5aQrW2yyPf4JvCYIUIF8DGLIQI4vfjoUyNq2dEX4cI33pZarzGOHr0npg==}
peerDependencies:
svelte: ^5
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -449,6 +469,96 @@ packages:
svelte: ^5.0.0
vite: ^6.0.0
'@tailwindcss/node@4.1.5':
resolution: {integrity: sha512-CBhSWo0vLnWhXIvpD0qsPephiaUYfHUX3U9anwDaHZAeuGpTiB3XmsxPAN6qX7bFhipyGBqOa1QYQVVhkOUGxg==}
'@tailwindcss/oxide-android-arm64@4.1.5':
resolution: {integrity: sha512-LVvM0GirXHED02j7hSECm8l9GGJ1RfgpWCW+DRn5TvSaxVsv28gRtoL4aWKGnXqwvI3zu1GABeDNDVZeDPOQrw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.1.5':
resolution: {integrity: sha512-//TfCA3pNrgnw4rRJOqavW7XUk8gsg9ddi8cwcsWXp99tzdBAZW0WXrD8wDyNbqjW316Pk2hiN/NJx/KWHl8oA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.1.5':
resolution: {integrity: sha512-XQorp3Q6/WzRd9OalgHgaqgEbjP3qjHrlSUb5k1EuS1Z9NE9+BbzSORraO+ecW432cbCN7RVGGL/lSnHxcd+7Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.1.5':
resolution: {integrity: sha512-bPrLWbxo8gAo97ZmrCbOdtlz/Dkuy8NK97aFbVpkJ2nJ2Jo/rsCbu0TlGx8joCuA3q6vMWTSn01JY46iwG+clg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.5':
resolution: {integrity: sha512-1gtQJY9JzMAhgAfvd/ZaVOjh/Ju/nCoAsvOVJenWZfs05wb8zq+GOTnZALWGqKIYEtyNpCzvMk+ocGpxwdvaVg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.1.5':
resolution: {integrity: sha512-dtlaHU2v7MtdxBXoqhxwsWjav7oim7Whc6S9wq/i/uUMTWAzq/gijq1InSgn2yTnh43kR+SFvcSyEF0GCNu1PQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-arm64-musl@4.1.5':
resolution: {integrity: sha512-fg0F6nAeYcJ3CriqDT1iVrqALMwD37+sLzXs8Rjy8Z1ZHshJoYceodfyUwGJEsQoTyWbliFNRs2wMQNXtT7MVA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-x64-gnu@4.1.5':
resolution: {integrity: sha512-SO+F2YEIAHa1AITwc8oPwMOWhgorPzzcbhWEb+4oLi953h45FklDmM8dPSZ7hNHpIk9p/SCZKUYn35t5fjGtHA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-linux-x64-musl@4.1.5':
resolution: {integrity: sha512-6UbBBplywkk/R+PqqioskUeXfKcBht3KU7juTi1UszJLx0KPXUo10v2Ok04iBJIaDPkIFkUOVboXms5Yxvaz+g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-wasm32-wasi@4.1.5':
resolution: {integrity: sha512-hwALf2K9FHuiXTPqmo1KeOb83fTRNbe9r/Ixv9ZNQ/R24yw8Ge1HOWDDgTdtzntIaIUJG5dfXCf4g9AD4RiyhQ==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.1.5':
resolution: {integrity: sha512-oDKncffWzaovJbkuR7/OTNFRJQVdiw/n8HnzaCItrNQUeQgjy7oUiYpsm9HUBgpmvmDpSSbGaCa2Evzvk3eFmA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.1.5':
resolution: {integrity: sha512-WiR4dtyrFdbb+ov0LK+7XsFOsG+0xs0PKZKkt41KDn9jYpO7baE3bXiudPVkTqUEwNfiglCygQHl2jklvSBi7Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.1.5':
resolution: {integrity: sha512-1n4br1znquEvyW/QuqMKQZlBen+jxAbvyduU87RS8R3tUSvByAkcaMTkJepNIrTlYhD+U25K4iiCIxE6BGdRYA==}
engines: {node: '>= 10'}
'@tailwindcss/vite@4.1.5':
resolution: {integrity: sha512-FE1stRoqdHSb7RxesMfCXE8icwI1W6zGE/512ae3ZDrpkQYTTYeSyUJPRCjZd8CwVAhpDUbi1YR8pcZioFJQ/w==}
peerDependencies:
vite: ^5.2.0 || ^6
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
@ -585,6 +695,9 @@ packages:
engines: {node: '>=4'}
hasBin: true
daisyui@5.0.35:
resolution: {integrity: sha512-AWi11n/x5++mps55jcwrBf0Lmip1euWY0FYcH/05SFGmoqrU7S7/aIUWaiaeqlJ5EcmEZ/7zEY73aOxMv6hcIg==}
debug@4.4.0:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
@ -601,9 +714,17 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
devalue@5.1.1:
resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==}
enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
esbuild@0.25.3:
resolution: {integrity: sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==}
engines: {node: '>=18'}
@ -741,6 +862,9 @@ packages:
resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==}
engines: {node: '>=18'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
@ -781,6 +905,10 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
@ -808,6 +936,70 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lightningcss-darwin-arm64@1.29.2:
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.29.2:
resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.29.2:
resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.29.2:
resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.29.2:
resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-arm64-musl@1.29.2:
resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-x64-gnu@1.29.2:
resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-linux-x64-musl@1.29.2:
resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-win32-arm64-msvc@1.29.2:
resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.29.2:
resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.29.2:
resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
engines: {node: '>= 12.0.0'}
lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
@ -1025,6 +1217,13 @@ packages:
resolution: {integrity: sha512-FbWBxgWOpQfhKvoGJv/TFwzqb4EhJbwCD17dB0tEpQiw1XyUEKZJtgm4nA4xq3LLsMo7hu5UY/BOFmroAxKTMg==}
engines: {node: '>=18'}
tailwindcss@4.1.5:
resolution: {integrity: sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==}
tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
tinyglobby@0.2.13:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
engines: {node: '>=12.0.0'}
@ -1215,16 +1414,16 @@ snapshots:
'@esbuild/win32-x64@0.25.3':
optional: true
'@eslint-community/eslint-utils@4.6.1(eslint@9.25.1)':
'@eslint-community/eslint-utils@4.6.1(eslint@9.25.1(jiti@2.4.2))':
dependencies:
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
'@eslint/compat@1.2.8(eslint@9.25.1)':
'@eslint/compat@1.2.8(eslint@9.25.1(jiti@2.4.2))':
optionalDependencies:
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
'@eslint/config-array@0.20.0':
dependencies:
@ -1293,6 +1492,10 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@lucide/svelte@0.507.0(svelte@5.28.2)':
dependencies:
svelte: 5.28.2
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@ -1371,13 +1574,13 @@ snapshots:
dependencies:
acorn: 8.14.1
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3))(svelte@5.28.2)(vite@6.3.3))':
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))':
dependencies:
'@sveltejs/kit': 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3))(svelte@5.28.2)(vite@6.3.3)
'@sveltejs/kit': 2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
'@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3))(svelte@5.28.2)(vite@6.3.3)':
'@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))':
dependencies:
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.28.2)(vite@6.3.3)
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
'@types/cookie': 0.6.0
cookie: 0.6.0
devalue: 5.1.1
@ -1390,45 +1593,110 @@ snapshots:
set-cookie-parser: 2.7.1
sirv: 3.0.1
svelte: 5.28.2
vite: 6.3.3
vite: 6.3.3(jiti@2.4.2)(lightningcss@1.29.2)
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3))(svelte@5.28.2)(vite@6.3.3)':
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))':
dependencies:
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.28.2)(vite@6.3.3)
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
debug: 4.4.0
svelte: 5.28.2
vite: 6.3.3
vite: 6.3.3(jiti@2.4.2)(lightningcss@1.29.2)
transitivePeerDependencies:
- supports-color
'@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3)':
'@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))':
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3))(svelte@5.28.2)(vite@6.3.3)
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.28.2)(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
debug: 4.4.0
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.17
svelte: 5.28.2
vite: 6.3.3
vitefu: 1.0.6(vite@6.3.3)
vite: 6.3.3(jiti@2.4.2)(lightningcss@1.29.2)
vitefu: 1.0.6(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))
transitivePeerDependencies:
- supports-color
'@tailwindcss/node@4.1.5':
dependencies:
enhanced-resolve: 5.18.1
jiti: 2.4.2
lightningcss: 1.29.2
tailwindcss: 4.1.5
'@tailwindcss/oxide-android-arm64@4.1.5':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.1.5':
optional: true
'@tailwindcss/oxide-darwin-x64@4.1.5':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.1.5':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.1.5':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.1.5':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.1.5':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.1.5':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.1.5':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.1.5':
optional: true
'@tailwindcss/oxide@4.1.5':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.1.5
'@tailwindcss/oxide-darwin-arm64': 4.1.5
'@tailwindcss/oxide-darwin-x64': 4.1.5
'@tailwindcss/oxide-freebsd-x64': 4.1.5
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.5
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.5
'@tailwindcss/oxide-linux-arm64-musl': 4.1.5
'@tailwindcss/oxide-linux-x64-gnu': 4.1.5
'@tailwindcss/oxide-linux-x64-musl': 4.1.5
'@tailwindcss/oxide-wasm32-wasi': 4.1.5
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.5
'@tailwindcss/oxide-win32-x64-msvc': 4.1.5
'@tailwindcss/vite@4.1.5(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2))':
dependencies:
'@tailwindcss/node': 4.1.5
'@tailwindcss/oxide': 4.1.5
tailwindcss: 4.1.5
vite: 6.3.3(jiti@2.4.2)(lightningcss@1.29.2)
'@types/cookie@0.6.0': {}
'@types/estree@1.0.7': {}
'@types/json-schema@7.0.15': {}
'@typescript-eslint/eslint-plugin@8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.25.1)(typescript@5.8.3))(eslint@9.25.1)(typescript@5.8.3)':
'@typescript-eslint/eslint-plugin@8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.31.1(eslint@9.25.1)(typescript@5.8.3)
'@typescript-eslint/parser': 8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.31.1
'@typescript-eslint/type-utils': 8.31.1(eslint@9.25.1)(typescript@5.8.3)
'@typescript-eslint/utils': 8.31.1(eslint@9.25.1)(typescript@5.8.3)
'@typescript-eslint/type-utils': 8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/utils': 8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.31.1
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
@ -1437,14 +1705,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.31.1(eslint@9.25.1)(typescript@5.8.3)':
'@typescript-eslint/parser@8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.31.1
'@typescript-eslint/types': 8.31.1
'@typescript-eslint/typescript-estree': 8.31.1(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.31.1
debug: 4.4.0
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@ -1454,12 +1722,12 @@ snapshots:
'@typescript-eslint/types': 8.31.1
'@typescript-eslint/visitor-keys': 8.31.1
'@typescript-eslint/type-utils@8.31.1(eslint@9.25.1)(typescript@5.8.3)':
'@typescript-eslint/type-utils@8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/typescript-estree': 8.31.1(typescript@5.8.3)
'@typescript-eslint/utils': 8.31.1(eslint@9.25.1)(typescript@5.8.3)
'@typescript-eslint/utils': 8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
debug: 4.4.0
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
@ -1481,13 +1749,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.31.1(eslint@9.25.1)(typescript@5.8.3)':
'@typescript-eslint/utils@8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1)
'@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1(jiti@2.4.2))
'@typescript-eslint/scope-manager': 8.31.1
'@typescript-eslint/types': 8.31.1
'@typescript-eslint/typescript-estree': 8.31.1(typescript@5.8.3)
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@ -1566,6 +1834,8 @@ snapshots:
cssesc@3.0.0: {}
daisyui@5.0.35: {}
debug@4.4.0:
dependencies:
ms: 2.1.3
@ -1574,8 +1844,15 @@ snapshots:
deepmerge@4.3.1: {}
detect-libc@2.0.4: {}
devalue@5.1.1: {}
enhanced-resolve@5.18.1:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.1
esbuild@0.25.3:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.3
@ -1606,15 +1883,15 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-prettier@10.1.2(eslint@9.25.1):
eslint-config-prettier@10.1.2(eslint@9.25.1(jiti@2.4.2)):
dependencies:
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
eslint-plugin-svelte@3.5.1(eslint@9.25.1)(svelte@5.28.2):
eslint-plugin-svelte@3.5.1(eslint@9.25.1(jiti@2.4.2))(svelte@5.28.2):
dependencies:
'@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1)
'@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1(jiti@2.4.2))
'@jridgewell/sourcemap-codec': 1.5.0
eslint: 9.25.1
eslint: 9.25.1(jiti@2.4.2)
esutils: 2.0.3
known-css-properties: 0.35.0
postcss: 8.5.3
@ -1636,9 +1913,9 @@ snapshots:
eslint-visitor-keys@4.2.0: {}
eslint@9.25.1:
eslint@9.25.1(jiti@2.4.2):
dependencies:
'@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1)
'@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1(jiti@2.4.2))
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.20.0
'@eslint/config-helpers': 0.2.1
@ -1673,6 +1950,8 @@ snapshots:
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
jiti: 2.4.2
transitivePeerDependencies:
- supports-color
@ -1757,6 +2036,8 @@ snapshots:
globals@16.0.0: {}
graceful-fs@4.2.11: {}
graphemer@1.4.0: {}
has-flag@4.0.0: {}
@ -1786,6 +2067,8 @@ snapshots:
isexe@2.0.0: {}
jiti@2.4.2: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
@ -1809,6 +2092,51 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lightningcss-darwin-arm64@1.29.2:
optional: true
lightningcss-darwin-x64@1.29.2:
optional: true
lightningcss-freebsd-x64@1.29.2:
optional: true
lightningcss-linux-arm-gnueabihf@1.29.2:
optional: true
lightningcss-linux-arm64-gnu@1.29.2:
optional: true
lightningcss-linux-arm64-musl@1.29.2:
optional: true
lightningcss-linux-x64-gnu@1.29.2:
optional: true
lightningcss-linux-x64-musl@1.29.2:
optional: true
lightningcss-win32-arm64-msvc@1.29.2:
optional: true
lightningcss-win32-x64-msvc@1.29.2:
optional: true
lightningcss@1.29.2:
dependencies:
detect-libc: 2.0.4
optionalDependencies:
lightningcss-darwin-arm64: 1.29.2
lightningcss-darwin-x64: 1.29.2
lightningcss-freebsd-x64: 1.29.2
lightningcss-linux-arm-gnueabihf: 1.29.2
lightningcss-linux-arm64-gnu: 1.29.2
lightningcss-linux-arm64-musl: 1.29.2
lightningcss-linux-x64-gnu: 1.29.2
lightningcss-linux-x64-musl: 1.29.2
lightningcss-win32-arm64-msvc: 1.29.2
lightningcss-win32-x64-msvc: 1.29.2
lilconfig@2.1.0: {}
locate-character@3.0.0: {}
@ -2022,6 +2350,10 @@ snapshots:
magic-string: 0.30.17
zimmerframe: 1.1.2
tailwindcss@4.1.5: {}
tapable@2.2.1: {}
tinyglobby@0.2.13:
dependencies:
fdir: 6.4.4(picomatch@4.0.2)
@ -2041,12 +2373,12 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
typescript-eslint@8.31.1(eslint@9.25.1)(typescript@5.8.3):
typescript-eslint@8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.25.1)(typescript@5.8.3))(eslint@9.25.1)(typescript@5.8.3)
'@typescript-eslint/parser': 8.31.1(eslint@9.25.1)(typescript@5.8.3)
'@typescript-eslint/utils': 8.31.1(eslint@9.25.1)(typescript@5.8.3)
eslint: 9.25.1
'@typescript-eslint/eslint-plugin': 8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/parser': 8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/utils': 8.31.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.25.1(jiti@2.4.2)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@ -2059,7 +2391,7 @@ snapshots:
util-deprecate@1.0.2: {}
vite@6.3.3:
vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2):
dependencies:
esbuild: 0.25.3
fdir: 6.4.4(picomatch@4.0.2)
@ -2069,10 +2401,12 @@ snapshots:
tinyglobby: 0.2.13
optionalDependencies:
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.29.2
vitefu@1.0.6(vite@6.3.3):
vitefu@1.0.6(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)):
optionalDependencies:
vite: 6.3.3
vite: 6.3.3(jiti@2.4.2)(lightningcss@1.29.2)
which@2.0.2:
dependencies: