style: format with prettier

This commit is contained in:
2025-04-18 11:57:50 +02:00
parent 11b9aa9edb
commit 332465b85c
35 changed files with 733 additions and 728 deletions

View File

@@ -4,5 +4,7 @@
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true, "singleQuote": true,
"trailingComma": "es5", "trailingComma": "es5",
"semi": true "semi": true,
} "plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

10
.vscode/settings.json vendored
View File

@@ -1,6 +1,6 @@
{ {
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.detectIndentation": false, "editor.detectIndentation": false,
"editor.insertSpaces": true, "editor.insertSpaces": true,
"editor.rulers": [100] "editor.rulers": [100]
} }

View File

@@ -1,17 +1,17 @@
{ {
"$schema": "https://next.shadcn-svelte.com/schema.json", "$schema": "https://next.shadcn-svelte.com/schema.json",
"style": "new-york", "style": "new-york",
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "tailwind.config.ts",
"css": "src/app.css", "css": "src/app.css",
"baseColor": "neutral" "baseColor": "neutral"
}, },
"aliases": { "aliases": {
"components": "$lib/components", "components": "$lib/components",
"utils": "$lib/utils", "utils": "$lib/utils",
"ui": "$lib/components/ui", "ui": "$lib/components/ui",
"hooks": "$lib/hooks" "hooks": "$lib/hooks"
}, },
"typescript": true, "typescript": true,
"registry": "https://next.shadcn-svelte.com/registry" "registry": "https://next.shadcn-svelte.com/registry"
} }

View File

@@ -1,37 +1,38 @@
{ {
"name": "chillhop", "name": "chillhop",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"prepare": "svelte-kit sync || echo ''", "prepare": "svelte-kit sync || echo ''",
"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",
"ui:add": "npx shadcn-svelte@next add", "ui:add": "npx shadcn-svelte@next add",
"format": "prettier --write ." "format": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"@lucide/svelte": "^0.488.0", "@lucide/svelte": "^0.488.0",
"@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/adapter-cloudflare": "^5.0.1", "@sveltejs/adapter-cloudflare": "^5.0.1",
"@sveltejs/kit": "^2.16.0", "@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"bits-ui": "^1.3.19", "bits-ui": "^1.3.19",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"svelte": "^5.0.0", "prettier-plugin-svelte": "^3.3.3",
"svelte-check": "^4.0.0", "svelte": "^5.0.0",
"tailwind-merge": "^3.2.0", "svelte-check": "^4.0.0",
"tailwind-variants": "^1.0.0", "tailwind-merge": "^3.2.0",
"tailwindcss": "^3.4.17", "tailwind-variants": "^1.0.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss": "^3.4.17",
"typescript": "^5.0.0", "tailwindcss-animate": "^1.0.7",
"vite": "^6.0.0", "typescript": "^5.0.0",
"wrangler": "^4.12.0" "vite": "^6.0.0",
}, "wrangler": "^4.12.0"
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@@ -1,6 +1,6 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {} autoprefixer: {},
} },
}; };

View File

@@ -24,7 +24,7 @@
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--ring: 0 0% 3.9%; --ring: 0 0% 3.9%;
--radius: 1rem; --radius: 1rem;
--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%;
@@ -54,7 +54,7 @@
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--ring: 0 0% 83.1%; --ring: 0 0% 83.1%;
--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%;
@@ -72,4 +72,4 @@
body { body {
@apply bg-background text-foreground m-0 p-0 overflow-hidden; @apply bg-background text-foreground m-0 p-0 overflow-hidden;
} }
} }

14
src/app.d.ts vendored
View File

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

View File

@@ -1,12 +1,12 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <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" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@@ -1,7 +1,8 @@
<video src="https://ch-cdn.srizan.dev/flower-shop-beachside-moewalls-com.mp4" <video
autoplay src="https://ch-cdn.srizan.dev/flower-shop-beachside-moewalls-com.mp4"
loop autoplay
muted loop
playsinline muted
class="absolute top-0 left-0 w-full h-full object-cover -z-10" playsinline
></video> class="absolute top-0 left-0 w-full h-full object-cover -z-10"
></video>

View File

