Skip to content

Commit 8d2dda0

Browse files
markschwindtv-ji
andauthored
Add localisation, blurb, back to top button (#17)
* Add "Back to top" button * Add text * Add language toggle * Add scroll to main button * Refactor header for improved responsiveness * Add overflow-x-hidden class to main * Hide back-to-top button on top * Update header width * Add fade transition to back-to-top button * Update language toggle * Move scroll listener to `<svelte:window>` * Update wording, add link to Wikisource * Move language state to $locale store, use in Dice3D component, add to main element --------- Co-authored-by: Viktor Illmer <1476338+v-ji@users.noreply.github.com>
1 parent 9e1e2c4 commit 8d2dda0

4 files changed

Lines changed: 167 additions & 19 deletions

File tree

src/lib/components/Dice3D.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
<script lang="ts">
2-
import { onMount, onDestroy } from "svelte"
2+
import { onDestroy, onMount } from "svelte"
3+
import { locale } from "$lib/stores/locale"
34
45
let {
56
isRolling = $bindable()
67
}: {
78
isRolling?: boolean
89
} = $props()
910
11+
let buttonLabel = $derived($locale === "de" ? "Würfeln" : "Roll the dice")
12+
1013
let attractMode = $state(false)
1114
let currentRotation = $state({ x: 0, y: 0 })
1215
let hoverInterval: ReturnType<typeof setInterval>
@@ -144,7 +147,8 @@
144147
onmouseleave={handleMouseLeave}
145148
onclick={handleClick}
146149
disabled={isRolling}
147-
aria-label="Roll dice"
150+
aria-label={buttonLabel}
151+
title={buttonLabel}
148152
>
149153
<div
150154
class="dice"

src/lib/stores/locale.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { writable } from "svelte/store"
2+
3+
export const locale = writable("en")

src/routes/+layout.svelte

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
<script lang="ts">
2+
import { onMount } from "svelte"
23
import "../app.css"
34
interface Props {
45
children?: import("svelte").Snippet
56
}
7+
import { browser } from "$app/environment"
8+
import { locale } from "$lib/stores/locale"
69
710
let { children }: Props = $props()
11+
12+
onMount(() => {
13+
if (browser) {
14+
const detectedLang = navigator.language.startsWith("de") ? "de" : "en"
15+
locale.set(detectedLang)
16+
}
17+
})
818
</script>
919

1020
<svelte:head>
1121
<title>Neunhundert neun und neunzig und noch etliche Almanachs-Lustspiele durch den Würfel</title>
1222
</svelte:head>
1323

14-
{@render children?.()}
24+
<div class="contents" lang={$locale}>
25+
{@render children?.()}
26+
</div>

src/routes/+page.svelte

Lines changed: 145 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<script lang="ts">
22
import { page } from "$app/state"
3+
import Carousel from "$lib/components/Carousel.svelte"
4+
import Dice3D from "$lib/components/Dice3D.svelte"
35
import { generateRandomSequence } from "$lib/dice"
6+
import { locale } from "$lib/stores/locale"
47
import emblaCarouselSvelte from "embla-carousel-svelte"
5-
import Dice3D from "$lib/components/Dice3D.svelte"
6-
7-
import Carousel from "$lib/components/Carousel.svelte"
88
import { onMount } from "svelte"
9+
import { fade } from "svelte/transition"
910
1011
emblaCarouselSvelte.globalOptions = {
1112
loop: true,
@@ -15,6 +16,10 @@
1516
let sequence: number[] = $state([])
1617
let isRolling = $state(false)
1718
19+
function toggleLanguage() {
20+
$locale = $locale === "de" ? "en" : "de"
21+
}
22+
1823
// On change to isRolling, set a new sequence
1924
$effect(() => {
2025
if (isRolling) {
@@ -40,32 +45,115 @@
4045
history.pushState("", document.title, window.location.pathname + window.location.search)
4146
return hashSequence
4247
}
48+
49+
// Track scroll position
50+
let scrollY = $state(0)
51+
let innerHeight = $state(0)
52+
let showBackToTop = $derived(scrollY > innerHeight)
53+
4354
onMount(() => {
4455
sequence = initialiseSequence()
4556
})
57+
58+
// Add function to scroll back to top
59+
function scrollToTop() {
60+
window.scrollTo({
61+
top: 0,
62+
behavior: "smooth"
63+
})
64+
}
65+
66+
// Reference to the main element
67+
let mainElement: HTMLElement
68+
69+
// Add function to scroll to main content
70+
function scrollToMain() {
71+
mainElement?.scrollIntoView({
72+
behavior: "smooth"
73+
})
74+
}
4675
</script>
4776

48-
<!-- Center column backdrop -->
49-
<div
50-
class="absolute inset-0 top-4 bottom-4 left-1/2 z-[-1] w-md -translate-x-1/2 border-4 border-sky-800 bg-amber-50"
51-
></div>
77+
<svelte:window bind:scrollY bind:innerHeight />
78+
79+
<header class="h-screen w-full p-4 md:p-16">
80+
<div
81+
class="relative flex h-full w-full flex-col items-center justify-evenly bg-sky-800 px-8 text-center text-amber-50 ring-4 ring-sky-800 ring-offset-4 ring-offset-amber-50"
82+
>
83+
<!-- Language toggle button -->
84+
<button
85+
class="relative flex h-26 w-26 cursor-pointer items-center justify-center rounded-full bg-amber-50 text-4xl text-sky-800 uppercase hover:bg-amber-100 focus:outline-none"
86+
onclick={toggleLanguage}
87+
aria-label={$locale === "de" ? "Sprache umschalten" : "Toggle language"}
88+
>
89+
{$locale}
90+
</button>
91+
92+
{#if $locale === "de"}
93+
<div lang="de" class="max-w-[75ch]">
94+
<h1 class="text-3xl md:text-6xl">Ein Dramenautomat von 1829 digital aufbereitet</h1>
95+
<p class="pt-8 md:text-xl">
96+
Der 1829 von Georg Nikolaus Bärmann veröffentlichte <a
97+
class="underline"
98+
target="_blank"
99+
href="https://de.wikisource.org/wiki/Neunhundert_neun_und_neunzig_und_noch_etliche_Almanachs-Lustspiele_durch_den_W%C3%BCrfel"
100+
><em>Würfelalmanach</em></a
101+
>
102+
ist ein spielerisches System zur Erzeugung von Einaktern per Würfelwurf. Diese kurzen Dramen
103+
waren auf der Bühne und im privaten Kreis beliebt, und Bärmanns Buch ermöglichte die Erstellung
104+
von 4×10<sup>155</sup> Variationen aus 1.200 Textfragmenten. Diese Webanwendung bringt den
105+
Almanach in digitaler Form zurück und lädt dazu ein, eine frühe Form algorithmischen Erzählens
106+
interaktiv zu erkunden.
107+
</p>
108+
</div>
109+
{:else}
110+
<div lang="en" class="max-w-[75ch]">
111+
<h1 class="text-3xl md:text-6xl">A literary automaton from 1829, reborn online</h1>
112+
<p class="pt-8 md:text-xl">
113+
The <a
114+
class="underline"
115+
target="_blank"
116+
href="https://de.wikisource.org/wiki/Neunhundert_neun_und_neunzig_und_noch_etliche_Almanachs-Lustspiele_durch_den_W%C3%BCrfel"
117+
><em>Würfelalmanach</em></a
118+
>, published by Georg Nikolaus Bärmann in 1829, is a playful system for generating one-act
119+
plays by rolling dice. These short dramas were popular on stage and in private gatherings,
120+
and Bärmann's book offered a way to create 4×10<sup>155</sup> possible variations from 1,200
121+
text fragments. This web app recreates the experience, letting you explore an early example
122+
of algorithmic storytelling in an interactive way.
123+
</p>
124+
</div>
125+
{/if}
126+
127+
<!-- Scroll to main button -->
128+
<button
129+
onclick={scrollToMain}
130+
class="pulse-animation flex h-26 w-26 cursor-pointer items-center justify-center rounded-full bg-amber-50 text-2xl text-sky-800 transition-colors hover:bg-amber-100 focus:outline-none"
131+
aria-label="Scroll to content"
132+
>
133+
START
134+
</button>
135+
</div>
136+
</header>
137+
138+
<main class="overflow-x-hidden p-4" bind:this={mainElement}>
139+
<!-- Center column backdrop -->
140+
<div
141+
class="absolute inset-0 bottom-4 left-1/2 z-[-1] w-[430px] -translate-x-1/2 border-4 border-sky-800 bg-amber-50"
142+
style="top: calc(100vh + 2em);"
143+
></div>
52144

53-
<header class="mx-auto flex w-md flex-col items-center">
54-
<h1 class="m-4 pt-10 text-center text-2xl font-bold">
145+
<h2 class="m-4 pt-10 text-center text-2xl font-bold">
55146
Neunhundert neun und neunzig <br /> <small>und noch etliche</small> <br /> Almanachs-Lustspiele
56147
<br /> <small>durch den Würfel</small>
57-
</h1>
148+
</h2>
58149

59-
<div class="flex justify-center" title="Würfeln">
60-
<div class="m-5 flex items-center justify-center rounded-full bg-sky-800 p-3" title="Würfeln">
150+
<div class="flex justify-center">
151+
<div class="m-5 flex items-center justify-center rounded-full bg-sky-800 p-3">
61152
<Dice3D bind:isRolling />
62153
</div>
63154
</div>
64-
</header>
65-
66-
<main class="p-4">
67155
<!-- Display all six versions side by side -->
68-
<div class="versions flex flex-col gap-10">
156+
<div class="versions flex flex-col gap-10" lang="de">
69157
{#each Array.from(new Array(200), (_x, i) => i + 1) as index}
70158
<div class="flex flex-col items-center">
71159
<h2 class="text-center text-xl font-bold" id={index.toString()}>{index}</h2>
@@ -79,3 +167,44 @@
79167
{/each}
80168
</div>
81169
</main>
170+
171+
<!-- Back to top button with conditional visibility -->
172+
{#if showBackToTop}
173+
<button
174+
onclick={scrollToTop}
175+
transition:fade={{ duration: 300 }}
176+
class="fixed right-6 bottom-6 flex h-18 w-18 cursor-pointer items-center justify-center rounded-full bg-sky-800 text-white shadow-lg transition-all hover:bg-sky-700 focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:outline-none"
177+
aria-label={$locale === "de" ? "Zurück nach oben" : "Back to top"}
178+
title={$locale === "de" ? "Zurück nach oben" : "Back to top"}
179+
>
180+
<svg
181+
xmlns="http://www.w3.org/2000/svg"
182+
class="h-6 w-6"
183+
fill="none"
184+
viewBox="0 0 24 24"
185+
stroke="currentColor"
186+
>
187+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
188+
</svg>
189+
</button>
190+
{/if}
191+
192+
<style>
193+
@keyframes pulse {
194+
0% {
195+
transform: scale(1);
196+
}
197+
198+
70% {
199+
transform: scale(1.11);
200+
}
201+
202+
100% {
203+
transform: scale(1);
204+
}
205+
}
206+
207+
.pulse-animation {
208+
animation: pulse 2s infinite;
209+
}
210+
</style>

0 commit comments

Comments
 (0)