[READ-ONLY] Mirror of https://github.com/mrgnw/zenfo.co.
3.9 kB
196 lines
1<script lang="ts">
2 import type { Photo } from '$lib/types.js';
3 import DepthPhoto from './DepthPhoto.svelte';
4 import DepthSlice from './DepthSlice.svelte';
5
6 let {
7 photos,
8 currentIndex = $bindable(0),
9 onclose,
10 }: {
11 photos: Photo[];
12 currentIndex: number;
13 onclose?: () => void;
14 } = $props();
15
16 let mode = $state<'parallax' | 'focus'>('parallax');
17 let photo = $derived(photos[currentIndex]);
18
19 function prev() {
20 if (currentIndex > 0) currentIndex--;
21 }
22
23 function next() {
24 if (currentIndex < photos.length - 1) currentIndex++;
25 }
26
27 function onkeydown(e: KeyboardEvent) {
28 if (e.key === 'ArrowLeft') prev();
29 else if (e.key === 'ArrowRight') next();
30 else if (e.key === 'Escape') onclose?.();
31 }
32
33 function stop(fn: (e: MouseEvent) => void) {
34 return (e: MouseEvent) => {
35 e.stopPropagation();
36 fn(e);
37 };
38 }
39</script>
40
41<svelte:window {onkeydown} />
42
43<!-- svelte-ignore a11y_no_static_element_interactions -->
44<div class="overlay" onclick={onclose}>
45 <!-- svelte-ignore a11y_no_static_element_interactions -->
46 <div class="viewer" onclick={stop(() => {})}>
47 {#key `${currentIndex}-${mode}`}
48 <div class="photo-wrap">
49 {#if mode === 'parallax'}
50 <DepthPhoto
51 src={photo.src}
52 depthSrc={photo.depthSrc}
53 alt={photo.title}
54 intensity={0.02}
55 />
56 {:else}
57 <DepthSlice
58 src={photo.src}
59 depthSrc={photo.depthSrc}
60 alt={photo.title}
61 />
62 {/if}
63 </div>
64 {/key}
65 <div class="caption">
66 <h2>{photo.title}</h2>
67 {#if photo.caption}
68 <p>{photo.caption}</p>
69 {/if}
70 <div class="controls">
71 <span class="counter">{currentIndex + 1} / {photos.length}</span>
72 <div class="mode-toggle">
73 <button
74 class="mode-btn"
75 class:active={mode === 'parallax'}
76 onclick={stop(() => { mode = 'parallax'; })}
77 >
78 Parallax
79 </button>
80 <button
81 class="mode-btn"
82 class:active={mode === 'focus'}
83 onclick={stop(() => { mode = 'focus'; })}
84 >
85 Focus
86 </button>
87 </div>
88 </div>
89 </div>
90 </div>
91
92 <button class="nav nav-prev" onclick={stop(prev)} disabled={currentIndex === 0}>
93 ←
94 </button>
95 <button class="nav nav-next" onclick={stop(next)} disabled={currentIndex === photos.length - 1}>
96 →
97 </button>
98</div>
99
100<style>
101 .overlay {
102 position: fixed;
103 inset: 0;
104 z-index: 100;
105 background: rgba(0, 0, 0, 0.92);
106 display: flex;
107 align-items: center;
108 justify-content: center;
109 }
110 .viewer {
111 max-width: 90vw;
112 max-height: 90vh;
113 display: flex;
114 flex-direction: column;
115 gap: 1rem;
116 }
117 .photo-wrap {
118 width: 90vw;
119 height: 80vh;
120 overflow: hidden;
121 }
122 .caption {
123 text-align: center;
124 padding: 0.5rem;
125 }
126 .caption h2 {
127 font-size: 1.1rem;
128 font-weight: 400;
129 margin: 0;
130 color: var(--color-text);
131 }
132 .caption p {
133 font-size: 0.85rem;
134 color: var(--color-text-muted);
135 margin: 0.25rem 0 0;
136 }
137 .controls {
138 display: flex;
139 align-items: center;
140 justify-content: center;
141 gap: 1rem;
142 margin-top: 0.5rem;
143 }
144 .counter {
145 font-size: 0.75rem;
146 color: var(--color-text-muted);
147 }
148 .mode-toggle {
149 display: flex;
150 gap: 0.25rem;
151 background: rgba(255, 255, 255, 0.06);
152 border-radius: 0.375rem;
153 padding: 0.15rem;
154 }
155 .mode-btn {
156 all: unset;
157 font-size: 0.7rem;
158 padding: 0.25rem 0.6rem;
159 border-radius: 0.25rem;
160 color: var(--color-text-muted);
161 cursor: pointer;
162 transition: all 0.15s;
163 }
164 .mode-btn:hover {
165 color: var(--color-text);
166 }
167 .mode-btn.active {
168 background: rgba(255, 255, 255, 0.12);
169 color: var(--color-text);
170 }
171 .nav {
172 all: unset;
173 position: fixed;
174 top: 50%;
175 transform: translateY(-50%);
176 font-size: 2rem;
177 color: var(--color-text-muted);
178 padding: 1rem;
179 cursor: pointer;
180 transition: color 0.15s;
181 user-select: none;
182 }
183 .nav:hover:not(:disabled) {
184 color: var(--color-text);
185 }
186 .nav:disabled {
187 opacity: 0.2;
188 cursor: default;
189 }
190 .nav-prev {
191 left: 1rem;
192 }
193 .nav-next {
194 right: 1rem;
195 }
196</style>