@@ -1,7 +1,9 @@
<script> <script>
import MusicPlayer from "@/components/app/now-playing.svelte"; import MusicPlayer from '@/components/app/now-playing.svelte';
</script> </script>
<div class="fixed bottom-5 left-2 right-2 z-50 flex items-center p-4 bg-white/10 backdrop-blur-lg rounded-xl shadow-lg"> <div
class="fixed bottom-5 left-2 right-2 z-50 flex items-center p-4 bg-white/10 backdrop-blur-lg rounded-xl shadow-lg"
>
<MusicPlayer /> <MusicPlayer />
</div> </div>

View File

@@ -1,138 +1,143 @@
<script lang="ts"> <script lang="ts">
import { state } from "@/state.svelte"; import { state } from '@/state.svelte';
import { getGeneralData, getStationSongs } from "@/utils"; import { getGeneralData, getStationSongs } from '@/utils';
import { onMount } from "svelte"; import { onMount } from 'svelte';
// svelte-ignore non_reactive_update
let audioElement: HTMLAudioElement;
let isInteracting = false; // Flag to prevent multiple interactions
function togglePlayback(play: boolean) { // svelte-ignore non_reactive_update
if (!audioElement) return; let audioElement: HTMLAudioElement;
let isInteracting = false; // Flag to prevent multiple interactions
if (play && state.hasInteracted) {
audioElement.play().catch(() => { function togglePlayback(play: boolean) {
state.error = "Audio playback failed. Please interact with the page first."; if (!audioElement) return;
state.isPlaying = false;
}); if (play && state.hasInteracted) {
audioElement.currentTime = state.currentTime; audioElement.play().catch(() => {
} else { state.error = 'Audio playback failed. Please interact with the page first.';
audioElement.pause(); state.isPlaying = false;
} });
audioElement.currentTime = state.currentTime;
} else {
audioElement.pause();
}
}
function playAudio() {
togglePlayback(true);
}
state.togglePlay = () => {
state.isPlaying = !state.isPlaying;
togglePlayback(state.isPlaying);
};
function handleInteraction() {
if (isInteracting) return;
isInteracting = true;
state.hasInteracted = true;
if (state.isPlaying) {
setTimeout(() => {
playAudio();
isInteracting = false;
}, 100);
} else {
isInteracting = false;
}
}
onMount(async () => {
const data = await getGeneralData();
state.presets = data.presets;
state.stations = data.stations;
state.backgrounds = data.backgrounds;
state.atmospheres = data.atmospheres;
if (data.stations.length > 0 && state.currentStation === null) {
state.currentStation = data.stations[0].id;
}
if (data.backgrounds.length > 0 && state.currentBackgroundId === null) {
const firstActiveBg = data.backgrounds.find((bg) => bg.isActive && !bg.parentId);
state.currentBackgroundId = firstActiveBg
? firstActiveBg.id
: data.backgrounds[0]?.id || null;
} else {
state.error = 'Failed to load initial data (empty response).';
} }
function playAudio() { const stationSongs = await getStationSongs(state.currentStation!);
togglePlayback(true); if (stationSongs) {
state.songQueue = stationSongs;
} else {
state.error = 'Failed to load songs.';
} }
state.togglePlay = () => { if (state.songQueue.length > 0) {
state.isPlaying = !state.isPlaying; state.currentSong = state.songQueue[0];
togglePlayback(state.isPlaying); state.duration = state.currentSong.duration;
}; } else {
state.error = 'No songs available.';
function handleInteraction() {
if (isInteracting) return;
isInteracting = true;
state.hasInteracted = true;
if (state.isPlaying) {
setTimeout(() => {
playAudio();
isInteracting = false;
}, 100);
} else {
isInteracting = false;
}
} }
onMount(async () => { const currentTime = new Date().getTime() / 1000;
const data = await getGeneralData(); const startTime = new Date(state.currentSong!.startTime).getTime() / 1000;
state.presets = data.presets; const endTime = new Date(state.currentSong!.endTime).getTime() / 1000;
state.stations = data.stations; const duration = endTime - startTime;
state.backgrounds = data.backgrounds; const elapsed = currentTime - startTime;
state.atmospheres = data.atmospheres; if (elapsed > 0 && elapsed < duration) {
state.currentTime = elapsed;
} else {
state.currentTime = 0;
}
if (data.stations.length > 0 && state.currentStation === null) { state.isLoading = false;
state.currentStation = data.stations[0].id; });
}
if (data.backgrounds.length > 0 && state.currentBackgroundId === null) {
const firstActiveBg = data.backgrounds.find(bg => bg.isActive && !bg.parentId);
state.currentBackgroundId = firstActiveBg ? firstActiveBg.id : (data.backgrounds[0]?.id || null);
} else {
state.error = "Failed to load initial data (empty response).";
}
const stationSongs = await getStationSongs(state.currentStation!); $effect(() => {
if (stationSongs) { if (!audioElement) return;
state.songQueue = stationSongs; togglePlayback(state.isPlaying);
} else { });
state.error = "Failed to load songs.";
}
if (state.songQueue.length > 0) {
state.currentSong = state.songQueue[0];
state.duration = state.currentSong.duration;
} else {
state.error = "No songs available.";
}
const currentTime = new Date().getTime() / 1000;
const startTime = new Date(state.currentSong!.startTime).getTime() / 1000;
const endTime = new Date(state.currentSong!.endTime).getTime() / 1000;
const duration = endTime - startTime;
const elapsed = currentTime - startTime;
if (elapsed > 0 && elapsed < duration) {
state.currentTime = elapsed;
} else {
state.currentTime = 0;
}
state.isLoading = false;
});
$effect(() => {
if (!audioElement) return;
togglePlayback(state.isPlaying);
});
</script> </script>
{#if !state.hasInteracted} {#if !state.hasInteracted}
<button class="flex flex-col h-screen w-full items-center justify-center space-y-2 cursor-pointer" onclick={handleInteraction}> <button
class="flex flex-col h-screen w-full items-center justify-center space-y-2 cursor-pointer"
onclick={handleInteraction}
>
<p>Click anywhere on the screen</p> <p>Click anywhere on the screen</p>
</button> </button>
{/if} {/if}
{#if !state.isLoading} {#if !state.isLoading}
<audio <audio
bind:this={audioElement} bind:this={audioElement}
src={`https://stream.chillhop.com/mp3/${state.currentSong!.fileId}`} src={`https://stream.chillhop.com/mp3/${state.currentSong!.fileId}`}
autoplay autoplay
volume={state.volume} volume={state.volume}
onended={async () => { onended={async () => {
state.currentSong = null; state.currentSong = null;
state.currentTime = 0; state.currentTime = 0;
state.songQueue.shift(); state.songQueue.shift();
if (state.songQueue.length > 0) { if (state.songQueue.length > 0) {
state.currentSong = state.songQueue[0]; state.currentSong = state.songQueue[0];
state.duration = state.currentSong.duration; state.duration = state.currentSong.duration;
} else { } else {
const stationSongs = await getStationSongs(state.currentStation!); const stationSongs = await getStationSongs(state.currentStation!);
if (stationSongs) { if (stationSongs) {
state.songQueue = stationSongs; state.songQueue = stationSongs;
state.currentSong = state.songQueue[0]; state.currentSong = state.songQueue[0];
state.duration = state.currentSong.duration; state.duration = state.currentSong.duration;
} else { } else {
state.error = "Failed to load songs."; state.error = 'Failed to load songs.';
} }
} }
}} }}
ontimeupdate={() => { ontimeupdate={() => {
if (audioElement) { if (audioElement) {
state.currentTime = audioElement.currentTime; state.currentTime = audioElement.currentTime;
} }
}} }}
class="hidden" class="hidden"
></audio> ></audio>
{/if} {/if}

View File

@@ -1,19 +1,15 @@
<script lang="ts"> <script lang="ts">
import { Button } from "@/components/ui/button"; import { Button } from '@/components/ui/button';
import { state } from "@/state.svelte"; import { state } from '@/state.svelte';
import Pause from "@lucide/svelte/icons/pause"; import Pause from '@lucide/svelte/icons/pause';
import Play from "@lucide/svelte/icons/play"; import Play from '@lucide/svelte/icons/play';
function togglePlay() { function togglePlay() {
state.togglePlay(); state.togglePlay();
} }
</script> </script>
<img <img src={state.currentSong?.image} alt="Cover Art" class="size-16 rounded-lg shadow-lg" />
src={state.currentSong?.image}
alt="Cover Art"
class="size-16 rounded-lg shadow-lg"
/>
<div class="flex flex-col ml-4"> <div class="flex flex-col ml-4">
<h2 class="text-lg font-semibold">{state.currentSong?.title}</h2> <h2 class="text-lg font-semibold">{state.currentSong?.title}</h2>
@@ -26,4 +22,4 @@
{:else} {:else}
<Play /> <Play />
{/if} {/if}
</Button> </Button>

View File

@@ -1,76 +1,71 @@
<script lang="ts" module> <script lang="ts" module>
import type { WithElementRef } from "bits-ui"; import type { WithElementRef } from 'bits-ui';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements"; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from "tailwind-variants"; import { type VariantProps, tv } from 'tailwind-variants';
export const buttonVariants = tv({ export const buttonVariants = tv({
base: "focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", base: 'focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
variants: { variants: {
variant: { variant: {
default: "bg-white/10 backdrop-blur-md hover:bg-white/20 text-foreground border border-white/20 shadow-lg", default:
olddefault: "bg-primary text-primary-foreground hover:bg-primary/90 shadow", 'bg-white/10 backdrop-blur-md hover:bg-white/20 text-foreground border border-white/20 shadow-lg',
destructive: olddefault: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow',
"bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm", destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm',
outline: outline:
"border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm", 'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm',
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm", secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm',
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: 'hover:bg-accent hover:text-accent-foreground',
link: "text-primary underline-offset-4 hover:underline", link: 'text-primary underline-offset-4 hover:underline',
}, },
size: { size: {
default: "h-9 px-4 py-2", default: 'h-9 px-4 py-2',
sm: "h-8 rounded-md px-3 text-xs", sm: 'h-8 rounded-md px-3 text-xs',
lg: "h-10 rounded-md px-8", lg: 'h-10 rounded-md px-8',
icon: "h-9 w-9", icon: 'h-9 w-9',
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
size: "default", size: 'default',
}, },
}); });
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]; export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"]; export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> & export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & { WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant; variant?: ButtonVariant;
size?: ButtonSize; size?: ButtonSize;
}; };
</script> </script>
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
class: className, class: className,
variant = "default", variant = 'default',
size = "default", size = 'default',
ref = $bindable(null), ref = $bindable(null),
href = undefined, href = undefined,
type = "button", type = 'button',
children, children,
...restProps ...restProps
}: ButtonProps = $props(); }: ButtonProps = $props();
</script> </script>
{#if href} {#if href}
<a <a bind:this={ref} class={cn(buttonVariants({ variant, size }), className)} {href} {...restProps}>
bind:this={ref} {@render children?.()}
class={cn(buttonVariants({ variant, size }), className)} </a>
{href}
{...restProps}
>
{@render children?.()}
</a>
{:else} {:else}
<button <button
bind:this={ref} bind:this={ref}
class={cn(buttonVariants({ variant, size }), className)} class={cn(buttonVariants({ variant, size }), className)}
{type} {type}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
</button> </button>
{/if} {/if}

View File

@@ -1,17 +1,17 @@
import Root, { import Root, {
type ButtonProps, type ButtonProps,
type ButtonSize, type ButtonSize,
type ButtonVariant, type ButtonVariant,
buttonVariants, buttonVariants,
} from "./button.svelte"; } from './button.svelte';
export { export {
Root, Root,
type ButtonProps as Props, type ButtonProps as Props,
// //
Root as Button, Root as Button,
buttonVariants, buttonVariants,
type ButtonProps, type ButtonProps,
type ButtonSize, type ButtonSize,
type ButtonVariant, type ButtonVariant,
}; };

View File

@@ -1,40 +1,40 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import Check from "@lucide/svelte/icons/check"; import Check from '@lucide/svelte/icons/check';
import Minus from "@lucide/svelte/icons/minus"; import Minus from '@lucide/svelte/icons/minus';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
import type { Snippet } from "svelte"; import type { Snippet } from 'svelte';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
children: childrenProp, children: childrenProp,
checked = $bindable(false), checked = $bindable(false),
indeterminate = $bindable(false), indeterminate = $bindable(false),
...restProps ...restProps
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & { }: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
children?: Snippet; children?: Snippet;
} = $props(); } = $props();
</script> </script>
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
bind:ref bind:ref
bind:checked bind:checked
bind:indeterminate bind:indeterminate
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className
)} )}
{...restProps} {...restProps}
> >
{#snippet children({ checked, indeterminate })} {#snippet children({ checked, indeterminate })}
<span class="absolute left-2 flex size-3.5 items-center justify-center"> <span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if indeterminate} {#if indeterminate}
<Minus class="size-4" /> <Minus class="size-4" />
{:else} {:else}
<Check class={cn("size-4", !checked && "text-transparent")} /> <Check class={cn('size-4', !checked && 'text-transparent')} />
{/if} {/if}
</span> </span>
{@render childrenProp?.()} {@render childrenProp?.()}
{/snippet} {/snippet}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>

View File

@@ -1,27 +1,27 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
sideOffset = 4, sideOffset = 4,
portalProps, portalProps,
...restProps ...restProps
}: DropdownMenuPrimitive.ContentProps & { }: DropdownMenuPrimitive.ContentProps & {
portalProps?: DropdownMenuPrimitive.PortalProps; portalProps?: DropdownMenuPrimitive.PortalProps;
} = $props(); } = $props();
</script> </script>
<DropdownMenuPrimitive.Portal {...portalProps}> <DropdownMenuPrimitive.Portal {...portalProps}>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
bind:ref bind:ref
{sideOffset} {sideOffset}
class={cn( class={cn(
"bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md", 'bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-none", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-none',
className className
)} )}
{...restProps} {...restProps}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>

