Compare commits
No commits in common. "019ec6cbaa4b884fe9d694f3a4db69406bf561cc" and "26b6c63caf3be1b2b50ae35dd2d81060c8cbd7a7" have entirely different histories.
019ec6cbaa
...
26b6c63caf
|
@ -1,3 +1 @@
|
||||||
CLIENT_ID=<spotifyAPIID>
|
DATABASE_URL=local.db
|
||||||
CLIENT_SECRET=<spotifyAPISecret>
|
|
||||||
SESH_SECRET=<longRandomString>
|
|
||||||
|
|
14
drizzle.config.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: './src/lib/server/db/schema.ts',
|
||||||
|
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.DATABASE_URL
|
||||||
|
},
|
||||||
|
|
||||||
|
verbose: true,
|
||||||
|
strict: true,
|
||||||
|
dialect: 'sqlite'
|
||||||
|
});
|
13
package.json
|
@ -10,7 +10,10 @@
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "prettier --check . && eslint ."
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"db:push": "drizzle-kit push",
|
||||||
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"db:studio": "drizzle-kit studio"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^1.2.5",
|
"@eslint/compat": "^1.2.5",
|
||||||
|
@ -20,15 +23,16 @@
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
|
"@types/better-sqlite3": "^7.6.12",
|
||||||
"@types/spotify-web-api-node": "^5.0.11",
|
"@types/spotify-web-api-node": "^5.0.11",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bits-ui": "1.0.0-next.78",
|
"bits-ui": "1.0.0-next.78",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"drizzle-kit": "^0.30.2",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^2.46.1",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"lucide-svelte": "^0.473.0",
|
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.10",
|
"prettier-plugin-tailwindcss": "^0.6.10",
|
||||||
|
@ -44,9 +48,8 @@
|
||||||
"vite": "^5.4.11"
|
"vite": "^5.4.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/kode-mono": "^5.1.1",
|
"better-sqlite3": "^11.8.0",
|
||||||
"@fontsource-variable/smooch-sans": "^5.1.1",
|
"drizzle-orm": "^0.38.4",
|
||||||
"mode-watcher": "^0.5.0",
|
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
"spotify-web-api-node": "^5.0.2",
|
"spotify-web-api-node": "^5.0.2",
|
||||||
"svelte-kit-sessions": "^0.4.0"
|
"svelte-kit-sessions": "^0.4.0"
|
||||||
|
|
930
pnpm-lock.yaml
generated
145
src/app.css
|
@ -3,86 +3,73 @@
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 20 14.3% 4.1%;
|
--foreground: 20 14.3% 4.1%;
|
||||||
--muted: 60 4.8% 95.9%;
|
--muted: 60 4.8% 95.9%;
|
||||||
--muted-foreground: 25 5.3% 44.7%;
|
--muted-foreground: 25 5.3% 44.7%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 20 14.3% 4.1%;
|
--popover-foreground: 20 14.3% 4.1%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 20 14.3% 4.1%;
|
--card-foreground: 20 14.3% 4.1%;
|
||||||
--border: 20 5.9% 90%;
|
--border: 20 5.9% 90%;
|
||||||
--input: 20 5.9% 90%;
|
--input: 20 5.9% 90%;
|
||||||
--primary: 24 9.8% 10%;
|
--primary: 24 9.8% 10%;
|
||||||
--primary-foreground: 60 9.1% 94%;
|
--primary-foreground: 60 9.1% 97.8%;
|
||||||
--secondary: 60 4.8% 95.9%;
|
--secondary: 60 4.8% 95.9%;
|
||||||
--secondary-foreground: 24 9.8% 10%;
|
--secondary-foreground: 24 9.8% 10%;
|
||||||
--accent: 60 4.8% 95.9%;
|
--accent: 60 4.8% 95.9%;
|
||||||
--accent-foreground: 24 9.8% 10%;
|
--accent-foreground: 24 9.8% 10%;
|
||||||
--destructive: 0 72.2% 50.6%;
|
--destructive: 0 72.2% 50.6%;
|
||||||
--destructive-foreground: 60 9.1% 94%;
|
--destructive-foreground: 60 9.1% 97.8%;
|
||||||
--ring: 20 14.3% 4.1%;
|
--ring: 20 14.3% 4.1%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--sidebar-background: 0 0% 98%;
|
--sidebar-background: 0 0% 98%;
|
||||||
--sidebar-foreground: 240 5.3% 26.1%;
|
--sidebar-foreground: 240 5.3% 26.1%;
|
||||||
--sidebar-primary: 240 5.9% 10%;
|
--sidebar-primary: 240 5.9% 10%;
|
||||||
--sidebar-primary-foreground: 0 0% 98%;
|
--sidebar-primary-foreground: 0 0% 98%;
|
||||||
--sidebar-accent: 240 4.8% 95.9%;
|
--sidebar-accent: 240 4.8% 95.9%;
|
||||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||||
--sidebar-border: 220 13% 91%;
|
--sidebar-border: 220 13% 91%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 20 14.3% 4.1%;
|
--background: 20 14.3% 4.1%;
|
||||||
--foreground: 60 9.1% 94%;
|
--foreground: 60 9.1% 97.8%;
|
||||||
--muted: 12 6.5% 15.1%;
|
--muted: 12 6.5% 15.1%;
|
||||||
--muted-foreground: 24 5.4% 63.9%;
|
--muted-foreground: 24 5.4% 63.9%;
|
||||||
--popover: 20 14.3% 4.1%;
|
--popover: 20 14.3% 4.1%;
|
||||||
--popover-foreground: 60 9.1% 94%;
|
--popover-foreground: 60 9.1% 97.8%;
|
||||||
--card: 20 14.3% 4.1%;
|
--card: 20 14.3% 4.1%;
|
||||||
--card-foreground: 60 9.1% 94%;
|
--card-foreground: 60 9.1% 97.8%;
|
||||||
--border: 12 6.5% 15.1%;
|
--border: 12 6.5% 15.1%;
|
||||||
--input: 12 6.5% 15.1%;
|
--input: 12 6.5% 15.1%;
|
||||||
--primary: 60 9.1% 94%;
|
--primary: 60 9.1% 97.8%;
|
||||||
--primary-foreground: 24 9.8% 10%;
|
--primary-foreground: 24 9.8% 10%;
|
||||||
--secondary: 12 6.5% 15.1%;
|
--secondary: 12 6.5% 15.1%;
|
||||||
--secondary-foreground: 60 9.1% 94%;
|
--secondary-foreground: 60 9.1% 97.8%;
|
||||||
--accent: 12 6.5% 15.1%;
|
--accent: 12 6.5% 15.1%;
|
||||||
--accent-foreground: 60 9.1% 94%;
|
--accent-foreground: 60 9.1% 97.8%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 60 9.1% 94%;
|
--destructive-foreground: 60 9.1% 97.8%;
|
||||||
--ring: 24 5.7% 82.9%;
|
--ring: 24 5.7% 82.9%;
|
||||||
--sidebar-background: 240 5.9% 10%;
|
--sidebar-background: 240 5.9% 10%;
|
||||||
--sidebar-foreground: 240 4.8% 95.9%;
|
--sidebar-foreground: 240 4.8% 95.9%;
|
||||||
--sidebar-primary: 224.3 76.3% 48%;
|
--sidebar-primary: 224.3 76.3% 48%;
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
--sidebar-primary-foreground: 0 0% 100%;
|
||||||
--sidebar-accent: 240 3.7% 15.9%;
|
--sidebar-accent: 240 3.7% 15.9%;
|
||||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||||
--sidebar-border: 240 3.7% 15.9%;
|
--sidebar-border: 240 3.7% 15.9%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
|
||||||
.shadow-sharp {
|
|
||||||
box-shadow: hsl(var(--primary)) 4px 3px 0px;
|
|
||||||
}
|
|
||||||
button.shadow-sharp,
|
|
||||||
a.shadow-sharp {
|
|
||||||
&:hover {
|
|
||||||
box-shadow: hsl(var(--primary)) 2px 1px 0px;
|
|
||||||
translate: 2px 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,13 +2,8 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
||||||
<meta name="apple-mobile-web-app-title" content="Kasterpalu" />
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
|
|
@ -22,21 +22,21 @@
|
||||||
items,
|
items,
|
||||||
flipDurationMs,
|
flipDurationMs,
|
||||||
type: type,
|
type: type,
|
||||||
dropTargetStyle: {}
|
dropTargetStyle: { outline: 'none' }
|
||||||
}}
|
}}
|
||||||
onconsider={handleDndConsider}
|
onconsider={handleDndConsider}
|
||||||
onfinalize={handleDndFinalize}
|
onfinalize={handleDndFinalize}
|
||||||
class="grid grid-cols-3 items-center gap-4 sm:gap-8 md:gap-10 lg:gap-14"
|
class="grid grid-cols-3 items-center gap-16"
|
||||||
>
|
>
|
||||||
{#each items as item, i (item.id)}
|
{#each items as item, i (item.id)}
|
||||||
<div animate:flip={{ duration: flipDurationMs, easing: expoOut }}>
|
<div animate:flip={{ duration: flipDurationMs, easing: expoOut }}>
|
||||||
<Card.Root
|
<Card.Root
|
||||||
class="select-none overflow-hidden rounded-xl border bg-card text-card-foreground shadow shadow-foreground/15 transition-shadow hover:shadow-foreground/25 {type ===
|
class="overflow-hidden rounded-xl border bg-card text-card-foreground shadow {type ===
|
||||||
'names'
|
'names'
|
||||||
? 'border-red-400 '
|
? 'border-orange-300'
|
||||||
: type === 'artists'
|
: type === 'artists'
|
||||||
? 'border-purple-400 '
|
? 'border-cyan-300'
|
||||||
: 'border-blue-400'}"
|
: ''}"
|
||||||
>
|
>
|
||||||
{#if image}
|
{#if image}
|
||||||
<Card.Content class="p-0">
|
<Card.Content class="p-0">
|
||||||
|
@ -45,13 +45,7 @@
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
{:else}
|
{:else}
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<p
|
<p class="text-center">
|
||||||
class="text-center {type === 'names'
|
|
||||||
? 'text-red-900 dark:text-red-200'
|
|
||||||
: type === 'artists'
|
|
||||||
? 'text-purple-900 dark:text-purple-200'
|
|
||||||
: ''}"
|
|
||||||
>
|
|
||||||
{#if type === 'artists'}
|
{#if type === 'artists'}
|
||||||
{truncate(item.value, 30)}
|
{truncate(item.value, 30)}
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import Root from "./separator.svelte";
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
//
|
|
||||||
Root as Separator,
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { Separator as SeparatorPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
orientation = "horizontal",
|
|
||||||
...restProps
|
|
||||||
}: SeparatorPrimitive.RootProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<SeparatorPrimitive.Root
|
|
||||||
bind:ref
|
|
||||||
class={cn(
|
|
||||||
"bg-border shrink-0",
|
|
||||||
orientation === "horizontal" ? "h-[1px] w-full" : "min-h-full w-[1px]",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{orientation}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Player } from '$lib/types';
|
import type { Player } from '$lib/types';
|
||||||
|
import { getContext, setContext } from 'svelte';
|
||||||
|
|
||||||
export class PlayerState {
|
export class PlayerState {
|
||||||
players = $state<Player[]>([]);
|
players = $state<Player[]>([]);
|
||||||
|
|
|
@ -17,6 +17,7 @@ class SpotifyAPI {
|
||||||
|
|
||||||
return await this.api.clientCredentialsGrant().then(
|
return await this.api.clientCredentialsGrant().then(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
console.log(data.body);
|
||||||
if (!data.body['expires_in'] || !data.body['access_token']) {
|
if (!data.body['expires_in'] || !data.body['access_token']) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
6
src/lib/server/db/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||||
|
const client = new Database(env.DATABASE_URL);
|
||||||
|
export const db = drizzle(client);
|
6
src/lib/server/db/schema.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||||
|
|
||||||
|
export const user = sqliteTable('user', {
|
||||||
|
id: integer('id').primaryKey(),
|
||||||
|
age: integer('age')
|
||||||
|
});
|
|
@ -5,7 +5,6 @@ export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://perryjanssen.medium.com/getting-random-tracks-using-the-spotify-api-61889b0c0c27
|
|
||||||
export function getRandomSearch() {
|
export function getRandomSearch() {
|
||||||
// A list of all characters that can be chosen.
|
// A list of all characters that can be chosen.
|
||||||
const characters = 'abcdefghijklmnopqrstuvwxyz';
|
const characters = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
@ -27,7 +26,6 @@ export function getRandomSearch() {
|
||||||
return randomSearch;
|
return randomSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created using Claude 3.5 Sonett
|
|
||||||
export function shuffleObjectValues<T extends object>(arr: Array<T>): Array<T> {
|
export function shuffleObjectValues<T extends object>(arr: Array<T>): Array<T> {
|
||||||
// Create a copy of the array
|
// Create a copy of the array
|
||||||
const copy = structuredClone(arr);
|
const copy = structuredClone(arr);
|
||||||
|
@ -54,7 +52,6 @@ export function shuffleObjectValues<T extends object>(arr: Array<T>): Array<T> {
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/12646864
|
|
||||||
export function shuffleArray<T>(array: T[]): T[] {
|
export function shuffleArray<T>(array: T[]): T[] {
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
for (let i = array.length - 1; i >= 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
@ -64,7 +61,6 @@ export function shuffleArray<T>(array: T[]): T[] {
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created using Claude 3.5 Sonett
|
|
||||||
export function truncate(text: string, maxLength: number): string {
|
export function truncate(text: string, maxLength: number): string {
|
||||||
// Return original text if it's shorter than or equal to maxLength
|
// Return original text if it's shorter than or equal to maxLength
|
||||||
if (text.length <= maxLength) {
|
if (text.length <= maxLength) {
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
let { children } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container mt-32 flex flex-col items-center py-4">
|
|
||||||
{@render children()}
|
|
||||||
</div>
|
|
|
@ -1,30 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
const games = [
|
|
||||||
{
|
|
||||||
name: 'Paku biiti',
|
|
||||||
image: '',
|
|
||||||
href: 'pakubiiti'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<header class="mb-24 flex flex-col items-center font-title">
|
|
||||||
<h1 class="mb-1 scroll-m-20 text-6xl font-extrabold tracking-tight lg:text-7xl">
|
|
||||||
stuff.kasterpalu.ee
|
|
||||||
</h1>
|
|
||||||
<p class="text-2xl font-semibold text-muted-foreground">Minimängud ja muud huvitavat</p>
|
|
||||||
</header>
|
|
||||||
<main class="grid w-full max-w-4xl justify-items-center">
|
|
||||||
{#each games as { name, image, href }}
|
|
||||||
<a
|
|
||||||
class="shadow-sharp flex aspect-[4/1] w-full max-w-sm items-center justify-center rounded-xl border-2 border-current bg-contain bg-no-repeat transition-shadow transition-transform"
|
|
||||||
style="background-image: url('{image}')"
|
|
||||||
draggable="false"
|
|
||||||
{href}
|
|
||||||
>
|
|
||||||
<span class="relative block select-none rounded font-mono text-2xl font-semibold lg:text-3xl">
|
|
||||||
{name}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
</main>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
let { children } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container flex min-h-screen flex-col items-center justify-center py-4">
|
|
||||||
{@render children()}
|
|
||||||
</div>
|
|
|
@ -1,125 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { Separator } from '$lib/components/ui/separator/index.js';
|
|
||||||
import LoaderCircle from 'lucide-svelte/icons/loader-circle';
|
|
||||||
import { Button } from '$lib/components/ui/button/index.js';
|
|
||||||
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
|
|
||||||
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
|
|
||||||
import DndGroup from '$lib/components/DNDGroup.svelte';
|
|
||||||
import type { PageData } from './$types';
|
|
||||||
import { enhance } from '$app/forms';
|
|
||||||
|
|
||||||
let { data, form }: { data: PageData; form: FormData } = $props();
|
|
||||||
|
|
||||||
let loading = $state(true);
|
|
||||||
let oldAlbums: SpotifyApi.AlbumObjectSimplified[] = $state([]);
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
// this is a hack to disable grayscale, please ignore
|
|
||||||
// eslint-disable-next-line no-constant-binary-expression
|
|
||||||
loading = false && form?.success;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Used when user answers wrong and no new data comes in
|
|
||||||
$effect(() => {
|
|
||||||
if (data.streamed?.albums) {
|
|
||||||
data.streamed.albums.then((data) => {
|
|
||||||
oldAlbums = data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#snippet footer(loading: boolean)}
|
|
||||||
<div class="mt-8 flex items-center justify-evenly">
|
|
||||||
<p class="font-title text-lg font-semibold">Skoor: {data.stage}</p>
|
|
||||||
{#if loading}
|
|
||||||
<Button disabled class="min-w-[4.5rem]">
|
|
||||||
<LoaderCircle class="animate-spin" />
|
|
||||||
Oota
|
|
||||||
</Button>
|
|
||||||
{:else}
|
|
||||||
<Button type="submit" class="min-w-[4.5rem]">Saada</Button>
|
|
||||||
{/if}
|
|
||||||
<p class="font-title text-lg font-semibold">Parim: {data.highscore}</p>
|
|
||||||
</div>
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
<AlertDialog.Root open={form?.solved === false}>
|
|
||||||
<AlertDialog.Content>
|
|
||||||
<AlertDialog.Header>
|
|
||||||
<AlertDialog.Title>
|
|
||||||
{#if data?.highscore && data?.stage && data.highscore === data.stage}
|
|
||||||
Uus parim tulemus!
|
|
||||||
{:else}
|
|
||||||
Seekord ei vedanud
|
|
||||||
{/if}
|
|
||||||
</AlertDialog.Title>
|
|
||||||
<AlertDialog.Description>
|
|
||||||
{#if data.stage === 0}
|
|
||||||
Põrusid esimesel katsel.
|
|
||||||
{:else}
|
|
||||||
Vastasid õigesti <strong>{data.stage} korda.</strong>
|
|
||||||
{/if}
|
|
||||||
</AlertDialog.Description>
|
|
||||||
</AlertDialog.Header>
|
|
||||||
<AlertDialog.Footer>
|
|
||||||
<form action="?/restart" method="POST" use:enhance>
|
|
||||||
<AlertDialog.Action type="submit">Uuesti</AlertDialog.Action>
|
|
||||||
</form>
|
|
||||||
</AlertDialog.Footer>
|
|
||||||
</AlertDialog.Content>
|
|
||||||
</AlertDialog.Root>
|
|
||||||
|
|
||||||
<header class="mb-24 flex flex-col items-center font-title">
|
|
||||||
<h1 class="mb-1 scroll-m-20 text-6xl font-extrabold tracking-tight lg:text-7xl">Paku biiti</h1>
|
|
||||||
<p class="text-2xl font-semibold text-muted-foreground">
|
|
||||||
Lohista kokku õiged albumi <span class="text-red-600 dark:text-red-400">nimed</span>,
|
|
||||||
<span class="text-purple-600 dark:text-purple-400">artistid</span> ja
|
|
||||||
<span class="text-blue-600 dark:text-blue-400">pildid</span>.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
<main class="w-full max-w-4xl">
|
|
||||||
<form
|
|
||||||
action="?/submit"
|
|
||||||
method="POST"
|
|
||||||
use:enhance
|
|
||||||
class="grid w-full gap-6 transition-all {loading || data?.playing === false ? 'grayscale' : ''}"
|
|
||||||
>
|
|
||||||
{#if data?.streamed?.albums}
|
|
||||||
{#await data.streamed.albums}
|
|
||||||
{#each { length: 2 } as _}
|
|
||||||
<section class="grid grid-cols-3 items-center gap-14">
|
|
||||||
{#each { length: 3 } as _}
|
|
||||||
<Skeleton class="h-[5.25rem] w-full rounded-xl " />
|
|
||||||
{/each}
|
|
||||||
</section>
|
|
||||||
<Separator />
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<section class="grid grid-cols-3 items-center gap-14">
|
|
||||||
{#each { length: 3 } as _}
|
|
||||||
<Skeleton class="aspect-square h-auto max-w-full rounded-xl object-cover" />
|
|
||||||
{/each}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{@render footer(true)}
|
|
||||||
{:then albums}
|
|
||||||
<DndGroup items={albums.names} type="names"></DndGroup>
|
|
||||||
<Separator />
|
|
||||||
<DndGroup items={albums.artists} type="artists"></DndGroup>
|
|
||||||
<Separator />
|
|
||||||
<DndGroup items={albums.images} image type="images"></DndGroup>
|
|
||||||
|
|
||||||
{@render footer(false)}
|
|
||||||
{/await}
|
|
||||||
{:else}
|
|
||||||
<DndGroup items={oldAlbums.names} type="names"></DndGroup>
|
|
||||||
<Separator />
|
|
||||||
<DndGroup items={oldAlbums.artists} type="artists"></DndGroup>
|
|
||||||
<Separator />
|
|
||||||
<DndGroup items={oldAlbums.images} image type="images"></DndGroup>
|
|
||||||
|
|
||||||
{@render footer(false)}
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
||||||
</main>
|
|
|
@ -1,50 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import '@fontsource-variable/smooch-sans';
|
|
||||||
import '@fontsource-variable/kode-mono';
|
|
||||||
|
|
||||||
import { ModeWatcher, resetMode, setMode } from 'mode-watcher';
|
|
||||||
import { Button } from '$lib/components/ui/button/index.js';
|
|
||||||
|
|
||||||
import Sun from 'lucide-svelte/icons/sun';
|
|
||||||
import Moon from 'lucide-svelte/icons/moon';
|
|
||||||
import LaptopMinimal from 'lucide-svelte/icons/laptop-minimal';
|
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
let theme: string = $state('system');
|
|
||||||
|
|
||||||
const cycleTheme = () => {
|
|
||||||
if (theme === 'dark') {
|
|
||||||
theme = 'light';
|
|
||||||
setMode('light');
|
|
||||||
} else if (theme === 'light') {
|
|
||||||
theme = 'system';
|
|
||||||
resetMode();
|
|
||||||
} else {
|
|
||||||
theme = 'dark';
|
|
||||||
setMode('dark');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModeWatcher />
|
<div class="flex min-h-screen items-center">
|
||||||
|
{@render children()}
|
||||||
<header class="container absolute top-0 flex justify-between py-6">
|
</div>
|
||||||
<a href="/">
|
|
||||||
<img src="/favicon.svg" alt="Mihkel Martin Kasterpalu logo" class="h-10" />
|
|
||||||
</a>
|
|
||||||
<Button onclick={() => cycleTheme()} variant="outline" size="icon">
|
|
||||||
{#if theme === 'dark'}
|
|
||||||
<Moon class="absolute h-[1.2rem] w-[1.2rem]" />
|
|
||||||
{:else if theme === 'light'}
|
|
||||||
<Sun class="h-[1.2rem] w-[1.2rem] " />
|
|
||||||
{:else}
|
|
||||||
<LaptopMinimal class="h-[1.2rem] w-[1.2rem]" />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<span class="sr-only">Toggle theme</span>
|
|
||||||
</Button>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{@render children()}
|
|
||||||
|
|
|
@ -33,20 +33,17 @@ export const load: PageServerLoad = async ({ fetch, locals }) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const albumData = fetch(`/api/pakubiiti/getAlbums/${count}`)
|
const albumData = fetch(`/api/getAlbums/${count}`)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
const albumNames = data.albums.map((album: SpotifyApi.AlbumObjectSimplified) => ({
|
const albumNames = data.albums.map((album) => ({ id: nanoid(), value: album.name }));
|
||||||
|
const albumImages = data.albums.map((album) => ({
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
value: album.name
|
value: album.images.at(0).url
|
||||||
}));
|
}));
|
||||||
const albumImages = data.albums.map((album: SpotifyApi.AlbumObjectSimplified) => ({
|
const albumArtists = data.albums.map((album) => ({
|
||||||
id: nanoid(),
|
|
||||||
value: album.images.at(0)?.url || ''
|
|
||||||
}));
|
|
||||||
const albumArtists = data.albums.map((album: SpotifyApi.AlbumObjectSimplified) => ({
|
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
value: album.artists.map((artist) => artist.name).join(', ')
|
value: album.artists.map((artist) => artist.name).join(', ')
|
||||||
}));
|
}));
|
93
src/routes/+page.svelte
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
|
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
|
||||||
|
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
|
||||||
|
import DndGroup from '$lib/components/DNDGroup.svelte';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
|
||||||
|
let { data, form }: { data: PageData; form: FormData } = $props();
|
||||||
|
|
||||||
|
let loading = $state(true);
|
||||||
|
|
||||||
|
let names: string[] | undefined = $state();
|
||||||
|
let artists: string[] | undefined = $state();
|
||||||
|
let images: string[] | undefined = $state();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
loading = false;
|
||||||
|
console.log(form);
|
||||||
|
});
|
||||||
|
|
||||||
|
$inspect(data);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialog.Root open={form?.solved === false}>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Header>
|
||||||
|
<AlertDialog.Title>
|
||||||
|
{#if data?.highscore && data?.stage && data.highscore === data.stage}
|
||||||
|
New high score!
|
||||||
|
{:else}
|
||||||
|
Maybe next time
|
||||||
|
{/if}
|
||||||
|
</AlertDialog.Title>
|
||||||
|
<AlertDialog.Description>
|
||||||
|
{#if data.stage === 0}
|
||||||
|
That's tough. <strong>0 right answers.</strong>
|
||||||
|
{:else}
|
||||||
|
You got it right <strong>{data.stage} times.</strong>
|
||||||
|
{/if}
|
||||||
|
</AlertDialog.Description>
|
||||||
|
</AlertDialog.Header>
|
||||||
|
<AlertDialog.Footer>
|
||||||
|
<form action="?/restart" method="POST" use:enhance>
|
||||||
|
<AlertDialog.Action type="submit">Try again</AlertDialog.Action>
|
||||||
|
</form>
|
||||||
|
</AlertDialog.Footer>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog.Root>
|
||||||
|
|
||||||
|
<form
|
||||||
|
action="?/submit"
|
||||||
|
method="POST"
|
||||||
|
use:enhance
|
||||||
|
class="mx-auto grid w-full max-w-3xl gap-8 px-8 transition-all {loading || data?.playing === false
|
||||||
|
? 'grayscale'
|
||||||
|
: ''}"
|
||||||
|
onsubmit={() => {
|
||||||
|
loading = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#await data.streamed.albums}
|
||||||
|
{#each { length: 2 } as _}
|
||||||
|
<section class="grid grid-cols-3 items-center gap-16">
|
||||||
|
{#each { length: 3 } as _}
|
||||||
|
<Skeleton class="h-[6rem] w-full rounded-xl " />
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<section class="grid grid-cols-3 items-center gap-16">
|
||||||
|
{#each { length: 3 } as _}
|
||||||
|
<Skeleton class="aspect-square h-auto max-w-full rounded-xl object-cover" />
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
{:then albums}
|
||||||
|
{#if albums.names && albums.artists && albums.images}
|
||||||
|
<DndGroup items={albums.names} type="names"></DndGroup>
|
||||||
|
<DndGroup items={albums.artists} type="artists"></DndGroup>
|
||||||
|
<DndGroup items={albums.images} image type="images"></DndGroup>
|
||||||
|
{:else}
|
||||||
|
<DndGroup items={names} type="names"></DndGroup>
|
||||||
|
<DndGroup items={artists} type="artists"></DndGroup>
|
||||||
|
<DndGroup items={images} image type="images"></DndGroup>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
<div class="flex justify-evenly">
|
||||||
|
<p>Stage: {data.stage}</p>
|
||||||
|
<Button type="submit" variant="outline">Submit</Button>
|
||||||
|
<p>High Score: {data.highscore}</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
12
src/routes/api/checkSolve/+server.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import type { AlbumSolveState } from '$lib/types';
|
||||||
|
import { albumState } from '$lib/server/AlbumState.svelte';
|
||||||
|
import { spotifyAPI } from '$lib/server/Spotify.svelte';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function POST({ request }) {
|
||||||
|
const { state }: { state: AlbumSolveState[] } = request.json();
|
||||||
|
|
||||||
|
const albums: SpotifyApi.AlbumObjectSimplified[] = [];
|
||||||
|
|
||||||
|
return json({ albums: albums });
|
||||||
|
}
|
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 15 KiB |
BIN
static/favicon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="1480" height="1480" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<svg id="SvgjsSvg1026" class="img-fluid" width="1480" height="1480" cursor="move"
|
|
||||||
style="transform-origin:50% 50% 0px;transform:matrix(1, 0, 0, 1, -4, -2);transition:none"
|
|
||||||
viewBox="0 0 14800 14800" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="SvgjsG1025" style="transform:none">
|
|
||||||
<g style="transform:none">
|
|
||||||
<path id="SvgjsPath1024"
|
|
||||||
d="m7170 14789c-113-3-232-7-265-8l-60-1 550-550-1300-1301-1300-1300 25 6c26 5 297 69 2204 515l1119 262 877-877v-20c0-11-196-785-434-1720-239-935-433-1701-431-1703 1-2 611 598 1356 1333l1353 1336 913-913c502-502 913-916 913-919 0-6-5948-5922-5959-5927-3-1-377 369-830 822l-823 823 639 2519c352 1386 639 2520 638 2522-2 2-488-114-1081-257-3433-828-3987-961-4002-961h-17l-480 480-479 480-7-8c-4-4-23-68-42-142-142-542-221-1089-236-1635l-6-240 14-500 15-165c58-610 171-1144 357-1700 554-1656 1707-3085 3216-3988 1082-648 2291-1002 3553-1041l245-8 247 8 246 8 169 16c232 22 648 79 661 91 2 1-65 69-149 151-404 393-664 761-823 1165l-46 116-34 134c-19 73-42 174-52 223l-16 90-10 410 16 113c18 120 60 300 74 314l9 9 718-256c395-141 721-258 726-260l7-5-14-53-15-52-12-220 17-85 18-85 58-142 52-78 52-79 85-86 86-86 56-36 55-36 62-21 61-21h44 43l48 16 47 16 60 55 59 55 30 61 30 62 42 160 10 320-204 2608 777 777 2533-2533 107 144c385 515 707 1093 948 1704l75 190 70 215c199 608 307 1171 352 1830l11 155v315 315l-11 155c-70 1025-326 1967-775 2855-825 1630-2206 2890-3904 3559-648 256-1308 412-2036 481l-160 15-190 5c-104 3-224 7-265 8-41 2-167 0-280-4z" />
|
|
||||||
<path id="SvgjsPath1023"
|
|
||||||
d="m11586 2508c9-69 65-831 72-987l7-164 167 125c91 69 230 179 308 243l141 119-348 348c-191 191-349 348-350 348s0-15 3-32z" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="SvgjsG1022" fill="#fff" style="transform:none"></g>
|
|
||||||
</svg>
|
|
||||||
<style>@media (prefers-color-scheme: light) { :root { filter: none; } }
|
|
||||||
@media (prefers-color-scheme: dark) { :root { filter: invert(100%); } }</style>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2 KiB |
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Kasterpalu",
|
|
||||||
"short_name": "Kasterpalu",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/web-app-manifest-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/web-app-manifest-512x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"display": "standalone"
|
|
||||||
}
|
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 10 KiB |
|
@ -1,101 +1,96 @@
|
||||||
import { fontFamily } from 'tailwindcss/defaultTheme';
|
import { fontFamily } from "tailwindcss/defaultTheme";
|
||||||
import type { Config } from 'tailwindcss';
|
import type { Config } from "tailwindcss";
|
||||||
import tailwindcssAnimate from 'tailwindcss-animate';
|
import tailwindcssAnimate from "tailwindcss-animate";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
darkMode: ['class'],
|
darkMode: ["class"],
|
||||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||||
safelist: ['dark'],
|
safelist: ["dark"],
|
||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: '2rem',
|
padding: "2rem",
|
||||||
screens: {
|
screens: {
|
||||||
'2xl': '1400px'
|
"2xl": "1400px"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: 'hsl(var(--border) / <alpha-value>)',
|
border: "hsl(var(--border) / <alpha-value>)",
|
||||||
input: 'hsl(var(--input) / <alpha-value>)',
|
input: "hsl(var(--input) / <alpha-value>)",
|
||||||
ring: 'hsl(var(--ring) / <alpha-value>)',
|
ring: "hsl(var(--ring) / <alpha-value>)",
|
||||||
background: 'hsl(var(--background) / <alpha-value>)',
|
background: "hsl(var(--background) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--foreground) / <alpha-value>)',
|
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary) / <alpha-value>)',
|
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--primary-foreground) / <alpha-value>)'
|
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary) / <alpha-value>)',
|
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--secondary-foreground) / <alpha-value>)'
|
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive) / <alpha-value>)',
|
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--destructive-foreground) / <alpha-value>)'
|
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: 'hsl(var(--muted) / <alpha-value>)',
|
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--muted-foreground) / <alpha-value>)'
|
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'hsl(var(--accent) / <alpha-value>)',
|
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--accent-foreground) / <alpha-value>)'
|
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: 'hsl(var(--popover) / <alpha-value>)',
|
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--popover-foreground) / <alpha-value>)'
|
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: 'hsl(var(--card) / <alpha-value>)',
|
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
||||||
foreground: 'hsl(var(--card-foreground) / <alpha-value>)'
|
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
|
||||||
},
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
DEFAULT: 'hsl(var(--sidebar-background))',
|
DEFAULT: "hsl(var(--sidebar-background))",
|
||||||
foreground: 'hsl(var(--sidebar-foreground))',
|
foreground: "hsl(var(--sidebar-foreground))",
|
||||||
primary: 'hsl(var(--sidebar-primary))',
|
primary: "hsl(var(--sidebar-primary))",
|
||||||
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
|
"primary-foreground": "hsl(var(--sidebar-primary-foreground))",
|
||||||
accent: 'hsl(var(--sidebar-accent))',
|
accent: "hsl(var(--sidebar-accent))",
|
||||||
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
|
"accent-foreground": "hsl(var(--sidebar-accent-foreground))",
|
||||||
border: 'hsl(var(--sidebar-border))',
|
border: "hsl(var(--sidebar-border))",
|
||||||
ring: 'hsl(var(--sidebar-ring))'
|
ring: "hsl(var(--sidebar-ring))",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
xl: 'calc(var(--radius) + 4px)',
|
xl: "calc(var(--radius) + 4px)",
|
||||||
lg: 'var(--radius)',
|
lg: "var(--radius)",
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: "calc(var(--radius) - 2px)",
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
sm: "calc(var(--radius) - 4px)"
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [...fontFamily.sans],
|
sans: [...fontFamily.sans]
|
||||||
title: ['Smooch Sans Variable', ...fontFamily.sans],
|
|
||||||
mono: ['Kode Mono Variable', ...fontFamily.mono]
|
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
'accordion-down': {
|
"accordion-down": {
|
||||||
from: { height: '0' },
|
from: { height: "0" },
|
||||||
to: { height: 'var(--bits-accordion-content-height)' }
|
to: { height: "var(--bits-accordion-content-height)" },
|
||||||
},
|
},
|
||||||
'accordion-up': {
|
"accordion-up": {
|
||||||
from: { height: 'var(--bits-accordion-content-height)' },
|
from: { height: "var(--bits-accordion-content-height)" },
|
||||||
to: { height: '0' }
|
to: { height: "0" },
|
||||||
|
},
|
||||||
|
"caret-blink": {
|
||||||
|
"0%,70%,100%": { opacity: "1" },
|
||||||
|
"20%,50%": { opacity: "0" },
|
||||||
},
|
},
|
||||||
'caret-blink': {
|
|
||||||
'0%,70%,100%': { opacity: '1' },
|
|
||||||
'20%,50%': { opacity: '0' }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
'caret-blink': 'caret-blink 1.25s ease-out infinite'
|
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
||||||
},
|
},
|
||||||
transitionTimingFunction: {
|
},
|
||||||
default: 'cubic-bezier(0.16, 1, 0.3, 1)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: [tailwindcssAnimate]
|
plugins: [tailwindcssAnimate],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|