aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/components/Slider.svelte79
-rw-r--r--lib/components/ToggleSwitch.svelte60
-rw-r--r--lib/screens/SettingsScreen.svelte166
3 files changed, 305 insertions, 0 deletions
diff --git a/lib/components/Slider.svelte b/lib/components/Slider.svelte
new file mode 100644
index 0000000..da58413
--- /dev/null
+++ b/lib/components/Slider.svelte
@@ -0,0 +1,79 @@
+<script lang="ts">
+ interface Props {
+ value: number
+ min?: number
+ max?: number
+ step?: number
+ label: string
+ onChange: (value: number) => void
+ }
+
+ let {
+ value,
+ min = 0,
+ max = 1,
+ step = 0.01,
+ label,
+ onChange
+ }: Props = $props()
+
+ function handleInput(event: Event): void {
+ const target = event.currentTarget as HTMLInputElement
+ onChange(Number(target.value))
+ }
+</script>
+
+<input
+ type="range"
+ {min}
+ {max}
+ {step}
+ {value}
+ aria-label={label}
+ class="slider"
+ oninput={handleInput}
+/>
+
+<style>
+ .slider {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 200px;
+ height: 2px;
+ background: rgba(232, 226, 213, 0.12);
+ outline: none;
+ border-radius: 1px;
+ }
+
+ .slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--color-accent);
+ cursor: pointer;
+ border: none;
+ transition: transform var(--transition-fast);
+ }
+
+ .slider::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--color-accent);
+ cursor: pointer;
+ border: none;
+ transition: transform var(--transition-fast);
+ }
+
+ .slider:hover::-webkit-slider-thumb,
+ .slider:focus-visible::-webkit-slider-thumb {
+ transform: scale(1.15);
+ }
+
+ .slider:hover::-moz-range-thumb,
+ .slider:focus-visible::-moz-range-thumb {
+ transform: scale(1.15);
+ }
+</style>
diff --git a/lib/components/ToggleSwitch.svelte b/lib/components/ToggleSwitch.svelte
new file mode 100644
index 0000000..9536d6d
--- /dev/null
+++ b/lib/components/ToggleSwitch.svelte
@@ -0,0 +1,60 @@
+<script lang="ts">
+ interface Props {
+ value: boolean
+ label: string
+ onChange: (value: boolean) => void
+ }
+
+ let { value, label, onChange }: Props = $props()
+
+ function handleClick(): void {
+ onChange(!value)
+ }
+</script>
+
+<button
+ type="button"
+ role="switch"
+ aria-checked={value}
+ aria-label={label}
+ class="toggle"
+ class:on={value}
+ onclick={handleClick}
+>
+ <span class="thumb"></span>
+</button>
+
+<style>
+ .toggle {
+ position: relative;
+ width: 38px;
+ height: 20px;
+ border-radius: 999px;
+ background: rgba(232, 226, 213, 0.08);
+ border: 1px solid rgba(232, 226, 213, 0.12);
+ transition:
+ background var(--transition-fast),
+ border-color var(--transition-fast);
+ }
+
+ .toggle.on {
+ background: var(--color-accent);
+ border-color: var(--color-accent);
+ }
+
+ .thumb {
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--color-text);
+ transition: transform var(--transition-fast);
+ }
+
+ .toggle.on .thumb {
+ transform: translateX(18px);
+ background: var(--color-bg);
+ }
+</style>
diff --git a/lib/screens/SettingsScreen.svelte b/lib/screens/SettingsScreen.svelte
new file mode 100644
index 0000000..3d502f1
--- /dev/null
+++ b/lib/screens/SettingsScreen.svelte
@@ -0,0 +1,166 @@
+<script lang="ts">
+ import Slider from '@hollowdark/lib/components/Slider.svelte'
+ import ToggleSwitch from '@hollowdark/lib/components/ToggleSwitch.svelte'
+ import { ambientVolume, masterMuted } from '@hollowdark/lib/audio/state'
+
+ interface Props {
+ onBack: () => void
+ }
+
+ let { onBack }: Props = $props()
+
+ const volumePercent = $derived(Math.round($ambientVolume * 100))
+
+ function setMuted(next: boolean): void {
+ masterMuted.set(next)
+ }
+
+ function setVolume(next: number): void {
+ ambientVolume.set(next)
+ }
+</script>
+
+<section class="settings">
+ <header class="top">
+ <button class="back" onclick={onBack}>← Back</button>
+ <h1 class="heading">Settings</h1>
+ </header>
+
+ <div class="body">
+ <section class="group">
+ <h2 class="group-label">Audio</h2>
+
+ <div class="row">
+ <div class="row-label">
+ <p class="row-name">Mute everything</p>
+ <p class="row-hint">Silence all sound, regardless of volume.</p>
+ </div>
+ <ToggleSwitch value={$masterMuted} label="Mute everything" onChange={setMuted} />
+ </div>
+
+ <div class="row">
+ <div class="row-label">
+ <p class="row-name">Ambient volume</p>
+ <p class="row-hint">Title music and region beds.</p>
+ </div>
+ <div class="slider-cell">
+ <Slider
+ value={$ambientVolume}
+ min={0}
+ max={1}
+ step={0.01}
+ label="Ambient volume"
+ onChange={setVolume}
+ />
+ <span class="slider-value">{volumePercent}%</span>
+ </div>
+ </div>
+ </section>
+ </div>
+</section>
+
+<style>
+ .settings {
+ min-height: 100dvh;
+ padding: var(--space-8) var(--space-6) var(--space-12);
+ max-width: 640px;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .top {
+ display: flex;
+ align-items: baseline;
+ gap: var(--space-6);
+ margin-bottom: var(--space-12);
+ }
+
+ .back {
+ font-family: var(--font-ui);
+ font-size: var(--text-sm);
+ color: var(--color-text-secondary);
+ letter-spacing: 0.3px;
+ transition: color var(--transition-fast);
+ }
+
+ .back:hover {
+ color: var(--color-text);
+ }
+
+ .heading {
+ font-family: var(--font-body);
+ font-size: var(--text-xl);
+ font-style: italic;
+ font-weight: 400;
+ letter-spacing: 1px;
+ color: var(--color-text);
+ margin: 0;
+ }
+
+ .body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-8);
+ }
+
+ .group {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-4);
+ }
+
+ .group-label {
+ font-family: var(--font-ui);
+ font-size: var(--text-xs);
+ color: var(--color-accent);
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ margin: 0 0 var(--space-2);
+ }
+
+ .row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-6);
+ padding: var(--space-3) 0;
+ border-bottom: 1px solid rgba(232, 226, 213, 0.06);
+ }
+
+ .row-label {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-1);
+ flex: 1;
+ min-width: 0;
+ }
+
+ .row-name {
+ font-family: var(--font-body);
+ font-size: var(--text-base);
+ color: var(--color-text);
+ margin: 0;
+ }
+
+ .row-hint {
+ font-family: var(--font-ui);
+ font-size: var(--text-sm);
+ color: var(--color-text-tertiary);
+ margin: 0;
+ }
+
+ .slider-cell {
+ display: flex;
+ align-items: center;
+ gap: var(--space-4);
+ }
+
+ .slider-value {
+ font-family: var(--font-ui);
+ font-size: var(--text-sm);
+ color: var(--color-text-secondary);
+ min-width: 3.5ch;
+ text-align: right;
+ }
+</style>