View File

@@ -1,19 +1,19 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
inset, inset,
...restProps ...restProps
}: DropdownMenuPrimitive.GroupHeadingProps & { }: DropdownMenuPrimitive.GroupHeadingProps & {
inset?: boolean; inset?: boolean;
} = $props(); } = $props();
</script> </script>
<DropdownMenuPrimitive.GroupHeading <DropdownMenuPrimitive.GroupHeading
bind:ref bind:ref
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)} class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,23 +1,23 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
inset, inset,
...restProps ...restProps
}: DropdownMenuPrimitive.ItemProps & { }: DropdownMenuPrimitive.ItemProps & {
inset?: boolean; inset?: boolean;
} = $props(); } = $props();
</script> </script>
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
bind:ref bind:ref
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", 'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
inset && "pl-8", inset && 'pl-8',
className className
)} )}
{...restProps} {...restProps}
/> />

View File

@@ -1,23 +1,23 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
import { type WithElementRef } from "bits-ui"; import { type WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from 'svelte/elements';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
inset, inset,
children, children,
...restProps ...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
inset?: boolean; inset?: boolean;
} = $props(); } = $props();
</script> </script>
<div <div
bind:this={ref} bind:this={ref}
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)} class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
</div> </div>

View File

@@ -1,30 +1,30 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from 'bits-ui';
import Circle from "@lucide/svelte/icons/circle"; import Circle from '@lucide/svelte/icons/circle';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
children: childrenProp, children: childrenProp,
...restProps ...restProps
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props(); }: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
</script> </script>
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
bind:ref bind:ref
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className
)} )}
{...restProps} {...restProps}
> >
{#snippet children({ checked })} {#snippet children({ checked })}
<span class="absolute left-2 flex size-3.5 items-center justify-center"> <span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if checked} {#if checked}
<Circle class="size-2 fill-current" /> <Circle class="size-2 fill-current" />
{/if} {/if}
</span> </span>
{@render childrenProp?.({ checked })} {@render childrenProp?.({ checked })}
{/snippet} {/snippet}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>

View File

@@ -1,16 +1,16 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
...restProps ...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props(); }: DropdownMenuPrimitive.SeparatorProps = $props();
</script> </script>
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
bind:ref bind:ref
class={cn("bg-muted -mx-1 my-1 h-px", className)} class={cn('bg-muted -mx-1 my-1 h-px', className)}
{...restProps} {...restProps}
/> />

