Epochalypse "game"

This commit is contained in:
Mihkel Martin Kasterpalu 2025-01-24 11:50:06 +02:00
parent db6fea57ba
commit be2c58d248
8 changed files with 259 additions and 0 deletions

View 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>

View 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} />

View 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>

View 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,
};

View file

@ -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: '',

View file

@ -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;
}

View file

@ -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
};
}

View 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>