Epochalypse "game"
This commit is contained in:
parent
db6fea57ba
commit
be2c58d248
8 changed files with 259 additions and 0 deletions
24
src/lib/components/ui/accordion/accordion-content.svelte
Normal file
24
src/lib/components/ui/accordion/accordion-content.svelte
Normal file
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive, type WithoutChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<AccordionPrimitive.ContentProps> = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Content
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<div class="pb-4 pt-0">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</AccordionPrimitive.Content>
|
12
src/lib/components/ui/accordion/accordion-item.svelte
Normal file
12
src/lib/components/ui/accordion/accordion-item.svelte
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AccordionPrimitive.ItemProps = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Item bind:ref class={cn("border-b", className)} {...restProps} />
|
31
src/lib/components/ui/accordion/accordion-trigger.svelte
Normal file
31
src/lib/components/ui/accordion/accordion-trigger.svelte
Normal file
|
@ -0,0 +1,31 @@
|
|||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive, type WithoutChild } from "bits-ui";
|
||||
import ChevronDown from "lucide-svelte/icons/chevron-down";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
level = 3,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<AccordionPrimitive.TriggerProps> & {
|
||||
level?: AccordionPrimitive.HeaderProps["level"];
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Header {level} class="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
bind:ref
|
||||
class={cn(
|
||||
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronDown
|
||||
class="text-muted-foreground size-4 shrink-0 transition-transform duration-200"
|
||||
/>
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
17
src/lib/components/ui/accordion/index.ts
Normal file
17
src/lib/components/ui/accordion/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
import Content from "./accordion-content.svelte";
|
||||
import Item from "./accordion-item.svelte";
|
||||
import Trigger from "./accordion-trigger.svelte";
|
||||
|
||||
const Root = AccordionPrimitive.Root;
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Item,
|
||||
Trigger,
|
||||
//
|
||||
Root as Accordion,
|
||||
Content as AccordionContent,
|
||||
Item as AccordionItem,
|
||||
Trigger as AccordionTrigger,
|
||||
};
|
|
@ -15,6 +15,11 @@ export const games: GamesObj = {
|
|||
image: '',
|
||||
description: 'Sorteeri kolme suvalise muusika albumi pealkiri, artistid ja pilt.'
|
||||
},
|
||||
'/epochalypse': {
|
||||
name: 'Epochalypse',
|
||||
image: '',
|
||||
description: 'Varsti veel üks Y2K. Kui nostalgiline!'
|
||||
},
|
||||
'': {
|
||||
name: 'Rohkem mänge soon™',
|
||||
image: '',
|
||||
|
|
|
@ -29,3 +29,12 @@ export type Game = {
|
|||
};
|
||||
|
||||
export type GamesObj = Record<string, Game>;
|
||||
|
||||
export interface TimeRemaining {
|
||||
years: number;
|
||||
months: number;
|
||||
days: number;
|
||||
hours: number;
|
||||
minutes: number;
|
||||
seconds: number;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import type { TimeRemaining } from './types';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
|
@ -109,3 +110,55 @@ export function truncate(text: string, maxLength: number): string {
|
|||
|
||||
return text.slice(0, cutoffIndex).trim() + ellipsis;
|
||||
}
|
||||
|
||||
// Created using Claude 3.5 Sonett
|
||||
export function getTimeRemaining(currentTime: Date, targetTime: Date): TimeRemaining {
|
||||
// Convert both dates to milliseconds and get the difference
|
||||
let delta = targetTime.getTime() - currentTime.getTime();
|
||||
|
||||
// If target is in the past, return all zeros
|
||||
if (delta < 0) {
|
||||
return {
|
||||
years: 0,
|
||||
months: 0,
|
||||
days: 0,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate each unit
|
||||
const millisecondsPerSecond = 1000;
|
||||
const millisecondsPerMinute = millisecondsPerSecond * 60;
|
||||
const millisecondsPerHour = millisecondsPerMinute * 60;
|
||||
const millisecondsPerDay = millisecondsPerHour * 24;
|
||||
const millisecondsPerMonth = millisecondsPerDay * 30.436875; // Average month length
|
||||
const millisecondsPerYear = millisecondsPerDay * 365.2425; // Average year length
|
||||
|
||||
const years = Math.floor(delta / millisecondsPerYear);
|
||||
delta -= years * millisecondsPerYear;
|
||||
|
||||
const months = Math.floor(delta / millisecondsPerMonth);
|
||||
delta -= months * millisecondsPerMonth;
|
||||
|
||||
const days = Math.floor(delta / millisecondsPerDay);
|
||||
delta -= days * millisecondsPerDay;
|
||||
|
||||
const hours = Math.floor(delta / millisecondsPerHour);
|
||||
delta -= hours * millisecondsPerHour;
|
||||
|
||||
const minutes = Math.floor(delta / millisecondsPerMinute);
|
||||
delta -= minutes * millisecondsPerMinute;
|
||||
|
||||
const seconds = Math.floor(delta / millisecondsPerSecond);
|
||||
|
||||
return {
|
||||
years,
|
||||
months,
|
||||
days,
|
||||
hours,
|
||||
minutes,
|
||||
seconds
|
||||
};
|
||||
}
|
||||
|
|
108
src/routes/(games)/epochalypse/+page.svelte
Normal file
108
src/routes/(games)/epochalypse/+page.svelte
Normal file
|
@ -0,0 +1,108 @@
|
|||
<script lang="ts">
|
||||
import { getTimeRemaining } from '$lib/utils';
|
||||
import * as Accordion from '$lib/components/ui/accordion/index.js';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const epochalypse = new Date(2038, 0, 19, 3, 14, 7);
|
||||
let currentTime = $state(new Date());
|
||||
|
||||
let timeUntil = $derived(getTimeRemaining(currentTime, epochalypse));
|
||||
|
||||
onMount(() => {
|
||||
setInterval(() => {
|
||||
currentTime = new Date();
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#snippet clockDigit(nr: number)}
|
||||
<div>{String(nr).padStart(2, '0')}</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet clockStage(count: number, index: number, title: string, divider = true)}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div
|
||||
class="card h-[2.75rem] overflow-hidden rounded-xl bg-muted font-mono text-xl ring ring-foreground/25 drop-shadow-md"
|
||||
>
|
||||
<div
|
||||
style="translate: 0 calc({index} * -2rem); transition-duration: 500ms;"
|
||||
class="flex flex-col gap-1 px-4 py-2 transition-all"
|
||||
>
|
||||
{#each { length: count } as _, i}
|
||||
{@render clockDigit(i)}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-muted-foreground">{title}</p>
|
||||
</div>
|
||||
{#if divider}
|
||||
<p class="text-lg font-semibold text-muted-foreground">:</p>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<header class="mb-24 flex flex-col items-center font-title">
|
||||
<h1 class="mb-1 scroll-m-20 text-5xl font-extrabold tracking-tight lg:text-6xl">Epochalypse</h1>
|
||||
<p class="text-xl font-semibold text-muted-foreground">Ära muretse! Sul on veel aega:</p>
|
||||
</header>
|
||||
<main class="w-full max-w-4xl">
|
||||
<div
|
||||
class="mx-auto flex w-full flex-wrap items-center justify-center gap-4 rounded-md bg-muted p-12"
|
||||
>
|
||||
{@render clockStage(13, timeUntil.years, 'aastat')}
|
||||
{@render clockStage(13, timeUntil.months, 'kuud')}
|
||||
{@render clockStage(31, timeUntil.days, 'päeva')}
|
||||
{@render clockStage(24, timeUntil.hours, 'tundi')}
|
||||
{@render clockStage(60, timeUntil.minutes, 'minutit')}
|
||||
{@render clockStage(60, timeUntil.seconds, 'sekundit', false)}
|
||||
</div>
|
||||
|
||||
<Accordion.Root type="single" class="mt-4 pb-4">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>Mis asi see on??</Accordion.Trigger>
|
||||
<Accordion.Content class="text-md max-w-prose">
|
||||
<p class="leading-7 [&:not(:first-child)]:mt-6">
|
||||
Epochalypse nimeline probleem on sarnane 1990 lõpus toimunud <a
|
||||
href="https://en.wikipedia.org/wiki/Year_2000_problem"
|
||||
class="font-medium text-primary underline underline-offset-4">Y2K</a
|
||||
>-le <br />
|
||||
Laialdaselt kasutatud süsteem aja ja kuupäeva märkimiseks <strong>saab otsa.</strong>
|
||||
</p>
|
||||
<p class="leading-7 [&:not(:first-child)]:mt-6">
|
||||
Enamus Linuxi baasiga Operatsioonisüsteeme kasutab aja märkimiseks <a
|
||||
href="https://en.wikipedia.org/wiki/Unix_time"
|
||||
class="font-medium text-primary underline underline-offset-4">UNIX aega</a
|
||||
>
|
||||
- mitu sekundit on möödunud UNIX <i>epoch</i>-ist 00:00:00 19.01.1970 UTC.
|
||||
</p>
|
||||
<p class="leading-7 [&:not(:first-child)]:mt-6">
|
||||
Neid sekundeid hoitaks <i>signed 32-bit</i> täisarvuna. Selle andmetüübi maksimaalne
|
||||
väärtus on
|
||||
<code
|
||||
class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
|
||||
>2<sup>31</sup>-1</code
|
||||
>. Viimane kuupäev (<code
|
||||
class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
|
||||
>UNIX epoch + 2<sup>31</sup>-1</code
|
||||
>) on 03:14:07 19.01.2038 UTC.
|
||||
</p>
|
||||
<p class="leading-7 [&:not(:first-child)]:mt-6">
|
||||
Sekund pärast seda, kui arvutid lisavad ajale sekundi juurde, juhtub <a
|
||||
href="https://en.wikipedia.org/wiki/Integer_overflow"
|
||||
class="font-medium text-primary underline underline-offset-4">täisarvu ületäitumine</a
|
||||
>
|
||||
ja aja väärtuseks saab
|
||||
<code
|
||||
class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
|
||||
>-2<sup>31</sup></code
|
||||
>. <br />
|
||||
Arvutid loevad seda kui 20:45:52 13.12.1901 UTC, ehk varaseim võimalik aeg
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/Unix_time"
|
||||
class="font-medium text-primary underline underline-offset-4">UNIX aja</a
|
||||
> järgi.
|
||||
</p>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
</main>
|
Loading…
Add table
Reference in a new issue