View File

@@ -1,20 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from 'svelte/elements';
import { type WithElementRef } from "bits-ui"; import { type WithElementRef } from 'bits-ui';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
children, children,
...restProps ...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props(); }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script> </script>
<span <span
bind:this={ref} bind:this={ref}
class={cn("ml-auto text-xs tracking-widest opacity-60", className)} class={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
</span> </span>

View File

@@ -1,19 +1,19 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
...restProps ...restProps
}: DropdownMenuPrimitive.SubContentProps = $props(); }: DropdownMenuPrimitive.SubContentProps = $props();
</script> </script>
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
bind:ref bind:ref
class={cn( class={cn(
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none", 'bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none',
className className
)} )}
{...restProps} {...restProps}
/> />

View File

@@ -1,28 +1,28 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from 'bits-ui';
import ChevronRight from "@lucide/svelte/icons/chevron-right"; import ChevronRight from '@lucide/svelte/icons/chevron-right';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
inset, inset,
children, children,
...restProps ...restProps
}: WithoutChild<DropdownMenuPrimitive.SubTriggerProps> & { }: WithoutChild<DropdownMenuPrimitive.SubTriggerProps> & {
inset?: boolean; inset?: boolean;
} = $props(); } = $props();
</script> </script>
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
bind:ref bind:ref
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 'data-[highlighted]:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && "pl-8", inset && 'pl-8',
className className
)} )}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}
<ChevronRight class="ml-auto" /> <ChevronRight class="ml-auto" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>

View File

@@ -1,14 +1,14 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
import Content from "./dropdown-menu-content.svelte"; import Content from './dropdown-menu-content.svelte';
import GroupHeading from "./dropdown-menu-group-heading.svelte"; import GroupHeading from './dropdown-menu-group-heading.svelte';
import Item from "./dropdown-menu-item.svelte"; import Item from './dropdown-menu-item.svelte';
import Label from "./dropdown-menu-label.svelte"; import Label from './dropdown-menu-label.svelte';
import RadioItem from "./dropdown-menu-radio-item.svelte"; import RadioItem from './dropdown-menu-radio-item.svelte';
import Separator from "./dropdown-menu-separator.svelte"; import Separator from './dropdown-menu-separator.svelte';
import Shortcut from "./dropdown-menu-shortcut.svelte"; import Shortcut from './dropdown-menu-shortcut.svelte';
import SubContent from "./dropdown-menu-sub-content.svelte"; import SubContent from './dropdown-menu-sub-content.svelte';
import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; import SubTrigger from './dropdown-menu-sub-trigger.svelte';
const Sub = DropdownMenuPrimitive.Sub; const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root; const Root = DropdownMenuPrimitive.Root;
@@ -17,34 +17,34 @@ const Group = DropdownMenuPrimitive.Group;
const RadioGroup = DropdownMenuPrimitive.RadioGroup; const RadioGroup = DropdownMenuPrimitive.RadioGroup;
export { export {
CheckboxItem, CheckboxItem,
Content, Content,
Root as DropdownMenu, Root as DropdownMenu,
CheckboxItem as DropdownMenuCheckboxItem, CheckboxItem as DropdownMenuCheckboxItem,
Content as DropdownMenuContent, Content as DropdownMenuContent,
Group as DropdownMenuGroup, Group as DropdownMenuGroup,
GroupHeading as DropdownMenuGroupHeading, GroupHeading as DropdownMenuGroupHeading,
Item as DropdownMenuItem, Item as DropdownMenuItem,
Label as DropdownMenuLabel, Label as DropdownMenuLabel,
RadioGroup as DropdownMenuRadioGroup, RadioGroup as DropdownMenuRadioGroup,
RadioItem as DropdownMenuRadioItem, RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator, Separator as DropdownMenuSeparator,
Shortcut as DropdownMenuShortcut, Shortcut as DropdownMenuShortcut,
Sub as DropdownMenuSub, Sub as DropdownMenuSub,
SubContent as DropdownMenuSubContent, SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger, SubTrigger as DropdownMenuSubTrigger,
Trigger as DropdownMenuTrigger, Trigger as DropdownMenuTrigger,
Group, Group,
GroupHeading, GroupHeading,
Item, Item,
Label, Label,
RadioGroup, RadioGroup,
RadioItem, RadioItem,
Root, Root,
Separator, Separator,
Shortcut, Shortcut,
Sub, Sub,
SubContent, SubContent,
SubTrigger, SubTrigger,
Trigger, Trigger,
}; };

View File

@@ -1,25 +1,25 @@
import type { Song, Atmosphere, Preset, Station, Background } from "./types"; // Added Preset, Station, Background import type { Song, Atmosphere, Preset, Station, Background } from './types'; // Added Preset, Station, Background
export const state = $state({ export const state = $state({
hasInteracted: false, hasInteracted: false,
currentStation: null as number | null, currentStation: null as number | null,
currentSong: null as Song | null, currentSong: null as Song | null,
songQueue: [] as Song[], songQueue: [] as Song[],
isPlaying: true, isPlaying: true,
volume: 0.5, volume: 0.5,
isMuted: false, isMuted: false,
currentBackgroundId: null as string | null, currentBackgroundId: null as string | null,
activeAtmospheres: {} as Record<string, number>, // { atmosphereId: volume (0-100) } activeAtmospheres: {} as Record<string, number>, // { atmosphereId: volume (0-100) }
isLoading: true, isLoading: true,
error: null as string | null, error: null as string | null,
currentTime: 0, currentTime: 0,
duration: 0, duration: 0,
presets: [] as Preset[], presets: [] as Preset[],
stations: [] as Station[], stations: [] as Station[],
backgrounds: [] as Background[], backgrounds: [] as Background[],
atmospheres: [] as Atmosphere[], atmospheres: [] as Atmosphere[],
// in daemon.svelte // in daemon.svelte
togglePlay: (() => {}) as () => void togglePlay: (() => {}) as () => void,
}); });

View File

@@ -1,77 +1,76 @@
export interface Song { export interface Song {
id: number id: number;
fileId: number fileId: number;
artists: string artists: string;
title: string title: string;
image: string image: string;
likes: number likes: number;
featured?: string featured?: string;
releaseDate: string releaseDate: string;
releaseDateText: string releaseDateText: string;
duration: number duration: number;
isrc: string isrc: string;
label: string label: string;
spotifyId: string spotifyId: string;
startTime: string startTime: string;
endTime: string endTime: string;
} }
export interface Preset { export interface Preset {
id: number; id: number;
userId: number; userId: number;
name: string; name: string;
backgroundId: string; backgroundId: string;
stationId: string; // Represented as string in JSON stationId: string; // Represented as string in JSON
atmospheres: string; // JSON stringified object atmospheres: string; // JSON stringified object
sortOrder: number; sortOrder: number;
key: string; key: string;
} }
export interface StationMetaSocials { export interface StationMetaSocials {
spotify: string | null; spotify: string | null;
apple: string | null; apple: string | null;
} }
export interface StationMetaIcon { export interface StationMetaIcon {
static: string; static: string;
} }
// Structure within the base64 decoded 'meta' string for stations // Structure within the base64 decoded 'meta' string for stations
export interface DecodedStationMeta { export interface DecodedStationMeta {
shortDescription: string; shortDescription: string;
icon: StationMetaIcon; icon: StationMetaIcon;
socials: StationMetaSocials; socials: StationMetaSocials;
} }
export interface Station { export interface Station {
name: string; name: string;
id: number; id: number;
meta: string; // Base64 encoded JSON string (DecodedStationMeta) meta: string; // Base64 encoded JSON string (DecodedStationMeta)
} }
export interface Background { export interface Background {
id: string; id: string;
name: string; name: string;
parentId: string | null; parentId: string | null;
landscapeUrl: string; landscapeUrl: string;
portraitUrl: string; portraitUrl: string;
thumbnailUrl: string; thumbnailUrl: string;
sortOrder: number; sortOrder: number;
isActive: number; // 0 or 1 isActive: number; // 0 or 1
} }
export interface Atmosphere { export interface Atmosphere {
id: string; id: string;
name: string; name: string;
url: string; url: string;
sortOrder: number; sortOrder: number;
urlMobile: string; urlMobile: string;
} }
export interface ChillhopData { export interface ChillhopData {
presets: Preset[]; presets: Preset[];
stations: Station[]; stations: Station[];
backgrounds: Background[]; backgrounds: Background[];
atmospheres: Atmosphere[]; atmospheres: Atmosphere[];
} }

View File

@@ -1,19 +1,19 @@
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from 'clsx';
import { twMerge } from "tailwind-merge"; import { twMerge } from 'tailwind-merge';
import type { ChillhopData, Song } from "./types"; import type { ChillhopData, Song } from './types';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export async function getGeneralData() { export async function getGeneralData() {
const res = await fetch('https://stream.chillhop.com/presets') const res = await fetch('https://stream.chillhop.com/presets');
const data = await res.json() as ChillhopData; const data = (await res.json()) as ChillhopData;
return data; return data;
} }
export async function getStationSongs(stationId: number) { export async function getStationSongs(stationId: number) {
const res = await fetch(`https://stream.chillhop.com/live/${stationId}`) const res = await fetch(`https://stream.chillhop.com/live/${stationId}`);
const data = await res.json() as Song[]; const data = (await res.json()) as Song[];
return data; return data;
} }

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import '../app.css'; import '../app.css';
let { children } = $props(); let { children } = $props();
</script> </script>
{@render children()} {@render children()}

View File

@@ -1,10 +1,9 @@
<script lang="ts"> <script lang="ts">
import BgImage from "@/components/app/bg-image.svelte"; import BgImage from '@/components/app/bg-image.svelte';
import BottomBar from "@/components/app/bottom-bar.svelte"; import BottomBar from '@/components/app/bottom-bar.svelte';
import Daemon from "@/components/app/daemon.svelte"; import Daemon from '@/components/app/daemon.svelte';
import Spinner from '@lucide/svelte/icons/loader' import Spinner from '@lucide/svelte/icons/loader';
import { state } from "@/state.svelte"; import { state } from '@/state.svelte';
</script> </script>
<BgImage /> <BgImage />
@@ -21,9 +20,9 @@
<p>Loading...</p> <p>Loading...</p>
</div> </div>
{:else if state.error} {:else if state.error}
<div class="flex h-screen w-full items-center justify-center text-red-500"> <div class="flex h-screen w-full items-center justify-center text-red-500">
<p>Error: {state.error}</p> <p>Error: {state.error}</p>
</div> </div>
{:else if state.hasInteracted} {:else if state.hasInteracted}
<BottomBar /> <BottomBar />
{/if} {/if}

View File

@@ -1,21 +1,21 @@
import adapter from "@sveltejs/adapter-auto"; import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
// Consult https://svelte.dev/docs/kit/integrations // Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
kit: { kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter. // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters. // See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter(), adapter: adapter(),
alias: { alias: {
"@/*": "./src/lib/*", '@/*': './src/lib/*',
}, },
} },
}; };
export default config; export default config;

View File

@@ -1,96 +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],
}, },
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": { 'caret-blink': {
"0%,70%,100%": { opacity: "1" }, '0%,70%,100%': { opacity: '1' },
"20%,50%": { opacity: "0" }, '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',
}, },
}, },
}, },
plugins: [tailwindcssAnimate], plugins: [tailwindcssAnimate],
}; };
export default config; export default config;

View File

@@ -1,19 +1,19 @@
{ {
"extends": "./.svelte-kit/tsconfig.json", "extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"moduleResolution": "bundler" "moduleResolution": "bundler"
} }
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
// //
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in // from the referenced tsconfig.json - TypeScript does not merge them in
} }

View File

@@ -2,5 +2,5 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()] plugins: [sveltekit()],
}); });

View File

@@ -1539,6 +1539,11 @@ postcss@^8.4.47, postcss@^8.5.3:
picocolors "^1.1.1" picocolors "^1.1.1"
source-map-js "^1.2.1" source-map-js "^1.2.1"
prettier-plugin-svelte@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz#49d5c025a1516063ac7ef026806f880caa310424"
integrity sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==
prettier@^3.5.3: prettier@^3.5.3:
version "3.5.3" version "3.5.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5"